android: implement audio capture support.
authorRyan C. Gordon <icculus@icculus.org>
Thu, 11 Aug 2016 22:04:49 -0400
changeset 10280985f92b2f8e9
parent 10279 f797806699e6
child 10281 2a002e96888f
android: implement audio capture support.
android-project/AndroidManifest.xml
android-project/src/org/libsdl/app/SDLActivity.java
src/audio/android/SDL_androidaudio.c
src/core/android/SDL_android.c
src/core/android/SDL_android.h
     1.1 --- a/android-project/AndroidManifest.xml	Wed Aug 10 16:00:16 2016 -0400
     1.2 +++ b/android-project/AndroidManifest.xml	Thu Aug 11 22:04:49 2016 -0400
     1.3 @@ -17,6 +17,9 @@
     1.4      <!-- Allow writing to external storage -->
     1.5      <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     1.6  
     1.7 +    <!-- if you want to capture audio, uncomment this. -->
     1.8 +    <!-- <uses-permission android:name="android.permission.RECORD_AUDIO" /> -->
     1.9 +
    1.10      <!-- Create a Java class extending SDLActivity and place it in a
    1.11           directory under src matching the package, e.g.
    1.12           	src/com/gamemaker/game/MyGame.java
     2.1 --- a/android-project/src/org/libsdl/app/SDLActivity.java	Wed Aug 10 16:00:16 2016 -0400
     2.2 +++ b/android-project/src/org/libsdl/app/SDLActivity.java	Thu Aug 11 22:04:49 2016 -0400
     2.3 @@ -59,6 +59,7 @@
     2.4  
     2.5      // Audio
     2.6      protected static AudioTrack mAudioTrack;
     2.7 +    protected static AudioRecord mAudioRecord;
     2.8  
     2.9      /**
    2.10       * This method is called by SDL before loading the native shared libraries.
    2.11 @@ -106,6 +107,7 @@
    2.12          mJoystickHandler = null;
    2.13          mSDLThread = null;
    2.14          mAudioTrack = null;
    2.15 +        mAudioRecord = null;
    2.16          mExitCalledFromJava = false;
    2.17          mBrokenLibraries = false;
    2.18          mIsPaused = false;
    2.19 @@ -544,7 +546,7 @@
    2.20      /**
    2.21       * This method is called by SDL using JNI.
    2.22       */
    2.23 -    public static int audioInit(int sampleRate, boolean is16Bit, boolean isStereo, int desiredFrames) {
    2.24 +    public static int audioOpen(int sampleRate, boolean is16Bit, boolean isStereo, int desiredFrames) {
    2.25          int channelConfig = isStereo ? AudioFormat.CHANNEL_CONFIGURATION_STEREO : AudioFormat.CHANNEL_CONFIGURATION_MONO;
    2.26          int audioFormat = is16Bit ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT;
    2.27          int frameSize = (isStereo ? 2 : 1) * (is16Bit ? 2 : 1);
    2.28 @@ -623,13 +625,72 @@
    2.29      /**
    2.30       * This method is called by SDL using JNI.
    2.31       */
    2.32 -    public static void audioQuit() {
    2.33 +    public static int captureOpen(int sampleRate, boolean is16Bit, boolean isStereo, int desiredFrames) {
    2.34 +        int channelConfig = isStereo ? AudioFormat.CHANNEL_CONFIGURATION_STEREO : AudioFormat.CHANNEL_CONFIGURATION_MONO;
    2.35 +        int audioFormat = is16Bit ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT;
    2.36 +        int frameSize = (isStereo ? 2 : 1) * (is16Bit ? 2 : 1);
    2.37 +
    2.38 +        Log.v(TAG, "SDL capture: wanted " + (isStereo ? "stereo" : "mono") + " " + (is16Bit ? "16-bit" : "8-bit") + " " + (sampleRate / 1000f) + "kHz, " + desiredFrames + " frames buffer");
    2.39 +
    2.40 +        // Let the user pick a larger buffer if they really want -- but ye
    2.41 +        // gods they probably shouldn't, the minimums are horrifyingly high
    2.42 +        // latency already
    2.43 +        desiredFrames = Math.max(desiredFrames, (AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat) + frameSize - 1) / frameSize);
    2.44 +
    2.45 +        if (mAudioRecord == null) {
    2.46 +            mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, sampleRate,
    2.47 +                    channelConfig, audioFormat, desiredFrames * frameSize);
    2.48 +
    2.49 +            // see notes about AudioTrack state in audioOpen(), above. Probably also applies here.
    2.50 +            if (mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
    2.51 +                Log.e(TAG, "Failed during initialization of AudioRecord");
    2.52 +                mAudioRecord.release();
    2.53 +                mAudioRecord = null;
    2.54 +                return -1;
    2.55 +            }
    2.56 +
    2.57 +            mAudioRecord.startRecording();
    2.58 +        }
    2.59 +
    2.60 +        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");
    2.61 +
    2.62 +        return 0;
    2.63 +    }
    2.64 +
    2.65 +    /** This method is called by SDL using JNI. */
    2.66 +    public static int captureReadShortBuffer(short[] buffer, boolean blocking) {
    2.67 +        // !!! FIXME: this is available in API Level 23. Until then, we always block.  :(
    2.68 +        //return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
    2.69 +        return mAudioRecord.read(buffer, 0, buffer.length);
    2.70 +    }
    2.71 +
    2.72 +    /** This method is called by SDL using JNI. */
    2.73 +    public static int captureReadByteBuffer(byte[] buffer, boolean blocking) {
    2.74 +        // !!! FIXME: this is available in API Level 23. Until then, we always block.  :(
    2.75 +        //return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
    2.76 +        return mAudioRecord.read(buffer, 0, buffer.length);
    2.77 +    }
    2.78 +
    2.79 +
    2.80 +    /** This method is called by SDL using JNI. */
    2.81 +    public static void audioClose() {
    2.82          if (mAudioTrack != null) {
    2.83              mAudioTrack.stop();
    2.84 +            mAudioTrack.release();
    2.85              mAudioTrack = null;
    2.86          }
    2.87      }
    2.88  
    2.89 +    /** This method is called by SDL using JNI. */
    2.90 +    public static void captureClose() {
    2.91 +        if (mAudioRecord != null) {
    2.92 +            mAudioRecord.stop();
    2.93 +            mAudioRecord.release();
    2.94 +            mAudioRecord = null;
    2.95 +        }
    2.96 +    }
    2.97 +
    2.98 +
    2.99      // Input
   2.100  
   2.101      /**
     3.1 --- a/src/audio/android/SDL_androidaudio.c	Wed Aug 10 16:00:16 2016 -0400
     3.2 +++ b/src/audio/android/SDL_androidaudio.c	Thu Aug 11 22:04:49 2016 -0400
     3.3 @@ -24,6 +24,7 @@
     3.4  
     3.5  /* Output audio to Android */
     3.6  
     3.7 +#include "SDL_assert.h"
     3.8  #include "SDL_audio.h"
     3.9  #include "../SDL_audio_c.h"
    3.10  #include "SDL_androidaudio.h"
    3.11 @@ -33,24 +34,22 @@
    3.12  #include <android/log.h>
    3.13  
    3.14  static SDL_AudioDevice* audioDevice = NULL;
    3.15 +static SDL_AudioDevice* captureDevice = NULL;
    3.16  
    3.17  static int
    3.18  AndroidAUD_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
    3.19  {
    3.20      SDL_AudioFormat test_format;
    3.21  
    3.22 +    SDL_assert((captureDevice == NULL) || !iscapture);
    3.23 +    SDL_assert((audioDevice == NULL) || iscapture);
    3.24 +
    3.25      if (iscapture) {
    3.26 -        /* TODO: implement capture */
    3.27 -        return SDL_SetError("Capture not supported on Android");
    3.28 +        captureDevice = this;
    3.29 +    } else {
    3.30 +        audioDevice = this;
    3.31      }
    3.32  
    3.33 -    /* !!! FIXME: higher level will prevent this now. Lose this check (and global?). */
    3.34 -    if (audioDevice != NULL) {
    3.35 -        return SDL_SetError("Only one audio device at a time please!");
    3.36 -    }
    3.37 -
    3.38 -    audioDevice = this;
    3.39 -    
    3.40      this->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, (sizeof *this->hidden));
    3.41      if (this->hidden == NULL) {
    3.42          return SDL_OutOfMemory();
    3.43 @@ -83,15 +82,16 @@
    3.44          this->spec.freq = 48000;
    3.45      }
    3.46  
    3.47 -    /* TODO: pass in/return a (Java) device ID, also whether we're opening for input or output */
    3.48 -    this->spec.samples = Android_JNI_OpenAudioDevice(this->spec.freq, this->spec.format == AUDIO_U8 ? 0 : 1, this->spec.channels, this->spec.samples);
    3.49 -    SDL_CalculateAudioSpec(&this->spec);
    3.50 +    /* TODO: pass in/return a (Java) device ID */
    3.51 +    this->spec.samples = Android_JNI_OpenAudioDevice(iscapture, this->spec.freq, this->spec.format == AUDIO_U8 ? 0 : 1, this->spec.channels, this->spec.samples);
    3.52  
    3.53      if (this->spec.samples == 0) {
    3.54          /* Init failed? */
    3.55          return SDL_SetError("Java-side initialization failed!");
    3.56      }
    3.57  
    3.58 +    SDL_CalculateAudioSpec(&this->spec);
    3.59 +
    3.60      return 0;
    3.61  }
    3.62  
    3.63 @@ -107,18 +107,33 @@
    3.64      return Android_JNI_GetAudioBuffer();
    3.65  }
    3.66  
    3.67 +static int
    3.68 +AndroidAUD_CaptureFromDevice(_THIS, void *buffer, int buflen)
    3.69 +{
    3.70 +    return Android_JNI_CaptureAudioBuffer(buffer, buflen);
    3.71 +}
    3.72 +
    3.73 +static void
    3.74 +AndroidAUD_FlushCapture(_THIS)
    3.75 +{
    3.76 +    Android_JNI_FlushCapturedAudio();
    3.77 +}
    3.78 +
    3.79  static void
    3.80  AndroidAUD_CloseDevice(_THIS)
    3.81  {
    3.82      /* At this point SDL_CloseAudioDevice via close_audio_device took care of terminating the audio thread
    3.83         so it's safe to terminate the Java side buffer and AudioTrack
    3.84       */
    3.85 -    Android_JNI_CloseAudioDevice();
    3.86 -
    3.87 -    if (audioDevice == this) {
    3.88 -        SDL_free(this->hidden);
    3.89 +    Android_JNI_CloseAudioDevice(this->iscapture);
    3.90 +    if (this->iscapture) {
    3.91 +        SDL_assert(captureDevice == this);
    3.92 +        captureDevice = NULL;
    3.93 +    } else {
    3.94 +        SDL_assert(audioDevice == this);
    3.95          audioDevice = NULL;
    3.96      }
    3.97 +    SDL_free(this->hidden);
    3.98  }
    3.99  
   3.100  static int
   3.101 @@ -129,9 +144,11 @@
   3.102      impl->PlayDevice = AndroidAUD_PlayDevice;
   3.103      impl->GetDeviceBuf = AndroidAUD_GetDeviceBuf;
   3.104      impl->CloseDevice = AndroidAUD_CloseDevice;
   3.105 +    impl->CaptureFromDevice = AndroidAUD_CaptureFromDevice;
   3.106 +    impl->FlushCapture = AndroidAUD_FlushCapture;
   3.107  
   3.108      /* and the capabilities */
   3.109 -    impl->HasCaptureSupport = 0; /* TODO */
   3.110 +    impl->HasCaptureSupport = SDL_TRUE;
   3.111      impl->OnlyHasDefaultOutputDevice = 1;
   3.112      impl->OnlyHasDefaultCaptureDevice = 1;
   3.113  
   3.114 @@ -159,6 +176,19 @@
   3.115              private->resume = SDL_TRUE;
   3.116          }
   3.117      }
   3.118 +
   3.119 +    if(captureDevice != NULL && captureDevice->hidden != NULL) {
   3.120 +        private = (struct SDL_PrivateAudioData *) captureDevice->hidden;
   3.121 +        if (SDL_AtomicGet(&captureDevice->paused)) {
   3.122 +            /* The device is already paused, leave it alone */
   3.123 +            private->resume = SDL_FALSE;
   3.124 +        }
   3.125 +        else {
   3.126 +            SDL_LockMutex(captureDevice->mixer_lock);
   3.127 +            SDL_AtomicSet(&captureDevice->paused, 1);
   3.128 +            private->resume = SDL_TRUE;
   3.129 +        }
   3.130 +    }
   3.131  }
   3.132  
   3.133  /* Resume (unblock) all non already paused audio devices by releasing their mixer lock */
   3.134 @@ -174,6 +204,15 @@
   3.135              SDL_UnlockMutex(audioDevice->mixer_lock);
   3.136          }
   3.137      }
   3.138 +
   3.139 +    if(captureDevice != NULL && captureDevice->hidden != NULL) {
   3.140 +        private = (struct SDL_PrivateAudioData *) captureDevice->hidden;
   3.141 +        if (private->resume) {
   3.142 +            SDL_AtomicSet(&captureDevice->paused, 0);
   3.143 +            private->resume = SDL_FALSE;
   3.144 +            SDL_UnlockMutex(captureDevice->mixer_lock);
   3.145 +        }
   3.146 +    }
   3.147  }
   3.148  
   3.149  
     4.1 --- a/src/core/android/SDL_android.c	Wed Aug 10 16:00:16 2016 -0400
     4.2 +++ b/src/core/android/SDL_android.c	Thu Aug 11 22:04:49 2016 -0400
     4.3 @@ -71,10 +71,14 @@
     4.4  
     4.5  /* method signatures */
     4.6  static jmethodID midGetNativeSurface;
     4.7 -static jmethodID midAudioInit;
     4.8 +static jmethodID midAudioOpen;
     4.9  static jmethodID midAudioWriteShortBuffer;
    4.10  static jmethodID midAudioWriteByteBuffer;
    4.11 -static jmethodID midAudioQuit;
    4.12 +static jmethodID midAudioClose;
    4.13 +static jmethodID midCaptureOpen;
    4.14 +static jmethodID midCaptureReadShortBuffer;
    4.15 +static jmethodID midCaptureReadByteBuffer;
    4.16 +static jmethodID midCaptureClose;
    4.17  static jmethodID midPollInputDevices;
    4.18  
    4.19  /* Accelerometer data storage */
    4.20 @@ -118,21 +122,31 @@
    4.21  
    4.22      midGetNativeSurface = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
    4.23                                  "getNativeSurface","()Landroid/view/Surface;");
    4.24 -    midAudioInit = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
    4.25 -                                "audioInit", "(IZZI)I");
    4.26 +    midAudioOpen = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
    4.27 +                                "audioOpen", "(IZZI)I");
    4.28      midAudioWriteShortBuffer = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
    4.29                                  "audioWriteShortBuffer", "([S)V");
    4.30      midAudioWriteByteBuffer = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
    4.31                                  "audioWriteByteBuffer", "([B)V");
    4.32 -    midAudioQuit = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
    4.33 -                                "audioQuit", "()V");
    4.34 +    midAudioClose = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
    4.35 +                                "audioClose", "()V");
    4.36 +    midCaptureOpen = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
    4.37 +                                "captureOpen", "(IZZI)I");
    4.38 +    midCaptureReadShortBuffer = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
    4.39 +                                "captureReadShortBuffer", "([SZ)I");
    4.40 +    midCaptureReadByteBuffer = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
    4.41 +                                "captureReadByteBuffer", "([BZ)I");
    4.42 +    midCaptureClose = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
    4.43 +                                "captureClose", "()V");
    4.44      midPollInputDevices = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
    4.45                                  "pollInputDevices", "()V");
    4.46  
    4.47      bHasNewData = SDL_FALSE;
    4.48  
    4.49 -    if (!midGetNativeSurface || !midAudioInit ||
    4.50 -       !midAudioWriteShortBuffer || !midAudioWriteByteBuffer || !midAudioQuit || !midPollInputDevices) {
    4.51 +    if (!midGetNativeSurface ||
    4.52 +       !midAudioOpen || !midAudioWriteShortBuffer || !midAudioWriteByteBuffer || !midAudioClose || !
    4.53 +       !midCaptureOpen || !midCaptureReadShortBuffer || !midCaptureReadByteBuffer || !midCaptureClose ||
    4.54 +       !midPollInputDevices) {
    4.55          __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL: Couldn't locate Java callbacks, check that they're named and typed correctly");
    4.56      }
    4.57      __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init() finished!");
    4.58 @@ -556,11 +570,14 @@
    4.59  static jboolean audioBuffer16Bit = JNI_FALSE;
    4.60  static jobject audioBuffer = NULL;
    4.61  static void* audioBufferPinned = NULL;
    4.62 +static jboolean captureBuffer16Bit = JNI_FALSE;
    4.63 +static jobject captureBuffer = NULL;
    4.64  
    4.65 -int Android_JNI_OpenAudioDevice(int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames)
    4.66 +int Android_JNI_OpenAudioDevice(int iscapture, int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames)
    4.67  {
    4.68      jboolean audioBufferStereo;
    4.69      int audioBufferFrames;
    4.70 +    jobject jbufobj = NULL;
    4.71      jboolean isCopy;
    4.72  
    4.73      JNIEnv *env = Android_JNI_GetEnv();
    4.74 @@ -570,14 +587,24 @@
    4.75      }
    4.76      Android_JNI_SetupThread();
    4.77  
    4.78 -    __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device");
    4.79 -    audioBuffer16Bit = is16Bit;
    4.80      audioBufferStereo = channelCount > 1;
    4.81  
    4.82 -    if ((*env)->CallStaticIntMethod(env, mActivityClass, midAudioInit, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames) != 0) {
    4.83 -        /* Error during audio initialization */
    4.84 -        __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: error on AudioTrack initialization!");
    4.85 -        return 0;
    4.86 +    if (iscapture) {
    4.87 +        __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device for capture");
    4.88 +        captureBuffer16Bit = is16Bit;
    4.89 +        if ((*env)->CallStaticIntMethod(env, mActivityClass, midCaptureOpen, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames) != 0) {
    4.90 +            /* Error during audio initialization */
    4.91 +            __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: error on AudioRecord initialization!");
    4.92 +            return 0;
    4.93 +        }
    4.94 +    } else {
    4.95 +        __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device for output");
    4.96 +        audioBuffer16Bit = is16Bit;
    4.97 +        if ((*env)->CallStaticIntMethod(env, mActivityClass, midAudioOpen, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames) != 0) {
    4.98 +            /* Error during audio initialization */
    4.99 +            __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: error on AudioTrack initialization!");
   4.100 +            return 0;
   4.101 +        }
   4.102      }
   4.103  
   4.104      /* Allocating the audio buffer from the Java side and passing it as the return value for audioInit no longer works on
   4.105 @@ -586,31 +613,43 @@
   4.106      if (is16Bit) {
   4.107          jshortArray audioBufferLocal = (*env)->NewShortArray(env, desiredBufferFrames * (audioBufferStereo ? 2 : 1));
   4.108          if (audioBufferLocal) {
   4.109 -            audioBuffer = (*env)->NewGlobalRef(env, audioBufferLocal);
   4.110 +            jbufobj = (*env)->NewGlobalRef(env, audioBufferLocal);
   4.111              (*env)->DeleteLocalRef(env, audioBufferLocal);
   4.112          }
   4.113      }
   4.114      else {
   4.115          jbyteArray audioBufferLocal = (*env)->NewByteArray(env, desiredBufferFrames * (audioBufferStereo ? 2 : 1));
   4.116          if (audioBufferLocal) {
   4.117 -            audioBuffer = (*env)->NewGlobalRef(env, audioBufferLocal);
   4.118 +            jbufobj = (*env)->NewGlobalRef(env, audioBufferLocal);
   4.119              (*env)->DeleteLocalRef(env, audioBufferLocal);
   4.120          }
   4.121      }
   4.122  
   4.123 -    if (audioBuffer == NULL) {
   4.124 +    if (jbufobj == NULL) {
   4.125          __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: could not allocate an audio buffer!");
   4.126          return 0;
   4.127      }
   4.128  
   4.129 +    if (iscapture) {
   4.130 +        captureBuffer = jbufobj;
   4.131 +    } else {
   4.132 +        audioBuffer = jbufobj;
   4.133 +    }
   4.134 +
   4.135      isCopy = JNI_FALSE;
   4.136 -    if (audioBuffer16Bit) {
   4.137 -        audioBufferPinned = (*env)->GetShortArrayElements(env, (jshortArray)audioBuffer, &isCopy);
   4.138 +
   4.139 +    if (is16Bit) {
   4.140 +        if (!iscapture) {
   4.141 +            audioBufferPinned = (*env)->GetShortArrayElements(env, (jshortArray)audioBuffer, &isCopy);
   4.142 +        }
   4.143          audioBufferFrames = (*env)->GetArrayLength(env, (jshortArray)audioBuffer);
   4.144      } else {
   4.145 -        audioBufferPinned = (*env)->GetByteArrayElements(env, (jbyteArray)audioBuffer, &isCopy);
   4.146 +        if (!iscapture) {
   4.147 +            audioBufferPinned = (*env)->GetByteArrayElements(env, (jbyteArray)audioBuffer, &isCopy);
   4.148 +        }
   4.149          audioBufferFrames = (*env)->GetArrayLength(env, (jbyteArray)audioBuffer);
   4.150      }
   4.151 +
   4.152      if (audioBufferStereo) {
   4.153          audioBufferFrames /= 2;
   4.154      }
   4.155 @@ -638,16 +677,73 @@
   4.156      /* JNI_COMMIT means the changes are committed to the VM but the buffer remains pinned */
   4.157  }
   4.158  
   4.159 -void Android_JNI_CloseAudioDevice(void)
   4.160 +int Android_JNI_CaptureAudioBuffer(void *buffer, int buflen)
   4.161 +{
   4.162 +    JNIEnv *env = Android_JNI_GetEnv();
   4.163 +    jboolean isCopy = JNI_FALSE;
   4.164 +    jint br;
   4.165 +
   4.166 +    if (captureBuffer16Bit) {
   4.167 +        SDL_assert((*env)->GetArrayLength(env, (jshortArray)captureBuffer) == (buflen / 2));
   4.168 +        br = (*env)->CallStaticIntMethod(env, mActivityClass, midCaptureReadShortBuffer, (jshortArray)captureBuffer, JNI_TRUE);
   4.169 +        if (br > 0) {
   4.170 +            jshort *ptr = (*env)->GetShortArrayElements(env, (jshortArray)captureBuffer, &isCopy);
   4.171 +            br *= 2;
   4.172 +            SDL_memcpy(buffer, ptr, br);
   4.173 +            (*env)->ReleaseShortArrayElements(env, (jshortArray)captureBuffer, (jshort *)ptr, JNI_ABORT);
   4.174 +        }
   4.175 +    } else {
   4.176 +        SDL_assert((*env)->GetArrayLength(env, (jshortArray)captureBuffer) == buflen);
   4.177 +        br = (*env)->CallStaticIntMethod(env, mActivityClass, midCaptureReadByteBuffer, (jbyteArray)captureBuffer, JNI_TRUE);
   4.178 +        if (br > 0) {
   4.179 +            jbyte *ptr = (*env)->GetByteArrayElements(env, (jbyteArray)captureBuffer, &isCopy);
   4.180 +            SDL_memcpy(buffer, ptr, br);
   4.181 +            (*env)->ReleaseByteArrayElements(env, (jbyteArray)captureBuffer, (jbyte *)ptr, JNI_ABORT);
   4.182 +        }
   4.183 +    }
   4.184 +
   4.185 +    return (int) br;
   4.186 +}
   4.187 +
   4.188 +void Android_JNI_FlushCapturedAudio(void)
   4.189 +{
   4.190 +    JNIEnv *env = Android_JNI_GetEnv();
   4.191 +    #if 0  /* !!! FIXME: this needs API 23, or it'll do blocking reads and never end. */
   4.192 +    if (captureBuffer16Bit) {
   4.193 +        const jint len = (*env)->GetArrayLength(env, (jshortArray)captureBuffer);
   4.194 +        while ((*env)->CallStaticIntMethod(env, mActivityClass, midCaptureReadShortBuffer, (jshortArray)captureBuffer, JNI_FALSE) == len) { /* spin */ }
   4.195 +    } else {
   4.196 +        const jint len = (*env)->GetArrayLength(env, (jbyteArray)captureBuffer);
   4.197 +        while ((*env)->CallStaticIntMethod(env, mActivityClass, midCaptureReadByteBuffer, (jbyteArray)captureBuffer, JNI_FALSE) == len) { /* spin */ }
   4.198 +    }
   4.199 +    #else
   4.200 +    if (captureBuffer16Bit) {
   4.201 +        const jint len = (*env)->GetArrayLength(env, (jshortArray)captureBuffer);
   4.202 +        (*env)->CallStaticIntMethod(env, mActivityClass, midCaptureReadShortBuffer, (jshortArray)captureBuffer, JNI_FALSE);
   4.203 +    } else {
   4.204 +        const jint len = (*env)->GetArrayLength(env, (jbyteArray)captureBuffer);
   4.205 +        (*env)->CallStaticIntMethod(env, mActivityClass, midCaptureReadByteBuffer, (jbyteArray)captureBuffer, JNI_FALSE);
   4.206 +    }
   4.207 +    #endif
   4.208 +}
   4.209 +
   4.210 +void Android_JNI_CloseAudioDevice(const int iscapture)
   4.211  {
   4.212      JNIEnv *env = Android_JNI_GetEnv();
   4.213  
   4.214 -    (*env)->CallStaticVoidMethod(env, mActivityClass, midAudioQuit);
   4.215 -
   4.216 -    if (audioBuffer) {
   4.217 -        (*env)->DeleteGlobalRef(env, audioBuffer);
   4.218 -        audioBuffer = NULL;
   4.219 -        audioBufferPinned = NULL;
   4.220 +    if (iscapture) {
   4.221 +        (*env)->CallStaticVoidMethod(env, mActivityClass, midCaptureClose);
   4.222 +        if (captureBuffer) {
   4.223 +            (*env)->DeleteGlobalRef(env, captureBuffer);
   4.224 +            captureBuffer = NULL;
   4.225 +        }
   4.226 +    } else {
   4.227 +        (*env)->CallStaticVoidMethod(env, mActivityClass, midAudioClose);
   4.228 +        if (audioBuffer) {
   4.229 +            (*env)->DeleteGlobalRef(env, audioBuffer);
   4.230 +            audioBuffer = NULL;
   4.231 +            audioBufferPinned = NULL;
   4.232 +        }
   4.233      }
   4.234  }
   4.235  
     5.1 --- a/src/core/android/SDL_android.h	Wed Aug 10 16:00:16 2016 -0400
     5.2 +++ b/src/core/android/SDL_android.h	Thu Aug 11 22:04:49 2016 -0400
     5.3 @@ -40,10 +40,12 @@
     5.4  extern ANativeWindow* Android_JNI_GetNativeWindow(void);
     5.5  
     5.6  /* Audio support */
     5.7 -extern int Android_JNI_OpenAudioDevice(int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames);
     5.8 +extern int Android_JNI_OpenAudioDevice(int iscapture, int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames);
     5.9  extern void* Android_JNI_GetAudioBuffer(void);
    5.10  extern void Android_JNI_WriteAudioBuffer(void);
    5.11 -extern void Android_JNI_CloseAudioDevice(void);
    5.12 +extern int Android_JNI_CaptureAudioBuffer(void *buffer, int buflen);
    5.13 +extern void Android_JNI_FlushCapturedAudio(void);
    5.14 +extern void Android_JNI_CloseAudioDevice(const int iscapture);
    5.15  
    5.16  #include "SDL_rwops.h"
    5.17