/*
* Copyright (C) 2010 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.
*/
// Play an audio file using buffer queue
#include <assert.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <SLES/OpenSLES.h>
#ifdef ANDROID
#include "sndfile.h"
#else
#include <sndfile.h>
#endif
#define max(a, b) ((a) > (b) ? (a) : (b))
#define min(a, b) ((a) < (b) ? (a) : (b))
unsigned numBuffers = 2;
int framesPerBuffer = 512;
SNDFILE *sndfile;
SF_INFO sfinfo;
unsigned which; // which buffer to use next
SLboolean eof; // whether we have hit EOF on input yet
short *buffers;
SLuint32 byteOrder; // desired to use for PCM buffers
SLuint32 nativeByteOrder; // of platform
SLuint32 bitsPerSample = 16;
// swap adjacent bytes; this would normally be in <unistd.h> but is missing here
static void swab(const void *from, void *to, ssize_t n)
{
// from and to as char pointers
const char *from_ch = (const char *) from;
char *to_ch = (char *) to;
// note that we don't swap the last odd byte
while (n >= 2) {
to_ch[0] = from_ch[1];
to_ch[1] = from_ch[0];
to_ch += 2;
from_ch += 2;
n -= 2;
}
}
// squeeze 16-bit signed PCM samples down to 8-bit unsigned PCM samples by truncation; no dithering
static void squeeze(const short *from, unsigned char *to, ssize_t n)
{
// note that we don't squeeze the last odd byte
while (n >= 2) {
*to++ = (*from++ + 32768) >> 8;
n -= 2;
}
}
// This callback is called each time a buffer finishes playing
static void callback(SLBufferQueueItf bufq, void *param)
{
assert(NULL == param);
if (!eof) {
short *buffer = &buffers[framesPerBuffer * sfinfo.channels * which];
sf_count_t count;
count = sf_readf_short(sndfile, buffer, (sf_count_t) framesPerBuffer);
if (0 >= count) {
eof = SL_BOOLEAN_TRUE;
} else {
SLuint32 nbytes = count * sfinfo.channels * sizeof(short);
if (byteOrder != nativeByteOrder) {
swab(buffer, buffer, nbytes);
}
if (bitsPerSample == 8) {
squeeze(buffer, (unsigned char *) buffer, nbytes);
nbytes /= 2;
}
SLresult result = (*bufq)->Enqueue(bufq, buffer, nbytes);
assert(SL_RESULT_SUCCESS == result);
if (++which >= numBuffers)
which = 0;
}
}
}
int main(int argc, char **argv)
{
// Determine the native byte order (SL_BYTEORDER_NATIVE not available until 1.1)
union {
short s;
char c[2];
} u;
u.s = 0x1234;
if (u.c[0] == 0x34) {
nativeByteOrder = SL_BYTEORDER_LITTLEENDIAN;
} else if (u.c[0] == 0x12) {
nativeByteOrder = SL_BYTEORDER_BIGENDIAN;
} else {
fprintf(stderr, "Unable to determine native byte order\n");
return EXIT_FAILURE;
}
byteOrder = nativeByteOrder;
SLboolean enableReverb = SL_BOOLEAN_FALSE;
SLboolean enablePlaybackRate = SL_BOOLEAN_FALSE;
SLpermille initialRate = 0;
SLpermille finalRate = 0;
SLpermille deltaRate = 1;
SLmillisecond deltaRateMs = 0;
// process command-line options
int i;
for (i = 1; i < argc; ++i) {
char *arg = argv[i];
if (arg[0] != '-') {
break;
}
if (!strcmp(arg, "-b")) {
byteOrder = SL_BYTEORDER_BIGENDIAN;
} else if (!strcmp(arg, "-l")) {
byteOrder = SL_BYTEORDER_LITTLEENDIAN;
} else if (!strcmp(arg, "-8")) {
bitsPerSample = 8;
} else if (!strncmp(arg, "-f", 2)) {
framesPerBuffer = atoi(&arg[2]);
} else if (!strncmp(arg, "-n", 2)) {
numBuffers = atoi(&arg[2]);
} else if (!strncmp(arg, "-p", 2)) {
initialRate = atoi(&arg[2]);
enablePlaybackRate = SL_BOOLEAN_TRUE;
} else if (!strncmp(arg, "-P", 2)) {
finalRate = atoi(&arg[2]);
enablePlaybackRate = SL_BOOLEAN_TRUE;
} else if (!strncmp(arg, "-q", 2)) {
deltaRate = atoi(&arg[2]);
// deltaRate is a magnitude, so take absolute value
if (deltaRate < 0) {
deltaRate = -deltaRate;
}
enablePlaybackRate = SL_BOOLEAN_TRUE;
} else if (!strncmp(arg, "-Q", 2)) {
deltaRateMs = atoi(&arg[2]);
enablePlaybackRate = SL_BOOLEAN_TRUE;
} else if (!strcmp(arg, "-r")) {
enableReverb = SL_BOOLEAN_TRUE;
} else {
fprintf(stderr, "option %s ignored\n", arg);
}
}
if (argc - i != 1) {
fprintf(stderr, "usage: [-b/l] [-8] [-f#] [-n#] [-p#] [-r] %s filename\n", argv[0]);
fprintf(stderr, " -b force big-endian byte order (default is native byte order)\n");
fprintf(stderr, " -l force little-endian byte order (default is native byte order)\n");
fprintf(stderr, " -8 output 8-bits per sample (default is 16-bits per sample)\n");
fprintf(stderr, " -f# frames per buffer (default 512)\n");
fprintf(stderr, " -n# number of buffers (default 2)\n");
fprintf(stderr, " -p# initial playback rate in per mille (default 1000)\n");
fprintf(stderr, " -P# final playback rate in per mille (default same as -p#)\n");
fprintf(stderr, " -q# magnitude of playback rate changes in per mille (default 1)\n");
fprintf(stderr, " -Q# period between playback rate changes in ms (default 50)\n");
fprintf(stderr, " -r enable reverb (default disabled)\n");
return EXIT_FAILURE;
}
const char *filename = argv[i];
//memset(&sfinfo, 0, sizeof(SF_INFO));
sfinfo.format = 0;
sndfile = sf_open(filename, SFM_READ, &sfinfo);
if (NULL == sndfile) {
perror(filename);
return EXIT_FAILURE;
}
// verify the file format
switch (sfinfo.channels) {
case 1:
case 2:
break;
default:
fprintf(stderr, "unsupported channel count %d\n", sfinfo.channels);
goto close_sndfile;
}
switch (sfinfo.samplerate) {
case 8000:
case 11025:
case 12000:
case 16000:
case 22050:
case 24000:
case 32000:
case 44100:
case 48000:
break;
default:
fprintf(stderr, "unsupported sample rate %d\n", sfinfo.samplerate);
goto close_sndfile;
}
switch (sfinfo.format & SF_FORMAT_TYPEMASK) {
case SF_FORMAT_WAV:
break;
default:
fprintf(stderr, "unsupported format type 0x%x\n", sfinfo.format & SF_FORMAT_TYPEMASK);
goto close_sndfile;
}
switch (sfinfo.format & SF_FORMAT_SUBMASK) {
case SF_FORMAT_PCM_16:
case SF_FORMAT_PCM_U8:
case SF_FORMAT_ULAW:
case SF_FORMAT_ALAW:
case SF_FORMAT_IMA_ADPCM:
break;
default:
fprintf(stderr, "unsupported sub-format 0x%x\n", sfinfo.format & SF_FORMAT_SUBMASK);
goto close_sndfile;
}
buffers = (short *) malloc(framesPerBuffer * sfinfo.channels * sizeof(short) * numBuffers);
// create engine
SLresult result;
SLObjectItf engineObject;
result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
assert(SL_RESULT_SUCCESS == result);
SLEngineItf engineEngine;
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);
// create output mix
SLObjectItf outputMixObject;
SLInterfaceID ids[1] = {SL_IID_ENVIRONMENTALREVERB};
SLboolean req[1] = {SL_BOOLEAN_TRUE};
result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, enableReverb ? 1 : 0,
ids, req);
assert(SL_RESULT_SUCCESS == result);
result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
assert(SL_RESULT_SUCCESS == result);
// configure environmental reverb on output mix
SLEnvironmentalReverbItf mixEnvironmentalReverb = NULL;
if (enableReverb) {
result = (*outputMixObject)->GetInterface(outputMixObject, SL_IID_ENVIRONMENTALREVERB,
&mixEnvironmentalReverb);
assert(SL_RESULT_SUCCESS == result);
SLEnvironmentalReverbSettings settings = SL_I3DL2_ENVIRONMENT_PRESET_STONECORRIDOR;
result = (*mixEnvironmentalReverb)->SetEnvironmentalReverbProperties(mixEnvironmentalReverb,
&settings);
assert(SL_RESULT_SUCCESS == result);
}
// configure audio source
SLDataLocator_BufferQueue loc_bufq;
loc_bufq.locatorType = SL_DATALOCATOR_BUFFERQUEUE;
loc_bufq.numBuffers = numBuffers;
SLDataFormat_PCM format_pcm;
format_pcm.formatType = SL_DATAFORMAT_PCM;
format_pcm.numChannels = sfinfo.channels;
format_pcm.samplesPerSec = sfinfo.samplerate * 1000;
format_pcm.bitsPerSample = bitsPerSample;
format_pcm.containerSize = format_pcm.bitsPerSample;
format_pcm.channelMask = 1 == format_pcm.numChannels ? SL_SPEAKER_FRONT_CENTER :
SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
format_pcm.endianness = byteOrder;
SLDataSource audioSrc;
audioSrc.pLocator = &loc_bufq;
audioSrc.pFormat = &format_pcm;
// configure audio sink
SLDataLocator_OutputMix loc_outmix;
loc_outmix.locatorType = SL_DATALOCATOR_OUTPUTMIX;
loc_outmix.outputMix = outputMixObject;
SLDataSink audioSnk;
audioSnk.pLocator = &loc_outmix;
audioSnk.pFormat = NULL;
// create audio player
SLInterfaceID ids2[3] = {SL_IID_BUFFERQUEUE, SL_IID_PLAYBACKRATE, SL_IID_EFFECTSEND};
SLboolean req2[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
SLObjectItf playerObject;
result = (*engineEngine)->CreateAudioPlayer(engineEngine, &playerObject, &audioSrc,
&audioSnk, enableReverb ? 3 : (enablePlaybackRate ? 2 : 1), ids2, req2);
if (SL_RESULT_SUCCESS != result) {
fprintf(stderr, "can't create audio player\n");
goto no_player;
}
// realize the player
result = (*playerObject)->Realize(playerObject, SL_BOOLEAN_FALSE);
assert(SL_RESULT_SUCCESS == result);
// get the effect send interface and enable effect send reverb for this player
if (enableReverb) {
SLEffectSendItf playerEffectSend;
result = (*playerObject)->GetInterface(playerObject, SL_IID_EFFECTSEND, &playerEffectSend);
assert(SL_RESULT_SUCCESS == result);
result = (*playerEffectSend)->EnableEffectSend(playerEffectSend, mixEnvironmentalReverb,
SL_BOOLEAN_TRUE, (SLmillibel) 0);
assert(SL_RESULT_SUCCESS == result);
}
// get the playback rate interface and configure the rate
SLPlaybackRateItf playerPlaybackRate;
SLpermille currentRate;
if (enablePlaybackRate) {
result = (*playerObject)->GetInterface(playerObject, SL_IID_PLAYBACKRATE,
&playerPlaybackRate);
assert(SL_RESULT_SUCCESS == result);
SLpermille defaultRate;
result = (*playerPlaybackRate)->GetRate(playerPlaybackRate, &defaultRate);
assert(SL_RESULT_SUCCESS == result);
SLuint32 defaultProperties;
result = (*playerPlaybackRate)->GetProperties(playerPlaybackRate, &defaultProperties);
assert(SL_RESULT_SUCCESS == result);
printf("default playback rate %d per mille, properties 0x%x\n", defaultRate,
defaultProperties);
if (initialRate <= 0) {
initialRate = defaultRate;
}
if (finalRate <= 0) {
finalRate = initialRate;
}
currentRate = defaultRate;
if (finalRate == initialRate) {
deltaRate = 0;
} else if (finalRate < initialRate) {
deltaRate = -deltaRate;
}
if (initialRate != defaultRate) {
result = (*playerPlaybackRate)->SetRate(playerPlaybackRate, initialRate);
if (SL_RESULT_FEATURE_UNSUPPORTED == result) {
fprintf(stderr, "initial playback rate %d is unsupported\n", initialRate);
deltaRate = 0;
} else if (SL_RESULT_PARAMETER_INVALID == result) {
fprintf(stderr, "initial playback rate %d is invalid\n", initialRate);
deltaRate = 0;
} else {
assert(SL_RESULT_SUCCESS == result);
currentRate = initialRate;
}
}
}
// get the play interface
SLPlayItf playerPlay;
result = (*playerObject)->GetInterface(playerObject, SL_IID_PLAY, &playerPlay);
assert(SL_RESULT_SUCCESS == result);
// get the buffer queue interface
SLBufferQueueItf playerBufferQueue;
result = (*playerObject)->GetInterface(playerObject, SL_IID_BUFFERQUEUE,
&playerBufferQueue);
assert(SL_RESULT_SUCCESS == result);
// loop until EOF or no more buffers
for (which = 0; which < numBuffers; ++which) {
short *buffer = &buffers[framesPerBuffer * sfinfo.channels * which];
sf_count_t frames = framesPerBuffer;
sf_count_t count;
count = sf_readf_short(sndfile, buffer, frames);
if (0 >= count) {
eof = SL_BOOLEAN_TRUE;
break;
}
// enqueue a buffer
SLuint32 nbytes = count * sfinfo.channels * sizeof(short);
if (byteOrder != nativeByteOrder) {
swab(buffer, buffer, nbytes);
}
if (bitsPerSample == 8) {
squeeze(buffer, (unsigned char *) buffer, nbytes);
nbytes /= 2;
}
result = (*playerBufferQueue)->Enqueue(playerBufferQueue, buffer, nbytes);
assert(SL_RESULT_SUCCESS == result);
}
if (which >= numBuffers) {
which = 0;
}
// register a callback on the buffer queue
result = (*playerBufferQueue)->RegisterCallback(playerBufferQueue, callback, NULL);
assert(SL_RESULT_SUCCESS == result);
// set the player's state to playing
result = (*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_PLAYING);
assert(SL_RESULT_SUCCESS == result);
// get the initial time
struct timespec prevTs;
clock_gettime(CLOCK_MONOTONIC, &prevTs);
long elapsedNs = 0;
long deltaRateNs = deltaRateMs * 1000000;
// wait until the buffer queue is empty
SLBufferQueueState bufqstate;
for (;;) {
result = (*playerBufferQueue)->GetState(playerBufferQueue, &bufqstate);
assert(SL_RESULT_SUCCESS == result);
if (0 >= bufqstate.count) {
break;
}
if (!enablePlaybackRate || deltaRate == 0) {
sleep(1);
} else {
struct timespec curTs;
clock_gettime(CLOCK_MONOTONIC, &curTs);
elapsedNs += (curTs.tv_sec - prevTs.tv_sec) * 1000000000 +
// this term can be negative
(curTs.tv_nsec - prevTs.tv_nsec);
prevTs = curTs;
if (elapsedNs < deltaRateNs) {
usleep((deltaRateNs - elapsedNs) / 1000);
continue;
}
elapsedNs -= deltaRateNs;
SLpermille nextRate = currentRate + deltaRate;
result = (*playerPlaybackRate)->SetRate(playerPlaybackRate, nextRate);
if (SL_RESULT_SUCCESS != result) {
fprintf(stderr, "next playback rate %d is unsupported\n", nextRate);
} else if (SL_RESULT_PARAMETER_INVALID == result) {
fprintf(stderr, "next playback rate %d is invalid\n", nextRate);
} else {
assert(SL_RESULT_SUCCESS == result);
}
currentRate = nextRate;
if (currentRate >= max(initialRate, finalRate)) {
currentRate = max(initialRate, finalRate);
deltaRate = -abs(deltaRate);
} else if (currentRate <= min(initialRate, finalRate)) {
currentRate = min(initialRate, finalRate);
deltaRate = abs(deltaRate);
}
}
}
// destroy audio player
(*playerObject)->Destroy(playerObject);
no_player:
// destroy output mix
(*outputMixObject)->Destroy(outputMixObject);
// destroy engine
(*engineObject)->Destroy(engineObject);
close_sndfile:
(void) sf_close(sndfile);
return EXIT_SUCCESS;
}