8 #include <SLES/OpenSLES.h>
9 #include <SLES/OpenSLES_Android.h>
15 void play(char const* uri
);
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
25 void setStreamType(SLint32 streamType
) { this->androidStreamType
= streamType
; }
27 SLObjectItf mSlEngineObject
{NULL
};
28 SLEngineItf mSlEngineInterface
{NULL
};
29 SLObjectItf mSlOutputMixObject
{NULL
};
30 SLint32 androidStreamType
{SL_ANDROID_STREAM_MEDIA
};
33 class MutexWithCondition
{
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
);
42 pthread_cond_signal(&condition
);
43 pthread_mutex_unlock(&mutex
);
46 volatile bool occurred
{false};
47 pthread_mutex_t mutex
{PTHREAD_MUTEX_INITIALIZER
};
48 pthread_cond_t condition
{PTHREAD_COND_INITIALIZER
};
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
);
61 result
= (*mSlEngineObject
)->Realize(mSlEngineObject
, SL_BOOLEAN_FALSE
);
62 assert(SL_RESULT_SUCCESS
== result
);
64 result
= (*mSlEngineObject
)->GetInterface(mSlEngineObject
, SL_IID_ENGINE
, &mSlEngineInterface
);
65 assert(SL_RESULT_SUCCESS
== result
);
67 SLuint32
const numWantedInterfaces
= 0;
68 result
= (*mSlEngineInterface
)->CreateOutputMix(mSlEngineInterface
, &mSlOutputMixObject
, numWantedInterfaces
, NULL
, NULL
);
69 assert(SL_RESULT_SUCCESS
== result
);
71 result
= (*mSlOutputMixObject
)->Realize(mSlOutputMixObject
, SL_BOOLEAN_FALSE
);
72 assert(SL_RESULT_SUCCESS
== result
);
76 void opensl_prefetch_callback(SLPrefetchStatusItf caller
, void* pContext
, SLuint32 event
) {
77 if (event
& SL_PREFETCHEVENT_STATUSCHANGE
) {
79 (*caller
)->GetFillLevel(caller
, &level
);
82 (*caller
)->GetPrefetchStatus(caller
, &status
);
83 if (status
== SL_PREFETCHSTATUS_UNDERFLOW
) {
84 // Level is 0 but we have SL_PREFETCHSTATUS_UNDERFLOW, implying an error.
85 printf("play-audio: underflow when prefetching data\n");
86 MutexWithCondition
* cond
= (MutexWithCondition
*) pContext
;
87 cond
->lockAndSignal();
93 void opensl_player_callback(SLPlayItf
/*caller*/, void* pContext
, SLuint32
/*event*/) {
94 MutexWithCondition
* condition
= (MutexWithCondition
*) pContext
;
95 condition
->lockAndSignal();
98 void AudioPlayer
::play(char const* uri
)
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
};
104 SLDataLocator_OutputMix loc_outmix
= {SL_DATALOCATOR_OUTPUTMIX
, mSlOutputMixObject
};
105 SLDataSink audioSnk
= {&loc_outmix
, NULL
};
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
};
112 SLObjectItf uriPlayerObject
= NULL
;
113 SLresult result
= (*mSlEngineInterface
)->CreateAudioPlayer(mSlEngineInterface
, &uriPlayerObject
, &audioSrc
, &audioSnk
,
114 numWantedInterfaces
, wantedInterfaces
, wantedInterfacesRequired
);
115 assert(SL_RESULT_SUCCESS
== result
);
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
);
125 result
= (*androidConfig
)->SetConfiguration(androidConfig
, SL_ANDROID_KEY_STREAM_TYPE
, &this->androidStreamType
, sizeof(SLint32
));
126 assert(SL_RESULT_SUCCESS
== result
);
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
);
132 SLPrefetchStatusItf prefetchInterface
;
133 result
= (*uriPlayerObject
)->GetInterface(uriPlayerObject
, SL_IID_PREFETCHSTATUS
, &prefetchInterface
);
134 assert(SL_RESULT_SUCCESS
== result
);
136 SLPlayItf uriPlayerPlay
= NULL
;
137 result
= (*uriPlayerObject
)->GetInterface(uriPlayerObject
, SL_IID_PLAY
, &uriPlayerPlay
);
138 assert(SL_RESULT_SUCCESS
== result
);
140 if (NULL
== uriPlayerPlay
) {
141 fprintf(stderr
, "Cannot play '%s'\n", uri
);
143 result
= (*uriPlayerPlay
)->SetCallbackEventsMask(uriPlayerPlay
, SL_PLAYEVENT_HEADSTALLED
| SL_PLAYEVENT_HEADATEND
);
144 assert(SL_RESULT_SUCCESS
== result
);
146 MutexWithCondition condition
;
147 result
= (*uriPlayerPlay
)->RegisterCallback(uriPlayerPlay
, opensl_player_callback
, &condition
);
148 assert(SL_RESULT_SUCCESS
== result
);
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
);
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
);
164 if (uriPlayerObject
!= NULL
) (*uriPlayerObject
)->Destroy(uriPlayerObject
);
168 AudioPlayer
::~AudioPlayer()
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
; }
178 int main(int argc
, char** argv
)
182 char* streamType
= NULL
;
183 while ((c
= getopt(argc
, argv
, "hs:")) != -1) {
186 case '?': help
= true; break;
187 case 's': streamType
= optarg
; break;
191 if (help
|| optind
== argc
) {
192 printf("usage: play-audio [-s streamtype] [files]\n");
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
;
213 fprintf(stderr
, "play-audio: invalid streamtype '%s'\n", streamType
);
216 player
.setStreamType(streamTypeEnum
);
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
]);
226 for (int i
= optind
; i
< argc
; i
++) {
227 player
.play(argv
[i
]);