mirror of
https://github.com/QB64Official/qb64.git
synced 2024-09-20 09:04:44 +00:00
801 lines
25 KiB
C
801 lines
25 KiB
C
|
/*
|
||
|
* Copyright (C) 2011 The Android Open Source Project
|
||
|
*
|
||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
* you may not use this file except in compliance with the License.
|
||
|
* You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*/
|
||
|
|
||
|
/* This is an OpenAL backend for Android using the native audio APIs based on OpenSL ES 1.0.1.
|
||
|
* It is based on source code for the native-audio sample app bundled with NDK.
|
||
|
*/
|
||
|
|
||
|
#include <stdlib.h>
|
||
|
#include <time.h>
|
||
|
#include <sys/types.h>
|
||
|
#include <sys/stat.h>
|
||
|
#include <unistd.h>
|
||
|
|
||
|
#include <dlfcn.h>
|
||
|
|
||
|
#include "alMain.h"
|
||
|
#include "AL/al.h"
|
||
|
#include "AL/alc.h"
|
||
|
|
||
|
#include <pthread.h>
|
||
|
#include <sched.h>
|
||
|
#include <sys/prctl.h>
|
||
|
|
||
|
#include <jni.h>
|
||
|
|
||
|
|
||
|
#define LOG_NDEBUG 0
|
||
|
#define LOG_TAG "OpenAL_SLES"
|
||
|
|
||
|
#if 1
|
||
|
#define LOGV(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
|
||
|
#else
|
||
|
#define LOGV(...)
|
||
|
#endif
|
||
|
|
||
|
// for native audio
|
||
|
#include <SLES/OpenSLES.h>
|
||
|
#include <SLES/OpenSLES_Android.h>
|
||
|
|
||
|
#include "apportable_openal_funcs.h"
|
||
|
|
||
|
#define MAKE_SYM_POINTER(sym) static typeof(sym) * p##sym = NULL
|
||
|
MAKE_SYM_POINTER(SL_IID_ENGINE);
|
||
|
MAKE_SYM_POINTER(SL_IID_ANDROIDSIMPLEBUFFERQUEUE);
|
||
|
MAKE_SYM_POINTER(SL_IID_PLAY);
|
||
|
MAKE_SYM_POINTER(SL_IID_BUFFERQUEUE);
|
||
|
MAKE_SYM_POINTER(slCreateEngine);
|
||
|
|
||
|
// engine interfaces
|
||
|
static SLObjectItf engineObject = NULL;
|
||
|
static SLEngineItf engineEngine;
|
||
|
|
||
|
// output mix interfaces
|
||
|
static SLObjectItf outputMixObject = NULL;
|
||
|
|
||
|
// JNI stuff so we can get the runtime OS version number
|
||
|
static JavaVM* javaVM = NULL;
|
||
|
|
||
|
static int alc_opensles_get_android_api()
|
||
|
{
|
||
|
jclass androidVersionClass = NULL;
|
||
|
jfieldID androidSdkIntField = NULL;
|
||
|
int androidApiLevel = 0;
|
||
|
JNIEnv* env = NULL;
|
||
|
|
||
|
(*javaVM)->GetEnv(javaVM, (void**)&env, JNI_VERSION_1_4);
|
||
|
androidVersionClass = (*env)->FindClass(env, "android/os/Build$VERSION");
|
||
|
if (androidVersionClass)
|
||
|
{
|
||
|
androidSdkIntField = (*env)->GetStaticFieldID(env, androidVersionClass, "SDK_INT", "I");
|
||
|
if (androidSdkIntField != NULL)
|
||
|
{
|
||
|
androidApiLevel = (int)((*env)->GetStaticIntField(env, androidVersionClass, androidSdkIntField));
|
||
|
}
|
||
|
(*env)->DeleteLocalRef(env, androidVersionClass);
|
||
|
}
|
||
|
LOGV("API:%d", androidApiLevel);
|
||
|
return androidApiLevel;
|
||
|
}
|
||
|
static char *androidModel = NULL;
|
||
|
static char *alc_opensles_get_android_model()
|
||
|
{
|
||
|
if (!androidModel) {
|
||
|
jclass androidBuildClass = NULL;
|
||
|
jfieldID androidModelField = NULL;
|
||
|
jstring androidModelString = NULL;
|
||
|
int androidApiLevel = 0;
|
||
|
JNIEnv* env = NULL;
|
||
|
|
||
|
(*javaVM)->GetEnv(javaVM, (void**)&env, JNI_VERSION_1_4);
|
||
|
(*env)->PushLocalFrame(env, 5);
|
||
|
androidBuildClass = (*env)->FindClass(env, "android/os/Build");
|
||
|
if (androidBuildClass)
|
||
|
{
|
||
|
androidModelField = (*env)->GetStaticFieldID(env, androidBuildClass, "MODEL", "Ljava/lang/String;");
|
||
|
androidModelString = (*env)->GetStaticObjectField(env, androidBuildClass, androidModelField);
|
||
|
const char *unichars = (*env)->GetStringUTFChars(env, androidModelString, NULL);
|
||
|
if (!(*env)->ExceptionOccurred(env))
|
||
|
{
|
||
|
jsize sz = (*env)->GetStringLength(env, androidModelString);
|
||
|
androidModel = malloc(sz+1);
|
||
|
if (androidModel) {
|
||
|
strncpy(androidModel, unichars, sz);
|
||
|
androidModel[sz] = '\0';
|
||
|
}
|
||
|
}
|
||
|
(*env)->ReleaseStringUTFChars(env, androidModelString, unichars);
|
||
|
}
|
||
|
(*env)->PopLocalFrame(env, NULL);
|
||
|
}
|
||
|
LOGV("Model:%s", androidModel);
|
||
|
return androidModel;
|
||
|
}
|
||
|
|
||
|
static long timespecdiff(struct timespec *starttime, struct timespec *finishtime)
|
||
|
{
|
||
|
long msec;
|
||
|
msec=(finishtime->tv_sec-starttime->tv_sec)*1000;
|
||
|
msec+=(finishtime->tv_nsec-starttime->tv_nsec)/1000000;
|
||
|
return msec;
|
||
|
}
|
||
|
|
||
|
// Cannot be a constant because we need to tweak differently depending on OS version.
|
||
|
static size_t bufferCount = 8;
|
||
|
static size_t bufferSize = (1024*4);
|
||
|
static size_t defaultBufferSize = (1024*4);
|
||
|
static size_t premixCount = 3;
|
||
|
#define bufferSizeMax (1024*4)
|
||
|
|
||
|
typedef enum {
|
||
|
OUTPUT_BUFFER_STATE_UNKNOWN,
|
||
|
OUTPUT_BUFFER_STATE_FREE,
|
||
|
OUTPUT_BUFFER_STATE_MIXED,
|
||
|
OUTPUT_BUFFER_STATE_ENQUEUED,
|
||
|
} outputBuffer_state_t;
|
||
|
|
||
|
typedef struct outputBuffer_s {
|
||
|
pthread_mutex_t mutex;
|
||
|
pthread_cond_t cond;
|
||
|
outputBuffer_state_t state;
|
||
|
char buffer[bufferSizeMax];
|
||
|
} outputBuffer_t;
|
||
|
|
||
|
// Will dynamically create the number of buffers (array elements) based on OS version.
|
||
|
|
||
|
|
||
|
typedef struct {
|
||
|
pthread_t playbackThread;
|
||
|
char threadShouldRun;
|
||
|
char threadIsReady;
|
||
|
char lastBufferEnqueued;
|
||
|
char lastBufferMixed;
|
||
|
|
||
|
outputBuffer_t *outputBuffers;
|
||
|
|
||
|
// buffer queue player interfaces
|
||
|
SLObjectItf bqPlayerObject;
|
||
|
SLPlayItf bqPlayerPlay;
|
||
|
SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue;
|
||
|
} opesles_data_t;
|
||
|
#define MAX_DEVICES 3
|
||
|
static ALCdevice *deviceList[MAX_DEVICES] = {NULL};
|
||
|
static pthread_mutex_t deviceListMutex = PTHREAD_MUTEX_INITIALIZER;
|
||
|
|
||
|
typedef void (*deviceListFn)(ALCdevice *);
|
||
|
|
||
|
static void devlist_add(ALCdevice *pDevice) {
|
||
|
int i;
|
||
|
pthread_mutex_lock(&(deviceListMutex));
|
||
|
for (i = 0; i < MAX_DEVICES; i++) {
|
||
|
if (deviceList[i] == pDevice) {
|
||
|
break;
|
||
|
} else if (deviceList[i] == NULL) {
|
||
|
deviceList[i] = pDevice;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
pthread_mutex_unlock(&(deviceListMutex));
|
||
|
}
|
||
|
|
||
|
static void devlist_remove(ALCdevice *pDevice) {
|
||
|
int i;
|
||
|
pthread_mutex_lock(&(deviceListMutex));
|
||
|
for (i = 0; i < MAX_DEVICES; i++) {
|
||
|
if (deviceList[i] == pDevice) {
|
||
|
deviceList[i] = NULL;
|
||
|
}
|
||
|
}
|
||
|
pthread_mutex_unlock(&(deviceListMutex));
|
||
|
}
|
||
|
|
||
|
static void devlist_process(deviceListFn mapFunction) {
|
||
|
int i;
|
||
|
pthread_mutex_lock(&(deviceListMutex));
|
||
|
for (i = 0; i < MAX_DEVICES; i++) {
|
||
|
if (deviceList[i]) {
|
||
|
pthread_mutex_unlock(&(deviceListMutex));
|
||
|
mapFunction(deviceList[i]);
|
||
|
pthread_mutex_lock(&(deviceListMutex));
|
||
|
}
|
||
|
}
|
||
|
pthread_mutex_unlock(&(deviceListMutex));
|
||
|
}
|
||
|
|
||
|
|
||
|
static void *playback_function(void * context) {
|
||
|
LOGV("playback_function started");
|
||
|
outputBuffer_t *buffer = NULL;
|
||
|
SLresult result;
|
||
|
struct timespec ts;
|
||
|
assert(NULL != context);
|
||
|
ALCdevice *pDevice = (ALCdevice *) context;
|
||
|
opesles_data_t *devState = (opesles_data_t *) pDevice->ExtraData;
|
||
|
unsigned int bufferIndex = devState->lastBufferMixed;
|
||
|
|
||
|
ALint frameSize = FrameSizeFromDevFmt(pDevice->FmtChans, pDevice->FmtType);
|
||
|
|
||
|
// Show a sensible name for the thread in debug tools
|
||
|
prctl(PR_SET_NAME, (unsigned long)"OpenAL/sl/m", 0, 0, 0);
|
||
|
|
||
|
while (1) {
|
||
|
if (devState->threadShouldRun == 0) {
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
bufferIndex = (++bufferIndex) % bufferCount;
|
||
|
buffer = &(devState->outputBuffers[bufferIndex]);
|
||
|
|
||
|
pthread_mutex_lock(&(buffer->mutex));
|
||
|
|
||
|
|
||
|
while (1) {
|
||
|
if (devState->threadShouldRun == 0) {
|
||
|
pthread_mutex_unlock(&(buffer->mutex));
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
// This is a little hacky, but here we avoid mixing too much data
|
||
|
if (buffer->state == OUTPUT_BUFFER_STATE_FREE) {
|
||
|
int i = (bufferIndex - premixCount) % bufferCount;
|
||
|
outputBuffer_t *buffer1 = &(devState->outputBuffers[i]);
|
||
|
if (buffer1->state == OUTPUT_BUFFER_STATE_ENQUEUED ||
|
||
|
buffer1->state == OUTPUT_BUFFER_STATE_FREE) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// No buffer available, wait for a buffer to become available
|
||
|
// or until playback is stopped/suspended
|
||
|
clock_gettime(CLOCK_REALTIME, &ts);
|
||
|
ts.tv_nsec += 5000000;
|
||
|
pthread_cond_timedwait(&(buffer->cond), &(buffer->mutex), &ts);
|
||
|
}
|
||
|
devState->threadIsReady = 1;
|
||
|
|
||
|
aluMixData(pDevice, buffer->buffer, bufferSize/frameSize);
|
||
|
buffer->state = OUTPUT_BUFFER_STATE_MIXED;
|
||
|
pthread_cond_signal(&(buffer->cond));
|
||
|
pthread_mutex_unlock(&(buffer->mutex));
|
||
|
|
||
|
devState->lastBufferMixed = bufferIndex;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
SLresult alc_opensles_init_extradata(ALCdevice *pDevice)
|
||
|
{
|
||
|
opesles_data_t *devState = NULL;
|
||
|
int i;
|
||
|
devState = malloc(sizeof(opesles_data_t));
|
||
|
if (!devState) {
|
||
|
return SL_RESULT_MEMORY_FAILURE;
|
||
|
}
|
||
|
bzero(devState, sizeof(opesles_data_t));
|
||
|
devState->outputBuffers = (outputBuffer_t*) malloc(sizeof(outputBuffer_t)*bufferCount);
|
||
|
if (!devState->outputBuffers) {
|
||
|
free(devState);
|
||
|
return SL_RESULT_MEMORY_FAILURE;
|
||
|
}
|
||
|
pDevice->ExtraData = devState;
|
||
|
bzero(devState->outputBuffers, sizeof(outputBuffer_t)*bufferCount);
|
||
|
devState->lastBufferEnqueued = -1;
|
||
|
devState->lastBufferMixed = -1;
|
||
|
for (i = 0; i < bufferCount; i++) {
|
||
|
if (pthread_mutex_init(&(devState->outputBuffers[i].mutex), (pthread_mutexattr_t*) NULL) != 0) {
|
||
|
LOGV("Error on init of mutex");
|
||
|
free(devState->outputBuffers);
|
||
|
free(devState);
|
||
|
return SL_RESULT_UNKNOWN_ERROR;
|
||
|
}
|
||
|
if (pthread_cond_init(&(devState->outputBuffers[i].cond), (pthread_condattr_t*) NULL) != 0) {
|
||
|
LOGV("Error on init of cond");
|
||
|
free(devState->outputBuffers);
|
||
|
free(devState);
|
||
|
return SL_RESULT_UNKNOWN_ERROR;
|
||
|
}
|
||
|
devState->outputBuffers[i].state = OUTPUT_BUFFER_STATE_FREE;
|
||
|
}
|
||
|
// For the Android suspend/resume functionaly, keep track of all device contexts
|
||
|
devlist_add(pDevice);
|
||
|
return SL_RESULT_SUCCESS;
|
||
|
}
|
||
|
|
||
|
static void start_playback(ALCdevice *pDevice) {
|
||
|
opesles_data_t *devState = NULL;
|
||
|
int i;
|
||
|
|
||
|
if (pDevice->ExtraData == NULL) {
|
||
|
alc_opensles_init_extradata(pDevice);
|
||
|
devState = pDevice->ExtraData;
|
||
|
assert(devState != NULL);
|
||
|
} else {
|
||
|
devState = (opesles_data_t *) pDevice->ExtraData;
|
||
|
}
|
||
|
|
||
|
if (devState->threadShouldRun == 1) {
|
||
|
// Gratuitous resume
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// start/restart playback thread
|
||
|
devState->threadShouldRun = 1;
|
||
|
|
||
|
pthread_attr_t playbackThreadAttr;
|
||
|
pthread_attr_init(&playbackThreadAttr);
|
||
|
struct sched_param playbackThreadParam;
|
||
|
playbackThreadParam.sched_priority = sched_get_priority_max(SCHED_RR);
|
||
|
pthread_attr_setschedpolicy(&playbackThreadAttr, SCHED_RR);
|
||
|
pthread_attr_setschedparam(&playbackThreadAttr, &playbackThreadParam);
|
||
|
pthread_create(&(devState->playbackThread), &playbackThreadAttr, playback_function, (void *) pDevice);
|
||
|
while (devState->threadShouldRun && (0 == devState->threadIsReady))
|
||
|
{
|
||
|
sched_yield();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void stop_playback(ALCdevice *pDevice) {
|
||
|
opesles_data_t *devState = (opesles_data_t *) pDevice->ExtraData;
|
||
|
devState->threadShouldRun = 0;
|
||
|
pthread_join(devState->playbackThread, NULL);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// this callback handler is called every time a buffer finishes playing
|
||
|
static void opensles_callback(SLAndroidSimpleBufferQueueItf bq, void *context)
|
||
|
{
|
||
|
ALCdevice *pDevice = (ALCdevice *) context;
|
||
|
opesles_data_t *devState = (opesles_data_t *) pDevice->ExtraData;
|
||
|
unsigned int bufferIndex = devState->lastBufferEnqueued;
|
||
|
unsigned int i;
|
||
|
struct timespec ts;
|
||
|
int rc;
|
||
|
SLresult result;
|
||
|
outputBuffer_t *buffer = NULL;
|
||
|
|
||
|
bufferIndex = (++bufferIndex) % bufferCount;
|
||
|
buffer = &(devState->outputBuffers[bufferIndex]);
|
||
|
|
||
|
pthread_mutex_lock(&(buffer->mutex));
|
||
|
// We will block until 'next' buffer has mixed audio, but first flag oldest equeued buffer as free
|
||
|
for (i = 1; i <= bufferCount; i++) {
|
||
|
unsigned int j = (devState->lastBufferEnqueued+i) % bufferCount;
|
||
|
outputBuffer_t *bufferFree = &(devState->outputBuffers[j]);
|
||
|
if (bufferFree->state == OUTPUT_BUFFER_STATE_ENQUEUED) {
|
||
|
bufferFree->state = OUTPUT_BUFFER_STATE_FREE;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
while (buffer->state != OUTPUT_BUFFER_STATE_MIXED) {
|
||
|
clock_gettime(CLOCK_REALTIME, &ts);
|
||
|
ts.tv_nsec += 100000;
|
||
|
rc = pthread_cond_timedwait(&(buffer->cond), &(buffer->mutex), &ts);
|
||
|
if (rc != 0) {
|
||
|
if (devState->threadShouldRun == 0) {
|
||
|
// we are probably suspended
|
||
|
pthread_mutex_unlock(&(buffer->mutex));
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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));
|
||
|
}
|
||
|
|
||
|
|
||
|
static const ALCchar opensles_device[] = "OpenSL ES";
|
||
|
|
||
|
// Apportable extensions
|
||
|
SLresult alc_opensles_create_native_audio_engine()
|
||
|
{
|
||
|
if (engineObject)
|
||
|
return SL_RESULT_SUCCESS;
|
||
|
|
||
|
SLresult result;
|
||
|
|
||
|
// create engine
|
||
|
result = pslCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
|
||
|
assert(SL_RESULT_SUCCESS == result);
|
||
|
|
||
|
// realize the engine
|
||
|
result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
|
||
|
assert(SL_RESULT_SUCCESS == result);
|
||
|
|
||
|
// get the engine interface, which is needed in order to create other objects
|
||
|
result = (*engineObject)->GetInterface(engineObject, *pSL_IID_ENGINE, &engineEngine);
|
||
|
assert(SL_RESULT_SUCCESS == result);
|
||
|
|
||
|
// create output mix
|
||
|
result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, NULL, NULL);
|
||
|
assert(SL_RESULT_SUCCESS == result);
|
||
|
|
||
|
// realize the output mix
|
||
|
result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
|
||
|
assert(SL_RESULT_SUCCESS == result);
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
// Backend functions, in same order as type BackendFuncs
|
||
|
static ALCboolean opensles_open_playback(ALCdevice *pDevice, const ALCchar *deviceName)
|
||
|
{
|
||
|
LOGV("opensles_open_playback pDevice=%p, deviceName=%s", pDevice, deviceName);
|
||
|
|
||
|
// Check if probe has linked the opensl symbols
|
||
|
if (pslCreateEngine == NULL) {
|
||
|
alc_opensles_probe(DEVICE_PROBE);
|
||
|
if (pslCreateEngine == NULL) {
|
||
|
return ALC_FALSE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (pDevice->ExtraData == NULL) {
|
||
|
alc_opensles_init_extradata(pDevice);
|
||
|
}
|
||
|
|
||
|
// create the engine and output mix objects
|
||
|
alc_opensles_create_native_audio_engine();
|
||
|
|
||
|
return ALC_TRUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
static void opensles_close_playback(ALCdevice *pDevice)
|
||
|
{
|
||
|
LOGV("opensles_close_playback pDevice=%p", pDevice);
|
||
|
opesles_data_t *devState = (opesles_data_t *) pDevice->ExtraData;
|
||
|
|
||
|
// shut down the native audio system
|
||
|
|
||
|
// destroy buffer queue audio player object, and invalidate all associated interfaces
|
||
|
if (devState->bqPlayerObject != NULL) {
|
||
|
(*devState->bqPlayerObject)->Destroy(devState->bqPlayerObject);
|
||
|
devState->bqPlayerObject = NULL;
|
||
|
devState->bqPlayerPlay = NULL;
|
||
|
devState->bqPlayerBufferQueue = NULL;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
devlist_remove(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;
|
||
|
unsigned channels = ChannelsFromDevFmt(pDevice->FmtChans);
|
||
|
unsigned samples = pDevice->UpdateSize;
|
||
|
unsigned size = samples * channels * bits / 8;
|
||
|
SLuint32 sampling_rate = pDevice->Frequency * 1000;
|
||
|
SLresult result;
|
||
|
LOGV("bits=%u, channels=%u, samples=%u, size=%u, freq=%u", bits, channels, samples, size, pDevice->Frequency);
|
||
|
if (pDevice->Frequency <= 22050) {
|
||
|
bufferSize = defaultBufferSize / 2;
|
||
|
}
|
||
|
|
||
|
devState = (opesles_data_t *) pDevice->ExtraData;
|
||
|
|
||
|
// create buffer queue audio player
|
||
|
|
||
|
// configure audio source
|
||
|
SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2};
|
||
|
// SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, 2, SL_SAMPLINGRATE_44_1,
|
||
|
SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, 2, sampling_rate,
|
||
|
SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16,
|
||
|
SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT, SL_BYTEORDER_LITTLEENDIAN};
|
||
|
SLDataSource audioSrc = {&loc_bufq, &format_pcm};
|
||
|
|
||
|
// configure audio sink
|
||
|
SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
|
||
|
SLDataSink audioSnk = {&loc_outmix, NULL};
|
||
|
|
||
|
// create audio player
|
||
|
LOGV("create audio player");
|
||
|
const SLInterfaceID ids[1] = {*pSL_IID_ANDROIDSIMPLEBUFFERQUEUE};
|
||
|
const SLboolean req[1] = {SL_BOOLEAN_TRUE};
|
||
|
result = (*engineEngine)->CreateAudioPlayer(engineEngine, &devState->bqPlayerObject, &audioSrc, &audioSnk,
|
||
|
1, ids, req);
|
||
|
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);
|
||
|
assert(SL_RESULT_SUCCESS == result);
|
||
|
|
||
|
// get the play interface
|
||
|
result = (*devState->bqPlayerObject)->GetInterface(devState->bqPlayerObject, *pSL_IID_PLAY, &devState->bqPlayerPlay);
|
||
|
assert(SL_RESULT_SUCCESS == result);
|
||
|
|
||
|
// get the buffer queue interface
|
||
|
result = (*devState->bqPlayerObject)->GetInterface(devState->bqPlayerObject, *pSL_IID_BUFFERQUEUE,
|
||
|
&devState->bqPlayerBufferQueue);
|
||
|
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);
|
||
|
assert(SL_RESULT_SUCCESS == result);
|
||
|
|
||
|
// playback_lock = createThreadLock();
|
||
|
start_playback(pDevice);
|
||
|
|
||
|
// set the player's state to playing
|
||
|
result = (*devState->bqPlayerPlay)->SetPlayState(devState->bqPlayerPlay, SL_PLAYSTATE_PLAYING);
|
||
|
assert(SL_RESULT_SUCCESS == result);
|
||
|
|
||
|
// enqueue the first buffer to kick off the callbacks
|
||
|
result = (*devState->bqPlayerBufferQueue)->Enqueue(devState->bqPlayerBufferQueue, "\0", 1);
|
||
|
assert(SL_RESULT_SUCCESS == result);
|
||
|
|
||
|
|
||
|
SetDefaultWFXChannelOrder(pDevice);
|
||
|
devlist_add(pDevice);
|
||
|
|
||
|
return ALC_TRUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
static void opensles_stop_playback(ALCdevice *pDevice)
|
||
|
{
|
||
|
LOGV("opensles_stop_playback device=%p", pDevice);
|
||
|
stop_playback(pDevice);
|
||
|
}
|
||
|
|
||
|
static ALCboolean opensles_open_capture(ALCdevice *pDevice, const ALCchar *deviceName)
|
||
|
{
|
||
|
LOGV("opensles_open_capture device=%p, deviceName=%s", pDevice, deviceName);
|
||
|
return ALC_FALSE;
|
||
|
}
|
||
|
|
||
|
static void opensles_close_capture(ALCdevice *pDevice)
|
||
|
{
|
||
|
LOGV("opensles_closed_capture device=%p", pDevice);
|
||
|
}
|
||
|
|
||
|
static void opensles_start_capture(ALCdevice *pDevice)
|
||
|
{
|
||
|
LOGV("opensles_start_capture device=%p", pDevice);
|
||
|
}
|
||
|
|
||
|
static void opensles_stop_capture(ALCdevice *pDevice)
|
||
|
{
|
||
|
LOGV("opensles_stop_capture device=%p", pDevice);
|
||
|
}
|
||
|
|
||
|
static void opensles_capture_samples(ALCdevice *pDevice, ALCvoid *pBuffer, ALCuint lSamples)
|
||
|
{
|
||
|
LOGV("opensles_capture_samples device=%p, pBuffer=%p, lSamples=%u", pDevice, pBuffer, lSamples);
|
||
|
}
|
||
|
|
||
|
static ALCuint opensles_available_samples(ALCdevice *pDevice)
|
||
|
{
|
||
|
LOGV("opensles_available_samples device=%p", pDevice);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// table of backend function pointers
|
||
|
|
||
|
BackendFuncs opensles_funcs = {
|
||
|
opensles_open_playback,
|
||
|
opensles_close_playback,
|
||
|
opensles_reset_playback,
|
||
|
opensles_stop_playback,
|
||
|
opensles_open_capture,
|
||
|
opensles_close_capture,
|
||
|
opensles_start_capture,
|
||
|
opensles_stop_capture,
|
||
|
opensles_capture_samples,
|
||
|
opensles_available_samples
|
||
|
};
|
||
|
|
||
|
// global entry points called from XYZZY
|
||
|
|
||
|
|
||
|
static void suspend_device(ALCdevice *pDevice) {
|
||
|
SLresult result;
|
||
|
if (pDevice) {
|
||
|
opesles_data_t *devState = (opesles_data_t *) pDevice->ExtraData;
|
||
|
if (devState->bqPlayerPlay) {
|
||
|
result = (*devState->bqPlayerPlay)->SetPlayState(devState->bqPlayerPlay, SL_PLAYSTATE_PAUSED);
|
||
|
if ((SL_RESULT_SUCCESS == result) && (devState->bqPlayerBufferQueue)) {
|
||
|
result = (*devState->bqPlayerBufferQueue)->Clear(devState->bqPlayerBufferQueue);
|
||
|
assert(SL_RESULT_SUCCESS == result);
|
||
|
}
|
||
|
}
|
||
|
stop_playback(pDevice);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void resume_device(ALCdevice *pDevice) {
|
||
|
SLresult result;
|
||
|
if (pDevice) {
|
||
|
opesles_data_t *devState = (opesles_data_t *) pDevice->ExtraData;
|
||
|
if (devState->bqPlayerPlay) {
|
||
|
result = (*devState->bqPlayerPlay)->SetPlayState(devState->bqPlayerPlay, SL_PLAYSTATE_PLAYING);
|
||
|
// Pump some blank data into the buffer to stimulate the callback
|
||
|
if ((SL_RESULT_SUCCESS == result) && (devState->bqPlayerBufferQueue)) {
|
||
|
result = (*devState->bqPlayerBufferQueue)->Enqueue(devState->bqPlayerBufferQueue, "\0", 1);
|
||
|
assert(SL_RESULT_SUCCESS == result);
|
||
|
}
|
||
|
}
|
||
|
start_playback(pDevice);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void alc_opensles_suspend()
|
||
|
{
|
||
|
devlist_process(&suspend_device);
|
||
|
}
|
||
|
|
||
|
void alc_opensles_resume()
|
||
|
{
|
||
|
devlist_process(&resume_device);
|
||
|
}
|
||
|
|
||
|
static void alc_opensles_set_java_vm(JavaVM *vm)
|
||
|
{
|
||
|
// Called once and only once from JNI_OnLoad
|
||
|
javaVM = vm;
|
||
|
int i;
|
||
|
char *android_model;
|
||
|
char *low_buffer_models[] = {
|
||
|
"GT-I9300",
|
||
|
"GT-I9305",
|
||
|
"SHV-E210",
|
||
|
"SGH-T999",
|
||
|
"SGH-I747",
|
||
|
"SGH-N064",
|
||
|
"SC-06D",
|
||
|
"SGH-N035",
|
||
|
"SC-03E",
|
||
|
"SCH-R530",
|
||
|
"SCH-I535",
|
||
|
"SPH-L710",
|
||
|
"GT-I9308",
|
||
|
"SCH-I939",
|
||
|
"Kindle Fire",
|
||
|
NULL};
|
||
|
|
||
|
if(NULL != javaVM)
|
||
|
{
|
||
|
int android_os_version = alc_opensles_get_android_api();
|
||
|
// If running on 4.1 (Jellybean) or later, use 8 buffers to avoid breakup/stuttering.
|
||
|
if(android_os_version >= 16)
|
||
|
{
|
||
|
premixCount = 5;
|
||
|
}
|
||
|
// Else, use 4 buffers to reduce latency
|
||
|
else
|
||
|
{
|
||
|
premixCount = 1;
|
||
|
}
|
||
|
android_model = alc_opensles_get_android_model();
|
||
|
for (i = 0; low_buffer_models[i] != NULL; i++) {
|
||
|
if (strncmp(android_model, low_buffer_models[i], strlen(low_buffer_models[i])) == 0) {
|
||
|
LOGV("Using less buffering");
|
||
|
defaultBufferSize = 1024;
|
||
|
bufferSize = 1024;
|
||
|
premixCount = 1;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void alc_opensles_init(BackendFuncs *func_list)
|
||
|
{
|
||
|
LOGV("alc_opensles_init");
|
||
|
|
||
|
struct stat statinfo;
|
||
|
if (stat("/system/lib/libOpenSLES.so", &statinfo) != 0) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
*func_list = opensles_funcs;
|
||
|
|
||
|
// We need the JavaVM for JNI so we can detect the OS version number at runtime.
|
||
|
// This is because we need to use different bufferCount values for Android 4.1 vs. pre-4.1.
|
||
|
// This must be set at constructor time before JNI_OnLoad is invoked.
|
||
|
apportableOpenALFuncs.alc_android_set_java_vm = alc_opensles_set_java_vm;
|
||
|
}
|
||
|
|
||
|
void alc_opensles_deinit(void)
|
||
|
{
|
||
|
LOGV("alc_opensles_deinit");
|
||
|
|
||
|
// destroy output mix object, and invalidate all associated interfaces
|
||
|
if (outputMixObject != NULL) {
|
||
|
(*outputMixObject)->Destroy(outputMixObject);
|
||
|
outputMixObject = NULL;
|
||
|
}
|
||
|
|
||
|
// destroy engine object, and invalidate all associated interfaces
|
||
|
if (engineObject != NULL) {
|
||
|
(*engineObject)->Destroy(engineObject);
|
||
|
engineObject = NULL;
|
||
|
engineEngine = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void alc_opensles_probe(int type)
|
||
|
{
|
||
|
char *error;
|
||
|
struct stat statinfo;
|
||
|
if (stat("/system/lib/libOpenSLES.so", &statinfo) != 0) {
|
||
|
LOGV("alc_opensles_probe OpenSLES support not found.");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
dlerror(); // Clear dl errors
|
||
|
void *dlHandle = dlopen("/system/lib/libOpenSLES.so", RTLD_NOW | RTLD_GLOBAL);
|
||
|
if (!dlHandle || (error = (typeof(error))dlerror()) != NULL) {
|
||
|
LOGV("OpenSLES could not be loaded.");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
#define LOAD_SYM_POINTER(sym) \
|
||
|
do { \
|
||
|
p##sym = dlsym(dlHandle, #sym); \
|
||
|
if((error=(typeof(error))dlerror()) != NULL) { \
|
||
|
LOGV("alc_opensles_probe could not load %s, error: %s", #sym, error); \
|
||
|
dlclose(dlHandle); \
|
||
|
return; \
|
||
|
} \
|
||
|
} while(0)
|
||
|
|
||
|
LOAD_SYM_POINTER(slCreateEngine);
|
||
|
LOAD_SYM_POINTER(SL_IID_ENGINE);
|
||
|
LOAD_SYM_POINTER(SL_IID_ANDROIDSIMPLEBUFFERQUEUE);
|
||
|
LOAD_SYM_POINTER(SL_IID_PLAY);
|
||
|
LOAD_SYM_POINTER(SL_IID_BUFFERQUEUE);
|
||
|
|
||
|
apportableOpenALFuncs.alc_android_suspend = alc_opensles_suspend;
|
||
|
apportableOpenALFuncs.alc_android_resume = alc_opensles_resume;
|
||
|
|
||
|
switch (type) {
|
||
|
case DEVICE_PROBE:
|
||
|
LOGV("alc_opensles_probe DEVICE_PROBE");
|
||
|
AppendDeviceList(opensles_device);
|
||
|
break;
|
||
|
case ALL_DEVICE_PROBE:
|
||
|
LOGV("alc_opensles_probe ALL_DEVICE_PROBE");
|
||
|
AppendAllDeviceList(opensles_device);
|
||
|
break;
|
||
|
default:
|
||
|
LOGV("alc_opensles_probe type=%d", type);
|
||
|
break;
|
||
|
}
|
||
|
}
|