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 );
+}