#include #include #include #include #include #include #include #include #include class AudioPlayer { public: AudioPlayer(); ~AudioPlayer(); void play(char const* uri); /** * This allows setting the stream type (default:SL_ANDROID_STREAM_MEDIA): * SL_ANDROID_STREAM_ALARM - same as android.media.AudioManager.STREAM_ALARM * SL_ANDROID_STREAM_MEDIA - same as android.media.AudioManager.STREAM_MUSIC * SL_ANDROID_STREAM_NOTIFICATION - same as android.media.AudioManager.STREAM_NOTIFICATION * SL_ANDROID_STREAM_RING - same as android.media.AudioManager.STREAM_RING * SL_ANDROID_STREAM_SYSTEM - same as android.media.AudioManager.STREAM_SYSTEM * SL_ANDROID_STREAM_VOICE - same as android.media.AudioManager.STREAM_VOICE_CALL */ void setStreamType(SLint32 streamType) { this->androidStreamType = streamType; } private: SLObjectItf mSlEngineObject{NULL}; SLEngineItf mSlEngineInterface{NULL}; SLObjectItf mSlOutputMixObject{NULL}; SLint32 androidStreamType{SL_ANDROID_STREAM_MEDIA}; }; class MutexWithCondition { public: MutexWithCondition() { pthread_mutex_init(&mutex, NULL); pthread_cond_init(&condition, NULL); pthread_mutex_lock(&mutex); } ~MutexWithCondition() { pthread_mutex_unlock(&mutex); } void waitFor() { while (!occurred) pthread_cond_wait(&condition, &mutex); } /** From waking thread. */ void lockAndSignal() { pthread_mutex_lock(&mutex); occurred = true; pthread_cond_signal(&condition); pthread_mutex_unlock(&mutex); } private: volatile bool occurred{false}; pthread_mutex_t mutex; pthread_cond_t condition; }; AudioPlayer::AudioPlayer() { // "OpenSL ES for Android is designed for multi-threaded applications, and is thread-safe. // OpenSL ES for Android supports a single engine per application, and up to 32 objects. // Available device memory and CPU may further restrict the usable number of objects. // slCreateEngine recognizes, but ignores, these engine options: SL_ENGINEOPTION_THREADSAFE SL_ENGINEOPTION_LOSSOFCONTROL" SLresult result = slCreateEngine(&mSlEngineObject, /*numOptions=*/0, /*options=*/NULL, /*numWantedInterfaces=*/0, /*wantedInterfaces=*/NULL, /*wantedInterfacesRequired=*/NULL); assert(SL_RESULT_SUCCESS == result); result = (*mSlEngineObject)->Realize(mSlEngineObject, SL_BOOLEAN_FALSE); assert(SL_RESULT_SUCCESS == result); result = (*mSlEngineObject)->GetInterface(mSlEngineObject, SL_IID_ENGINE, &mSlEngineInterface); assert(SL_RESULT_SUCCESS == result); SLuint32 const numWantedInterfaces = 0; result = (*mSlEngineInterface)->CreateOutputMix(mSlEngineInterface, &mSlOutputMixObject, numWantedInterfaces, NULL, NULL); assert(SL_RESULT_SUCCESS == result); result = (*mSlOutputMixObject)->Realize(mSlOutputMixObject, SL_BOOLEAN_FALSE); assert(SL_RESULT_SUCCESS == result); } void opensl_prefetch_callback(SLPrefetchStatusItf caller, void* pContext, SLuint32 event) { if (event & SL_PREFETCHEVENT_STATUSCHANGE) { SLpermille level = 0; (*caller)->GetFillLevel(caller, &level); if (level == 0) { SLuint32 status; (*caller)->GetPrefetchStatus(caller, &status); if (status == SL_PREFETCHSTATUS_UNDERFLOW) { // Level is 0 but we have SL_PREFETCHSTATUS_UNDERFLOW, implying an error. printf("play-audio: underflow when prefetching data\n"); MutexWithCondition* cond = (MutexWithCondition*) pContext; cond->lockAndSignal(); } } } } void opensl_player_callback(SLPlayItf /*caller*/, void* pContext, SLuint32 /*event*/) { MutexWithCondition* condition = (MutexWithCondition*) pContext; condition->lockAndSignal(); } void AudioPlayer::play(char const* uri) { SLDataLocator_URI loc_uri = {SL_DATALOCATOR_URI, (SLchar *) uri}; SLDataFormat_MIME format_mime = {SL_DATAFORMAT_MIME, NULL, SL_CONTAINERTYPE_UNSPECIFIED}; SLDataSource audioSrc = {&loc_uri, &format_mime}; SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, mSlOutputMixObject}; SLDataSink audioSnk = {&loc_outmix, NULL}; // SL_IID_ANDROIDCONFIGURATION is Android specific interface, SL_IID_PREFETCHSTATUS is general: SLuint32 const numWantedInterfaces = 2; SLInterfaceID wantedInterfaces[numWantedInterfaces]{ SL_IID_ANDROIDCONFIGURATION, SL_IID_PREFETCHSTATUS }; SLboolean wantedInterfacesRequired[numWantedInterfaces]{ SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE }; SLObjectItf uriPlayerObject = NULL; SLresult result = (*mSlEngineInterface)->CreateAudioPlayer(mSlEngineInterface, &uriPlayerObject, &audioSrc, &audioSnk, numWantedInterfaces, wantedInterfaces, wantedInterfacesRequired); assert(SL_RESULT_SUCCESS == result); // Android specific interface - usage: // SLresult (*GetInterface) (SLObjectItf self, const SLInterfaceID iid, void* pInterface); // This function gives different interfaces. One is android-specific, from // , done before realization: SLAndroidConfigurationItf androidConfig; result = (*uriPlayerObject)->GetInterface(uriPlayerObject, SL_IID_ANDROIDCONFIGURATION, &androidConfig); assert(SL_RESULT_SUCCESS == result); result = (*androidConfig)->SetConfiguration(androidConfig, SL_ANDROID_KEY_STREAM_TYPE, &this->androidStreamType, sizeof(SLint32)); assert(SL_RESULT_SUCCESS == result); // We now Realize(). Note that the android config needs to be done before, but getting the SLPrefetchStatusItf after. result = (*uriPlayerObject)->Realize(uriPlayerObject, /*async=*/SL_BOOLEAN_FALSE); assert(SL_RESULT_SUCCESS == result); SLPrefetchStatusItf prefetchInterface; result = (*uriPlayerObject)->GetInterface(uriPlayerObject, SL_IID_PREFETCHSTATUS, &prefetchInterface); assert(SL_RESULT_SUCCESS == result); SLPlayItf uriPlayerPlay = NULL; result = (*uriPlayerObject)->GetInterface(uriPlayerObject, SL_IID_PLAY, &uriPlayerPlay); assert(SL_RESULT_SUCCESS == result); if (NULL == uriPlayerPlay) { fprintf(stderr, "Cannot play '%s'\n", uri); } else { result = (*uriPlayerPlay)->SetCallbackEventsMask(uriPlayerPlay, SL_PLAYEVENT_HEADSTALLED | SL_PLAYEVENT_HEADATEND); assert(SL_RESULT_SUCCESS == result); MutexWithCondition condition; result = (*uriPlayerPlay)->RegisterCallback(uriPlayerPlay, opensl_player_callback, &condition); assert(SL_RESULT_SUCCESS == result); result = (*prefetchInterface)->RegisterCallback(prefetchInterface, opensl_prefetch_callback, &condition); assert(SL_RESULT_SUCCESS == result); result = (*prefetchInterface)->SetCallbackEventsMask(prefetchInterface, SL_PREFETCHEVENT_FILLLEVELCHANGE | SL_PREFETCHEVENT_STATUSCHANGE); // "For an audio player with URI data source, Object::Realize allocates resources but does not // connect to the data source (i.e. "prepare") or begin pre-fetching data. These occur once the // player state is set to either SL_PLAYSTATE_PAUSED or SL_PLAYSTATE_PLAYING." // - http://mobilepearls.com/labs/native-android-api/ndk/docs/opensles/index.html result = (*uriPlayerPlay)->SetPlayState(uriPlayerPlay, SL_PLAYSTATE_PLAYING); assert(SL_RESULT_SUCCESS == result); condition.waitFor(); } if (uriPlayerObject != NULL) (*uriPlayerObject)->Destroy(uriPlayerObject); } AudioPlayer::~AudioPlayer() { // "Be sure to destroy all objects on exit from your application. Objects should be destroyed in reverse order of their creation, // as it is not safe to destroy an object that has any dependent objects. For example, destroy in this order: audio players // and recorders, output mix, then finally the engine." if (mSlOutputMixObject != NULL) { (*mSlOutputMixObject)->Destroy(mSlOutputMixObject); mSlOutputMixObject = NULL; } if (mSlEngineObject != NULL) { (*mSlEngineObject)->Destroy(mSlEngineObject); mSlEngineObject = NULL; } } int main(int argc, char** argv) { bool help = false; int c; char* streamType = NULL; while ((c = getopt(argc, argv, "hs:")) != -1) { switch (c) { case 'h': case '?': help = true; break; case 's': streamType = optarg; break; } } if (help || optind == argc) { printf("usage: play-audio [-s streamtype] [files]\n"); return 1; } AudioPlayer player; if (streamType != NULL) { SLint32 streamTypeEnum; if (strcmp("alarm", streamType) == 0) { streamTypeEnum = SL_ANDROID_STREAM_ALARM; } else if (strcmp("media", streamType) == 0) { streamTypeEnum = SL_ANDROID_STREAM_MEDIA; } else if (strcmp("notification", streamType) == 0) { streamTypeEnum = SL_ANDROID_STREAM_NOTIFICATION; } else if (strcmp("ring", streamType) == 0) { streamTypeEnum = SL_ANDROID_STREAM_RING; } else if (strcmp("system", streamType) == 0) { streamTypeEnum = SL_ANDROID_STREAM_SYSTEM; } else if (strcmp("voice", streamType) == 0) { streamTypeEnum = SL_ANDROID_STREAM_VOICE; } else { fprintf(stderr, "play-audio: invalid streamtype '%s'\n", streamType); return 1; } player.setStreamType(streamTypeEnum); } for (int i = optind; i < argc; i++) { if (access(argv[i], R_OK) != 0) { fprintf(stderr, "play-audio: '%s' is not a readable file\n", argv[i]); return 1; } } for (int i = optind; i < argc; i++) { player.play(argv[i]); } return 0; }