Add timestamp stats gathering

The android_glue unit now collects timestamp stats and reports them up
through a ring buffer to the app. The app currently just displays a raw
line in a text view, but we'll expand that out to smarter aggregation.
master
Raph Levien 12 years ago
parent 766b572a64
commit c934c830b6
  1. 12
      android/res/layout/piano2.xml
  2. 7
      android/src/com/google/synthesizer/android/AndroidGlue.java
  3. 24
      android/src/com/google/synthesizer/android/ui/PianoActivity2.java
  4. 49
      cpp/src/android_glue.cc
  5. 4
      cpp/src/ringbuffer.cc
  6. 7
      cpp/src/ringbuffer.h

@ -46,11 +46,21 @@
app:min="0" app:min="0"
app:max="1" app:max="1"
android:layout_margin="2px" /> android:layout_margin="2px" />
<LinearLayout
android:orientation="vertical"
android:layout_span="4" >
<Spinner <Spinner
android:id="@+id/presetSpinner" android:id="@+id/presetSpinner"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_span="4" /> />
<TextView
android:id="@+id/status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_span="6"
/>
</LinearLayout>
</TableRow> </TableRow>
<TableRow> <TableRow>
<LinearLayout <LinearLayout

@ -40,6 +40,13 @@ public class AndroidGlue extends MessageOutputProcessor {
sendMidi(midiData); sendMidi(midiData);
} }
/**
* @return Number of stats bytes available from synth core
*/
public native int statsBytesAvailable();
public native int readStatsBytes(byte[] buf, int off, int len);
static { static {
System.loadLibrary("synth"); System.loadLibrary("synth");
} }

@ -18,6 +18,7 @@ package com.google.synthesizer.android.ui;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
@ -33,6 +34,7 @@ import android.hardware.usb.UsbManager;
import android.media.AudioManager; import android.media.AudioManager;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import android.view.WindowManager; import android.view.WindowManager;
@ -117,6 +119,26 @@ public class PianoActivity2 extends Activity {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
tryConnectUsb(); tryConnectUsb();
} }
statusHandler_ = new Handler();
statusRunnable_ = new Runnable() {
public void run() {
int n = androidGlue_.statsBytesAvailable();
if (n > 0) {
byte[] buf = new byte[n];
androidGlue_.readStatsBytes(buf, 0, n);
TextView statusTextView = (TextView)findViewById(R.id.status);
String statusString = new String(buf);
int nlIndex = statusString.indexOf('\n');
if (nlIndex >= 0) {
statusString = statusString.substring(0, nlIndex);
}
statusTextView.setText(statusString);
}
statusHandler_.postDelayed(statusRunnable_, 10);
}
};
statusRunnable_.run();
} }
@Override @Override
@ -228,4 +250,6 @@ public class PianoActivity2 extends Activity {
private KnobView cutoffKnob_; private KnobView cutoffKnob_;
private KnobView resonanceKnob_; private KnobView resonanceKnob_;
private Spinner presetSpinner_; private Spinner presetSpinner_;
private Handler statusHandler_;
private Runnable statusRunnable_;
} }

@ -16,6 +16,8 @@
#include <assert.h> #include <assert.h>
#include <stddef.h> #include <stddef.h>
#include <stdio.h>
#include <time.h>
#include <jni.h> #include <jni.h>
#include <SLES/OpenSLES.h> #include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h> #include <SLES/OpenSLES_Android.h>
@ -29,6 +31,7 @@
#include "synth_unit.h" #include "synth_unit.h"
RingBuffer *ring_buffer; RingBuffer *ring_buffer;
RingBuffer *stats_ring_buffer;
SynthUnit *synth_unit; SynthUnit *synth_unit;
const int N_BUFFERS = 2; const int N_BUFFERS = 2;
@ -37,7 +40,6 @@ int buffer_size;
int16_t buffer[MAX_BUFFER_SIZE * N_BUFFERS]; int16_t buffer[MAX_BUFFER_SIZE * N_BUFFERS];
int cur_buffer = 0; int cur_buffer = 0;
int count = 0;
// engine interfaces // engine interfaces
static SLObjectItf engineObject = NULL; static SLObjectItf engineObject = NULL;
@ -52,15 +54,30 @@ static SLPlayItf bq_player_play;
static SLAndroidSimpleBufferQueueItf bq_player_buffer_queue; static SLAndroidSimpleBufferQueueItf bq_player_buffer_queue;
static SLBufferQueueItf buffer_queue_itf; static SLBufferQueueItf buffer_queue_itf;
double ts_to_double(const struct timespec *tp) {
return tp->tv_sec + 1e-9 * tp->tv_nsec;
}
extern "C" void BqPlayerCallback(SLAndroidSimpleBufferQueueItf queueItf, extern "C" void BqPlayerCallback(SLAndroidSimpleBufferQueueItf queueItf,
void *data) { void *data) {
if (count >= 1000) return; struct timespec tp;
clock_gettime(CLOCK_MONOTONIC, &tp);
double start_time = ts_to_double(&tp);
int16_t *buf_ptr = buffer + buffer_size * cur_buffer; int16_t *buf_ptr = buffer + buffer_size * cur_buffer;
synth_unit->GetSamples(buffer_size, buf_ptr); synth_unit->GetSamples(buffer_size, buf_ptr);
clock_gettime(CLOCK_MONOTONIC, &tp);
double end_time = ts_to_double(&tp);
SLresult result = (*queueItf)->Enqueue(bq_player_buffer_queue, SLresult result = (*queueItf)->Enqueue(bq_player_buffer_queue,
buf_ptr, buffer_size * 2); buf_ptr, buffer_size * 2);
assert(SL_RESULT_SUCCESS == result); assert(SL_RESULT_SUCCESS == result);
cur_buffer = (cur_buffer + 1) % N_BUFFERS; cur_buffer = (cur_buffer + 1) % N_BUFFERS;
char buf[64];
int n = sprintf(buf, "ts %.6f %.6f\n", start_time, end_time);
if (n <= stats_ring_buffer->WriteBytesAvailable()) {
stats_ring_buffer->Write((const uint8_t *)buf, n);
}
// Could potentially defer writing indication of overrun, but probably
// not worth it.
} }
void CreateEngine() { void CreateEngine() {
@ -122,6 +139,7 @@ Java_com_google_synthesizer_android_AndroidGlue_start(JNIEnv *env,
Freqlut::init(sample_rate); Freqlut::init(sample_rate);
Sin::init(); Sin::init();
ring_buffer = new RingBuffer(); ring_buffer = new RingBuffer();
stats_ring_buffer = new RingBuffer();
synth_unit = new SynthUnit(ring_buffer); synth_unit = new SynthUnit(ring_buffer);
for (int i = 0; i < N_BUFFERS - 1; ++i) { for (int i = 0; i < N_BUFFERS - 1; ++i) {
BqPlayerCallback(bq_player_buffer_queue, NULL); BqPlayerCallback(bq_player_buffer_queue, NULL);
@ -153,6 +171,8 @@ Java_com_google_synthesizer_android_AndroidGlue_shutdown(JNIEnv *env,
} }
delete ring_buffer; delete ring_buffer;
ring_buffer = NULL; ring_buffer = NULL;
delete stats_ring_buffer;
stats_ring_buffer = NULL;
delete synth_unit; delete synth_unit;
synth_unit = NULL; synth_unit = NULL;
} }
@ -175,3 +195,28 @@ Java_com_google_synthesizer_android_AndroidGlue_setPlayState(JNIEnv *env,
assert(SL_RESULT_SUCCESS == result); assert(SL_RESULT_SUCCESS == result);
} }
extern "C" JNIEXPORT jint JNICALL
Java_com_google_synthesizer_android_AndroidGlue_statsBytesAvailable(
JNIEnv *env, jobject thiz) {
return stats_ring_buffer->BytesAvailable();
}
extern "C" JNIEXPORT jint JNICALL
Java_com_google_synthesizer_android_AndroidGlue_readStatsBytes(
JNIEnv *env, jobject thiz, jbyteArray jb, jint off, jint len) {
int bytes_available = stats_ring_buffer->BytesAvailable();
int n = min(bytes_available, len);
if (n) {
size_t uoff = off;
size_t ulen = len;
if (off >= 0 && len >= 0 && uoff + ulen <= env->GetArrayLength(jb)) {
uint8_t *buf = (uint8_t *)env->GetByteArrayElements(jb, NULL);
stats_ring_buffer->Read(n, buf + uoff);
env->ReleaseByteArrayElements(jb, (jbyte *)buf, 0);
} else {
env->ThrowNew(env->FindClass("java/lang/ArrayIndexOutOfBoundsException"),
"out of bounds in AndroidGlue.readStatsBytes");
}
}
return n;
}

@ -29,6 +29,10 @@ int RingBuffer::BytesAvailable() {
return (wr_ix_ - rd_ix_) & (kBufSize - 1); return (wr_ix_ - rd_ix_) & (kBufSize - 1);
} }
int RingBuffer::WriteBytesAvailable() {
return (rd_ix_ - wr_ix_ - 1) & (kBufSize - 1);
}
int RingBuffer::Read(int size, uint8_t *bytes) { int RingBuffer::Read(int size, uint8_t *bytes) {
int rd_ix = rd_ix_; int rd_ix = rd_ix_;
SynthMemoryBarrier(); // read barrier, make sure data is committed before ix SynthMemoryBarrier(); // read barrier, make sure data is committed before ix

@ -24,6 +24,9 @@ class RingBuffer {
// Returns number of bytes available for reading. // Returns number of bytes available for reading.
int BytesAvailable(); int BytesAvailable();
// Returns number of bytes that can be written without blocking.
int WriteBytesAvailable();
// Reads bytes. It is the caller's responsibility to make sure that // Reads bytes. It is the caller's responsibility to make sure that
// size <= a previous value of BytesAvailable(). // size <= a previous value of BytesAvailable().
int Read(int size, uint8_t *bytes); int Read(int size, uint8_t *bytes);
@ -34,8 +37,8 @@ class RingBuffer {
private: private:
static const int kBufSize = 8192; static const int kBufSize = 8192;
uint8_t buf_[kBufSize]; uint8_t buf_[kBufSize];
volatile int rd_ix_; volatile unsigned int rd_ix_;
volatile int wr_ix_; volatile unsigned int wr_ix_;
}; };
#endif // SYNTH_RINGBUFFER_H_ #endif // SYNTH_RINGBUFFER_H_

Loading…
Cancel
Save