| // 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 <assert.h> | |
| #include <string.h> | |
| // for native audio | |
| #include <SLES/OpenSLES.h> | |
| #include <SLES/OpenSLES_Android.h> | |
| #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; | |
| #define BUFFER_SIZE 512 | |
| #define BUFFER_SIZE_IN_SAMPLES (BUFFER_SIZE / 2) | |
| // Double buffering. | |
| static short buffer[2][BUFFER_SIZE]; | |
| static int curBuffer = 0; | |
| 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) { | |
| assert(bq == bqPlayerBufferQueue); | |
| assert(NULL == context); | |
| short *nextBuffer = buffer[curBuffer]; | |
| int nextSize = sizeof(buffer[0]); | |
| SLresult result; | |
| result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, nextBuffer, nextSize); | |
| // Comment from sample code: | |
| // the most likely other result is SL_RESULT_BUFFER_INSUFFICIENT, | |
| // which for this code example would indicate a programming error | |
| assert(SL_RESULT_SUCCESS == result); | |
| curBuffer ^= 1; // Switch buffer | |
| // Render to the fresh buffer | |
| audioCallback(buffer[curBuffer], BUFFER_SIZE_IN_SAMPLES); | |
| } | |
| // create the engine and output mix objects | |
| extern "C" bool OpenSLWrap_Init(AndroidAudioCallback cb) { | |
| audioCallback = cb; | |
| 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); | |
| SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2}; | |
| SLDataFormat_PCM format_pcm = { | |
| SL_DATAFORMAT_PCM, | |
| 2, | |
| SL_SAMPLINGRATE_44_1, | |
| 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], BUFFER_SIZE_IN_SAMPLES); | |
| 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() { | |
| if (bqPlayerObject != NULL) { | |
| (*bqPlayerObject)->Destroy(bqPlayerObject); | |
| bqPlayerObject = NULL; | |
| bqPlayerPlay = NULL; | |
| bqPlayerBufferQueue = NULL; | |
| bqPlayerMuteSolo = NULL; | |
| bqPlayerVolume = NULL; | |
| } | |
| if (outputMixObject != NULL) { | |
| (*outputMixObject)->Destroy(outputMixObject); | |
| outputMixObject = NULL; | |
| } | |
| if (engineObject != NULL) { | |
| (*engineObject)->Destroy(engineObject); | |
| engineObject = NULL; | |
| engineEngine = NULL; | |
| } | |
| } |
| #pragma once | |
| typedef void (*AndroidAudioCallback)(short *buffer, int num_samples); | |
| bool OpenSLWrap_Init(AndroidAudioCallback cb); | |
| void OpenSLWrap_Shutdown(); |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
|
It's pretty crazy how much boilerplate you need for something simple like this. So, opinions requested, is this the best way to do it? Should I use more or less buffers than two for optimal performance? How do I know which buffer size is best? |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
apuder
Nov 20, 2014
OK, I'm new to OpenSL. I tried your code with the following callback:
#define LEN (44100 * 2 * 2 * 5)
static int bytesServed = 0;
void audioCallback(short *buffer, int num_samples) {
if (bytesServed > LEN) {
memset(buffer, 0, num_samples * 2);
} else {
bytesServed += num_samples * 2;
memset(buffer, 128, num_samples * 2);
}
}
What I expect is a 5 second beep followed by silence. However, the beep lasts for 10 seconds. Why? The number of bytes I send is 44100 (sampling rate) * 2 (sizeof(short)) * 2 (two channels) * 5 (5 seconds).
Also: when I change BUFFER_SIZE the pitch of the beep changes. Why? I would expect the pitch to be the same independent of the buffer size. TIA.
apuder
commented
Nov 20, 2014
|
OK, I'm new to OpenSL. I tried your code with the following callback:
What I expect is a 5 second beep followed by silence. However, the beep lasts for 10 seconds. Why? The number of bytes I send is 44100 (sampling rate) * 2 (sizeof(short)) * 2 (two channels) * 5 (5 seconds). Also: when I change BUFFER_SIZE the pitch of the beep changes. Why? I would expect the pitch to be the same independent of the buffer size. TIA. |
It's pretty crazy how much boilerplate you need for something simple like this.
So, opinions requested, is this the best way to do it? Should I use more or less buffers than two for optimal performance? How do I know which buffer size is best?