--- a/android-project/src/org/libsdl/app/SDLActivity.java Wed Aug 10 16:00:16 2016 -0400
+++ b/android-project/src/org/libsdl/app/SDLActivity.java Thu Aug 11 22:04:49 2016 -0400
@@ -59,6 +59,7 @@
// Audio
protected static AudioTrack mAudioTrack;
+ protected static AudioRecord mAudioRecord;
/**
* This method is called by SDL before loading the native shared libraries.
@@ -106,6 +107,7 @@
mJoystickHandler = null;
mSDLThread = null;
mAudioTrack = null;
+ mAudioRecord = null;
mExitCalledFromJava = false;
mBrokenLibraries = false;
mIsPaused = false;
@@ -544,7 +546,7 @@
/**
* This method is called by SDL using JNI.
*/
- public static int audioInit(int sampleRate, boolean is16Bit, boolean isStereo, int desiredFrames) {
+ public static int audioOpen(int sampleRate, boolean is16Bit, boolean isStereo, int desiredFrames) {
int channelConfig = isStereo ? AudioFormat.CHANNEL_CONFIGURATION_STEREO : AudioFormat.CHANNEL_CONFIGURATION_MONO;
int audioFormat = is16Bit ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT;
int frameSize = (isStereo ? 2 : 1) * (is16Bit ? 2 : 1);
@@ -623,13 +625,72 @@
/**
* This method is called by SDL using JNI.
*/
- public static void audioQuit() {
+ public static int captureOpen(int sampleRate, boolean is16Bit, boolean isStereo, int desiredFrames) {
+ int channelConfig = isStereo ? AudioFormat.CHANNEL_CONFIGURATION_STEREO : AudioFormat.CHANNEL_CONFIGURATION_MONO;
+ int audioFormat = is16Bit ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT;
+ int frameSize = (isStereo ? 2 : 1) * (is16Bit ? 2 : 1);
+
+ Log.v(TAG, "SDL capture: wanted " + (isStereo ? "stereo" : "mono") + " " + (is16Bit ? "16-bit" : "8-bit") + " " + (sampleRate / 1000f) + "kHz, " + desiredFrames + " frames buffer");
+
+ // Let the user pick a larger buffer if they really want -- but ye
+ // gods they probably shouldn't, the minimums are horrifyingly high
+ // latency already
+ desiredFrames = Math.max(desiredFrames, (AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat) + frameSize - 1) / frameSize);
+
+ if (mAudioRecord == null) {
+ mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, sampleRate,
+ channelConfig, audioFormat, desiredFrames * frameSize);
+
+ // see notes about AudioTrack state in audioOpen(), above. Probably also applies here.
+ if (mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
+ Log.e(TAG, "Failed during initialization of AudioRecord");
+ mAudioRecord.release();
+ mAudioRecord = null;
+ return -1;
+ }
+
+ mAudioRecord.startRecording();
+ }
+
+ Log.v(TAG, "SDL capture: got " + ((mAudioRecord.getChannelCount() >= 2) ? "stereo" : "mono") + " " + ((mAudioRecord.getAudioFormat() == AudioFormat.ENCODING_PCM_16BIT) ? "16-bit" : "8-bit") + " " + (mAudioRecord.getSampleRate() / 1000f) + "kHz, " + desiredFrames + " frames buffer");
+
+ return 0;
+ }
+
+ /** This method is called by SDL using JNI. */
+ public static int captureReadShortBuffer(short[] buffer, boolean blocking) {
+ // !!! FIXME: this is available in API Level 23. Until then, we always block. :(
+ //return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
+ return mAudioRecord.read(buffer, 0, buffer.length);
+ }
+
+ /** This method is called by SDL using JNI. */
+ public static int captureReadByteBuffer(byte[] buffer, boolean blocking) {
+ // !!! FIXME: this is available in API Level 23. Until then, we always block. :(
+ //return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
+ return mAudioRecord.read(buffer, 0, buffer.length);
+ }
+
+
+ /** This method is called by SDL using JNI. */
+ public static void audioClose() {
if (mAudioTrack != null) {
mAudioTrack.stop();
+ mAudioTrack.release();
mAudioTrack = null;
}
}
+ /** This method is called by SDL using JNI. */
+ public static void captureClose() {
+ if (mAudioRecord != null) {
+ mAudioRecord.stop();
+ mAudioRecord.release();
+ mAudioRecord = null;
+ }
+ }
+
+
// Input
/**
--- a/src/audio/android/SDL_androidaudio.c Wed Aug 10 16:00:16 2016 -0400
+++ b/src/audio/android/SDL_androidaudio.c Thu Aug 11 22:04:49 2016 -0400
@@ -24,6 +24,7 @@
/* Output audio to Android */
+#include "SDL_assert.h"
#include "SDL_audio.h"
#include "../SDL_audio_c.h"
#include "SDL_androidaudio.h"
@@ -33,24 +34,22 @@
#include <android/log.h>
static SDL_AudioDevice* audioDevice = NULL;
+static SDL_AudioDevice* captureDevice = NULL;
static int
AndroidAUD_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
{
SDL_AudioFormat test_format;
+ SDL_assert((captureDevice == NULL) || !iscapture);
+ SDL_assert((audioDevice == NULL) || iscapture);
+
if (iscapture) {
- /* TODO: implement capture */
- return SDL_SetError("Capture not supported on Android");
+ captureDevice = this;
+ } else {
+ audioDevice = this;
}
- /* !!! FIXME: higher level will prevent this now. Lose this check (and global?). */
- if (audioDevice != NULL) {
- return SDL_SetError("Only one audio device at a time please!");
- }
-
- audioDevice = this;
-
this->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, (sizeof *this->hidden));
if (this->hidden == NULL) {
return SDL_OutOfMemory();
@@ -83,15 +82,16 @@
this->spec.freq = 48000;
}
- /* TODO: pass in/return a (Java) device ID, also whether we're opening for input or output */
- this->spec.samples = Android_JNI_OpenAudioDevice(this->spec.freq, this->spec.format == AUDIO_U8 ? 0 : 1, this->spec.channels, this->spec.samples);
- SDL_CalculateAudioSpec(&this->spec);
+ /* TODO: pass in/return a (Java) device ID */
+ this->spec.samples = Android_JNI_OpenAudioDevice(iscapture, this->spec.freq, this->spec.format == AUDIO_U8 ? 0 : 1, this->spec.channels, this->spec.samples);
if (this->spec.samples == 0) {
/* Init failed? */
return SDL_SetError("Java-side initialization failed!");
}
+ SDL_CalculateAudioSpec(&this->spec);
+
return 0;
}
@@ -107,18 +107,33 @@
return Android_JNI_GetAudioBuffer();
}
+static int
+AndroidAUD_CaptureFromDevice(_THIS, void *buffer, int buflen)
+{
+ return Android_JNI_CaptureAudioBuffer(buffer, buflen);
+}
+
+static void
+AndroidAUD_FlushCapture(_THIS)
+{
+ Android_JNI_FlushCapturedAudio();
+}
+
static void
AndroidAUD_CloseDevice(_THIS)
{
/* At this point SDL_CloseAudioDevice via close_audio_device took care of terminating the audio thread
so it's safe to terminate the Java side buffer and AudioTrack
*/
- Android_JNI_CloseAudioDevice();
-
- if (audioDevice == this) {
- SDL_free(this->hidden);
+ Android_JNI_CloseAudioDevice(this->iscapture);
+ if (this->iscapture) {
+ SDL_assert(captureDevice == this);
+ captureDevice = NULL;
+ } else {
+ SDL_assert(audioDevice == this);
audioDevice = NULL;
}
+ SDL_free(this->hidden);
}
static int
@@ -129,9 +144,11 @@
impl->PlayDevice = AndroidAUD_PlayDevice;
impl->GetDeviceBuf = AndroidAUD_GetDeviceBuf;
impl->CloseDevice = AndroidAUD_CloseDevice;
+ impl->CaptureFromDevice = AndroidAUD_CaptureFromDevice;
+ impl->FlushCapture = AndroidAUD_FlushCapture;
/* and the capabilities */
- impl->HasCaptureSupport = 0; /* TODO */
+ impl->HasCaptureSupport = SDL_TRUE;
impl->OnlyHasDefaultOutputDevice = 1;
impl->OnlyHasDefaultCaptureDevice = 1;
@@ -159,6 +176,19 @@
private->resume = SDL_TRUE;
}
}
+
+ if(captureDevice != NULL && captureDevice->hidden != NULL) {
+ private = (struct SDL_PrivateAudioData *) captureDevice->hidden;
+ if (SDL_AtomicGet(&captureDevice->paused)) {
+ /* The device is already paused, leave it alone */
+ private->resume = SDL_FALSE;
+ }
+ else {
+ SDL_LockMutex(captureDevice->mixer_lock);
+ SDL_AtomicSet(&captureDevice->paused, 1);
+ private->resume = SDL_TRUE;
+ }
+ }
}
/* Resume (unblock) all non already paused audio devices by releasing their mixer lock */
@@ -174,6 +204,15 @@
SDL_UnlockMutex(audioDevice->mixer_lock);
}
}
+
+ if(captureDevice != NULL && captureDevice->hidden != NULL) {
+ private = (struct SDL_PrivateAudioData *) captureDevice->hidden;
+ if (private->resume) {
+ SDL_AtomicSet(&captureDevice->paused, 0);
+ private->resume = SDL_FALSE;
+ SDL_UnlockMutex(captureDevice->mixer_lock);
+ }
+ }
}
--- a/src/core/android/SDL_android.c Wed Aug 10 16:00:16 2016 -0400
+++ b/src/core/android/SDL_android.c Thu Aug 11 22:04:49 2016 -0400
@@ -71,10 +71,14 @@
/* method signatures */
static jmethodID midGetNativeSurface;
-static jmethodID midAudioInit;
+static jmethodID midAudioOpen;
static jmethodID midAudioWriteShortBuffer;
static jmethodID midAudioWriteByteBuffer;
-static jmethodID midAudioQuit;
+static jmethodID midAudioClose;
+static jmethodID midCaptureOpen;
+static jmethodID midCaptureReadShortBuffer;
+static jmethodID midCaptureReadByteBuffer;
+static jmethodID midCaptureClose;
static jmethodID midPollInputDevices;
/* Accelerometer data storage */
@@ -118,21 +122,31 @@
midGetNativeSurface = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
"getNativeSurface","()Landroid/view/Surface;");
- midAudioInit = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
- "audioInit", "(IZZI)I");
+ midAudioOpen = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
+ "audioOpen", "(IZZI)I");
midAudioWriteShortBuffer = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
"audioWriteShortBuffer", "([S)V");
midAudioWriteByteBuffer = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
"audioWriteByteBuffer", "([B)V");
- midAudioQuit = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
- "audioQuit", "()V");
+ midAudioClose = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
+ "audioClose", "()V");
+ midCaptureOpen = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
+ "captureOpen", "(IZZI)I");
+ midCaptureReadShortBuffer = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
+ "captureReadShortBuffer", "([SZ)I");
+ midCaptureReadByteBuffer = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
+ "captureReadByteBuffer", "([BZ)I");
+ midCaptureClose = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
+ "captureClose", "()V");
midPollInputDevices = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
"pollInputDevices", "()V");
bHasNewData = SDL_FALSE;
- if (!midGetNativeSurface || !midAudioInit ||
- !midAudioWriteShortBuffer || !midAudioWriteByteBuffer || !midAudioQuit || !midPollInputDevices) {
+ if (!midGetNativeSurface ||
+ !midAudioOpen || !midAudioWriteShortBuffer || !midAudioWriteByteBuffer || !midAudioClose || !
+ !midCaptureOpen || !midCaptureReadShortBuffer || !midCaptureReadByteBuffer || !midCaptureClose ||
+ !midPollInputDevices) {
__android_log_print(ANDROID_LOG_WARN, "SDL", "SDL: Couldn't locate Java callbacks, check that they're named and typed correctly");
}
__android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init() finished!");
@@ -556,11 +570,14 @@
static jboolean audioBuffer16Bit = JNI_FALSE;
static jobject audioBuffer = NULL;
static void* audioBufferPinned = NULL;
+static jboolean captureBuffer16Bit = JNI_FALSE;
+static jobject captureBuffer = NULL;
-int Android_JNI_OpenAudioDevice(int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames)
+int Android_JNI_OpenAudioDevice(int iscapture, int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames)
{
jboolean audioBufferStereo;
int audioBufferFrames;
+ jobject jbufobj = NULL;
jboolean isCopy;
JNIEnv *env = Android_JNI_GetEnv();
@@ -570,14 +587,24 @@
}
Android_JNI_SetupThread();
- __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device");
- audioBuffer16Bit = is16Bit;
audioBufferStereo = channelCount > 1;
- if ((*env)->CallStaticIntMethod(env, mActivityClass, midAudioInit, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames) != 0) {
- /* Error during audio initialization */
- __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: error on AudioTrack initialization!");
- return 0;
+ if (iscapture) {
+ __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device for capture");
+ captureBuffer16Bit = is16Bit;
+ if ((*env)->CallStaticIntMethod(env, mActivityClass, midCaptureOpen, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames) != 0) {
+ /* Error during audio initialization */
+ __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: error on AudioRecord initialization!");
+ return 0;
+ }
+ } else {
+ __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device for output");
+ audioBuffer16Bit = is16Bit;
+ if ((*env)->CallStaticIntMethod(env, mActivityClass, midAudioOpen, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames) != 0) {
+ /* Error during audio initialization */
+ __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: error on AudioTrack initialization!");
+ return 0;
+ }
}
/* Allocating the audio buffer from the Java side and passing it as the return value for audioInit no longer works on
@@ -586,31 +613,43 @@
if (is16Bit) {
jshortArray audioBufferLocal = (*env)->NewShortArray(env, desiredBufferFrames * (audioBufferStereo ? 2 : 1));
if (audioBufferLocal) {
- audioBuffer = (*env)->NewGlobalRef(env, audioBufferLocal);
+ jbufobj = (*env)->NewGlobalRef(env, audioBufferLocal);
(*env)->DeleteLocalRef(env, audioBufferLocal);
}
}
else {
jbyteArray audioBufferLocal = (*env)->NewByteArray(env, desiredBufferFrames * (audioBufferStereo ? 2 : 1));
if (audioBufferLocal) {
- audioBuffer = (*env)->NewGlobalRef(env, audioBufferLocal);
+ jbufobj = (*env)->NewGlobalRef(env, audioBufferLocal);
(*env)->DeleteLocalRef(env, audioBufferLocal);
}
}
- if (audioBuffer == NULL) {
+ if (jbufobj == NULL) {
__android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: could not allocate an audio buffer!");
return 0;
}
+ if (iscapture) {
+ captureBuffer = jbufobj;
+ } else {
+ audioBuffer = jbufobj;
+ }
+
isCopy = JNI_FALSE;
- if (audioBuffer16Bit) {
- audioBufferPinned = (*env)->GetShortArrayElements(env, (jshortArray)audioBuffer, &isCopy);
+
+ if (is16Bit) {
+ if (!iscapture) {
+ audioBufferPinned = (*env)->GetShortArrayElements(env, (jshortArray)audioBuffer, &isCopy);
+ }
audioBufferFrames = (*env)->GetArrayLength(env, (jshortArray)audioBuffer);
} else {
- audioBufferPinned = (*env)->GetByteArrayElements(env, (jbyteArray)audioBuffer, &isCopy);
+ if (!iscapture) {
+ audioBufferPinned = (*env)->GetByteArrayElements(env, (jbyteArray)audioBuffer, &isCopy);
+ }
audioBufferFrames = (*env)->GetArrayLength(env, (jbyteArray)audioBuffer);
}
+
if (audioBufferStereo) {
audioBufferFrames /= 2;
}
@@ -638,16 +677,73 @@
/* JNI_COMMIT means the changes are committed to the VM but the buffer remains pinned */
}
-void Android_JNI_CloseAudioDevice(void)
+int Android_JNI_CaptureAudioBuffer(void *buffer, int buflen)
+{
+ JNIEnv *env = Android_JNI_GetEnv();
+ jboolean isCopy = JNI_FALSE;
+ jint br;
+
+ if (captureBuffer16Bit) {
+ SDL_assert((*env)->GetArrayLength(env, (jshortArray)captureBuffer) == (buflen / 2));
+ br = (*env)->CallStaticIntMethod(env, mActivityClass, midCaptureReadShortBuffer, (jshortArray)captureBuffer, JNI_TRUE);
+ if (br > 0) {
+ jshort *ptr = (*env)->GetShortArrayElements(env, (jshortArray)captureBuffer, &isCopy);
+ br *= 2;
+ SDL_memcpy(buffer, ptr, br);
+ (*env)->ReleaseShortArrayElements(env, (jshortArray)captureBuffer, (jshort *)ptr, JNI_ABORT);
+ }
+ } else {
+ SDL_assert((*env)->GetArrayLength(env, (jshortArray)captureBuffer) == buflen);
+ br = (*env)->CallStaticIntMethod(env, mActivityClass, midCaptureReadByteBuffer, (jbyteArray)captureBuffer, JNI_TRUE);
+ if (br > 0) {
+ jbyte *ptr = (*env)->GetByteArrayElements(env, (jbyteArray)captureBuffer, &isCopy);
+ SDL_memcpy(buffer, ptr, br);
+ (*env)->ReleaseByteArrayElements(env, (jbyteArray)captureBuffer, (jbyte *)ptr, JNI_ABORT);
+ }
+ }
+
+ return (int) br;
+}
+
+void Android_JNI_FlushCapturedAudio(void)
+{
+ JNIEnv *env = Android_JNI_GetEnv();
+ #if 0 /* !!! FIXME: this needs API 23, or it'll do blocking reads and never end. */
+ if (captureBuffer16Bit) {
+ const jint len = (*env)->GetArrayLength(env, (jshortArray)captureBuffer);
+ while ((*env)->CallStaticIntMethod(env, mActivityClass, midCaptureReadShortBuffer, (jshortArray)captureBuffer, JNI_FALSE) == len) { /* spin */ }
+ } else {
+ const jint len = (*env)->GetArrayLength(env, (jbyteArray)captureBuffer);
+ while ((*env)->CallStaticIntMethod(env, mActivityClass, midCaptureReadByteBuffer, (jbyteArray)captureBuffer, JNI_FALSE) == len) { /* spin */ }
+ }
+ #else
+ if (captureBuffer16Bit) {
+ const jint len = (*env)->GetArrayLength(env, (jshortArray)captureBuffer);
+ (*env)->CallStaticIntMethod(env, mActivityClass, midCaptureReadShortBuffer, (jshortArray)captureBuffer, JNI_FALSE);
+ } else {
+ const jint len = (*env)->GetArrayLength(env, (jbyteArray)captureBuffer);
+ (*env)->CallStaticIntMethod(env, mActivityClass, midCaptureReadByteBuffer, (jbyteArray)captureBuffer, JNI_FALSE);
+ }
+ #endif
+}
+
+void Android_JNI_CloseAudioDevice(const int iscapture)
{
JNIEnv *env = Android_JNI_GetEnv();
- (*env)->CallStaticVoidMethod(env, mActivityClass, midAudioQuit);
-
- if (audioBuffer) {
- (*env)->DeleteGlobalRef(env, audioBuffer);
- audioBuffer = NULL;
- audioBufferPinned = NULL;
+ if (iscapture) {
+ (*env)->CallStaticVoidMethod(env, mActivityClass, midCaptureClose);
+ if (captureBuffer) {
+ (*env)->DeleteGlobalRef(env, captureBuffer);
+ captureBuffer = NULL;
+ }
+ } else {
+ (*env)->CallStaticVoidMethod(env, mActivityClass, midAudioClose);
+ if (audioBuffer) {
+ (*env)->DeleteGlobalRef(env, audioBuffer);
+ audioBuffer = NULL;
+ audioBufferPinned = NULL;
+ }
}
}