Update play-audio to 0.2
[termux-packages] / packages / play-audio / play-audio.cpp
CommitLineData
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
11class 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
33class 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
51AudioPlayer::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
76void 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
93void opensl_player_callback(SLPlayItf /*caller*/, void* pContext, SLuint32 /*event*/) {
94 MutexWithCondition* condition = (MutexWithCondition*) pContext;
95 condition->lockAndSignal();
96}
97
98void 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
168AudioPlayer::~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
178int 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}