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.
master
Raph Levien 12 years ago
parent 7f9c21cd2e
commit 766b572a64
  1. 2
      android/AndroidManifest.xml
  2. 2
      android/project.properties
  3. 2
      android/src/com/google/synthesizer/android/AndroidGlue.java
  4. 39
      android/src/com/google/synthesizer/android/ui/PianoActivity2.java
  5. 17
      cpp/src/android_glue.cc
  6. 28
      cpp/src/synth_unit.cc
  7. 6
      cpp/src/synth_unit.h

@ -5,7 +5,7 @@
android:versionCode="2" android:versionCode="2"
android:versionName="0.9"> android:versionName="0.9">
<uses-sdk android:minSdkVersion="9" <uses-sdk android:minSdkVersion="9"
android:targetSdkVersion="14" android:targetSdkVersion="17"
/> <!-- 9 = Gingerbread --> /> <!-- 9 = Gingerbread -->
<uses-feature android:name="android.hardware.usb.host" /> <uses-feature android:name="android.hardware.usb.host" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

@ -11,4 +11,4 @@
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
# Project target. # Project target.
target=android-14 target=android-17

@ -14,7 +14,7 @@ public class AndroidGlue extends MessageOutputProcessor {
/** /**
* Create and initialize the engine. This should be done once per process. * 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. * Shut down the OpenSL ES engine and audio synthesizer.

@ -21,6 +21,7 @@ import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import android.annotation.TargetApi;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.hardware.usb.UsbConstants; import android.hardware.usb.UsbConstants;
@ -29,6 +30,7 @@ import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint; import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface; import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbManager; import android.hardware.usb.UsbManager;
import android.media.AudioManager;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log; import android.util.Log;
@ -64,8 +66,15 @@ public class PianoActivity2 extends Activity {
resonanceKnob_ = (KnobView)findViewById(R.id.resonanceKnob); resonanceKnob_ = (KnobView)findViewById(R.id.resonanceKnob);
presetSpinner_ = (Spinner)findViewById(R.id.presetSpinner); 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_ = new AndroidGlue();
androidGlue_.start(); androidGlue_.start(params.sampleRate, params.bufferSize);
InputStream patchIs = getResources().openRawResource(R.raw.rom1a); InputStream patchIs = getResources().openRawResource(R.raw.rom1a);
byte[] patchData = new byte[4104]; byte[] patchData = new byte[4104];
@ -128,6 +137,7 @@ public class PianoActivity2 extends Activity {
super.onResume(); super.onResume();
} }
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
private UsbEndpoint getInputEndpoint(UsbInterface usbIf) { private UsbEndpoint getInputEndpoint(UsbInterface usbIf) {
int nEndpoints = usbIf.getEndpointCount(); int nEndpoints = usbIf.getEndpointCount();
for (int i = 0; i < nEndpoints; i++) { for (int i = 0; i < nEndpoints; i++) {
@ -140,6 +150,7 @@ public class PianoActivity2 extends Activity {
return null; return null;
} }
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
private void startUsbThread(final UsbDeviceConnection connection, final UsbEndpoint endpoint) { private void startUsbThread(final UsbDeviceConnection connection, final UsbEndpoint endpoint) {
Thread thread = new Thread(new Runnable() { Thread thread = new Thread(new Runnable() {
public void run() { public void run() {
@ -166,6 +177,7 @@ public class PianoActivity2 extends Activity {
}); });
thread.start(); thread.start();
} }
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
private void tryConnectUsb() { private void tryConnectUsb() {
UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE); UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
HashMap<String, UsbDevice> deviceList = usbManager.getDeviceList(); HashMap<String, UsbDevice> deviceList = usbManager.getDeviceList();
@ -186,6 +198,31 @@ 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 AndroidGlue androidGlue_;
private PianoView piano_; private PianoView piano_;
private KnobView cutoffKnob_; private KnobView cutoffKnob_;

@ -32,9 +32,10 @@ RingBuffer *ring_buffer;
SynthUnit *synth_unit; SynthUnit *synth_unit;
const int N_BUFFERS = 2; 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 cur_buffer = 0;
int count = 0; int count = 0;
@ -54,10 +55,10 @@ static SLBufferQueueItf buffer_queue_itf;
extern "C" void BqPlayerCallback(SLAndroidSimpleBufferQueueItf queueItf, extern "C" void BqPlayerCallback(SLAndroidSimpleBufferQueueItf queueItf,
void *data) { void *data) {
if (count >= 1000) return; if (count >= 1000) return;
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);
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;
} }
@ -84,12 +85,12 @@ SLresult result;
extern "C" JNIEXPORT void JNICALL extern "C" JNIEXPORT void JNICALL
Java_com_google_synthesizer_android_AndroidGlue_start(JNIEnv *env, Java_com_google_synthesizer_android_AndroidGlue_start(JNIEnv *env,
jobject thiz) { jobject thiz, jint sample_rate, jint buf_size) {
CreateEngine(); CreateEngine();
SLDataLocator_AndroidSimpleBufferQueue loc_bufq = SLDataLocator_AndroidSimpleBufferQueue loc_bufq =
{SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, N_BUFFERS}; {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, N_BUFFERS};
SLDataFormat_PCM format_pcm = { 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_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16,
SL_SPEAKER_FRONT_CENTER, SL_BYTEORDER_LITTLEENDIAN SL_SPEAKER_FRONT_CENTER, SL_BYTEORDER_LITTLEENDIAN
// TODO: compute real endianness // TODO: compute real endianness
@ -117,7 +118,7 @@ Java_com_google_synthesizer_android_AndroidGlue_start(JNIEnv *env,
&BqPlayerCallback, NULL); &BqPlayerCallback, NULL);
assert(SL_RESULT_SUCCESS == result); assert(SL_RESULT_SUCCESS == result);
double sample_rate = 44100.0; buffer_size = buf_size;
Freqlut::init(sample_rate); Freqlut::init(sample_rate);
Sin::init(); Sin::init();
ring_buffer = new RingBuffer(); ring_buffer = new RingBuffer();

@ -18,6 +18,11 @@
#include <iostream> #include <iostream>
#endif #endif
#ifdef __ANDROID__
#include <android/log.h>
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "synth", __VA_ARGS__)
#endif
#include <string.h> #include <string.h>
#include "synth.h" #include "synth.h"
@ -50,6 +55,7 @@ SynthUnit::SynthUnit(RingBuffer *ring_buffer) {
filter_control_[0] = 258847126; filter_control_[0] = 258847126;
filter_control_[1] = 0; filter_control_[1] = 0;
sustain_ = false; sustain_ = false;
extra_buf_size_ = 0;
} }
// Transfer as many bytes as possible from ring buffer to input buffer. // 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); 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<int32_t, N> audiobuf; AlignedBuf<int32_t, N> audiobuf;
int32_t audiobuf2[N]; int32_t audiobuf2[N];
for (int j = 0; j < N; ++j) { 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() }; const int32_t *bufs[] = { audiobuf.get() };
int32_t *bufs2[] = { audiobuf2 }; int32_t *bufs2[] = { audiobuf2 };
filter_.process(bufs, filter_control_, filter_control_, bufs2); filter_.process(bufs, filter_control_, filter_control_, bufs2);
int jmax = n_samples - i;
for (int j = 0; j < N; ++j) { for (int j = 0; j < N; ++j) {
int32_t val = audiobuf2[j] >> 4; int32_t val = audiobuf2[j] >> 4;
int clip_val = val < -(1 << 24) ? 0x8000 : val >= (1 << 24) ? 0x7fff : int clip_val = val < -(1 << 24) ? 0x8000 : val >= (1 << 24) ? 0x7fff :
val >> 9; val >> 9;
// TODO: maybe some dithering? // 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;
} }

@ -42,7 +42,7 @@ class SynthUnit {
int ProcessMidiMessage(const uint8_t *buf, int buf_size); int ProcessMidiMessage(const uint8_t *buf, int buf_size);
RingBuffer *ring_buffer_; RingBuffer *ring_buffer_;
static const int max_active_notes = 8; static const int max_active_notes = 16;
ActiveNote active_note_[max_active_notes]; ActiveNote active_note_[max_active_notes];
int current_note_; int current_note_;
uint8_t input_buffer_[8192]; uint8_t input_buffer_[8192];
@ -54,4 +54,8 @@ class SynthUnit {
ResoFilter filter_; ResoFilter filter_;
int32_t filter_control_[2]; int32_t filter_control_[2];
bool sustain_; bool sustain_;
// Extra buffering for when GetSamples wants a buffer not a multiple of N
int16_t extra_buf_[N];
int extra_buf_size_;
}; };

Loading…
Cancel
Save