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