# HG changeset patch # User Sam Lantinga # Date 1294946060 28800 # Node ID 9f9bea41e88f8ff32368f5b887edcaf5c78e195f # Parent e4ed74189d63c3c07bf3190399022c4a73403f16 Working audio implementation contributed by Joseph Lunderville diff -r e4ed74189d63 -r 9f9bea41e88f android-project/src/org/libsdl/app/SDLActivity.java --- a/android-project/src/org/libsdl/app/SDLActivity.java Thu Jan 13 09:15:51 2011 -0800 +++ b/android-project/src/org/libsdl/app/SDLActivity.java Thu Jan 13 11:14:20 2011 -0800 @@ -29,11 +29,15 @@ private static SDLSurface mSurface; // Audio + private static Thread mAudioThread; private static AudioTrack mAudioTrack; // Load the .so static { System.loadLibrary("SDL"); + //System.loadLibrary("SDL_image"); + //System.loadLibrary("SDL_mixer"); + //System.loadLibrary("SDL_ttf"); System.loadLibrary("main"); } @@ -67,12 +71,13 @@ // C functions we call public static native void nativeInit(); public static native void nativeQuit(); + public static native void onNativeResize(int x, int y, int format); public static native void onNativeKeyDown(int keycode); public static native void onNativeKeyUp(int keycode); public static native void onNativeTouch(int action, float x, float y, float p); - public static native void onNativeResize(int x, int y, int format); public static native void onNativeAccel(float x, float y, float z); + public static native void nativeRunAudioThread(); // Java functions called from C @@ -84,23 +89,83 @@ mSurface.flipEGL(); } - public static void updateAudio(byte [] buf) { + // Audio + private static Object buf; + + public static Object audioInit(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("SDL", "SDL audio: wanted " + (isStereo ? "stereo" : "mono") + " " + (is16Bit ? "16-bit" : "8-bit") + " " + ((float)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, (AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat) + frameSize - 1) / frameSize); + + mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, + channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM); + + audioStartThread(); + + Log.v("SDL", "SDL audio: got " + ((mAudioTrack.getChannelCount() >= 2) ? "stereo" : "mono") + " " + ((mAudioTrack.getAudioFormat() == AudioFormat.ENCODING_PCM_16BIT) ? "16-bit" : "8-bit") + " " + ((float)mAudioTrack.getSampleRate() / 1000f) + "kHz, " + desiredFrames + " frames buffer"); + + if (is16Bit) { + buf = new short[desiredFrames * (isStereo ? 2 : 1)]; + } else { + buf = new byte[desiredFrames * (isStereo ? 2 : 1)]; + } + return buf; + } - if(mAudioTrack == null) { - // Hardcoded things are bad. FIXME when we have more sound stuff - // working properly. - mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, - 11025, - AudioFormat.CHANNEL_CONFIGURATION_MONO, - AudioFormat.ENCODING_PCM_8BIT, - 2048, - AudioTrack.MODE_STREAM); + public static void audioStartThread() { + mAudioThread = new Thread(new Runnable() { + public void run() { + mAudioTrack.play(); + nativeRunAudioThread(); + } + }); + + // I'd take REALTIME if I could get it! + mAudioThread.setPriority(Thread.MAX_PRIORITY); + mAudioThread.start(); + } + + public static void audioWriteShortBuffer(short[] buffer) { + for (int i = 0; i < buffer.length; ) { + int result = mAudioTrack.write(buffer, i, buffer.length - i); + if (result > 0) { + i += result; + } else if (result == 0) { + try { + Thread.sleep(10); + } catch(InterruptedException e) { + // Nom nom + } + } else { + Log.w("SDL", "SDL audio: error return from write(short)"); + return; + } } - - mAudioTrack.write(buf, 0, buf.length); - mAudioTrack.play(); - - Log.v("SDL", "Played some audio"); + } + + public static void audioWriteByteBuffer(byte[] buffer) { + for (int i = 0; i < buffer.length; ) { + int result = mAudioTrack.write(buffer, i, buffer.length - i); + if (result > 0) { + i += result; + } else if (result == 0) { + try { + Thread.sleep(10); + } catch(InterruptedException e) { + // Nom nom + } + } else { + Log.w("SDL", "SDL audio: error return from write(short)"); + return; + } + } } } @@ -279,7 +344,7 @@ } catch(Exception e) { Log.v("SDL", e + ""); - for(StackTraceElement s : e.getStackTrace()) { + for (StackTraceElement s : e.getStackTrace()) { Log.v("SDL", s.toString()); } } @@ -303,7 +368,7 @@ } catch(Exception e) { Log.v("SDL", "flipEGL(): " + e); - for(StackTraceElement s : e.getStackTrace()) { + for (StackTraceElement s : e.getStackTrace()) { Log.v("SDL", s.toString()); } } diff -r e4ed74189d63 -r 9f9bea41e88f src/SDL_android.cpp --- a/src/SDL_android.cpp Thu Jan 13 09:15:51 2011 -0800 +++ b/src/SDL_android.cpp Thu Jan 13 11:14:20 2011 -0800 @@ -27,7 +27,10 @@ #include "events/SDL_events_c.h" #include "video/android/SDL_androidkeyboard.h" #include "video/android/SDL_androidvideo.h" -} + +/* Impelemented in audio/android/SDL_androidaudio.c */ +extern void Android_RunAudioThread(); +} // C /******************************************************************************* This file links the Java side of Android with libsdl @@ -39,23 +42,21 @@ /******************************************************************************* Globals *******************************************************************************/ -JavaVM* mVM = NULL; -JNIEnv* mEnv = NULL; -JNIEnv* mAudioThreadEnv = NULL; //See the note below for why this is necessary +static JavaVM* mVM = NULL; +static JNIEnv* mEnv = NULL; +static JNIEnv* mAudioEnv = NULL; -//Main activity -jclass mActivityInstance; +// Main activity +static jclass mActivityInstance; -//method signatures -jmethodID midCreateGLContext; -jmethodID midFlipBuffers; -jmethodID midUpdateAudio; +// method signatures +static jmethodID midCreateGLContext; +static jmethodID midFlipBuffers; +static jmethodID midAudioInit; +static jmethodID midAudioWriteShortBuffer; +static jmethodID midAudioWriteByteBuffer; -//Feature IDs -static const int FEATURE_AUDIO = 1; -static const int FEATURE_ACCEL = 2; - -//Accelerometer data storage +// Accelerometer data storage float fLastAccelerometer[3]; @@ -82,42 +83,42 @@ mActivityInstance = cls; midCreateGLContext = mEnv->GetStaticMethodID(cls,"createGLContext","()V"); midFlipBuffers = mEnv->GetStaticMethodID(cls,"flipBuffers","()V"); - midUpdateAudio = mEnv->GetStaticMethodID(cls,"updateAudio","([B)V"); + midAudioInit = mEnv->GetStaticMethodID(cls, "audioInit", "(IZZI)Ljava/lang/Object;"); + midAudioWriteShortBuffer = mEnv->GetStaticMethodID(cls, "audioWriteShortBuffer", "([S)V"); + midAudioWriteByteBuffer = mEnv->GetStaticMethodID(cls, "audioWriteByteBuffer", "([B)V"); - if(!midCreateGLContext || !midFlipBuffers || !midUpdateAudio) { - __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL: Bad mids\n"); - } else { -#ifdef DEBUG - __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL: Good mids\n"); -#endif + if(!midCreateGLContext || !midFlipBuffers || !midAudioInit || + !midAudioWriteShortBuffer || !midAudioWriteByteBuffer) { + __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL: Couldn't locate Java callbacks, check that they're named and typed correctly"); } } -// Keydown -extern "C" void Java_org_libsdl_app_SDLActivity_onNativeKeyDown(JNIEnv* env, - jobject obj, jint keycode) +// Resize +extern "C" void Java_org_libsdl_app_SDLActivity_onNativeResize( + JNIEnv* env, jobject obj, + jint width, jint height, jint format) { -#ifdef DEBUG - __android_log_print(ANDROID_LOG_INFO, "SDL", - "SDL: native key down %d\n", keycode); -#endif + Android_SetScreenResolution(width, height, format); +} + +// Keydown +extern "C" void Java_org_libsdl_app_SDLActivity_onNativeKeyDown( + JNIEnv* env, jobject obj, jint keycode) +{ Android_OnKeyDown(keycode); } // Keyup -extern "C" void Java_org_libsdl_app_SDLActivity_onNativeKeyUp(JNIEnv* env, - jobject obj, jint keycode) +extern "C" void Java_org_libsdl_app_SDLActivity_onNativeKeyUp( + JNIEnv* env, jobject obj, jint keycode) { -#ifdef DEBUG - __android_log_print(ANDROID_LOG_INFO, "SDL", - "SDL: native key up %d\n", keycode); -#endif Android_OnKeyUp(keycode); } // Touch -extern "C" void Java_org_libsdl_app_SDLActivity_onNativeTouch(JNIEnv* env, - jobject obj, jint action, jfloat x, jfloat y, jfloat p) +extern "C" void Java_org_libsdl_app_SDLActivity_onNativeTouch( + JNIEnv* env, jobject obj, + jint action, jfloat x, jfloat y, jfloat p) { #ifdef DEBUG __android_log_print(ANDROID_LOG_INFO, "SDL", @@ -128,31 +129,30 @@ //TODO: Pass this off to the SDL multitouch stuff } -// Quit -extern "C" void Java_org_libsdl_app_SDLActivity_nativeQuit( JNIEnv* env, - jobject obj ) -{ - // Inject a SDL_QUIT event - SDL_SendQuit(); -} - -// Resize -extern "C" void Java_org_libsdl_app_SDLActivity_onNativeResize( - JNIEnv* env, jobject obj, jint width, - jint height, jint format) -{ - Android_SetScreenResolution(width, height, format); -} - +// Accelerometer extern "C" void Java_org_libsdl_app_SDLActivity_onNativeAccel( - JNIEnv* env, jobject obj, - jfloat x, jfloat y, jfloat z) + JNIEnv* env, jobject obj, + jfloat x, jfloat y, jfloat z) { fLastAccelerometer[0] = x; fLastAccelerometer[1] = y; fLastAccelerometer[2] = z; } +// Quit +extern "C" void Java_org_libsdl_app_SDLActivity_nativeQuit( + JNIEnv* env, jobject obj) +{ + // Inject a SDL_QUIT event + SDL_SendQuit(); +} + +extern "C" void Java_org_libsdl_app_SDLActivity_nativeRunAudioThread( + JNIEnv* env) +{ + mVM->AttachCurrentThread(&mAudioEnv, NULL); + Android_RunAudioThread(); +} /******************************************************************************* @@ -168,33 +168,81 @@ mEnv->CallStaticVoidMethod(mActivityInstance, midFlipBuffers); } -extern "C" void Android_JNI_UpdateAudioBuffer(unsigned char *buf, int len) +// +// Audio support +// +static jint audioBufferFrames = 0; +static bool audioBuffer16Bit = false; +static bool audioBufferStereo = false; + +static jobject audioBuffer; +static void * audioPinnedBuffer; + +extern "C" int Android_JNI_OpenAudioDevice(int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames) +{ + __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device"); + audioBuffer16Bit = is16Bit; + audioBufferStereo = channelCount > 1; + + audioBuffer = mEnv->CallStaticObjectMethod(mActivityInstance, midAudioInit, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames); + audioBuffer = mEnv->NewGlobalRef(audioBuffer); + + if (audioBuffer == NULL) { + __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: didn't get back a good audio buffer!"); + return 0; + } + + if (audioBufferStereo) { + audioBufferFrames = mEnv->GetArrayLength((jshortArray)audioBuffer) / 2; + } else { + audioBufferFrames = mEnv->GetArrayLength((jbyteArray)audioBuffer); + } + + return audioBufferFrames; +} + +extern "C" void * Android_JNI_PinAudioBuffer() { - //Annoyingly we can't just call into Java from any thread. Because the audio - //callback is dispatched from the SDL audio thread (that wasn't made from - //java, we have to do some magic here to let the JVM know about the thread. - //Because everything it touches on the Java side is static anyway, it's - //not a big deal, just annoying. - if(!mAudioThreadEnv) { - __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL: Need to set up audio thread env\n"); + jboolean isCopy = JNI_FALSE; + + if (audioPinnedBuffer != NULL) { + return audioPinnedBuffer; + } + + if (audioBuffer16Bit) { + audioPinnedBuffer = mAudioEnv->GetShortArrayElements((jshortArray)audioBuffer, &isCopy); + } else { + audioPinnedBuffer = mAudioEnv->GetByteArrayElements((jbyteArray)audioBuffer, &isCopy); + } + + return audioPinnedBuffer; +} - mVM->AttachCurrentThread(&mAudioThreadEnv, NULL); +extern "C" void Android_JNI_WriteAudioBufferAndUnpin() +{ + if (audioPinnedBuffer == NULL) { + return; + } - __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL: ok\n"); + if (audioBuffer16Bit) { + mAudioEnv->ReleaseShortArrayElements((jshortArray)audioBuffer, (jshort *)audioPinnedBuffer, JNI_COMMIT); + mAudioEnv->CallStaticVoidMethod(mActivityInstance, midAudioWriteShortBuffer, (jshortArray)audioBuffer); + } else { + mAudioEnv->ReleaseByteArrayElements((jbyteArray)audioBuffer, (jbyte *)audioPinnedBuffer, JNI_COMMIT); + mAudioEnv->CallStaticVoidMethod(mActivityInstance, midAudioWriteByteBuffer, (jbyteArray)audioBuffer); + } + + audioPinnedBuffer = NULL; +} + +extern "C" void Android_JNI_CloseAudioDevice() +{ + if (audioBuffer) { + mEnv->DeleteGlobalRef(audioBuffer); + audioBuffer = NULL; } - - jbyteArray arr = mAudioThreadEnv->NewByteArray(len); - //blah. We probably should rework this so we avoid the copy. - mAudioThreadEnv->SetByteArrayRegion(arr, 0, len, (jbyte *)buf); - - __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL: copied\n"); - - mAudioThreadEnv->CallStaticVoidMethod( mActivityInstance, - midUpdateAudio, arr ); - - __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL: invoked\n"); - + // TODO: Implement } /* vi: set ts=4 sw=4 expandtab: */ diff -r e4ed74189d63 -r 9f9bea41e88f src/SDL_android.h --- a/src/SDL_android.h Thu Jan 13 09:15:51 2011 -0800 +++ b/src/SDL_android.h Thu Jan 13 11:14:20 2011 -0800 @@ -31,7 +31,12 @@ /* Interface from the SDL library into the Android Java activity */ void Android_JNI_CreateContext(); void Android_JNI_SwapWindow(); -void Android_JNI_UpdateAudioBuffer(unsigned char *buf, int len); + +// Audio support +int Android_JNI_OpenAudioDevice(int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames); +void* Android_JNI_PinAudioBuffer(); +void Android_JNI_WriteAudioBufferAndUnpin(); +void Android_JNI_CloseAudioDevice(); /* Ends C function definitions when using C++ */ #ifdef __cplusplus diff -r e4ed74189d63 -r 9f9bea41e88f src/audio/android/SDL_androidaudio.c --- a/src/audio/android/SDL_androidaudio.c Thu Jan 13 09:15:51 2011 -0800 +++ b/src/audio/android/SDL_androidaudio.c Thu Jan 13 11:14:20 2011 -0800 @@ -18,8 +18,6 @@ Sam Lantinga slouken@libsdl.org - - This file written by Ryan C. Gordon (icculus@icculus.org) */ #include "SDL_config.h" @@ -28,18 +26,31 @@ #include "SDL_audio.h" #include "../SDL_audio_c.h" #include "SDL_androidaudio.h" + #include "../../SDL_android.h" #include +static void * audioDevice; + static int AndroidAUD_OpenDevice(_THIS, const char *devname, int iscapture) { - SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format); + SDL_AudioFormat test_format; int valid_datatype = 0; - //TODO: Sample rates etc - __android_log_print(ANDROID_LOG_INFO, "SDL", "AndroidAudio Open\n"); + if (iscapture) { + //TODO: implement capture + SDL_SetError("Capture not supported on Android"); + return 0; + } + + if (audioDevice != NULL) { + SDL_SetError("Only one audio device at a time please!"); + return 0; + } + + audioDevice = this; this->hidden = SDL_malloc(sizeof(*(this->hidden))); if (!this->hidden) { @@ -48,68 +59,78 @@ } SDL_memset(this->hidden, 0, (sizeof *this->hidden)); - while ((!valid_datatype) && (test_format)) { - this->spec.format = test_format; - switch (test_format) { - case AUDIO_S8: - /*case AUDIO_S16LSB: */ - valid_datatype = 1; - break; - default: - test_format = SDL_NextAudioFormat(); + test_format = SDL_FirstAudioFormat(this->spec.format); + while (test_format != 0) { // no "UNKNOWN" constant + if ((test_format == AUDIO_U8) || (test_format == AUDIO_S16LSB)) { + this->spec.format = test_format; break; } + test_format = SDL_NextAudioFormat(); } + if (test_format == 0) { + // Didn't find a compatible format :( + SDL_SetError("No compatible audio format!"); + return 0; + } + + if (this->spec.channels > 1) { + this->spec.channels = 2; + } else { + this->spec.channels = 1; + } + + if (this->spec.freq < 8000) { + this->spec.freq = 8000; + } + if (this->spec.freq > 48000) { + 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); + + if (this->spec.samples == 0) { + // Init failed? + SDL_SetError("Java-side initialization failed!"); + return 0; + } + return 1; } static void AndroidAUD_PlayDevice(_THIS) { - __android_log_print(ANDROID_LOG_INFO, "SDL", "AndroidAudio Play\n"); - - - //playGenericSound(this->hidden->mixbuf, this->hidden->mixlen); - -#if 0 - -// sound->rate = 22050; /* sample rate = 22050Hz */ -// sound->vol = 127; /* volume [0..127] for [min..max] */ -// sound->pan = 64; /* balance [0..127] for [left..right] */ -// sound->format = 0; /* 0 for 16-bit, 1 for 8-bit */ -// playSound(sound); -#endif + Android_JNI_WriteAudioBufferAndUnpin(); + this->hidden->mixbuf = NULL; } - static Uint8 * AndroidAUD_GetDeviceBuf(_THIS) { - //__android_log_print(ANDROID_LOG_INFO, "SDL", "****** get device buf\n"); - - - // sound->data = this->hidden->mixbuf;/* pointer to raw audio data */ -// sound->len = this->hidden->mixlen; /* size of raw data pointed to above */ - - - Android_JNI_UpdateAudioBuffer(this->hidden->mixbuf, this->hidden->mixlen); - - return this->hidden->mixbuf; /* is this right? */ -} - -static void -AndroidAUD_WaitDevice(_THIS) -{ - /* stub */ - __android_log_print(ANDROID_LOG_INFO, "SDL", "****** wait device buf\n"); + if (this->hidden->mixbuf == NULL) { + this->hidden->mixbuf = Android_JNI_PinAudioBuffer(); + } + return this->hidden->mixbuf; } static void AndroidAUD_CloseDevice(_THIS) { - /* stub */ - __android_log_print(ANDROID_LOG_INFO, "SDL", "****** close device buf\n"); + if (this->hidden != NULL) { + if (this->hidden->mixbuf != NULL) { + Android_JNI_WriteAudioBufferAndUnpin(); + } + SDL_free(this->hidden); + this->hidden = NULL; + } + Android_JNI_CloseAudioDevice(); + + if (audioDevice == this) { + audioDevice = NULL; + } } static int @@ -118,17 +139,15 @@ /* Set the function pointers */ impl->OpenDevice = AndroidAUD_OpenDevice; impl->PlayDevice = AndroidAUD_PlayDevice; - impl->WaitDevice = AndroidAUD_WaitDevice; impl->GetDeviceBuf = AndroidAUD_GetDeviceBuf; impl->CloseDevice = AndroidAUD_CloseDevice; /* and the capabilities */ + impl->ProvidesOwnCallbackThread = 1; impl->HasCaptureSupport = 0; //TODO impl->OnlyHasDefaultOutputDevice = 1; impl->OnlyHasDefaultInputDevice = 1; - __android_log_print(ANDROID_LOG_INFO, "SDL","Audio init\n"); - return 1; /* this audio target is available. */ } @@ -136,4 +155,11 @@ "android", "SDL Android audio driver", AndroidAUD_Init, 0 /*1? */ }; +/* Called by the Java code to start the audio processing on a thread */ +void +Android_RunAudioThread() +{ + SDL_RunAudio(audioDevice); +} + /* vi: set ts=4 sw=4 expandtab: */ diff -r e4ed74189d63 -r 9f9bea41e88f src/audio/android/SDL_androidaudio.h --- a/src/audio/android/SDL_androidaudio.h Thu Jan 13 09:15:51 2011 -0800 +++ b/src/audio/android/SDL_androidaudio.h Thu Jan 13 11:14:20 2011 -0800 @@ -34,9 +34,8 @@ /* The file descriptor for the audio device */ Uint8 *mixbuf; Uint32 mixlen; - Uint32 write_delay; - Uint32 initial_calls; }; #endif /* _SDL_androidaudio_h */ + /* vi: set ts=4 sw=4 expandtab: */