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:max="1"
android:layout_margin="2px" />
<LinearLayout
android:orientation="vertical"
android:layout_span="4" >
<Spinner
android:id="@+id/presetSpinner"
android:layout_width="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>
<LinearLayout

@ -40,6 +40,13 @@ public class AndroidGlue extends MessageOutputProcessor {
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 {
System.loadLibrary("synth");
}

@ -18,6 +18,7 @@ package com.google.synthesizer.android.ui;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
@ -33,6 +34,7 @@ import android.hardware.usb.UsbManager;
import android.media.AudioManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
@ -117,6 +119,26 @@ public class PianoActivity2 extends Activity {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
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
@ -228,4 +250,6 @@ public class PianoActivity2 extends Activity {
private KnobView cutoffKnob_;
private KnobView resonanceKnob_;
private Spinner presetSpinner_;
private Handler statusHandler_;
private Runnable statusRunnable_;
}

@ -16,6 +16,8 @@
#include <assert.h>
#include <stddef.h>
#include <stdio.h>
#include <time.h>
#include <jni.h>
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
@ -29,6 +31,7 @@
#include "synth_unit.h"
RingBuffer *ring_buffer;
RingBuffer *stats_ring_buffer;
SynthUnit *synth_unit;
const int N_BUFFERS = 2;
@ -37,7 +40,6 @@ int buffer_size;
int16_t buffer[MAX_BUFFER_SIZE * N_BUFFERS];
int cur_buffer = 0;
int count = 0;
// engine interfaces
static SLObjectItf engineObject = NULL;
@ -52,15 +54,30 @@ static SLPlayItf bq_player_play;
static SLAndroidSimpleBufferQueueItf bq_player_buffer_queue;
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,
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;
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,
buf_ptr, buffer_size * 2);
assert(SL_RESULT_SUCCESS == result);
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() {
@ -122,6 +139,7 @@ Java_com_google_synthesizer_android_AndroidGlue_start(JNIEnv *env,
Freqlut::init(sample_rate);
Sin::init();
ring_buffer = new RingBuffer();
stats_ring_buffer = new RingBuffer();
synth_unit = new SynthUnit(ring_buffer);
for (int i = 0; i < N_BUFFERS - 1; ++i) {
BqPlayerCallback(bq_player_buffer_queue, NULL);
@ -153,6 +171,8 @@ Java_com_google_synthesizer_android_AndroidGlue_shutdown(JNIEnv *env,
}
delete ring_buffer;
ring_buffer = NULL;
delete stats_ring_buffer;
stats_ring_buffer = NULL;
delete synth_unit;
synth_unit = NULL;
}
@ -175,3 +195,28 @@ Java_com_google_synthesizer_android_AndroidGlue_setPlayState(JNIEnv *env,
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);
}
int RingBuffer::WriteBytesAvailable() {
return (rd_ix_ - wr_ix_ - 1) & (kBufSize - 1);
}
int RingBuffer::Read(int size, uint8_t *bytes) {
int rd_ix = rd_ix_;
SynthMemoryBarrier(); // read barrier, make sure data is committed before ix

@ -24,6 +24,9 @@ class RingBuffer {
// Returns number of bytes available for reading.
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
// size <= a previous value of BytesAvailable().
int Read(int size, uint8_t *bytes);
@ -34,8 +37,8 @@ class RingBuffer {
private:
static const int kBufSize = 8192;
uint8_t buf_[kBufSize];
volatile int rd_ix_;
volatile int wr_ix_;
volatile unsigned int rd_ix_;
volatile unsigned int wr_ix_;
};
#endif // SYNTH_RINGBUFFER_H_

Loading…
Cancel
Save