Skip to content

Instantly share code, notes, and snippets.

@mox1
Last active August 29, 2015 14:19
Show Gist options
  • Save mox1/3516112bc52c54aabb98 to your computer and use it in GitHub Desktop.
Save mox1/3516112bc52c54aabb98 to your computer and use it in GitHub Desktop.
How to use Opensl ES (aka low level audio) on Android - Part of a Spotify mobile App I wrote
/* Code Written by mox1
* http://moxone.me/
* The code contained within is part of a larger Android Application. Portions of this code
* also rely on the "Poco" C++ library.
*
* Copyright (c) 2015, [email protected]
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* ( (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those
* of the authors and should not be interpreted as representing official policies,
* either expressed or implied, of the author's affiliated organizations.
*/
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
bool SpotLib::init_audio() {
engineObject = NULL;
outputMixObject = NULL;
bqPlayerObject = NULL;
bqPlayerBufferQueue = NULL;
bqPlayerPlay = NULL;
Poco::Int16 x;
SLresult result;
// create engine
result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
if(result != SL_RESULT_SUCCESS) {
SSDEBUG("Android audio error: %?u", result);
return false;
}
// realize the engine
result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE );
if(result != SL_RESULT_SUCCESS) {
SSDEBUG("Android audio error: %?u", result);
return false;
}
// get the engine interface, which is needed in order to create other objects
result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
if(result != SL_RESULT_SUCCESS) {
SSDEBUG("Android audio error: %?u", result);
return false;
}
/*If we ever encounter a Spotify song with > 2 channels, this is going to be a problem here */
for(x=0; x<NUM_ANDROID_BUFFERS; x++) {
audio_data[x] = (Poco::Int16 *) calloc(1,(MAX_SL_AUDIO_FRAMES * sizeof(Poco::Int16) * 2));
if (audio_data[x] == NULL) {
SSDEBUG("calloc() error!");
return false;
}
}
cur_audio_buf = 0;
need_audio_start = true;
SSINFO("Android audio initialized successfully");
return true;
}
bool SpotLib::open_audio_stream(const int sample_format,const int sample_rate,const int channels) {
SLresult result;
SLuint32 sf;
SLuint32 sr;
int speakers;
if (audiobuffer == NULL) {
//allocate a 1mb audio buffer
audiobuffer = new AudioBuffer2((1024*1024*1),sample_rate,channels);
//allocate a 64k audio buffer
//audiobuffer = new AudioBuffer2((64*1024*1),sample_rate,channels);
SSDEBUG("Allocated audiobuffer2!");
}
else {
audiobuffer->reset(sample_rate,channels);
}
switch (channels) {
case (1):
speakers = SL_SPEAKER_FRONT_CENTER;
break;
default:
speakers = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
break;
}
switch(sample_format) {
case SP_SAMPLETYPE_INT16_NATIVE_ENDIAN:
sf = SL_PCMSAMPLEFORMAT_FIXED_16;
break;
default:
SSERROR("UNKNOWN SPOTIFY SAMPLE RATE!!! %?d", sample_format);
return false;
}
switch(sample_rate) {
case 44100:
sr = SL_SAMPLINGRATE_44_1;
break;
default:
SSWARN("Unknown Spotify sample format, %?u setting for 44.1",sample_format);
sr = SL_SAMPLINGRATE_44_1;
break;
}
const SLInterfaceID ids[] = { SL_IID_VOLUME };
const SLboolean req[] = { SL_BOOLEAN_FALSE };
result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 1, ids, req);
if(result != SL_RESULT_SUCCESS) {
SSDEBUG("Android audio error: %?u", result);
return false;
}
result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE );
if(result != SL_RESULT_SUCCESS) {
SSDEBUG("Android audio error: %?u", result);
return false;
}
// The source is the buffer
SLDataLocator_AndroidSimpleBufferQueue loc_bufq = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, NUM_ANDROID_BUFFERS };
SLDataFormat_PCM format_pcm = { SL_DATAFORMAT_PCM, channels, sr, sf, sf,speakers, SL_BYTEORDER_LITTLEENDIAN };
SLDataSource audioSrc = { &loc_bufq, &format_pcm };
// configure audio sink as the outputMix
SLDataLocator_OutputMix loc_outmix = { SL_DATALOCATOR_OUTPUTMIX, outputMixObject };
SLDataSink audioSnk = { &loc_outmix, NULL };
//create audio player
const SLInterfaceID ids1[] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE};
const SLboolean req1[] = {SL_BOOLEAN_TRUE};
result = (*engineEngine)->CreateAudioPlayer(engineEngine,&bqPlayerObject, &audioSrc, &audioSnk,1, ids1, req1);
if(result != SL_RESULT_SUCCESS) {
SSDEBUG("Android audio error: %?u", result);
return false;
}
//realize player
result = (*bqPlayerObject)->Realize(bqPlayerObject,SL_BOOLEAN_FALSE);
if(result != SL_RESULT_SUCCESS) {
SSDEBUG("Android audio error: %?u", result);
return false;
}
//get the play interface
result = (*bqPlayerObject)->GetInterface(bqPlayerObject,SL_IID_PLAY,&bqPlayerPlay);
if(result != SL_RESULT_SUCCESS) {
SSDEBUG("Android audio error: %?u", result);
return false;
}
//get buffer q interface
result = (*bqPlayerObject)->GetInterface(bqPlayerObject,SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bqPlayerBufferQueue);
if(result != SL_RESULT_SUCCESS) {
SSDEBUG("Android audio error: %?u", result);
return false;
}
//register callback on buffer q
result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue,wrapper_bqPlayerCallback, NULL);
if(result != SL_RESULT_SUCCESS) {
SSDEBUG("Android audio error: %?u", result);
return false;
}
SSINFO("Audio stream opened successfully");
return true;
}
void SpotLib::bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq,void *context) {
SLresult result;
Poco::Int32 bytes;
if (audiobuffer == NULL) {
SSERROR("In audio callback, with null audiobuffer! WTF!!!");
return;
}
bytes = audiobuffer->readFromBuffer(audio_data[cur_audio_buf],MAX_SL_AUDIO_FRAMES);
while (bytes <= 0) {
//This means we don't have enough / any audio to play, sleep
//for 2 seconds
SSDEBUG("Not enough audio, waiting");
audiobuffer->waitOnData(500);
//lets make sure audio hasn't "Stopped"
if (audio_status != AS_PLAYING ) {
SSDEBUG("bqPlayerCallback, audio state changed %?u", audio_status);
return;
}
//nope were good, loop around
bytes = audiobuffer->readFromBuffer(audio_data[cur_audio_buf],MAX_SL_AUDIO_FRAMES);
}
//Now call EnQueue
//bytes = MAX_SL_AUDIO_FRAMES*sizeof(Poco::Int16)*audiobuffer->cur_channels;
//SSDEBUG("bqPlayerCallback queueing %?d bytes!",bytes);
result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue,audio_data[cur_audio_buf],bytes);
if(result != SL_RESULT_SUCCESS) {
SSDEBUG("Android audio error: %?u", result);
}
cur_audio_buf += 1;
cur_audio_buf = cur_audio_buf % NUM_ANDROID_BUFFERS;
}
bool SpotLib::start_audio() {
SLresult result;
if(need_audio_start == false) return false;
if (bqPlayerPlay == false) {
SSERROR("Call to start_audio() with NULL bqPlayer!");
return false;
}
need_audio_start = false;
attempt_start_audio = false;
SSDEBUG("STarting audio playback, OpenSL");
events->rem_q_by_t(ET_AUDIO_START);
//set the players state to playing
result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay,SL_PLAYSTATE_PLAYING);
if(result != SL_RESULT_SUCCESS) {
SSDEBUG("Android audio error: %?u", result);
}
bqPlayerCallback(bqPlayerBufferQueue,NULL);
return true;
}
void SpotLib::stop_audio() {
SLresult result;
if (need_audio_start == true) return;
//set the players state to paused
result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay,SL_PLAYSTATE_STOPPED);
if(result != SL_RESULT_SUCCESS) {
SSDEBUG("Android audio error: %?u", result);
}
need_audio_start = true;
}
void SpotLib::pause_audio() {
SLresult result;
if (need_audio_start == true) return;
//set the players state to paused
result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay,SL_PLAYSTATE_PAUSED);
if(result != SL_RESULT_SUCCESS) {
SSDEBUG("Android audio error: %?u", result);
}
}
bool SpotLib::resume_audio() {
SLresult result;
//set the players state to paused
result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay,SL_PLAYSTATE_PLAYING);
if(result != SL_RESULT_SUCCESS) {
SSDEBUG("Android audio error: %?u", result);
return false;
}
return true;
}
void SpotLib::flush_audio_stream() {
SLresult result;
result = (*bqPlayerBufferQueue)->Clear(bqPlayerBufferQueue);
if(result != SL_RESULT_SUCCESS) {
SSDEBUG("Android audio error: %?u", result);
}
}
/* Code Written by mox1
* http://moxone.me/
* The code contained within is part of a larger Android Application. Portions of this code
* also rely on the "Poco" C++ library.
*
* Copyright (c) 2015, [email protected]
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* ( (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those
* of the authors and should not be interpreted as representing official policies,
* either expressed or implied, of the author's affiliated organizations.
*/
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
SLObjectItf engineObject;
SLEngineItf engineEngine;
// output mix interfaces
SLObjectItf outputMixObject;
SLObjectItf bqPlayerObject;
SLPlayItf bqPlayerPlay;
Poco::Int16 *audio_data[3];
Poco::Int8 cur_audio_buf;
//Buffer object
SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue;
#define MAX_SL_AUDIO_FRAMES 8096*4
#define NUM_ANDROID_BUFFERS 2
void SP_CALLCONV wrapper_bqPlayerCallback( SLAndroidSimpleBufferQueueItf bq,void *context);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment