Compare commits

...

5 Commits

Author SHA1 Message Date
Ian Fischer 9658f0dc27 Don't crash from a NULL device. 2013-07-17 22:12:54 -07:00
Aaron Culliney 46ad9cb7e1 test the correct obj for NULL and add some release logging for exceptional conditions 2013-07-10 19:23:04 -07:00
Aaron Culliney 7de1c1778d clean up some dead stores and some defensive coding for NULLs 2013-07-10 18:46:13 -07:00
Ryan Prichard 4921b53913 Revert part of "Switch audiotrack from JNI to BridgeKit."
* Restore the C file.

 * Revert the opensles.c changes.

 * Remove the AndroidAudioTrack class.  It is moved to BridgeKit.
2013-05-23 17:01:44 -07:00
Ryan Prichard 70217271b2 Switch audiotrack from JNI to BridgeKit.
It seems possible for two worker threads run concurrently, and when they
do, they trample on the global JNIEnv.  The AudioTrack.write call still
uses JNI for efficiency.
2013-05-23 16:11:01 -07:00
2 changed files with 337 additions and 21 deletions

304
jni/OpenAL/Alc/audiotrack.m Executable file
View File

@ -0,0 +1,304 @@
/**
* OpenAL cross platform audio library
*
* Copyright 2013 Apportable Inc.
*
* Copyright (C) 2010 by Chris Robinson
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#import "config.h"
#import <stdlib.h>
#import <jni.h>
#import <pthread.h>
#import "alMain.h"
#import "AL/al.h"
#import "AL/alc.h"
#import "apportable_openal_funcs.h"
#import <BridgeKit/JavaClass.h>
#import <BridgeKit/AndroidAudioTrack.h>
static const ALCchar android_device[] = "Android Default";
static int suspended = 0;
static int audioTrackPlaying = 0;
static int audioTrackWasPlaying = 0;
typedef struct
{
pthread_t thread;
volatile int running;
} AndroidData;
#define STREAM_MUSIC 3
#define CHANNEL_CONFIGURATION_MONO 2
#define CHANNEL_CONFIGURATION_STEREO 3
#define ENCODING_PCM_8BIT 3
#define ENCODING_PCM_16BIT 2
#define MODE_STREAM 1
static void* thread_function(void* arg)
{
@autoreleasepool
{
ALCdevice* device = (ALCdevice*)arg;
AndroidData* data = (AndroidData*)device->ExtraData;
int sampleRateInHz = device->Frequency;
int channelConfig = ChannelsFromDevFmt(device->FmtChans) == 1 ? CHANNEL_CONFIGURATION_MONO : CHANNEL_CONFIGURATION_STEREO;
int audioFormat = BytesFromDevFmt(device->FmtType) == 1 ? ENCODING_PCM_8BIT : ENCODING_PCM_16BIT;
int bufferSizeInBytes = [AndroidAudioTrack minBufferSizeWithSampleRate:sampleRateInHz channelConfig:channelConfig audioFormat:audioFormat];
// Suggestion from Eric Wing <ewmailing@gmail.com>
/* According to the author Martins Mozelko, I should multiply bufferSizeInBytes to tune performance.
Say, multiply by 2.
But making this number smaller seems to reduce latency...
I have tried dividing by 2, 4, and 8. 8 refuses to play any sound.
It seems that this just divides out the multiplication of NumUpdates (default=4)
which returns it to min buffer size.
bufferSizeInBytes is used in multiple places and
bufferSizeInSamples is tied directly to bufferSizeInBytes though, so we need to be careful
about what we want to change.
I'm assuming Martins is correct and this is the indeed the place we want to change it.
Dividing out the bufferSizeInSamples separately and skipping the multiply did not work.
Omitting the multiply and not dividing did work, but the buffers may be unnecessarily large.
*/
bufferSizeInBytes = bufferSizeInBytes / device->NumUpdates;
int bufferSizeInSamples = bufferSizeInBytes / FrameSizeFromDevFmt(device->FmtChans, device->FmtType);
AndroidAudioTrack *track = [[AndroidAudioTrack alloc]
initWithStreamType:STREAM_MUSIC
sampleRate:sampleRateInHz
channelConfig:channelConfig
audioFormat:audioFormat
bufferSize:device->NumUpdates * bufferSizeInBytes
mode:MODE_STREAM];
[track play];
audioTrackPlaying = 1;
// Use JNI to avoid an extra copy of the audio buffer. Calling write through BridgeKit
// would require using an NSMutableData for the buffer, which would be copied to a new
// byte[] object on every write call.
JNIEnv *env = [[NSThread currentThread] env];
(*env)->PushLocalFrame(env, 1);
jarray buffer = (*env)->NewByteArray(env, bufferSizeInBytes);
jclass trackClass = [track javaClass].javaClass;
jmethodID writeMethod = (*env)->GetMethodID(env, trackClass, "write", "([BII)I");
while (data->running)
{
if (suspended) {
if (audioTrackPlaying) {
[track pause];
audioTrackPlaying = 0;
}
usleep(5000);
continue;
} else if (!audioTrackPlaying) {
[track play];
audioTrackPlaying = 1;
}
void* pBuffer = (*env)->GetPrimitiveArrayCritical(env, buffer, NULL);
if (pBuffer)
{
aluMixData(device, pBuffer, bufferSizeInSamples);
(*env)->ReleasePrimitiveArrayCritical(env, buffer, pBuffer, 0);
(*env)->CallNonvirtualIntMethod(env, track->_object, trackClass, writeMethod, buffer, 0, bufferSizeInBytes);
}
else
{
AL_PRINT("Failed to get pointer to array bytes");
}
}
[track stop];
[track release];
audioTrackPlaying = 0;
(*env)->PopLocalFrame(env, NULL);
return NULL;
}
}
static ALCboolean android_open_playback(ALCdevice *device, const ALCchar *deviceName)
{
AndroidData* data;
int channels;
int bytes;
if (!deviceName)
{
deviceName = android_device;
}
else if (strcmp(deviceName, android_device) != 0)
{
return ALC_FALSE;
}
data = (AndroidData*)calloc(1, sizeof(*data));
device->szDeviceName = strdup(deviceName);
device->ExtraData = data;
return ALC_TRUE;
}
static void android_close_playback(ALCdevice *device)
{
AndroidData* data = (AndroidData*)device->ExtraData;
if (data != NULL)
{
free(data);
device->ExtraData = NULL;
}
}
static ALCboolean android_reset_playback(ALCdevice *device)
{
AndroidData* data = (AndroidData*)device->ExtraData;
// if (ChannelsFromDevFmt(device->FmtChans) >= 2)
// {
// device->Format = BytesFromDevFmt(device->FmtType) >= 2 ? AL_FORMAT_STEREO16 : AL_FORMAT_STEREO8;
// }
// else
// {
// device->Format = BytesFromDevFmt(device->FmtType) >= 2 ? AL_FORMAT_MONO16 : AL_FORMAT_MONO8;
// }
SetDefaultChannelOrder(device);
data->running = 1;
pthread_create(&data->thread, NULL, thread_function, device);
return ALC_TRUE;
}
static void android_stop_playback(ALCdevice *device)
{
AndroidData* data = (AndroidData*)device->ExtraData;
if (data->running)
{
data->running = 0;
pthread_join(data->thread, NULL);
}
suspended = 0;
}
static ALCboolean android_open_capture(ALCdevice *pDevice, const ALCchar *deviceName)
{
(void)pDevice;
(void)deviceName;
return ALC_FALSE;
}
static void android_close_capture(ALCdevice *pDevice)
{
(void)pDevice;
}
static void android_start_capture(ALCdevice *pDevice)
{
(void)pDevice;
}
static void android_stop_capture(ALCdevice *pDevice)
{
(void)pDevice;
}
static void android_capture_samples(ALCdevice *pDevice, ALCvoid *pBuffer, ALCuint lSamples)
{
(void)pDevice;
(void)pBuffer;
(void)lSamples;
}
static ALCuint android_available_samples(ALCdevice *pDevice)
{
(void)pDevice;
return 0;
}
static const BackendFuncs android_funcs = {
android_open_playback,
android_close_playback,
android_reset_playback,
android_stop_playback,
android_open_capture,
android_close_capture,
android_start_capture,
android_stop_capture,
android_capture_samples,
android_available_samples
};
static void alc_audiotrack_suspend()
{
suspended = 1;
audioTrackWasPlaying = audioTrackPlaying;
}
static void alc_audiotrack_resume()
{
suspended = 0;
if (audioTrackWasPlaying)
{
while (!audioTrackPlaying)
{
sched_yield();
}
audioTrackWasPlaying = 0;
}
}
void alc_audiotrack_init(BackendFuncs *func_list)
{
*func_list = android_funcs;
if (apportableOpenALFuncs.alc_android_suspend == NULL
&& apportableOpenALFuncs.alc_android_set_java_vm == NULL) {
apportableOpenALFuncs.alc_android_suspend = alc_audiotrack_suspend;
apportableOpenALFuncs.alc_android_resume = alc_audiotrack_resume;
}
}
void alc_audiotrack_deinit(void)
{
}
void alc_audiotrack_probe(int type)
{
if (type == DEVICE_PROBE)
{
AppendDeviceList(android_device);
}
else if (type == ALL_DEVICE_PROBE)
{
AppendAllDeviceList(android_device);
}
}

View File

@ -221,7 +221,6 @@ static void *playback_function(void * context) {
outputBuffer_t *buffer = NULL;
SLresult result;
struct timespec ts;
int rc;
assert(NULL != context);
ALCdevice *pDevice = (ALCdevice *) context;
opesles_data_t *devState = (opesles_data_t *) pDevice->ExtraData;
@ -263,7 +262,7 @@ static void *playback_function(void * context) {
// or until playback is stopped/suspended
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_nsec += 5000000;
rc = pthread_cond_timedwait(&(buffer->cond), &(buffer->mutex), &ts);
pthread_cond_timedwait(&(buffer->cond), &(buffer->mutex), &ts);
}
devState->threadIsReady = 1;
@ -320,6 +319,8 @@ static void start_playback(ALCdevice *pDevice) {
if (pDevice->ExtraData == NULL) {
alc_opensles_init_extradata(pDevice);
devState = pDevice->ExtraData;
assert(devState != NULL);
} else {
devState = (opesles_data_t *) pDevice->ExtraData;
}
@ -390,13 +391,15 @@ static void opensles_callback(SLAndroidSimpleBufferQueueItf bq, void *context)
}
}
result = (*devState->bqPlayerBufferQueue)->Enqueue(devState->bqPlayerBufferQueue, buffer->buffer, bufferSize);
if (SL_RESULT_SUCCESS == result) {
buffer->state = OUTPUT_BUFFER_STATE_ENQUEUED;
devState->lastBufferEnqueued = bufferIndex;
pthread_cond_signal(&(buffer->cond));
} else {
bufferIndex--;
if (devState->bqPlayerBufferQueue) {
result = (*devState->bqPlayerBufferQueue)->Enqueue(devState->bqPlayerBufferQueue, buffer->buffer, bufferSize);
if (SL_RESULT_SUCCESS == result) {
buffer->state = OUTPUT_BUFFER_STATE_ENQUEUED;
devState->lastBufferEnqueued = bufferIndex;
pthread_cond_signal(&(buffer->cond));
} else {
bufferIndex--;
}
}
pthread_mutex_unlock(&(buffer->mutex));
}
@ -439,7 +442,6 @@ SLresult alc_opensles_create_native_audio_engine()
static ALCboolean opensles_open_playback(ALCdevice *pDevice, const ALCchar *deviceName)
{
LOGV("opensles_open_playback pDevice=%p, deviceName=%s", pDevice, deviceName);
opesles_data_t *devState;
// Check if probe has linked the opensl symbols
if (pslCreateEngine == NULL) {
@ -451,12 +453,10 @@ static ALCboolean opensles_open_playback(ALCdevice *pDevice, const ALCchar *devi
if (pDevice->ExtraData == NULL) {
alc_opensles_init_extradata(pDevice);
} else {
devState = (opesles_data_t *) pDevice->ExtraData;
}
// create the engine and output mix objects
SLresult result = alc_opensles_create_native_audio_engine();
alc_opensles_create_native_audio_engine();
return ALC_TRUE;
}
@ -484,6 +484,10 @@ static void opensles_close_playback(ALCdevice *pDevice)
static ALCboolean opensles_reset_playback(ALCdevice *pDevice)
{
if (pDevice == NULL) {
LOGE("Received a NULL ALCdevice! Returning ALC_FALSE from opensles_reset_playback");
return ALC_FALSE;
}
LOGV("opensles_reset_playback pDevice=%p", pDevice);
opesles_data_t *devState;
unsigned bits = BytesFromDevFmt(pDevice->FmtType) * 8;
@ -519,7 +523,10 @@ static ALCboolean opensles_reset_playback(ALCdevice *pDevice)
const SLboolean req[1] = {SL_BOOLEAN_TRUE};
result = (*engineEngine)->CreateAudioPlayer(engineEngine, &devState->bqPlayerObject, &audioSrc, &audioSnk,
1, ids, req);
assert(SL_RESULT_SUCCESS == result);
if ((result != SL_RESULT_SUCCESS) || (devState->bqPlayerObject == NULL)) {
RELEASE_LOG("create audio player is null or errored: %lx", result);
return ALC_FALSE;
}
// realize the player
result = (*devState->bqPlayerObject)->Realize(devState->bqPlayerObject, SL_BOOLEAN_FALSE);
@ -532,7 +539,10 @@ static ALCboolean opensles_reset_playback(ALCdevice *pDevice)
// get the buffer queue interface
result = (*devState->bqPlayerObject)->GetInterface(devState->bqPlayerObject, *pSL_IID_BUFFERQUEUE,
&devState->bqPlayerBufferQueue);
assert(SL_RESULT_SUCCESS == result);
if ((result != SL_RESULT_SUCCESS) || (devState->bqPlayerBufferQueue == NULL)) {
RELEASE_LOG("get the buffer queue interface is null or errored: %lx", result);
return ALC_FALSE;
}
// register callback on the buffer queue
result = (*devState->bqPlayerBufferQueue)->RegisterCallback(devState->bqPlayerBufferQueue, opensles_callback, (void *) pDevice);
@ -619,9 +629,10 @@ static void suspend_device(ALCdevice *pDevice) {
opesles_data_t *devState = (opesles_data_t *) pDevice->ExtraData;
if (devState->bqPlayerPlay) {
result = (*devState->bqPlayerPlay)->SetPlayState(devState->bqPlayerPlay, SL_PLAYSTATE_PAUSED);
assert(SL_RESULT_SUCCESS == result);
result = (*devState->bqPlayerBufferQueue)->Clear(devState->bqPlayerBufferQueue);
assert(SL_RESULT_SUCCESS == result);
if ((SL_RESULT_SUCCESS == result) && (devState->bqPlayerBufferQueue)) {
result = (*devState->bqPlayerBufferQueue)->Clear(devState->bqPlayerBufferQueue);
assert(SL_RESULT_SUCCESS == result);
}
}
stop_playback(pDevice);
}
@ -633,10 +644,11 @@ static void resume_device(ALCdevice *pDevice) {
opesles_data_t *devState = (opesles_data_t *) pDevice->ExtraData;
if (devState->bqPlayerPlay) {
result = (*devState->bqPlayerPlay)->SetPlayState(devState->bqPlayerPlay, SL_PLAYSTATE_PLAYING);
assert(SL_RESULT_SUCCESS == result);
// Pump some blank data into the buffer to stimulate the callback
result = (*devState->bqPlayerBufferQueue)->Enqueue(devState->bqPlayerBufferQueue, "\0", 1);
assert(SL_RESULT_SUCCESS == result);
if ((SL_RESULT_SUCCESS == result) && (devState->bqPlayerBufferQueue)) {
result = (*devState->bqPlayerBufferQueue)->Enqueue(devState->bqPlayerBufferQueue, "\0", 1);
assert(SL_RESULT_SUCCESS == result);
}
}
start_playback(pDevice);
}