/* * 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 #include #include #include #include #include #include "alMain.h" #include "AL/al.h" #include "AL/alc.h" #include #include #include #include #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 #include #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; } }