| /* | |
| * 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. | |
| */ | |
| #include <stdlib.h> | |
| #include <stdio.h> | |
| //#include <string.h> | |
| #include <unistd.h> | |
| //#include <sys/time.h> | |
| #include <SLES/OpenSLES.h> | |
| #include <SLES/OpenSLES_Android.h> | |
| //#define TEST_DISPLAY_FIRST_BUFFER_ITEM | |
| //#define TEST_CLEAR | |
| //#define TEST_PTS | |
| #define MAX_NUMBER_INTERFACES 2 | |
| #define PREFETCHEVENT_ERROR_CANDIDATE \ | |
| (SL_PREFETCHEVENT_STATUSCHANGE | SL_PREFETCHEVENT_FILLLEVELCHANGE) | |
| #define NB_BUFFERS 16 | |
| #define MPEG2_TS_BLOCK_SIZE 188 | |
| #define BUFFER_SIZE (20*MPEG2_TS_BLOCK_SIZE) | |
| #define DISCONTINUITY_MAGIC 1977 | |
| /* Where we store the data to play */ | |
| char dataCache[BUFFER_SIZE * NB_BUFFERS]; | |
| /* From where we read the data to play */ | |
| FILE *file; | |
| /* Has the app reached the end of the file */ | |
| bool reachedEof = false; | |
| /* Special discontinuity buffer context */ | |
| int myDiscBufferContext = DISCONTINUITY_MAGIC; | |
| /* structure to store my discontinuity with PTS command */ | |
| typedef struct { | |
| SLuint32 discKey; // identifies the item | |
| SLuint32 discSize; | |
| SLAuint64 discPts; | |
| } DiscPts; | |
| //----------------------------------------------------------------- | |
| /* Exits the application if an error is encountered */ | |
| #define CheckErr(x) ExitOnErrorFunc(x,__LINE__) | |
| void ExitOnErrorFunc( SLresult result , int line) | |
| { | |
| if (SL_RESULT_SUCCESS != result) { | |
| fprintf(stderr, "%u error code encountered at line %d, exiting\n", result, line); | |
| exit(EXIT_FAILURE); | |
| } | |
| } | |
| bool prefetchError = false; | |
| //----------------------------------------------------------------- | |
| /* AndroidBufferQueueItf callback for an audio player */ | |
| SLresult AndroidBufferQueueCallback( | |
| SLAndroidBufferQueueItf caller, | |
| void *pCallbackContext __unused, /* input */ | |
| void *pBufferContext, /* input */ | |
| void *pBufferData __unused, /* input */ | |
| SLuint32 dataSize __unused, /* input */ | |
| SLuint32 dataUsed __unused, /* input */ | |
| const SLAndroidBufferItem *pItems __unused, /* input */ | |
| SLuint32 itemsLength __unused /* input */) | |
| { | |
| // assert(BUFFER_SIZE <= dataSize); | |
| //-------------------------------------------------------------------------------- | |
| // this section is for testing only, this is NOT an example of how to use the API | |
| // to play a .ts file, but rather shows more ways to exercise the API | |
| //-------------------------------------------------------------------------------- | |
| SLAndroidBufferQueueState state; | |
| (*caller)->GetState(caller, &state); | |
| //fprintf(stdout, "ABQ state count=%lu, index=%lu\n", state.count, state.index); | |
| // just to test, clear the queue to see what happens | |
| if (state.index == 500) { | |
| #ifdef TEST_CLEAR | |
| (*caller)->Clear(caller); | |
| // we've cleared the queue, and have introduced a discontinuity, so signal it | |
| SLAndroidBufferItem msgDiscontinuity; | |
| msgDiscontinuity.itemKey = SL_ANDROID_ITEMKEY_DISCONTINUITY; | |
| msgDiscontinuity.itemSize = 0; | |
| // message has no parameters, so the total size of the message is the size of the key | |
| // plus the size if itemSize, both SLuint32 | |
| (*caller)->Enqueue(caller, (void*)&myDiscBufferContext /*pBufferContext*/, | |
| NULL /*pData*/, 0 /*dataLength*/, | |
| &msgDiscontinuity /*pMsg*/, | |
| sizeof(SLuint32)*2 /*msgLength*/); | |
| // we've cleared the queue, it's now empty: let's rebuffer a bit so playback doesn't starve | |
| size_t nbRead = fread((void*)dataCache, 1, BUFFER_SIZE*(NB_BUFFERS/2), file); | |
| if (nbRead == BUFFER_SIZE*(NB_BUFFERS/2)) { | |
| for (int i=0 ; i < NB_BUFFERS/2 ; i++) { | |
| SLresult res = (*caller)->Enqueue(caller, NULL /*pBufferContext*/, | |
| dataCache + i*BUFFER_SIZE, | |
| BUFFER_SIZE, NULL, 0); | |
| CheckErr(res); | |
| } | |
| } | |
| #endif | |
| #ifdef TEST_PTS | |
| DiscPts msgDiscontinuity = { SL_ANDROID_ITEMKEY_DISCONTINUITY, | |
| sizeof(SLAuint64), 15*90*1000 /*15s in 90kHz clock */ }; | |
| // enqueue discontinuity message with our PTS | |
| (*caller)->Enqueue(caller, (void*)&myDiscBufferContext /*pBufferContext*/, | |
| NULL /*pData*/, 0 /*dataLength*/, | |
| (SLAndroidBufferItem*)&msgDiscontinuity, | |
| sizeof(DiscPts) ); | |
| fprintf(stdout, "rendering will resume at 15s mark"); | |
| #endif | |
| return SL_RESULT_SUCCESS; | |
| } | |
| //-------------------------------------------------------------------------------- | |
| // end of test only section | |
| //-------------------------------------------------------------------------------- | |
| else { | |
| #ifdef TEST_DISPLAY_FIRST_BUFFER_ITEM | |
| // display item data (only parsing first item) | |
| if (itemsLength !=0) { | |
| fprintf(stdout, "item key=0x%lx size=%lu data=0x%lx\n", | |
| pItems->itemKey, pItems->itemSize, *((SLuint32*)&pItems->itemData)); | |
| } | |
| #endif | |
| // pBufferData can be null if the last consumed buffer contained only a command | |
| // just like we do for signalling DISCONTINUITY (above) or EOS (below) | |
| if ((pBufferContext != NULL) && (*((int*)pBufferContext) == DISCONTINUITY_MAGIC)) { | |
| fprintf(stdout, "Successfully detected my discontinuity buffer having been consumed\n"); | |
| } | |
| if (pBufferData != NULL) { | |
| size_t nbRead = fread((void*)pBufferData, 1, BUFFER_SIZE, file); | |
| if (nbRead > 0) { | |
| (*caller)->Enqueue(caller, NULL /*pBufferContext*/, | |
| pBufferData /*pData*/, | |
| nbRead /*dataLength*/, | |
| NULL /*pMsg*/, | |
| 0 /*msgLength*/); | |
| } else if (!reachedEof) { | |
| // signal EOS | |
| SLAndroidBufferItem msgEos; | |
| msgEos.itemKey = SL_ANDROID_ITEMKEY_EOS; | |
| msgEos.itemSize = 0; | |
| // EOS message has no parameters, so the total size of the message is the size of | |
| // the key plus the size if itemSize, both SLuint32 | |
| (*caller)->Enqueue(caller, NULL /*pBufferContext*/, | |
| NULL /*pData*/, 0 /*dataLength*/, | |
| &msgEos /*pMsg*/, | |
| sizeof(SLuint32)*2 /*msgLength*/); | |
| reachedEof = true; | |
| } | |
| } | |
| return SL_RESULT_SUCCESS; | |
| } | |
| } | |
| //----------------------------------------------------------------- | |
| /* Play some music from a URI */ | |
| void TestPlayStream( SLObjectItf sl, const char* path) | |
| { | |
| SLEngineItf EngineItf; | |
| SLresult res; | |
| SLDataSource audioSource; | |
| SLDataLocator_AndroidBufferQueue streamLocator; | |
| SLDataFormat_MIME mime; | |
| SLDataSink audioSink; | |
| SLDataLocator_OutputMix locator_outputmix; | |
| SLObjectItf player; | |
| SLPlayItf playItf; | |
| SLVolumeItf volItf; | |
| SLAndroidBufferQueueItf abqItf; | |
| SLObjectItf OutputMix; | |
| SLboolean required[MAX_NUMBER_INTERFACES]; | |
| SLInterfaceID iidArray[MAX_NUMBER_INTERFACES]; | |
| int playTimeInSec = 60; | |
| file = fopen(path, "rb"); | |
| /* Get the SL Engine Interface which is implicit */ | |
| res = (*sl)->GetInterface(sl, SL_IID_ENGINE, (void*)&EngineItf); | |
| CheckErr(res); | |
| /* Initialize arrays required[] and iidArray[] */ | |
| for (int i=0 ; i < MAX_NUMBER_INTERFACES ; i++) { | |
| required[i] = SL_BOOLEAN_FALSE; | |
| iidArray[i] = SL_IID_NULL; | |
| } | |
| // Set arrays required[] and iidArray[] for VOLUME and PREFETCHSTATUS interface | |
| required[0] = SL_BOOLEAN_TRUE; | |
| iidArray[0] = SL_IID_VOLUME; | |
| required[1] = SL_BOOLEAN_TRUE; | |
| iidArray[1] = SL_IID_ANDROIDBUFFERQUEUESOURCE; | |
| // Create Output Mix object to be used by player | |
| res = (*EngineItf)->CreateOutputMix(EngineItf, &OutputMix, 0, | |
| iidArray, required); CheckErr(res); | |
| // Realizing the Output Mix object in synchronous mode. | |
| res = (*OutputMix)->Realize(OutputMix, SL_BOOLEAN_FALSE); | |
| CheckErr(res); | |
| /* Setup the data source structure for the URI */ | |
| streamLocator.locatorType = SL_DATALOCATOR_ANDROIDBUFFERQUEUE; | |
| streamLocator.numBuffers = NB_BUFFERS; | |
| mime.formatType = SL_DATAFORMAT_MIME; | |
| mime.mimeType = (SLchar *) "video/mp2ts";//(SLchar*)NULL; | |
| mime.containerType = SL_CONTAINERTYPE_MPEG_TS; | |
| audioSource.pFormat = (void *)&mime; | |
| audioSource.pLocator = (void *)&streamLocator; | |
| /* Setup the data sink structure */ | |
| locator_outputmix.locatorType = SL_DATALOCATOR_OUTPUTMIX; | |
| locator_outputmix.outputMix = OutputMix; | |
| audioSink.pLocator = (void *)&locator_outputmix; | |
| audioSink.pFormat = NULL; | |
| /* Create the audio player */ | |
| res = (*EngineItf)->CreateAudioPlayer(EngineItf, &player, &audioSource, &audioSink, | |
| MAX_NUMBER_INTERFACES, iidArray, required); CheckErr(res); | |
| /* Realizing the player in synchronous mode. */ | |
| res = (*player)->Realize(player, SL_BOOLEAN_FALSE); CheckErr(res); | |
| fprintf(stdout, "URI example: after Realize\n"); | |
| /* Get interfaces */ | |
| res = (*player)->GetInterface(player, SL_IID_PLAY, (void*)&playItf); CheckErr(res); | |
| res = (*player)->GetInterface(player, SL_IID_VOLUME, (void*)&volItf); CheckErr(res); | |
| res = (*player)->GetInterface(player, SL_IID_ANDROIDBUFFERQUEUESOURCE, (void*)&abqItf); | |
| CheckErr(res); | |
| res = (*abqItf)->RegisterCallback(abqItf, AndroidBufferQueueCallback, | |
| // context is not used in the example, but can be used to track who registered | |
| // the buffer queue callback | |
| NULL /*pContext*/); CheckErr(res); | |
| res = (*abqItf)->SetCallbackEventsMask(abqItf, SL_ANDROIDBUFFERQUEUEEVENT_PROCESSED); | |
| CheckErr(res); | |
| /* Display duration */ | |
| SLmillisecond durationInMsec = SL_TIME_UNKNOWN; | |
| res = (*playItf)->GetDuration(playItf, &durationInMsec); | |
| CheckErr(res); | |
| if (durationInMsec == SL_TIME_UNKNOWN) { | |
| fprintf(stdout, "Content duration is unknown (before starting to prefetch)\n"); | |
| } else { | |
| fprintf(stdout, "Content duration is %u ms (before starting to prefetch)\n", | |
| durationInMsec); | |
| } | |
| /* Set the player volume */ | |
| res = (*volItf)->SetVolumeLevel( volItf, 0);//-300); | |
| CheckErr(res); | |
| /* Play the URI */ | |
| /* first cause the player to prefetch the data */ | |
| fprintf(stdout, "Before set to PAUSED\n"); | |
| res = (*playItf)->SetPlayState( playItf, SL_PLAYSTATE_PAUSED ); | |
| fprintf(stdout, "After set to PAUSED\n"); | |
| CheckErr(res); | |
| /* Fill our cache */ | |
| if (fread(dataCache, 1, BUFFER_SIZE * NB_BUFFERS, file) <= 0) { | |
| fprintf(stderr, "Error filling cache, exiting\n"); | |
| goto destroyRes; | |
| } | |
| /* Enqueue the content of our cache before starting to play, | |
| * we don't want to starve the player */ | |
| for (int i=0 ; i < NB_BUFFERS ; i++) { | |
| res = (*abqItf)->Enqueue(abqItf, NULL /*pBufferContext*/, | |
| dataCache + i*BUFFER_SIZE, BUFFER_SIZE, NULL, 0); | |
| CheckErr(res); | |
| } | |
| #if 0 // used to test ABQ starving where only one buffer is enqueued before playback | |
| /* Fill our cache */ | |
| if (fread(dataCache, 1, BUFFER_SIZE * 1, file) <= 0) { | |
| fprintf(stderr, "Error filling cache, exiting\n"); | |
| goto destroyRes; | |
| } | |
| /* Enqueue the content of our cache before starting to play, | |
| * we don't want to starve the player */ | |
| for (int i=0 ; i < 1 ; i++) { | |
| res = (*abqItf)->Enqueue(abqItf, dataCache + i*BUFFER_SIZE, BUFFER_SIZE, NULL, 0); | |
| CheckErr(res); | |
| } | |
| #endif | |
| /* wait until there's data to play */ | |
| //SLpermille fillLevel = 0; | |
| /* | |
| SLuint32 prefetchStatus = SL_PREFETCHSTATUS_UNDERFLOW; | |
| SLuint32 timeOutIndex = 2; | |
| while ((prefetchStatus != SL_PREFETCHSTATUS_SUFFICIENTDATA) && (timeOutIndex > 0) && | |
| !prefetchError) { | |
| usleep(1 * 1000 * 1000); // 1s | |
| //(*prefetchItf)->GetPrefetchStatus(prefetchItf, &prefetchStatus); | |
| timeOutIndex--; | |
| } | |
| if (timeOutIndex == 0 || prefetchError) { | |
| fprintf(stderr, "We\'re done waiting, failed to prefetch data in time, exiting\n"); | |
| goto destroyRes; | |
| } | |
| */ | |
| /* Display duration again, */ | |
| /* res = (*playItf)->GetDuration(playItf, &durationInMsec); | |
| CheckErr(res); | |
| if (durationInMsec == SL_TIME_UNKNOWN) { | |
| fprintf(stdout, "Content duration is unknown (after prefetch completed)\n"); | |
| } else { | |
| fprintf(stdout, "Content duration is %lu ms (after prefetch completed)\n", durationInMsec); | |
| } | |
| */ | |
| fprintf(stdout, "URI example: starting to play\n"); | |
| res = (*playItf)->SetPlayState( playItf, SL_PLAYSTATE_PLAYING ); | |
| CheckErr(res); | |
| /* Wait as long as the duration of the content before stopping */ | |
| fprintf(stdout, "Letting playback go on for %d sec\n", playTimeInSec); | |
| usleep(playTimeInSec /*s*/ * 1000 * 1000); | |
| /* Make sure player is stopped */ | |
| fprintf(stdout, "URI example: stopping playback\n"); | |
| res = (*playItf)->SetPlayState(playItf, SL_PLAYSTATE_STOPPED); | |
| CheckErr(res); | |
| fprintf(stdout, "sleeping to verify playback stopped\n"); | |
| usleep(2 /*s*/ * 1000 * 1000); | |
| destroyRes: | |
| /* Destroy the player */ | |
| (*player)->Destroy(player); | |
| /* Destroy Output Mix object */ | |
| (*OutputMix)->Destroy(OutputMix); | |
| fclose(file); | |
| } | |
| //----------------------------------------------------------------- | |
| int main(int argc, char* const argv[]) | |
| { | |
| SLresult res; | |
| SLObjectItf sl; | |
| fprintf(stdout, "OpenSL ES test %s: exercises SLPlayItf, SLVolumeItf, SLAndroidBufferQueue \n", | |
| argv[0]); | |
| fprintf(stdout, "and AudioPlayer with SL_DATALOCATOR_ANDROIDBUFFERQUEUE source / OutputMix " | |
| "sink\n"); | |
| fprintf(stdout, "Plays a sound and stops after its reported duration\n\n"); | |
| if (argc != 2) { | |
| fprintf(stdout, "Usage: %s path.ts\n", argv[0]); | |
| exit(EXIT_FAILURE); | |
| } | |
| SLEngineOption EngineOption[] = { | |
| {(SLuint32) SL_ENGINEOPTION_THREADSAFE, | |
| (SLuint32) SL_BOOLEAN_TRUE}}; | |
| res = slCreateEngine( &sl, 1, EngineOption, 0, NULL, NULL); | |
| CheckErr(res); | |
| /* Realizing the SL Engine in synchronous mode. */ | |
| res = (*sl)->Realize(sl, SL_BOOLEAN_FALSE); | |
| CheckErr(res); | |
| TestPlayStream(sl, argv[1]); | |
| /* Shutdown OpenSL ES */ | |
| (*sl)->Destroy(sl); | |
| return EXIT_SUCCESS; | |
| } |