From 6852234e929ae52e28bd1da57f3cf65a1d57789c Mon Sep 17 00:00:00 2001 From: pelya Date: Sun, 10 Mar 2013 23:31:41 +0200 Subject: [PATCH] WIP audio recording, it crashes yet. --- changeAppSettings.sh | 17 +++ project/AndroidManifestTemplate.xml | 4 +- project/java/Audio.java | 126 +++++++++++++++++- .../openarena/AndroidAppSettings.cfg | 5 +- .../jni/application/openarena/AndroidBuild.sh | 2 +- project/jni/sdl-1.2/include/SDL_android.h | 13 ++ .../src/audio/android/SDL_androidaudio.c | 68 +++++++++- 7 files changed, 219 insertions(+), 16 deletions(-) diff --git a/changeAppSettings.sh b/changeAppSettings.sh index 143342cf9..149abdbd8 100755 --- a/changeAppSettings.sh +++ b/changeAppSettings.sh @@ -359,6 +359,18 @@ if [ -n "$var" ] ; then fi fi +if [ -z "$AppRecordsAudio" -o -z "$AUTO" ]; then +echo +echo "Application records audio (it will use any available source, such a s microphone)" +echo "API is defined in file SDL_android.h: int SDL_ANDROID_OpenAudioRecording(SDL_AudioSpec *spec); void SDL_ANDROID_CloseAudioRecording(void);" +echo -n "This option will add additional permission to Android manifest ($AppRecordsAudio): " +read var +if [ -n "$var" ] ; then + AppRecordsAudio="$var" + CHANGED=1 +fi +fi + if [ -z "$NonBlockingSwapBuffers" -o -z "$AUTO" ]; then echo echo "Application implements Android-specific routines to put to background, and will not draw anything to screen" @@ -723,6 +735,7 @@ echo AppUsesJoystick=$AppUsesJoystick >> AndroidAppSettings.cfg echo AppUsesAccelerometer=$AppUsesAccelerometer >> AndroidAppSettings.cfg echo AppUsesGyroscope=$AppUsesGyroscope >> AndroidAppSettings.cfg echo AppUsesMultitouch=$AppUsesMultitouch >> AndroidAppSettings.cfg +echo AppRecordsAudio=$AppRecordsAudio >> AndroidAppSettings.cfg echo NonBlockingSwapBuffers=$NonBlockingSwapBuffers >> AndroidAppSettings.cfg echo RedefinedKeys=\"$RedefinedKeys\" >> AndroidAppSettings.cfg echo AppTouchscreenKeyboardKeysAmount=$AppTouchscreenKeyboardKeysAmount >> AndroidAppSettings.cfg @@ -1004,6 +1017,10 @@ else cat $F | sed "s/package .*;/package $AppFullName;/" >> project/src/Advertisement.java fi +if [ "$AppRecordsAudio" = "n" -o -z "$AppRecordsAudio" ] ; then + sed -i "/==RECORD_AUDIO==/ d" project/AndroidManifest.xml +fi + case "$MinimumScreenSize" in n|m) sed -i "/==SCREEN-SIZE-SMALL==/ d" project/AndroidManifest.xml diff --git a/project/AndroidManifestTemplate.xml b/project/AndroidManifestTemplate.xml index 594a9a789..7d3a640d2 100644 --- a/project/AndroidManifestTemplate.xml +++ b/project/AndroidManifestTemplate.xml @@ -29,9 +29,11 @@ + - + + diff --git a/project/java/Audio.java b/project/java/Audio.java index b98e4c2fc..a7d365bdb 100644 --- a/project/java/Audio.java +++ b/project/java/Audio.java @@ -32,6 +32,8 @@ import android.view.WindowManager; import android.media.AudioTrack; import android.media.AudioManager; import android.media.AudioFormat; +import android.media.AudioRecord; +import android.media.MediaRecorder.AudioSource; import java.io.*; import android.util.Log; import java.lang.Thread; @@ -132,9 +134,12 @@ class AudioThread if( mAudio != null ) { mAudio.pause(); - return 1; } - return 0; + if( mRecorder != null ) + { + mRecorder.stop(); + } + return 1; } public int resumeAudioPlayback() @@ -142,11 +147,118 @@ class AudioThread if( mAudio != null ) { mAudio.play(); - return 1; } - return 0; + if( mRecorder != null ) + { + mRecorder.startRecording(); + } + return 1; } - - private native int nativeAudioInitJavaCallbacks(); -} + private native int nativeAudioInitJavaCallbacks(); + + // ----- Audio recording ----- + + private AudioRecord mRecorder = null; + private RecordingThread mRecordThread = null; + //private int mRecordSize; + //private int mRecordPos; + + private int startRecording(int rate, int channels, int encoding, int bufsize) + { + //mRecordSize = bufsize; + //mRecordPos = 0; + if( mRecordThread != null ) + { + System.out.println("SDL: error: application already opened audio recording device"); + return 0; + } + channels = ( channels == 1 ) ? AudioFormat.CHANNEL_IN_MONO : + AudioFormat.CHANNEL_IN_STEREO; + encoding = ( encoding == 1 ) ? AudioFormat.ENCODING_PCM_16BIT : + AudioFormat.ENCODING_PCM_8BIT; + + int minBufDevice = AudioRecord.getMinBufferSize(rate, channels, encoding); + int minBufferSize = Math.max(bufsize * 8, minBufDevice + (bufsize - (minBufDevice % bufsize))); + System.out.println("SDL: app opened recording device, rate " + rate + " channels " + channels + " sample size " + (encoding+1) + " bufsize " + bufsize + " internal bufsize " + minBufferSize); + try { + mRecorder = new AudioRecord(AudioSource.DEFAULT, rate, channels, encoding, minBufferSize); + } catch (IllegalArgumentException e) { + System.out.println("SDL: error: failed to open recording device!"); + return 0; + } + mRecordThread = new RecordingThread(bufsize); + mRecorder.startRecording(); + mRecordThread.start(); + return minBufferSize; + } + + private void stopRecording() + { + if( mRecordThread == null ) + { + System.out.println("SDL: error: application already closed audio recording device"); + return; + } + mRecordThread.terminate = true; + while( !mRecordThread.stopped ) + { + try{ + Thread.sleep(100); + } catch (InterruptedException e) {} + } + mRecordThread = null; + mRecorder.stop(); + mRecorder.release(); + mRecorder = null; + System.out.println("SDL: app closed recording device"); + } + + private class RecordingThread extends Thread + { + public boolean terminate = false; + public boolean stopped = false; + private byte[] mRecordBuffer; + + RecordingThread(int bufsize) + { + super(); + mRecordBuffer = new byte[bufsize]; + } + + public void run() + { + while( !terminate ) + { + int got = mRecorder.read(mRecordBuffer, 0, mRecordBuffer.length); + if( got != mRecordBuffer.length ) + { + System.out.println("SDL: warning: RecordingThread: mRecorder.read returned short byte count " + got + " for bufsize " + mRecordBuffer.length); + // TODO: record in a loop? + } + System.out.println("SDL: nativeAudioRecordCallback with len " + mRecordBuffer.length); + nativeAudioRecordCallback(mRecordBuffer); + System.out.println("SDL: nativeAudioRecordCallback returned"); + } + stopped = true; + } + } + + private native void nativeAudioRecordCallback(byte[] buffer); + + /* + private int recordRead() + { + mRecordPos += mRecordSize; + if( mRecordPos >= mRecordBuffer.length ) + mRecordPos = 0; + mRecorder.read(mRecordBuffer, mRecordPos, mRecordSize); + return mRecordPos; + } + + public byte[] getRecordBuffer() + { + return mRecordBuffer; + } + */ +} diff --git a/project/jni/application/openarena/AndroidAppSettings.cfg b/project/jni/application/openarena/AndroidAppSettings.cfg index f9d5a4f09..c6005cf64 100644 --- a/project/jni/application/openarena/AndroidAppSettings.cfg +++ b/project/jni/application/openarena/AndroidAppSettings.cfg @@ -31,6 +31,7 @@ AppUsesJoystick=y AppUsesAccelerometer=n AppUsesGyroscope=y AppUsesMultitouch=y +AppRecordsAudio=y NonBlockingSwapBuffers=n RedefinedKeys="SPACE SPACE NO_REMAP NO_REMAP RETURN ESCAPE LCTRL" AppTouchscreenKeyboardKeysAmount=5 @@ -42,8 +43,8 @@ HiddenMenuOptions='OptionalDownloadConfig DisplaySizeConfig' FirstStartMenuOptions='' MultiABI=y AppMinimumRAM=300 -AppVersionCode=08820 -AppVersionName="0.8.8.20" +AppVersionCode=08821 +AppVersionName="0.8.8.21" ResetSdlConfigForThisVersion=n DeleteFilesOnUpgrade="libsdl-DownloadFinished-10.flag" CompiledLibraries="sdl_mixer sdl_image freetype curl vorbis ogg" diff --git a/project/jni/application/openarena/AndroidBuild.sh b/project/jni/application/openarena/AndroidBuild.sh index 0aaacfeed..9d93ed9d0 100755 --- a/project/jni/application/openarena/AndroidBuild.sh +++ b/project/jni/application/openarena/AndroidBuild.sh @@ -24,7 +24,7 @@ env NO_SHARED_LIBS=1 BUILD_EXECUTABLE=1 V=1 ../setEnvironment-armeabi.sh make -C ../setEnvironment-$1.sh make -j8 -C engine release \ PLATFORM=android ARCH=$1 USE_GLES=1 USE_LOCAL_HEADERS=0 \ -USE_OPENAL=0 USE_CURL=1 USE_CURL_DLOPEN=0 USE_CODEC_VORBIS=1 USE_MUMBLE=0 USE_FREETYPE=1 \ +USE_OPENAL=0 USE_VOIP=1 USE_CURL=1 USE_CURL_DLOPEN=0 USE_CODEC_VORBIS=1 USE_MUMBLE=0 USE_FREETYPE=1 \ USE_RENDERER_DLOPEN=0 USE_INTERNAL_ZLIB=0 USE_INTERNAL_JPEG=1 BUILD_RENDERER_REND2=0 && \ echo "Copying engine/build/release-android-$1/openarena.$1 -> libapplication-$1.so" && \ cp -f engine/build/release-android-$1/openarena.$1 libapplication-$1.so || exit 1 diff --git a/project/jni/sdl-1.2/include/SDL_android.h b/project/jni/sdl-1.2/include/SDL_android.h index 1acee67a7..d93b1294a 100644 --- a/project/jni/sdl-1.2/include/SDL_android.h +++ b/project/jni/sdl-1.2/include/SDL_android.h @@ -25,6 +25,7 @@ #include "SDL_video.h" #include "SDL_screenkeyboard.h" +#include "SDL_audio.h" #include #include "begin_code.h" @@ -82,6 +83,18 @@ extern DECLSPEC int SDLCALL SDL_ANDROID_RequestNewAdvertisement(void); extern DECLSPEC JNIEnv* SDL_ANDROID_JniEnv(); extern DECLSPEC jobject SDL_ANDROID_JniVideoObject(); +/* +Open audio recording device, it will use parameters freq, format, channels, size and callback, +and return internal buffer size on success, which you may ignore, +because your callback will always be called with buffer size you specified. +Returns 0 on failure (most probably hardware does not support requested audio rate). +SDL_Init(SDL_INIT_AUDIO) has to be done before calling this function. +*/ +extern DECLSPEC int SDLCALL SDL_ANDROID_OpenAudioRecording(SDL_AudioSpec *spec); +/* Close audio recording device, SDL_Init(SDL_INIT_AUDIO) has to be done before calling this function. */ +extern DECLSPEC void SDLCALL SDL_ANDROID_CloseAudioRecording(void); + + #ifdef __cplusplus } #endif diff --git a/project/jni/sdl-1.2/src/audio/android/SDL_androidaudio.c b/project/jni/sdl-1.2/src/audio/android/SDL_androidaudio.c index e63e98cfd..f53cc4fa3 100644 --- a/project/jni/sdl-1.2/src/audio/android/SDL_androidaudio.c +++ b/project/jni/sdl-1.2/src/audio/android/SDL_androidaudio.c @@ -146,6 +146,12 @@ static jmethodID JavaDeinitAudio = NULL; static jmethodID JavaPauseAudioPlayback = NULL; static jmethodID JavaResumeAudioPlayback = NULL; +// Audio recording + +static SDL_AudioSpec recording; +static jmethodID JavaStartRecording = NULL; +static jmethodID JavaStopRecording = NULL; + static Uint8 *ANDROIDAUD_GetAudioBuf(_THIS) { @@ -199,7 +205,7 @@ static int ANDROIDAUD_OpenAudio (_THIS, SDL_AudioSpec *spec) if( !jniEnv ) { __android_log_print(ANDROID_LOG_ERROR, "libSDL", "ANDROIDAUD_OpenAudio: Java VM AttachCurrentThread() failed"); - return (-1); // TODO: enable format conversion? Don't know how to do that in SDL + return (-1); } // The returned audioBufferSize may be huge, up to 100 Kb for 44100 because user may have selected large audio buffer to get rid of choppy sound @@ -312,6 +318,10 @@ static void ANDROIDAUD_ThreadInit(_THIS) //__android_log_print(ANDROID_LOG_INFO, "libSDL", "ANDROIDAUD_ThreadInit()"); SDL_memset(audioBuffer, this->spec.silence, this->spec.size); + + // Audio recording + JavaStartRecording = (*jniEnvPlaying)->GetMethodID(jniEnvPlaying, JavaAudioThreadClass, "startRecording", "(IIII)I"); + JavaStopRecording = (*jniEnvPlaying)->GetMethodID(jniEnvPlaying, JavaAudioThreadClass, "stopRecording", "()V"); }; static void ANDROIDAUD_ThreadDeinit(_THIS) @@ -322,19 +332,19 @@ static void ANDROIDAUD_ThreadDeinit(_THIS) static void ANDROIDAUD_SendAudioToJava(void) { //__android_log_print(ANDROID_LOG_INFO, "libSDL", "ANDROIDAUD_PlayAudio()"); - jboolean isCopy = JNI_TRUE; + //jboolean isCopy = JNI_TRUE; (*jniEnvPlaying)->ReleaseByteArrayElements(jniEnvPlaying, audioBufferJNI, (jbyte *)audioBuffer, 0); audioBuffer = NULL; (*jniEnvPlaying)->CallIntMethod( jniEnvPlaying, JavaAudioThread, JavaFillBuffer ); - audioBuffer = (unsigned char *) (*jniEnvPlaying)->GetByteArrayElements(jniEnvPlaying, audioBufferJNI, &isCopy); + audioBuffer = (unsigned char *) (*jniEnvPlaying)->GetByteArrayElements(jniEnvPlaying, audioBufferJNI, NULL); if( !audioBuffer ) __android_log_print(ANDROID_LOG_ERROR, "libSDL", "ANDROIDAUD_PlayAudio() JNI::GetByteArrayElements() failed! we will crash now"); - if( isCopy == JNI_TRUE ) - __android_log_print(ANDROID_LOG_INFO, "libSDL", "ANDROIDAUD_PlayAudio() JNI returns a copy of byte array - that's slow"); + //if( isCopy == JNI_TRUE ) + // __android_log_print(ANDROID_LOG_INFO, "libSDL", "ANDROIDAUD_PlayAudio() JNI returns a copy of byte array - that's slow"); } #ifdef SDL_AUDIO_APP_IGNORES_RETURNED_BUFFER_SIZE @@ -403,3 +413,51 @@ JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) jniVM = vm; }; +// ----- Audio recording ----- + +JNIEXPORT void JNICALL JAVA_EXPORT_NAME(AudioThread_nativeAudioRecordCallback) (JNIEnv * jniEnv, jobject thiz, jbyteArray jbuffer) +{ + int len = (*jniEnv)->GetArrayLength(jniEnv, jbuffer); + Uint8 *buffer = (Uint8 *) (*jniEnv)->GetByteArrayElements(jniEnv, jbuffer, NULL); + if( !buffer ) + __android_log_print(ANDROID_LOG_ERROR, "libSDL", "AudioThread_nativeAudioRecordCallbacks() JNI::GetByteArrayElements() failed! we will crash now"); + + __android_log_print(ANDROID_LOG_INFO, "libSDL", "AudioThread_nativeAudioRecordCallbacks(): got buffer %p len %d", buffer, len); + + recording.callback(recording.userdata, buffer, len); + + (*jniEnv)->ReleaseByteArrayElements(jniEnv, jbuffer, (jbyte *)buffer, 0); +} + +extern DECLSPEC int SDLCALL SDL_ANDROID_OpenAudioRecording(SDL_AudioSpec *spec) +{ + JNIEnv * jniEnv = NULL; + + recording = *spec; + + if( ! (recording.format == AUDIO_S8 || recording.format == AUDIO_S16) ) + { + __android_log_print(ANDROID_LOG_ERROR, "libSDL", "Application requested unsupported audio format - only S8 and S16 are supported"); + return 0; + } + + if( ! recording.callback ) + { + __android_log_print(ANDROID_LOG_ERROR, "libSDL", "Application did not provide callback"); + return 0; + } + + (*jniVM)->AttachCurrentThread(jniVM, &jniEnv, NULL); + + return (*jniEnv)->CallIntMethod( jniEnv, JavaAudioThread, JavaStartRecording, (jint)recording.freq, (jint)recording.channels, + (jint)(recording.format == AUDIO_S16 ? 1 : 0), (jint)recording.size ); +} + +extern DECLSPEC void SDLCALL SDL_ANDROID_CloseAudioRecording(void) +{ + JNIEnv * jniEnv = NULL; + + (*jniVM)->AttachCurrentThread(jniVM, &jniEnv, NULL); + + (*jniEnv)->CallVoidMethod( jniEnv, JavaAudioThread, JavaStopRecording ); +}