#include #include #include #include #include #include #include #include #include class AudioPlayer { public: AudioPlayer(); ~AudioPlayer(); void play(char const* uri); private: SLObjectItf mSlEngineObject{NULL}; SLEngineItf mSlEngineInterface{NULL}; SLObjectItf mSlOutputMixObject{NULL}; }; class MutexWithCondition { public: MutexWithCondition() { 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_MUTEX_INITIALIZER}; pthread_cond_t condition{PTHREAD_COND_INITIALIZER}; }; 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 = 1; SLInterfaceID wantedInterfaces[numWantedInterfaces]{ SL_IID_ENVIRONMENTALREVERB }; SLboolean wantedInterfacesRequired[numWantedInterfaces]{ SL_BOOLEAN_TRUE }; result = (*mSlEngineInterface)->CreateOutputMix(mSlEngineInterface, &mSlOutputMixObject, numWantedInterfaces, wantedInterfaces, wantedInterfacesRequired); 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("- ERROR: Underflow when prefetching data and fill level zero\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_VOLUME is general: SLuint32 const numWantedInterfaces = 5; SLInterfaceID wantedInterfaces[numWantedInterfaces]{ SL_IID_ANDROIDCONFIGURATION, SL_IID_VOLUME, SL_IID_PREFETCHSTATUS, SL_IID_PLAYBACKRATE, SL_IID_EFFECTSEND }; SLboolean wantedInterfacesRequired[numWantedInterfaces]{ SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, 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); // This allows setting the stream type (default:SL_ANDROID_STREAM_MEDIA): /* same as android.media.AudioManager.STREAM_VOICE_CALL */ // #define SL_ANDROID_STREAM_VOICE ((SLint32) 0x00000000) /* same as android.media.AudioManager.STREAM_SYSTEM */ // #define SL_ANDROID_STREAM_SYSTEM ((SLint32) 0x00000001) /* same as android.media.AudioManager.STREAM_RING */ // #define SL_ANDROID_STREAM_RING ((SLint32) 0x00000002) /* same as android.media.AudioManager.STREAM_MUSIC */ // #define SL_ANDROID_STREAM_MEDIA ((SLint32) 0x00000003) /* same as android.media.AudioManager.STREAM_ALARM */ // #define SL_ANDROID_STREAM_ALARM ((SLint32) 0x00000004) /* same as android.media.AudioManager.STREAM_NOTIFICATION */ // #define SL_ANDROID_STREAM_NOTIFICATION ((SLint32) 0x00000005) SLint32 androidStreamType = SL_ANDROID_STREAM_ALARM; result = (*androidConfig)->SetConfiguration(androidConfig, SL_ANDROID_KEY_STREAM_TYPE, &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); SLPlaybackRateItf playbackRateInterface; result = (*uriPlayerObject)->GetInterface(uriPlayerObject, SL_IID_PLAYBACKRATE, &playbackRateInterface); 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; while ((c = getopt(argc, argv, "h")) != -1) { switch (c) { case 'h': help = true; break; } } if (help || optind == argc) { printf("usage: %s [files]\n", argv[0]); exit(0); } AudioPlayer player; for (int i = optind; i < argc; i++) player.play(argv[i]); return 0; }