From 766b572a6426d0109294c1f3fd32302608ed6ed5 Mon Sep 17 00:00:00 2001 From: Raph Levien Date: Fri, 26 Apr 2013 22:18:19 -0700 Subject: [PATCH] Get audio parameters from platform On API 17+ (JellyBean MR1), get the buffer size and sample rate from the platform, and use that, rather than hardcoding the defaults. We're still using an internal buffer size (N) hardcoded to 64, so the amount of computation per callback is not as consistent as it would be if that were more flexible. --- android/AndroidManifest.xml | 2 +- android/project.properties | 2 +- .../synthesizer/android/AndroidGlue.java | 2 +- .../android/ui/PianoActivity2.java | 41 ++++++++++++++++++- cpp/src/android_glue.cc | 17 ++++---- cpp/src/synth_unit.cc | 28 ++++++++++++- cpp/src/synth_unit.h | 6 ++- 7 files changed, 82 insertions(+), 16 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index b8757cf..7110f1b 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -5,7 +5,7 @@ android:versionCode="2" android:versionName="0.9"> diff --git a/android/project.properties b/android/project.properties index 8937e94..a3ee5ab 100644 --- a/android/project.properties +++ b/android/project.properties @@ -11,4 +11,4 @@ #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt # Project target. -target=android-14 +target=android-17 diff --git a/android/src/com/google/synthesizer/android/AndroidGlue.java b/android/src/com/google/synthesizer/android/AndroidGlue.java index 9a86a64..5e33c51 100644 --- a/android/src/com/google/synthesizer/android/AndroidGlue.java +++ b/android/src/com/google/synthesizer/android/AndroidGlue.java @@ -14,7 +14,7 @@ public class AndroidGlue extends MessageOutputProcessor { /** * Create and initialize the engine. This should be done once per process. */ - public native void start(); + public native void start(int sample_rate, int buf_size); /** * Shut down the OpenSL ES engine and audio synthesizer. diff --git a/android/src/com/google/synthesizer/android/ui/PianoActivity2.java b/android/src/com/google/synthesizer/android/ui/PianoActivity2.java index efb66db..9173159 100644 --- a/android/src/com/google/synthesizer/android/ui/PianoActivity2.java +++ b/android/src/com/google/synthesizer/android/ui/PianoActivity2.java @@ -21,6 +21,7 @@ import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; +import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; import android.hardware.usb.UsbConstants; @@ -29,6 +30,7 @@ import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbEndpoint; import android.hardware.usb.UsbInterface; import android.hardware.usb.UsbManager; +import android.media.AudioManager; import android.os.Build; import android.os.Bundle; import android.util.Log; @@ -64,8 +66,15 @@ public class PianoActivity2 extends Activity { resonanceKnob_ = (KnobView)findViewById(R.id.resonanceKnob); presetSpinner_ = (Spinner)findViewById(R.id.presetSpinner); + AudioParams params = new AudioParams(44100, 384); + // TODO: for pre-JB-MR1 devices, do some matching against known devices to + // get best audio parameters. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + getJbMr1Params(params); + } + androidGlue_ = new AndroidGlue(); - androidGlue_.start(); + androidGlue_.start(params.sampleRate, params.bufferSize); InputStream patchIs = getResources().openRawResource(R.raw.rom1a); byte[] patchData = new byte[4104]; @@ -128,6 +137,7 @@ public class PianoActivity2 extends Activity { super.onResume(); } + @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1) private UsbEndpoint getInputEndpoint(UsbInterface usbIf) { int nEndpoints = usbIf.getEndpointCount(); for (int i = 0; i < nEndpoints; i++) { @@ -140,6 +150,7 @@ public class PianoActivity2 extends Activity { return null; } + @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1) private void startUsbThread(final UsbDeviceConnection connection, final UsbEndpoint endpoint) { Thread thread = new Thread(new Runnable() { public void run() { @@ -166,6 +177,7 @@ public class PianoActivity2 extends Activity { }); thread.start(); } + @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1) private void tryConnectUsb() { UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE); HashMap deviceList = usbManager.getDeviceList(); @@ -185,7 +197,32 @@ public class PianoActivity2 extends Activity { } } } - + + class AudioParams { + AudioParams(int sr, int bs) { + confident = false; + sampleRate = sr; + bufferSize = bs; + } + public String toString() { + return "sampleRate=" + sampleRate + " bufferSize=" + bufferSize; + } + boolean confident; + int sampleRate; + int bufferSize; + } + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) + void getJbMr1Params(AudioParams params) { + AudioManager audioManager = (AudioManager) this.getSystemService(Context.AUDIO_SERVICE); + String sr = audioManager.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE); + String bs = audioManager.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER); + params.confident = true; + params.sampleRate = Integer.parseInt(sr); + params.bufferSize = Integer.parseInt(bs); + //log("from platform: " + params); + } + private AndroidGlue androidGlue_; private PianoView piano_; private KnobView cutoffKnob_; diff --git a/cpp/src/android_glue.cc b/cpp/src/android_glue.cc index 8f6604b..0e647ee 100644 --- a/cpp/src/android_glue.cc +++ b/cpp/src/android_glue.cc @@ -32,9 +32,10 @@ RingBuffer *ring_buffer; SynthUnit *synth_unit; const int N_BUFFERS = 2; -const int BUFFER_SIZE = 384; +const int MAX_BUFFER_SIZE = 1024; +int buffer_size; -int16_t buffer[BUFFER_SIZE * N_BUFFERS]; +int16_t buffer[MAX_BUFFER_SIZE * N_BUFFERS]; int cur_buffer = 0; int count = 0; @@ -54,10 +55,10 @@ static SLBufferQueueItf buffer_queue_itf; extern "C" void BqPlayerCallback(SLAndroidSimpleBufferQueueItf queueItf, void *data) { if (count >= 1000) return; - int16_t *buf_ptr = buffer + BUFFER_SIZE * cur_buffer; - synth_unit->GetSamples(BUFFER_SIZE, buf_ptr); + int16_t *buf_ptr = buffer + buffer_size * cur_buffer; + synth_unit->GetSamples(buffer_size, buf_ptr); SLresult result = (*queueItf)->Enqueue(bq_player_buffer_queue, - buf_ptr, BUFFER_SIZE * 2); + buf_ptr, buffer_size * 2); assert(SL_RESULT_SUCCESS == result); cur_buffer = (cur_buffer + 1) % N_BUFFERS; } @@ -84,12 +85,12 @@ SLresult result; extern "C" JNIEXPORT void JNICALL Java_com_google_synthesizer_android_AndroidGlue_start(JNIEnv *env, - jobject thiz) { + jobject thiz, jint sample_rate, jint buf_size) { CreateEngine(); SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, N_BUFFERS}; SLDataFormat_PCM format_pcm = { - SL_DATAFORMAT_PCM, 1, SL_SAMPLINGRATE_44_1, + SL_DATAFORMAT_PCM, 1, sample_rate * 1000, SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16, SL_SPEAKER_FRONT_CENTER, SL_BYTEORDER_LITTLEENDIAN // TODO: compute real endianness @@ -117,7 +118,7 @@ Java_com_google_synthesizer_android_AndroidGlue_start(JNIEnv *env, &BqPlayerCallback, NULL); assert(SL_RESULT_SUCCESS == result); - double sample_rate = 44100.0; + buffer_size = buf_size; Freqlut::init(sample_rate); Sin::init(); ring_buffer = new RingBuffer(); diff --git a/cpp/src/synth_unit.cc b/cpp/src/synth_unit.cc index 63a59e2..f024d0a 100644 --- a/cpp/src/synth_unit.cc +++ b/cpp/src/synth_unit.cc @@ -18,6 +18,11 @@ #include #endif +#ifdef __ANDROID__ +#include +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "synth", __VA_ARGS__) +#endif + #include #include "synth.h" @@ -50,6 +55,7 @@ SynthUnit::SynthUnit(RingBuffer *ring_buffer) { filter_control_[0] = 258847126; filter_control_[1] = 0; sustain_ = false; + extra_buf_size_ = 0; } // Transfer as many bytes as possible from ring buffer to input buffer. @@ -193,7 +199,19 @@ void SynthUnit::GetSamples(int n_samples, int16_t *buffer) { } ConsumeInput(input_offset); - for (int i = 0; i < n_samples; i += N) { + int i; + for (i = 0; i < n_samples && i < extra_buf_size_; i++) { + buffer[i] = extra_buf_[i]; + } + if (extra_buf_size_ > n_samples) { + for (int j = 0; j < extra_buf_size_ - n_samples; j++) { + extra_buf_[j] = extra_buf_[j + n_samples]; + } + extra_buf_size_ -= n_samples; + return; + } + + for (; i < n_samples; i += N) { AlignedBuf audiobuf; int32_t audiobuf2[N]; for (int j = 0; j < N; ++j) { @@ -207,12 +225,18 @@ void SynthUnit::GetSamples(int n_samples, int16_t *buffer) { const int32_t *bufs[] = { audiobuf.get() }; int32_t *bufs2[] = { audiobuf2 }; filter_.process(bufs, filter_control_, filter_control_, bufs2); + int jmax = n_samples - i; for (int j = 0; j < N; ++j) { int32_t val = audiobuf2[j] >> 4; int clip_val = val < -(1 << 24) ? 0x8000 : val >= (1 << 24) ? 0x7fff : val >> 9; // TODO: maybe some dithering? - buffer[i + j] = clip_val; + if (j < jmax) { + buffer[i + j] = clip_val; + } else { + extra_buf_[j - jmax] = clip_val; + } } } + extra_buf_size_ = i - n_samples; } diff --git a/cpp/src/synth_unit.h b/cpp/src/synth_unit.h index fdcd3ed..31cea4f 100644 --- a/cpp/src/synth_unit.h +++ b/cpp/src/synth_unit.h @@ -42,7 +42,7 @@ class SynthUnit { int ProcessMidiMessage(const uint8_t *buf, int buf_size); RingBuffer *ring_buffer_; - static const int max_active_notes = 8; + static const int max_active_notes = 16; ActiveNote active_note_[max_active_notes]; int current_note_; uint8_t input_buffer_[8192]; @@ -54,4 +54,8 @@ class SynthUnit { ResoFilter filter_; int32_t filter_control_[2]; bool sustain_; + + // Extra buffering for when GetSamples wants a buffer not a multiple of N + int16_t extra_buf_[N]; + int extra_buf_size_; };