Skip to content

Commit

Permalink
android: implement audio capture support.
Browse files Browse the repository at this point in the history
  • Loading branch information
icculus committed Aug 12, 2016
1 parent b78ec97 commit 8f0af77
Show file tree
Hide file tree
Showing 5 changed files with 249 additions and 48 deletions.
3 changes: 3 additions & 0 deletions android-project/AndroidManifest.xml
Expand Up @@ -17,6 +17,9 @@
<!-- Allow writing to external storage -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

<!-- if you want to capture audio, uncomment this. -->
<!-- <uses-permission android:name="android.permission.RECORD_AUDIO" /> -->

<!-- Create a Java class extending SDLActivity and place it in a
directory under src matching the package, e.g.
src/com/gamemaker/game/MyGame.java
Expand Down
65 changes: 63 additions & 2 deletions android-project/src/org/libsdl/app/SDLActivity.java
Expand Up @@ -59,6 +59,7 @@ public class SDLActivity extends Activity {

// Audio
protected static AudioTrack mAudioTrack;
protected static AudioRecord mAudioRecord;

/**
* This method is called by SDL before loading the native shared libraries.
Expand Down Expand Up @@ -106,6 +107,7 @@ public static void initialize() {
mJoystickHandler = null;
mSDLThread = null;
mAudioTrack = null;
mAudioRecord = null;
mExitCalledFromJava = false;
mBrokenLibraries = false;
mIsPaused = false;
Expand Down Expand Up @@ -544,7 +546,7 @@ public static Surface getNativeSurface() {
/**
* This method is called by SDL using JNI.
*/
public static int audioInit(int sampleRate, boolean is16Bit, boolean isStereo, int desiredFrames) {
public static int audioOpen(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);
Expand Down Expand Up @@ -623,13 +625,72 @@ public static void audioWriteByteBuffer(byte[] buffer) {
/**
* This method is called by SDL using JNI.
*/
public static void audioQuit() {
public static int captureOpen(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(TAG, "SDL capture: wanted " + (isStereo ? "stereo" : "mono") + " " + (is16Bit ? "16-bit" : "8-bit") + " " + (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, (AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat) + frameSize - 1) / frameSize);

if (mAudioRecord == null) {
mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, sampleRate,
channelConfig, audioFormat, desiredFrames * frameSize);

// see notes about AudioTrack state in audioOpen(), above. Probably also applies here.
if (mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
Log.e(TAG, "Failed during initialization of AudioRecord");
mAudioRecord.release();
mAudioRecord = null;
return -1;
}

mAudioRecord.startRecording();
}

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");

return 0;
}

/** This method is called by SDL using JNI. */
public static int captureReadShortBuffer(short[] buffer, boolean blocking) {
// !!! FIXME: this is available in API Level 23. Until then, we always block. :(
//return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
return mAudioRecord.read(buffer, 0, buffer.length);
}

/** This method is called by SDL using JNI. */
public static int captureReadByteBuffer(byte[] buffer, boolean blocking) {
// !!! FIXME: this is available in API Level 23. Until then, we always block. :(
//return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
return mAudioRecord.read(buffer, 0, buffer.length);
}


/** This method is called by SDL using JNI. */
public static void audioClose() {
if (mAudioTrack != null) {
mAudioTrack.stop();
mAudioTrack.release();
mAudioTrack = null;
}
}

/** This method is called by SDL using JNI. */
public static void captureClose() {
if (mAudioRecord != null) {
mAudioRecord.stop();
mAudioRecord.release();
mAudioRecord = null;
}
}


// Input

/**
Expand Down
73 changes: 56 additions & 17 deletions src/audio/android/SDL_androidaudio.c
Expand Up @@ -24,6 +24,7 @@

/* Output audio to Android */

#include "SDL_assert.h"
#include "SDL_audio.h"
#include "../SDL_audio_c.h"
#include "SDL_androidaudio.h"
Expand All @@ -33,24 +34,22 @@
#include <android/log.h>

static SDL_AudioDevice* audioDevice = NULL;
static SDL_AudioDevice* captureDevice = NULL;

static int
AndroidAUD_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
{
SDL_AudioFormat test_format;

if (iscapture) {
/* TODO: implement capture */
return SDL_SetError("Capture not supported on Android");
}
SDL_assert((captureDevice == NULL) || !iscapture);
SDL_assert((audioDevice == NULL) || iscapture);

/* !!! FIXME: higher level will prevent this now. Lose this check (and global?). */
if (audioDevice != NULL) {
return SDL_SetError("Only one audio device at a time please!");
if (iscapture) {
captureDevice = this;
} else {
audioDevice = this;
}

audioDevice = this;

this->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, (sizeof *this->hidden));
if (this->hidden == NULL) {
return SDL_OutOfMemory();
Expand Down Expand Up @@ -83,15 +82,16 @@ AndroidAUD_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
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);
/* TODO: pass in/return a (Java) device ID */
this->spec.samples = Android_JNI_OpenAudioDevice(iscapture, this->spec.freq, this->spec.format == AUDIO_U8 ? 0 : 1, this->spec.channels, this->spec.samples);

if (this->spec.samples == 0) {
/* Init failed? */
return SDL_SetError("Java-side initialization failed!");
}

SDL_CalculateAudioSpec(&this->spec);

return 0;
}

Expand All @@ -107,18 +107,33 @@ AndroidAUD_GetDeviceBuf(_THIS)
return Android_JNI_GetAudioBuffer();
}

static int
AndroidAUD_CaptureFromDevice(_THIS, void *buffer, int buflen)
{
return Android_JNI_CaptureAudioBuffer(buffer, buflen);
}

static void
AndroidAUD_FlushCapture(_THIS)
{
Android_JNI_FlushCapturedAudio();
}

static void
AndroidAUD_CloseDevice(_THIS)
{
/* At this point SDL_CloseAudioDevice via close_audio_device took care of terminating the audio thread
so it's safe to terminate the Java side buffer and AudioTrack
*/
Android_JNI_CloseAudioDevice();

if (audioDevice == this) {
SDL_free(this->hidden);
Android_JNI_CloseAudioDevice(this->iscapture);
if (this->iscapture) {
SDL_assert(captureDevice == this);
captureDevice = NULL;
} else {
SDL_assert(audioDevice == this);
audioDevice = NULL;
}
SDL_free(this->hidden);
}

static int
Expand All @@ -129,9 +144,11 @@ AndroidAUD_Init(SDL_AudioDriverImpl * impl)
impl->PlayDevice = AndroidAUD_PlayDevice;
impl->GetDeviceBuf = AndroidAUD_GetDeviceBuf;
impl->CloseDevice = AndroidAUD_CloseDevice;
impl->CaptureFromDevice = AndroidAUD_CaptureFromDevice;
impl->FlushCapture = AndroidAUD_FlushCapture;

/* and the capabilities */
impl->HasCaptureSupport = 0; /* TODO */
impl->HasCaptureSupport = SDL_TRUE;
impl->OnlyHasDefaultOutputDevice = 1;
impl->OnlyHasDefaultCaptureDevice = 1;

Expand Down Expand Up @@ -159,6 +176,19 @@ void AndroidAUD_PauseDevices(void)
private->resume = SDL_TRUE;
}
}

if(captureDevice != NULL && captureDevice->hidden != NULL) {
private = (struct SDL_PrivateAudioData *) captureDevice->hidden;
if (SDL_AtomicGet(&captureDevice->paused)) {
/* The device is already paused, leave it alone */
private->resume = SDL_FALSE;
}
else {
SDL_LockMutex(captureDevice->mixer_lock);
SDL_AtomicSet(&captureDevice->paused, 1);
private->resume = SDL_TRUE;
}
}
}

/* Resume (unblock) all non already paused audio devices by releasing their mixer lock */
Expand All @@ -174,6 +204,15 @@ void AndroidAUD_ResumeDevices(void)
SDL_UnlockMutex(audioDevice->mixer_lock);
}
}

if(captureDevice != NULL && captureDevice->hidden != NULL) {
private = (struct SDL_PrivateAudioData *) captureDevice->hidden;
if (private->resume) {
SDL_AtomicSet(&captureDevice->paused, 0);
private->resume = SDL_FALSE;
SDL_UnlockMutex(captureDevice->mixer_lock);
}
}
}


Expand Down

0 comments on commit 8f0af77

Please sign in to comment.