Commit | Line | Data |
---|---|---|
1c934e8a FF |
1 | #include <assert.h> |
2 | #include <getopt.h> | |
3 | #include <pthread.h> | |
4 | #include <stdbool.h> | |
5 | #include <stdio.h> | |
6 | #include <stdlib.h> | |
7 | #include <unistd.h> | |
8 | #include <SLES/OpenSLES.h> | |
9 | #include <SLES/OpenSLES_Android.h> | |
10 | ||
11 | class AudioPlayer { | |
12 | public: | |
13 | AudioPlayer(); | |
14 | ~AudioPlayer(); | |
15 | void play(char const* uri); | |
16 | private: | |
17 | SLObjectItf mSlEngineObject{NULL}; | |
18 | SLEngineItf mSlEngineInterface{NULL}; | |
19 | SLObjectItf mSlOutputMixObject{NULL}; | |
20 | }; | |
21 | ||
22 | class MutexWithCondition { | |
23 | public: | |
24 | MutexWithCondition() { pthread_mutex_lock(&mutex); } | |
25 | ~MutexWithCondition() { pthread_mutex_unlock(&mutex); } | |
26 | void waitFor() { while (!occurred) pthread_cond_wait(&condition, &mutex); } | |
27 | /** From waking thread. */ | |
28 | void lockAndSignal() { | |
29 | pthread_mutex_lock(&mutex); | |
30 | occurred = true; | |
31 | pthread_cond_signal(&condition); | |
32 | pthread_mutex_unlock(&mutex); | |
33 | } | |
34 | private: | |
35 | volatile bool occurred{false}; | |
36 | pthread_mutex_t mutex{PTHREAD_MUTEX_INITIALIZER}; | |
37 | pthread_cond_t condition{PTHREAD_COND_INITIALIZER}; | |
38 | }; | |
39 | ||
40 | AudioPlayer::AudioPlayer() { | |
41 | // "OpenSL ES for Android is designed for multi-threaded applications, and is thread-safe. | |
42 | // OpenSL ES for Android supports a single engine per application, and up to 32 objects. | |
43 | // Available device memory and CPU may further restrict the usable number of objects. | |
44 | // slCreateEngine recognizes, but ignores, these engine options: SL_ENGINEOPTION_THREADSAFE SL_ENGINEOPTION_LOSSOFCONTROL" | |
45 | SLresult result = slCreateEngine(&mSlEngineObject, | |
46 | /*numOptions=*/0, /*options=*/NULL, | |
47 | /*numWantedInterfaces=*/0, /*wantedInterfaces=*/NULL, /*wantedInterfacesRequired=*/NULL); | |
48 | assert(SL_RESULT_SUCCESS == result); | |
49 | ||
50 | result = (*mSlEngineObject)->Realize(mSlEngineObject, SL_BOOLEAN_FALSE); | |
51 | assert(SL_RESULT_SUCCESS == result); | |
52 | ||
53 | result = (*mSlEngineObject)->GetInterface(mSlEngineObject, SL_IID_ENGINE, &mSlEngineInterface); | |
54 | assert(SL_RESULT_SUCCESS == result); | |
55 | ||
56 | SLuint32 const numWantedInterfaces = 1; | |
57 | SLInterfaceID wantedInterfaces[numWantedInterfaces]{ SL_IID_ENVIRONMENTALREVERB }; | |
58 | SLboolean wantedInterfacesRequired[numWantedInterfaces]{ SL_BOOLEAN_TRUE }; | |
59 | result = (*mSlEngineInterface)->CreateOutputMix(mSlEngineInterface, &mSlOutputMixObject, numWantedInterfaces, wantedInterfaces, wantedInterfacesRequired); | |
60 | assert(SL_RESULT_SUCCESS == result); | |
61 | ||
62 | result = (*mSlOutputMixObject)->Realize(mSlOutputMixObject, SL_BOOLEAN_FALSE); | |
63 | assert(SL_RESULT_SUCCESS == result); | |
64 | ||
65 | } | |
66 | ||
67 | void opensl_prefetch_callback(SLPrefetchStatusItf caller, void* pContext, SLuint32 event) { | |
68 | if (event & SL_PREFETCHEVENT_STATUSCHANGE) { | |
69 | SLpermille level = 0; | |
70 | (*caller)->GetFillLevel(caller, &level); | |
71 | if (level == 0) { | |
72 | SLuint32 status; | |
73 | (*caller)->GetPrefetchStatus(caller, &status); | |
74 | if (status == SL_PREFETCHSTATUS_UNDERFLOW) { | |
75 | // Level is 0 but we have SL_PREFETCHSTATUS_UNDERFLOW, implying an error. | |
76 | printf("- ERROR: Underflow when prefetching data and fill level zero\n"); | |
77 | MutexWithCondition* cond = (MutexWithCondition*) pContext; | |
78 | cond->lockAndSignal(); | |
79 | } | |
80 | } | |
81 | } | |
82 | } | |
83 | ||
84 | void opensl_player_callback(SLPlayItf /*caller*/, void* pContext, SLuint32 /*event*/) { | |
85 | MutexWithCondition* condition = (MutexWithCondition*) pContext; | |
86 | condition->lockAndSignal(); | |
87 | } | |
88 | ||
89 | void AudioPlayer::play(char const* uri) | |
90 | { | |
91 | SLDataLocator_URI loc_uri = {SL_DATALOCATOR_URI, (SLchar *) uri}; | |
92 | SLDataFormat_MIME format_mime = {SL_DATAFORMAT_MIME, NULL, SL_CONTAINERTYPE_UNSPECIFIED}; | |
93 | SLDataSource audioSrc = {&loc_uri, &format_mime}; | |
94 | ||
95 | SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, mSlOutputMixObject}; | |
96 | SLDataSink audioSnk = {&loc_outmix, NULL}; | |
97 | ||
98 | // SL_IID_ANDROIDCONFIGURATION is Android specific interface, SL_IID_VOLUME is general: | |
99 | SLuint32 const numWantedInterfaces = 5; | |
100 | SLInterfaceID wantedInterfaces[numWantedInterfaces]{ | |
101 | SL_IID_ANDROIDCONFIGURATION, | |
102 | SL_IID_VOLUME, | |
103 | SL_IID_PREFETCHSTATUS, | |
104 | SL_IID_PLAYBACKRATE, | |
105 | SL_IID_EFFECTSEND | |
106 | }; | |
107 | SLboolean wantedInterfacesRequired[numWantedInterfaces]{ SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE }; | |
108 | ||
109 | SLObjectItf uriPlayerObject = NULL; | |
110 | SLresult result = (*mSlEngineInterface)->CreateAudioPlayer(mSlEngineInterface, &uriPlayerObject, &audioSrc, &audioSnk, | |
111 | numWantedInterfaces, wantedInterfaces, wantedInterfacesRequired); | |
112 | assert(SL_RESULT_SUCCESS == result); | |
113 | ||
114 | // Android specific interface - usage: | |
115 | // SLresult (*GetInterface) (SLObjectItf self, const SLInterfaceID iid, void* pInterface); | |
116 | // This function gives different interfaces. One is android-specific, from | |
117 | // <SLES/OpenSLES_AndroidConfiguration.h>, done before realization: | |
118 | SLAndroidConfigurationItf androidConfig; | |
119 | result = (*uriPlayerObject)->GetInterface(uriPlayerObject, SL_IID_ANDROIDCONFIGURATION, &androidConfig); | |
120 | assert(SL_RESULT_SUCCESS == result); | |
121 | ||
122 | // This allows setting the stream type (default:SL_ANDROID_STREAM_MEDIA): | |
123 | /* same as android.media.AudioManager.STREAM_VOICE_CALL */ | |
124 | // #define SL_ANDROID_STREAM_VOICE ((SLint32) 0x00000000) | |
125 | /* same as android.media.AudioManager.STREAM_SYSTEM */ | |
126 | // #define SL_ANDROID_STREAM_SYSTEM ((SLint32) 0x00000001) | |
127 | /* same as android.media.AudioManager.STREAM_RING */ | |
128 | // #define SL_ANDROID_STREAM_RING ((SLint32) 0x00000002) | |
129 | /* same as android.media.AudioManager.STREAM_MUSIC */ | |
130 | // #define SL_ANDROID_STREAM_MEDIA ((SLint32) 0x00000003) | |
131 | /* same as android.media.AudioManager.STREAM_ALARM */ | |
132 | // #define SL_ANDROID_STREAM_ALARM ((SLint32) 0x00000004) | |
133 | /* same as android.media.AudioManager.STREAM_NOTIFICATION */ | |
134 | // #define SL_ANDROID_STREAM_NOTIFICATION ((SLint32) 0x00000005) | |
135 | SLint32 androidStreamType = SL_ANDROID_STREAM_ALARM; | |
136 | result = (*androidConfig)->SetConfiguration(androidConfig, SL_ANDROID_KEY_STREAM_TYPE, &androidStreamType, sizeof(SLint32)); | |
137 | assert(SL_RESULT_SUCCESS == result); | |
138 | ||
139 | // We now Realize(). Note that the android config needs to be done before, but getting the SLPrefetchStatusItf after. | |
140 | result = (*uriPlayerObject)->Realize(uriPlayerObject, /*async=*/SL_BOOLEAN_FALSE); | |
141 | assert(SL_RESULT_SUCCESS == result); | |
142 | ||
143 | SLPrefetchStatusItf prefetchInterface; | |
144 | result = (*uriPlayerObject)->GetInterface(uriPlayerObject, SL_IID_PREFETCHSTATUS, &prefetchInterface); | |
145 | assert(SL_RESULT_SUCCESS == result); | |
146 | ||
147 | SLPlayItf uriPlayerPlay = NULL; | |
148 | result = (*uriPlayerObject)->GetInterface(uriPlayerObject, SL_IID_PLAY, &uriPlayerPlay); | |
149 | assert(SL_RESULT_SUCCESS == result); | |
150 | ||
151 | SLPlaybackRateItf playbackRateInterface; | |
152 | result = (*uriPlayerObject)->GetInterface(uriPlayerObject, SL_IID_PLAYBACKRATE, &playbackRateInterface); | |
153 | assert(SL_RESULT_SUCCESS == result); | |
154 | ||
155 | if (NULL == uriPlayerPlay) { | |
156 | fprintf(stderr, "Cannot play '%s'\n", uri); | |
157 | } else { | |
158 | result = (*uriPlayerPlay)->SetCallbackEventsMask(uriPlayerPlay, SL_PLAYEVENT_HEADSTALLED | SL_PLAYEVENT_HEADATEND); | |
159 | assert(SL_RESULT_SUCCESS == result); | |
160 | ||
161 | MutexWithCondition condition; | |
162 | result = (*uriPlayerPlay)->RegisterCallback(uriPlayerPlay, opensl_player_callback, &condition); | |
163 | assert(SL_RESULT_SUCCESS == result); | |
164 | ||
165 | result = (*prefetchInterface)->RegisterCallback(prefetchInterface, opensl_prefetch_callback, &condition); | |
166 | assert(SL_RESULT_SUCCESS == result); | |
167 | result = (*prefetchInterface)->SetCallbackEventsMask(prefetchInterface, SL_PREFETCHEVENT_FILLLEVELCHANGE | SL_PREFETCHEVENT_STATUSCHANGE); | |
168 | ||
169 | // "For an audio player with URI data source, Object::Realize allocates resources but does not | |
170 | // connect to the data source (i.e. "prepare") or begin pre-fetching data. These occur once the | |
171 | // player state is set to either SL_PLAYSTATE_PAUSED or SL_PLAYSTATE_PLAYING." | |
172 | // - http://mobilepearls.com/labs/native-android-api/ndk/docs/opensles/index.html | |
173 | result = (*uriPlayerPlay)->SetPlayState(uriPlayerPlay, SL_PLAYSTATE_PLAYING); | |
174 | assert(SL_RESULT_SUCCESS == result); | |
175 | ||
176 | condition.waitFor(); | |
177 | } | |
178 | ||
179 | if (uriPlayerObject != NULL) (*uriPlayerObject)->Destroy(uriPlayerObject); | |
180 | } | |
181 | ||
182 | ||
183 | AudioPlayer::~AudioPlayer() | |
184 | { | |
185 | // "Be sure to destroy all objects on exit from your application. Objects should be destroyed in reverse order of their creation, | |
186 | // as it is not safe to destroy an object that has any dependent objects. For example, destroy in this order: audio players | |
187 | // and recorders, output mix, then finally the engine." | |
188 | if (mSlOutputMixObject != NULL) { (*mSlOutputMixObject)->Destroy(mSlOutputMixObject); mSlOutputMixObject = NULL; } | |
189 | if (mSlEngineObject != NULL) { (*mSlEngineObject)->Destroy(mSlEngineObject); mSlEngineObject = NULL; } | |
190 | } | |
191 | ||
192 | ||
193 | int main(int argc, char** argv) | |
194 | { | |
195 | bool help = false; | |
196 | int c; | |
197 | while ((c = getopt(argc, argv, "h")) != -1) { | |
198 | switch (c) { | |
199 | case 'h': help = true; break; | |
200 | } | |
201 | } | |
202 | ||
203 | if (help || optind == argc) { | |
204 | printf("usage: %s [files]\n", argv[0]); | |
205 | exit(0); | |
206 | } | |
207 | ||
208 | AudioPlayer player; | |
209 | for (int i = optind; i < argc; i++) player.play(argv[i]); | |
210 | ||
211 | return 0; | |
212 | } |