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