chiark / gitweb /
tcl: Update from 8.6.5 to 8.6.6
[termux-packages] / packages / play-audio / play-audio.cpp
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                 /**
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; }
26         private:
27                 SLObjectItf mSlEngineObject{NULL};
28                 SLEngineItf mSlEngineInterface{NULL};
29                 SLObjectItf mSlOutputMixObject{NULL};
30                 SLint32 androidStreamType{SL_ANDROID_STREAM_MEDIA};
31 };
32
33 class MutexWithCondition {
34         public:
35                 MutexWithCondition() {
36                         pthread_mutex_init(&mutex, NULL);
37                         pthread_cond_init(&condition, NULL);
38                         pthread_mutex_lock(&mutex);
39                 }
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};
51                 pthread_mutex_t mutex;
52                 pthread_cond_t condition;
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
71         SLuint32 const numWantedInterfaces = 0;
72         result = (*mSlEngineInterface)->CreateOutputMix(mSlEngineInterface, &mSlOutputMixObject, numWantedInterfaces, NULL, NULL);
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.
89                                 printf("play-audio: underflow when prefetching data\n");
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
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 };
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
129         result = (*androidConfig)->SetConfiguration(androidConfig, SL_ANDROID_KEY_STREAM_TYPE, &this->androidStreamType, sizeof(SLint32));
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
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;
186         char* streamType = NULL;
187         while ((c = getopt(argc, argv, "hs:")) != -1) {
188                 switch (c) {
189                         case 'h':
190                         case '?': help = true; break;
191                         case 's': streamType = optarg; break;
192                 }
193         }
194
195         if (help || optind == argc) {
196                 printf("usage: play-audio [-s streamtype] [files]\n");
197                 return 1;
198         }
199
200         AudioPlayer player;
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         }
233
234         return 0;
235 }