From 865e4f619b453675c62750b1d9da084db8e80cff Mon Sep 17 00:00:00 2001 From: pelya Date: Thu, 10 Dec 2009 16:34:51 +0200 Subject: [PATCH] Small fix for crashing, made audio to use Java byte array instead of copying bytes, and made it to use array size returned from Java - seems to add latency to the audio --- .../project/jni/alienblaster/src/surfaceDB.cc | 11 ++ .../sdl/src/audio/android/SDL_androidaudio.c | 159 +++++++++++++----- alienblaster/project/src/DemoActivity.java | 31 +++- 3 files changed, 155 insertions(+), 46 deletions(-) diff --git a/alienblaster/project/jni/alienblaster/src/surfaceDB.cc b/alienblaster/project/jni/alienblaster/src/surfaceDB.cc index 8715e8e71..da0ed2031 100644 --- a/alienblaster/project/jni/alienblaster/src/surfaceDB.cc +++ b/alienblaster/project/jni/alienblaster/src/surfaceDB.cc @@ -22,6 +22,9 @@ using namespace std; #include "surfaceDB.h" #include #include +#ifdef ANDROID +#include +#endif SurfaceDB surfaceDB; @@ -56,6 +59,14 @@ SDL_Surface *SurfaceDB::loadSurface( string fn, bool alpha ) { } SDL_Surface *newSurface = SDL_LoadBMP( fn.c_str() ); + if( newSurface == NULL ) + { + cout << "ERROR: Cannot load image " << fn << endl; +#ifdef ANDROID + __android_log_print(ANDROID_LOG_ERROR, "Alien Blaster", (string( "Cannot load image " ) + fn).c_str() ); +#endif + exit(1); + } SDL_SetColorKey( newSurface, SDL_SRCCOLORKEY, SDL_MapRGB(newSurface->format, transR, transG, transB) ); if ( alpha ) { diff --git a/alienblaster/project/jni/sdl/src/audio/android/SDL_androidaudio.c b/alienblaster/project/jni/sdl/src/audio/android/SDL_androidaudio.c index 2a15df319..0562b561a 100644 --- a/alienblaster/project/jni/sdl/src/audio/android/SDL_androidaudio.c +++ b/alienblaster/project/jni/sdl/src/audio/android/SDL_androidaudio.c @@ -98,16 +98,14 @@ AudioBootStrap ANDROIDAUD_bootstrap = { static SDL_mutex * audioMutex = NULL; static SDL_cond * audioCond = NULL; +static SDL_cond * audioCond2 = NULL; static unsigned char * audioBuffer = NULL; static size_t audioBufferSize = 0; static SDL_AudioSpec *audioFormat = NULL; static int audioInitialized = 0; - -/* This function waits until it is possible to write a full sound buffer */ -static void ANDROIDAUD_WaitAudio(_THIS) -{ - /* We will block in PlayAudio(), do nothing here */ -} +static int audioPlayed = 0; +static jbyteArray audioBufferJNI = NULL; +static JNIEnv * jniEnv = NULL; static Uint8 *ANDROIDAUD_GetAudioBuf(_THIS) { @@ -117,25 +115,38 @@ static Uint8 *ANDROIDAUD_GetAudioBuf(_THIS) static void ANDROIDAUD_CloseAudio(_THIS) { SDL_mutex * audioMutex1; - + + /* if ( this->hidden->mixbuf != NULL ) { SDL_FreeAudioMem(this->hidden->mixbuf); this->hidden->mixbuf = NULL; } + */ if( audioMutex != NULL ) { audioMutex1 = audioMutex; SDL_mutexP(audioMutex1); audioInitialized = 0; SDL_CondSignal(audioCond); + SDL_CondSignal(audioCond2); audioMutex = NULL; SDL_DestroyCond(audioCond); + SDL_DestroyCond(audioCond2); audioCond = NULL; + audioCond2 = NULL; audioFormat = NULL; + // TODO: this crashes JNI, so we're just memleaking it + /* + (*jniEnv)->ReleaseByteArrayElements(jniEnv, audioBufferJNI, (jbyte *)audioBuffer, 0); + (*jniEnv)->DeleteGlobalRef(jniEnv, audioBufferJNI); + */ + jniEnv = NULL; + audioBufferJNI = NULL; audioBuffer = NULL; audioBufferSize = 0; SDL_mutexV(audioMutex1); SDL_DestroyMutex(audioMutex1); + } } @@ -143,23 +154,15 @@ static int ANDROIDAUD_OpenAudio(_THIS, SDL_AudioSpec *spec) { if( ! (spec->format == AUDIO_S8 || spec->format == AUDIO_S16) ) return (-1); // TODO: enable format conversion? Don't know how to do that in SDL - - /* Allocate mixing buffer */ - this->hidden->mixlen = spec->size; - this->hidden->mixbuf = (Uint8 *) SDL_AllocAudioMem(this->hidden->mixlen); - if ( this->hidden->mixbuf == NULL ) { - return(-1); - } - SDL_memset(this->hidden->mixbuf, spec->silence, spec->size); - + if( audioMutex == NULL ) { audioInitialized = 0; audioFormat = spec; - audioBuffer = this->hidden->mixbuf; - audioBufferSize = this->hidden->mixlen; audioMutex = SDL_CreateMutex(); audioCond = SDL_CreateCond(); + audioCond2 = SDL_CreateCond(); + audioPlayed == 0; } SDL_mutexP(audioMutex); @@ -175,6 +178,9 @@ static int ANDROIDAUD_OpenAudio(_THIS, SDL_AudioSpec *spec) } } + this->hidden->mixbuf = audioBuffer; + this->hidden->mixlen = audioBufferSize; + audioFormat = NULL; SDL_mutexV(audioMutex); @@ -182,16 +188,26 @@ static int ANDROIDAUD_OpenAudio(_THIS, SDL_AudioSpec *spec) return(0); } +/* This function waits until it is possible to write a full sound buffer */ +static void ANDROIDAUD_WaitAudio(_THIS) +{ + /* We will block in PlayAudio(), do nothing here */ +} + static void ANDROIDAUD_PlayAudio(_THIS) { SDL_mutexP(audioMutex); - audioBuffer = this->hidden->mixbuf; - audioBufferSize = this->hidden->mixlen; - - while( audioBuffer != NULL ) - SDL_CondWait( audioCond, audioMutex ); + //audioBuffer = this->hidden->mixbuf; + //audioBufferSize = this->hidden->mixlen; + audioPlayed = 1; + + SDL_CondSignal(audioCond2); + SDL_CondWaitTimeout( audioCond, audioMutex, 1000 ); + + this->hidden->mixbuf = audioBuffer; + SDL_mutexV(audioMutex); } @@ -216,12 +232,12 @@ extern jintArray JAVA_EXPORT_NAME(AudioThread_nativeAudioInit) (JNIEnv * env, jo { initData[0] = audioFormat->freq; initData[1] = audioFormat->channels; - initData[2] = ( audioFormat->format == AUDIO_S16 ) ? 1 : 0; + int bytesPerSample = (audioFormat->format & 0xFF) / 8; + initData[2] = ( bytesPerSample == 2 ) ? 1 : 0; + audioFormat->format = ( bytesPerSample == 2 ) ? AUDIO_S16 : AUDIO_S8; initData[3] = audioFormat->size; ret=(*env)->NewIntArray(env, 4); (*env)->SetIntArrayRegion(env, ret, 0, 4, (jint *)initData); - audioInitialized = 1; - SDL_CondSignal(audioCond); } SDL_mutexV(audioMutex); @@ -229,33 +245,100 @@ extern jintArray JAVA_EXPORT_NAME(AudioThread_nativeAudioInit) (JNIEnv * env, jo return (ret); }; -extern jint JAVA_EXPORT_NAME(AudioThread_nativeAudioBuffer) ( JNIEnv * env, jobject jobj, jbyteArray data ) +extern jobject JAVA_EXPORT_NAME(AudioThread_nativeAudioInit2) (JNIEnv * env, jobject jobj, jbyteArray buf) +{ + jobject ret = NULL; + + if( audioMutex == NULL ) + return; + + SDL_mutexP(audioMutex); + + if( audioInitialized == 0 ) + { + /* Allocate mixing buffer */ + audioBufferJNI = (jbyteArray*)(*env)->NewGlobalRef(env, buf); + audioBufferSize = (*env)->GetArrayLength(env, audioBufferJNI); + jboolean isCopy = JNI_TRUE; + audioBuffer = (unsigned char *) (*env)->GetByteArrayElements(env, audioBufferJNI, &isCopy); + if( isCopy == JNI_TRUE ) + __android_log_print(ANDROID_LOG_ERROR, "libSDL", "AudioThread_nativeAudioInit2() JNI returns a copy of byte array - no audio will be played"); + + jniEnv = env; + + int bytesPerSample = (audioFormat->format & 0xFF) / 8; + audioFormat->samples = audioBufferSize / bytesPerSample / audioFormat->channels; + audioFormat->size = audioBufferSize; + SDL_memset(audioBuffer, audioFormat->silence, audioFormat->size); + char t[512]; + //sprintf(t, "AudioThread_nativeAudioInit2() got byte array from JNI: size %i samples %i direct memory %i", audioBufferSize, audioFormat->samples, (isCopy == JNI_FALSE) ); + __android_log_print(ANDROID_LOG_INFO, "libSDL", t); + + /* + audioBuffer = (Uint8 *) SDL_AllocAudioMem(audioBufferSize); + if ( audioBuffer == NULL ) { + SDL_mutexV(audioMutex); + return NULL; + } + + ret = (*env)->NewDirectByteBuffer(env, audioBuffer, audioBufferSize); + */ + + audioInitialized = 1; + SDL_CondSignal(audioCond); + } + + SDL_mutexV(audioMutex); + + return 0; +} + +extern jint JAVA_EXPORT_NAME(AudioThread_nativeAudioBufferLock) ( JNIEnv * env, jobject jobj ) { int ret = 0; if( audioMutex == NULL ) - return; + return(-1); SDL_mutexP(audioMutex); if( !audioInitialized ) - ret = -1; - - if( audioBuffer == NULL ) { - ret = 0; + SDL_mutexV(audioMutex); + SDL_CondSignal(audioCond); + return (-1); } + + if( audioPlayed == 0 ) + SDL_CondWaitTimeout(audioCond2, audioMutex, 1000); + + if( audioBuffer == NULL ) // Should not happen + ret = 0; else { - (*env)->SetByteArrayRegion(env, data, 0, audioBufferSize, (jbyte *)audioBuffer); + (*jniEnv)->ReleaseByteArrayElements(jniEnv, audioBufferJNI, (jbyte *)audioBuffer, 0); + audioBuffer == NULL; ret = audioBufferSize; - audioBuffer = NULL; - audioBufferSize = 0; - SDL_CondSignal(audioCond); } - SDL_mutexV(audioMutex); - return ret; }; +extern jint JAVA_EXPORT_NAME(AudioThread_nativeAudioBufferUnlock) ( JNIEnv * env, jobject jobj ) +{ + if( audioMutex == NULL ) + return(-1); + + jboolean isCopy = JNI_TRUE; + audioBuffer = (unsigned char *) (*env)->GetByteArrayElements(env, audioBufferJNI, &isCopy); + if( isCopy == JNI_TRUE ) + __android_log_print(ANDROID_LOG_INFO, "libSDL", "AudioThread_nativeAudioBufferUnlock() JNI returns a copy of byte array - that's slow"); + + audioPlayed = 0; + + SDL_mutexV(audioMutex); + SDL_CondSignal(audioCond); + + return 0; +} + diff --git a/alienblaster/project/src/DemoActivity.java b/alienblaster/project/src/DemoActivity.java index b3c262937..bf5777499 100644 --- a/alienblaster/project/src/DemoActivity.java +++ b/alienblaster/project/src/DemoActivity.java @@ -63,6 +63,7 @@ import org.apache.http.impl.*; import org.apache.http.impl.client.*; import java.util.zip.*; import java.io.*; +import java.nio.ByteBuffer; // TODO: export vibrator to SDL - interface is available in SDL 1.3 @@ -164,13 +165,14 @@ class AudioThread extends Thread { private Activity mParent; private AudioTrack mAudio; private byte[] mAudioBuffer; + private ByteBuffer mAudioBufferNative; public AudioThread(Activity parent) { - android.util.Log.i("SDL Java", "AudioThread created"); mParent = parent; mAudio = null; mAudioBuffer = null; + this.setPriority(Thread.MAX_PRIORITY); this.start(); } @@ -197,10 +199,11 @@ class AudioThread extends Thread { int encoding = initParams[2]; encoding = ( encoding == 1 ) ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT; - int bufSize = AudioTrack.getMinBufferSize( rate, channels, encoding); + int bufSize = AudioTrack.getMinBufferSize( rate, channels, encoding ); if( initParams[3] > bufSize ) bufSize = initParams[3]; mAudioBuffer = new byte[bufSize]; + nativeAudioInit2(mAudioBuffer); mAudio = new AudioTrack(AudioManager.STREAM_MUSIC, rate, channels, @@ -212,19 +215,26 @@ class AudioThread extends Thread { } else { - int len = nativeAudioBuffer( mAudioBuffer ); + int len = nativeAudioBufferLock(); if( len > 0 ) mAudio.write( mAudioBuffer, 0, len ); if( len < 0 ) break; + nativeAudioBufferUnlock(); } } if( mAudio != null ) + { mAudio.stop(); + mAudio.release(); + mAudio = null; + } } private static native int[] nativeAudioInit(); - private static native int nativeAudioBuffer( byte[] data ); + private static native int nativeAudioInit2(byte[] buf); + private static native int nativeAudioBufferLock(); + private static native int nativeAudioBufferUnlock(); } @@ -313,7 +323,7 @@ public class DemoActivity extends Activity { - class DataDownloader extends Thread +class DataDownloader extends Thread { class StatusWriter { @@ -365,9 +375,9 @@ public class DemoActivity extends Activity { } catch( SecurityException e ) { }; if( checkFile != null ) { - Status.setText( "Already downloaded" ); + Status.setText( "No need to download" ); DownloadComplete = true; - Parent.initSDL(); + initParent(); return; } checkFile = null; @@ -487,6 +497,11 @@ public class DemoActivity extends Activity { Status.setText( "Finished" ); DownloadComplete = true; + initParent(); + }; + + private void initParent() + { class Callback implements Runnable { public DemoActivity Parent; @@ -498,7 +513,7 @@ public class DemoActivity extends Activity { Callback cb = new Callback(); cb.Parent = Parent; Parent.runOnUiThread(cb); - }; + } private String getOutFilePath(final String filename) {