// Minimal audio streaming using OpenSL. // // Loosely based on the Android NDK sample code. // Hardcoded to 44.1kHz stereo 16-bit audio, because as far as I'm concerned, // that's the only format that makes any sense. #include #include #include // for native audio #include #include #include "../base/logging.h" #include "native-audio-so.h" // This is kinda ugly, but for simplicity I've left these as globals just like in the sample, // as there's not really any use case for this where we have multiple audio devices yet. // engine interfaces static SLObjectItf engineObject; static SLEngineItf engineEngine; static SLObjectItf outputMixObject; // buffer queue player interfaces static SLObjectItf bqPlayerObject = NULL; static SLPlayItf bqPlayerPlay; static SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue; static SLMuteSoloItf bqPlayerMuteSolo; static SLVolumeItf bqPlayerVolume; // Double buffering. static short *buffer[2]; static int curBuffer = 0; static int framesPerBuffer; int sampleRate; static AndroidAudioCallback audioCallback; // This callback handler is called every time a buffer finishes playing. // The documentation available is very unclear about how to best manage buffers. // I've chosen to this approach: Instantly enqueue a buffer that was rendered to the last time, // and then render the next. Hopefully it's okay to spend time in this callback after having enqueued. static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context) { if (bq != bqPlayerBufferQueue) { ELOG("Wrong bq!"); return; } int renderedFrames = audioCallback(buffer[curBuffer], framesPerBuffer); int sizeInBytes = framesPerBuffer * 2 * sizeof(short); memset(buffer[curBuffer] + renderedFrames * 2, 0, (framesPerBuffer - renderedFrames) * 4); SLresult result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, buffer[curBuffer], sizeInBytes); // Comment from sample code: // the most likely other result is SL_RESULT_BUFFER_INSUFFICIENT, // which for this code example would indicate a programming error if (result != SL_RESULT_SUCCESS) { ELOG("OpenSL ES: Failed to enqueue! %i %i", renderedFrames, sizeInBytes); } curBuffer ^= 1; // Switch buffer } // create the engine and output mix objects extern "C" bool OpenSLWrap_Init(AndroidAudioCallback cb, int _FramesPerBuffer, int _SampleRate) { audioCallback = cb; framesPerBuffer = _FramesPerBuffer; if (framesPerBuffer == 0) framesPerBuffer = 256; if (framesPerBuffer < 32) framesPerBuffer = 32; sampleRate = _SampleRate; if (sampleRate != 44100 && sampleRate != 48000) { ELOG("Invalid sample rate %i - choosing 44100", sampleRate); sampleRate = 44100; } buffer[0] = new short[framesPerBuffer * 2]; buffer[1] = new short[framesPerBuffer * 2]; SLresult result; // create engine result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL); assert(SL_RESULT_SUCCESS == result); result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE); assert(SL_RESULT_SUCCESS == result); result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine); assert(SL_RESULT_SUCCESS == result); result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, 0, 0); assert(SL_RESULT_SUCCESS == result); result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE); assert(SL_RESULT_SUCCESS == result); int sr = SL_SAMPLINGRATE_44_1; if (sampleRate == 48000) { sr = SL_SAMPLINGRATE_48; } SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2}; SLDataFormat_PCM format_pcm = { SL_DATAFORMAT_PCM, 2, sr, 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 const SLInterfaceID ids[2] = {SL_IID_BUFFERQUEUE, SL_IID_VOLUME}; const SLboolean req[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE}; result = (*engineEngine)->CreateAudioPlayer(engineEngine, &bqPlayerObject, &audioSrc, &audioSnk, 2, ids, req); assert(SL_RESULT_SUCCESS == result); result = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE); assert(SL_RESULT_SUCCESS == result); result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay); assert(SL_RESULT_SUCCESS == result); result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE, &bqPlayerBufferQueue); assert(SL_RESULT_SUCCESS == result); result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, NULL); assert(SL_RESULT_SUCCESS == result); result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_VOLUME, &bqPlayerVolume); assert(SL_RESULT_SUCCESS == result); result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING); assert(SL_RESULT_SUCCESS == result); // Render and enqueue a first buffer. (or should we just play the buffer empty?) curBuffer = 0; audioCallback(buffer[curBuffer], framesPerBuffer); result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, buffer[curBuffer], sizeof(buffer[curBuffer])); if (SL_RESULT_SUCCESS != result) { return false; } curBuffer ^= 1; return true; } // shut down the native audio system extern "C" void OpenSLWrap_Shutdown() { SLresult result; ILOG("OpenSLWrap_Shutdown - stopping playback"); result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_STOPPED); assert(SL_RESULT_SUCCESS == result); ILOG("OpenSLWrap_Shutdown - deleting player object"); if (bqPlayerObject != NULL) { (*bqPlayerObject)->Destroy(bqPlayerObject); bqPlayerObject = NULL; bqPlayerPlay = NULL; bqPlayerBufferQueue = NULL; bqPlayerMuteSolo = NULL; bqPlayerVolume = NULL; } ILOG("OpenSLWrap_Shutdown - deleting mix object"); if (outputMixObject != NULL) { (*outputMixObject)->Destroy(outputMixObject); outputMixObject = NULL; } ILOG("OpenSLWrap_Shutdown - deleting engine object"); if (engineObject != NULL) { (*engineObject)->Destroy(engineObject); engineObject = NULL; engineEngine = NULL; } delete [] buffer[0]; delete [] buffer[1]; ILOG("OpenSLWrap_Shutdown - finished"); }