1 /*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <assert.h>
18 #include <jni.h>
19 #include <pthread.h>
20 #include <string.h>
21 //#define LOG_NDEBUG 0
22 #define LOG_TAG "NativeMedia"
23 #include <utils/Log.h>
24
25 #include <OMXAL/OpenMAXAL.h>
26 #include <OMXAL/OpenMAXAL_Android.h>
27
28 #include <android/native_window_jni.h>
29
30 // engine interfaces
31 static XAObjectItf engineObject = NULL;
32 static XAEngineItf engineEngine = NULL;
33
34 // output mix interfaces
35 static XAObjectItf outputMixObject = NULL;
36
37 // streaming media player interfaces
38 static XAObjectItf playerObj = NULL;
39 static XAPlayItf playerPlayItf = NULL;
40 static XAAndroidBufferQueueItf playerBQItf = NULL;
41 static XAStreamInformationItf playerStreamInfoItf = NULL;
42 static XAVolumeItf playerVolItf = NULL;
43
44 // number of required interfaces for the MediaPlayer creation
45 #define NB_MAXAL_INTERFACES 3 // XAAndroidBufferQueueItf, XAStreamInformationItf and XAPlayItf
46
47 // video sink for the player
48 static ANativeWindow* theNativeWindow;
49
50 // number of buffers in our buffer queue, an arbitrary number
51 #define NB_BUFFERS 16
52
53 // we're streaming MPEG-2 transport stream data, operate on transport stream block size
54 #define MPEG2_TS_BLOCK_SIZE 188
55
56 // number of MPEG-2 transport stream blocks per buffer, an arbitrary number
57 #define BLOCKS_PER_BUFFER 20
58
59 // determines how much memory we're dedicating to memory caching
60 #define BUFFER_SIZE (BLOCKS_PER_BUFFER*MPEG2_TS_BLOCK_SIZE)
61
62 // where we cache in memory the data to play
63 // note this memory is re-used by the buffer queue callback
64 char dataCache[BUFFER_SIZE * NB_BUFFERS];
65
66 // handle of the file to play
67 FILE *file;
68
69 // has the app reached the end of the file
70 jboolean reachedEof = JNI_FALSE;
71
72 // constant to identify a buffer context which is the end of the stream to decode
73 static const int kEosBufferCntxt = 1980; // a magic value we can compare against
74
75 // for mutual exclusion between callback thread and application thread(s)
76 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
77 pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
78
79 // whether a discontinuity is in progress
80 jboolean discontinuity = JNI_FALSE;
81
82 static jboolean enqueueInitialBuffers(jboolean discontinuity);
83
84 // Callback for XAPlayItf through which we receive the XA_PLAYEVENT_HEADATEND event */
PlayCallback(XAPlayItf caller,void * pContext,XAuint32 event)85 void PlayCallback(XAPlayItf caller, void *pContext, XAuint32 event) {
86 if (event & XA_PLAYEVENT_HEADATEND) {
87 ALOGV("XA_PLAYEVENT_HEADATEND received, all MP2TS data has been decoded\n");
88 }
89 }
90
91 // AndroidBufferQueueItf callback for an audio player
AndroidBufferQueueCallback(XAAndroidBufferQueueItf caller,void * pCallbackContext,void * pBufferContext,void * pBufferData,XAuint32 dataSize,XAuint32 dataUsed,const XAAndroidBufferItem * pItems,XAuint32 itemsLength)92 XAresult AndroidBufferQueueCallback(
93 XAAndroidBufferQueueItf caller,
94 void *pCallbackContext, /* input */
95 void *pBufferContext, /* input */
96 void *pBufferData, /* input */
97 XAuint32 dataSize, /* input */
98 XAuint32 dataUsed, /* input */
99 const XAAndroidBufferItem *pItems,/* input */
100 XAuint32 itemsLength /* input */)
101 {
102 XAresult res;
103 int ok;
104
105 // pCallbackContext was specified as NULL at RegisterCallback and is unused here
106 assert(NULL == pCallbackContext);
107
108 // note there is never any contention on this mutex unless a discontinuity request is active
109 ok = pthread_mutex_lock(&mutex);
110 assert(0 == ok);
111
112 // was a discontinuity requested?
113 if (discontinuity) {
114 // FIXME sorry, can't rewind after EOS
115 if (!reachedEof) {
116 // clear the buffer queue
117 res = (*playerBQItf)->Clear(playerBQItf);
118 assert(XA_RESULT_SUCCESS == res);
119 // rewind the data source so we are guaranteed to be at an appropriate point
120 rewind(file);
121 // Enqueue the initial buffers, with a discontinuity indicator on first buffer
122 (void) enqueueInitialBuffers(JNI_TRUE);
123 }
124 // acknowledge the discontinuity request
125 discontinuity = JNI_FALSE;
126 ok = pthread_cond_signal(&cond);
127 assert(0 == ok);
128 goto exit;
129 }
130
131 if ((pBufferData == NULL) && (pBufferContext != NULL)) {
132 const int processedCommand = *(int *)pBufferContext;
133 if (kEosBufferCntxt == processedCommand) {
134 ALOGV("EOS was processed\n");
135 // our buffer with the EOS message has been consumed
136 assert(0 == dataSize);
137 goto exit;
138 }
139 }
140
141 // pBufferData is a pointer to a buffer that we previously Enqueued
142 assert(BUFFER_SIZE == dataSize);
143 assert(dataCache <= (char *) pBufferData && (char *) pBufferData <
144 &dataCache[BUFFER_SIZE * NB_BUFFERS]);
145 assert(0 == (((char *) pBufferData - dataCache) % BUFFER_SIZE));
146
147 #if 0
148 // sample code to use the XAVolumeItf
149 XAAndroidBufferQueueState state;
150 (*caller)->GetState(caller, &state);
151 switch (state.index) {
152 case 300:
153 (*playerVolItf)->SetVolumeLevel(playerVolItf, -600); // -6dB
154 ALOGV("setting volume to -6dB");
155 break;
156 case 400:
157 (*playerVolItf)->SetVolumeLevel(playerVolItf, -1200); // -12dB
158 ALOGV("setting volume to -12dB");
159 break;
160 case 500:
161 (*playerVolItf)->SetVolumeLevel(playerVolItf, 0); // full volume
162 ALOGV("setting volume to 0dB (full volume)");
163 break;
164 case 600:
165 (*playerVolItf)->SetMute(playerVolItf, XA_BOOLEAN_TRUE); // mute
166 ALOGV("muting player");
167 break;
168 case 700:
169 (*playerVolItf)->SetMute(playerVolItf, XA_BOOLEAN_FALSE); // unmute
170 ALOGV("unmuting player");
171 break;
172 case 800:
173 (*playerVolItf)->SetStereoPosition(playerVolItf, -1000);
174 (*playerVolItf)->EnableStereoPosition(playerVolItf, XA_BOOLEAN_TRUE);
175 ALOGV("pan sound to the left (hard-left)");
176 break;
177 case 900:
178 (*playerVolItf)->EnableStereoPosition(playerVolItf, XA_BOOLEAN_FALSE);
179 ALOGV("disabling stereo position");
180 break;
181 default:
182 break;
183 }
184 #endif
185
186 // don't bother trying to read more data once we've hit EOF
187 if (reachedEof) {
188 goto exit;
189 }
190
191 size_t nbRead;
192 // note we do call fread from multiple threads, but never concurrently
193 nbRead = fread(pBufferData, BUFFER_SIZE, 1, file);
194 if (nbRead > 0) {
195 assert(1 == nbRead);
196 res = (*caller)->Enqueue(caller, NULL /*pBufferContext*/,
197 pBufferData /*pData*/,
198 nbRead * BUFFER_SIZE /*dataLength*/,
199 NULL /*pMsg*/,
200 0 /*msgLength*/);
201 assert(XA_RESULT_SUCCESS == res);
202 } else {
203 // signal EOS
204 XAAndroidBufferItem msgEos[1];
205 msgEos[0].itemKey = XA_ANDROID_ITEMKEY_EOS;
206 msgEos[0].itemSize = 0;
207 // EOS message has no parameters, so the total size of the message is the size of the key
208 // plus the size if itemSize, both XAuint32
209 res = (*caller)->Enqueue(caller, (void *)&kEosBufferCntxt /*pBufferContext*/,
210 NULL /*pData*/, 0 /*dataLength*/,
211 msgEos /*pMsg*/,
212 // FIXME == sizeof(BufferItem)? */
213 sizeof(XAuint32)*2 /*msgLength*/);
214 assert(XA_RESULT_SUCCESS == res);
215 reachedEof = JNI_TRUE;
216 }
217
218 exit:
219 ok = pthread_mutex_unlock(&mutex);
220 assert(0 == ok);
221 return XA_RESULT_SUCCESS;
222 }
223
224
StreamChangeCallback(XAStreamInformationItf caller,XAuint32 eventId,XAuint32 streamIndex,void * pEventData,void * pContext)225 void StreamChangeCallback (XAStreamInformationItf caller,
226 XAuint32 eventId,
227 XAuint32 streamIndex,
228 void * pEventData,
229 void * pContext )
230 {
231 ALOGV("StreamChangeCallback called for stream %u", streamIndex);
232 // pContext was specified as NULL at RegisterStreamChangeCallback and is unused here
233 assert(NULL == pContext);
234 switch (eventId) {
235 case XA_STREAMCBEVENT_PROPERTYCHANGE: {
236 /** From spec 1.0.1:
237 "This event indicates that stream property change has occurred.
238 The streamIndex parameter identifies the stream with the property change.
239 The pEventData parameter for this event is not used and shall be ignored."
240 */
241
242 XAresult res;
243 XAuint32 domain;
244 res = (*caller)->QueryStreamType(caller, streamIndex, &domain);
245 assert(XA_RESULT_SUCCESS == res);
246 switch (domain) {
247 case XA_DOMAINTYPE_VIDEO: {
248 XAVideoStreamInformation videoInfo;
249 res = (*caller)->QueryStreamInformation(caller, streamIndex, &videoInfo);
250 assert(XA_RESULT_SUCCESS == res);
251 ALOGI("Found video size %u x %u, codec ID=%u, frameRate=%u, bitRate=%u, duration=%u ms",
252 videoInfo.width, videoInfo.height, videoInfo.codecId, videoInfo.frameRate,
253 videoInfo.bitRate, videoInfo.duration);
254 } break;
255 default:
256 fprintf(stderr, "Unexpected domain %u\n", domain);
257 break;
258 }
259 } break;
260 default:
261 fprintf(stderr, "Unexpected stream event ID %u\n", eventId);
262 break;
263 }
264 }
265
266
267 // create the engine and output mix objects
Java_com_example_nativemedia_NativeMedia_createEngine(JNIEnv * env,jclass clazz)268 void Java_com_example_nativemedia_NativeMedia_createEngine(JNIEnv* env, jclass clazz)
269 {
270 XAresult res;
271
272 // create engine
273 res = xaCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
274 assert(XA_RESULT_SUCCESS == res);
275
276 // realize the engine
277 res = (*engineObject)->Realize(engineObject, XA_BOOLEAN_FALSE);
278 assert(XA_RESULT_SUCCESS == res);
279
280 // get the engine interface, which is needed in order to create other objects
281 res = (*engineObject)->GetInterface(engineObject, XA_IID_ENGINE, &engineEngine);
282 assert(XA_RESULT_SUCCESS == res);
283
284 // create output mix
285 res = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, NULL, NULL);
286 assert(XA_RESULT_SUCCESS == res);
287
288 // realize the output mix
289 res = (*outputMixObject)->Realize(outputMixObject, XA_BOOLEAN_FALSE);
290 assert(XA_RESULT_SUCCESS == res);
291
292 }
293
294
295 // Enqueue the initial buffers, and optionally signal a discontinuity in the first buffer
enqueueInitialBuffers(jboolean discontinuity)296 static jboolean enqueueInitialBuffers(jboolean discontinuity)
297 {
298
299 /* Fill our cache */
300 size_t nbRead;
301 nbRead = fread(dataCache, BUFFER_SIZE, NB_BUFFERS, file);
302 if (nbRead <= 0) {
303 // could be premature EOF or I/O error
304 ALOGE("Error filling cache, exiting\n");
305 return JNI_FALSE;
306 }
307 assert(1 <= nbRead && nbRead <= NB_BUFFERS);
308 ALOGV("Initially queueing %zu buffers of %u bytes each", nbRead, BUFFER_SIZE);
309
310 /* Enqueue the content of our cache before starting to play,
311 we don't want to starve the player */
312 size_t i;
313 for (i = 0; i < nbRead; i++) {
314 XAresult res;
315 if (discontinuity) {
316 // signal discontinuity
317 XAAndroidBufferItem items[1];
318 items[0].itemKey = XA_ANDROID_ITEMKEY_DISCONTINUITY;
319 items[0].itemSize = 0;
320 // DISCONTINUITY message has no parameters,
321 // so the total size of the message is the size of the key
322 // plus the size if itemSize, both XAuint32
323 res = (*playerBQItf)->Enqueue(playerBQItf, NULL /*pBufferContext*/,
324 dataCache + i*BUFFER_SIZE, BUFFER_SIZE, items /*pMsg*/,
325 // FIXME == sizeof(BufferItem)? */
326 sizeof(XAuint32)*2 /*msgLength*/);
327 discontinuity = JNI_FALSE;
328 } else {
329 res = (*playerBQItf)->Enqueue(playerBQItf, NULL /*pBufferContext*/,
330 dataCache + i*BUFFER_SIZE, BUFFER_SIZE, NULL, 0);
331 }
332 assert(XA_RESULT_SUCCESS == res);
333 }
334
335 return JNI_TRUE;
336 }
337
338
339 // create streaming media player
Java_com_example_nativemedia_NativeMedia_createStreamingMediaPlayer(JNIEnv * env,jclass clazz,jstring filename)340 jboolean Java_com_example_nativemedia_NativeMedia_createStreamingMediaPlayer(JNIEnv* env,
341 jclass clazz, jstring filename)
342 {
343 XAresult res;
344
345 // convert Java string to UTF-8
346 const char *utf8 = (*env)->GetStringUTFChars(env, filename, NULL);
347 assert(NULL != utf8);
348
349 // open the file to play
350 file = fopen(utf8, "rb");
351 if (file == NULL) {
352 ALOGE("Failed to open %s", utf8);
353 return JNI_FALSE;
354 }
355
356 // configure data source
357 XADataLocator_AndroidBufferQueue loc_abq = { XA_DATALOCATOR_ANDROIDBUFFERQUEUE, NB_BUFFERS };
358 XADataFormat_MIME format_mime = {
359 XA_DATAFORMAT_MIME, XA_ANDROID_MIME_MP2TS, XA_CONTAINERTYPE_MPEG_TS };
360 XADataSource dataSrc = {&loc_abq, &format_mime};
361
362 // configure audio sink
363 XADataLocator_OutputMix loc_outmix = { XA_DATALOCATOR_OUTPUTMIX, outputMixObject };
364 XADataSink audioSnk = { &loc_outmix, NULL };
365
366 // configure image video sink
367 XADataLocator_NativeDisplay loc_nd = {
368 XA_DATALOCATOR_NATIVEDISPLAY, // locatorType
369 // the video sink must be an ANativeWindow created from a Surface or SurfaceTextureClient
370 (void*)theNativeWindow, // hWindow
371 // must be NULL
372 NULL // hDisplay
373 };
374 XADataSink imageVideoSink = {&loc_nd, NULL};
375
376 // declare interfaces to use
377 XAboolean required[NB_MAXAL_INTERFACES]
378 = {XA_BOOLEAN_TRUE, XA_BOOLEAN_TRUE, XA_BOOLEAN_TRUE};
379 XAInterfaceID iidArray[NB_MAXAL_INTERFACES]
380 = {XA_IID_PLAY, XA_IID_ANDROIDBUFFERQUEUESOURCE,
381 XA_IID_STREAMINFORMATION};
382
383
384 // create media player
385 res = (*engineEngine)->CreateMediaPlayer(engineEngine, &playerObj, &dataSrc,
386 NULL, &audioSnk, &imageVideoSink, NULL, NULL,
387 NB_MAXAL_INTERFACES /*XAuint32 numInterfaces*/,
388 iidArray /*const XAInterfaceID *pInterfaceIds*/,
389 required /*const XAboolean *pInterfaceRequired*/);
390 assert(XA_RESULT_SUCCESS == res);
391
392 // release the Java string and UTF-8
393 (*env)->ReleaseStringUTFChars(env, filename, utf8);
394
395 // realize the player
396 res = (*playerObj)->Realize(playerObj, XA_BOOLEAN_FALSE);
397 assert(XA_RESULT_SUCCESS == res);
398
399 // get the play interface
400 res = (*playerObj)->GetInterface(playerObj, XA_IID_PLAY, &playerPlayItf);
401 assert(XA_RESULT_SUCCESS == res);
402
403 // get the stream information interface (for video size)
404 res = (*playerObj)->GetInterface(playerObj, XA_IID_STREAMINFORMATION, &playerStreamInfoItf);
405 assert(XA_RESULT_SUCCESS == res);
406
407 // get the volume interface
408 res = (*playerObj)->GetInterface(playerObj, XA_IID_VOLUME, &playerVolItf);
409 assert(XA_RESULT_SUCCESS == res);
410
411 // get the Android buffer queue interface
412 res = (*playerObj)->GetInterface(playerObj, XA_IID_ANDROIDBUFFERQUEUESOURCE, &playerBQItf);
413 assert(XA_RESULT_SUCCESS == res);
414
415 // specify which events we want to be notified of
416 res = (*playerBQItf)->SetCallbackEventsMask(playerBQItf, XA_ANDROIDBUFFERQUEUEEVENT_PROCESSED);
417
418 // use the play interface to set up a callback for the XA_PLAYEVENT_HEADATEND event */
419 res = (*playerPlayItf)->SetCallbackEventsMask(playerPlayItf, XA_PLAYEVENT_HEADATEND);
420 assert(XA_RESULT_SUCCESS == res);
421 res = (*playerPlayItf)->RegisterCallback(playerPlayItf,
422 PlayCallback /*callback*/, NULL /*pContext*/);
423 assert(XA_RESULT_SUCCESS == res);
424
425 // register the callback from which OpenMAX AL can retrieve the data to play
426 res = (*playerBQItf)->RegisterCallback(playerBQItf, AndroidBufferQueueCallback, NULL);
427 assert(XA_RESULT_SUCCESS == res);
428
429 // we want to be notified of the video size once it's found, so we register a callback for that
430 res = (*playerStreamInfoItf)->RegisterStreamChangeCallback(playerStreamInfoItf,
431 StreamChangeCallback, NULL);
432
433 // enqueue the initial buffers
434 if (!enqueueInitialBuffers(JNI_FALSE)) {
435 return JNI_FALSE;
436 }
437
438 // prepare the player
439 res = (*playerPlayItf)->SetPlayState(playerPlayItf, XA_PLAYSTATE_PAUSED);
440 assert(XA_RESULT_SUCCESS == res);
441
442 // set the volume
443 res = (*playerVolItf)->SetVolumeLevel(playerVolItf, 0);//-300);
444 assert(XA_RESULT_SUCCESS == res);
445
446 // start the playback
447 res = (*playerPlayItf)->SetPlayState(playerPlayItf, XA_PLAYSTATE_PLAYING);
448 assert(XA_RESULT_SUCCESS == res);
449
450 return JNI_TRUE;
451 }
452
453
454 // set the playing state for the streaming media player
Java_com_example_nativemedia_NativeMedia_setPlayingStreamingMediaPlayer(JNIEnv * env,jclass clazz,jboolean isPlaying)455 void Java_com_example_nativemedia_NativeMedia_setPlayingStreamingMediaPlayer(JNIEnv* env,
456 jclass clazz, jboolean isPlaying)
457 {
458 XAresult res;
459
460 // make sure the streaming media player was created
461 if (NULL != playerPlayItf) {
462
463 // set the player's state
464 res = (*playerPlayItf)->SetPlayState(playerPlayItf, isPlaying ?
465 XA_PLAYSTATE_PLAYING : XA_PLAYSTATE_PAUSED);
466 assert(XA_RESULT_SUCCESS == res);
467
468 }
469
470 }
471
472
473 // shut down the native media system
Java_com_example_nativemedia_NativeMedia_shutdown(JNIEnv * env,jclass clazz)474 void Java_com_example_nativemedia_NativeMedia_shutdown(JNIEnv* env, jclass clazz)
475 {
476 // destroy streaming media player object, and invalidate all associated interfaces
477 if (playerObj != NULL) {
478 (*playerObj)->Destroy(playerObj);
479 playerObj = NULL;
480 playerPlayItf = NULL;
481 playerBQItf = NULL;
482 playerStreamInfoItf = NULL;
483 playerVolItf = NULL;
484 }
485
486 // destroy output mix object, and invalidate all associated interfaces
487 if (outputMixObject != NULL) {
488 (*outputMixObject)->Destroy(outputMixObject);
489 outputMixObject = NULL;
490 }
491
492 // destroy engine object, and invalidate all associated interfaces
493 if (engineObject != NULL) {
494 (*engineObject)->Destroy(engineObject);
495 engineObject = NULL;
496 engineEngine = NULL;
497 }
498
499 // close the file
500 if (file != NULL) {
501 fclose(file);
502 file = NULL;
503 }
504
505 // make sure we don't leak native windows
506 if (theNativeWindow != NULL) {
507 ANativeWindow_release(theNativeWindow);
508 theNativeWindow = NULL;
509 }
510 }
511
512
513 // set the surface
Java_com_example_nativemedia_NativeMedia_setSurface(JNIEnv * env,jclass clazz,jobject surface)514 void Java_com_example_nativemedia_NativeMedia_setSurface(JNIEnv *env, jclass clazz, jobject surface)
515 {
516 // obtain a native window from a Java surface
517 theNativeWindow = ANativeWindow_fromSurface(env, surface);
518 }
519
520
521 // rewind the streaming media player
Java_com_example_nativemedia_NativeMedia_rewindStreamingMediaPlayer(JNIEnv * env,jclass clazz)522 void Java_com_example_nativemedia_NativeMedia_rewindStreamingMediaPlayer(JNIEnv *env, jclass clazz)
523 {
524 // make sure the streaming media player was created
525 if (NULL != playerBQItf && NULL != file) {
526 // first wait for buffers currently in queue to be drained
527 int ok;
528 ok = pthread_mutex_lock(&mutex);
529 assert(0 == ok);
530 discontinuity = JNI_TRUE;
531 // wait for discontinuity request to be observed by buffer queue callback
532 // FIXME sorry, can't rewind after EOS
533 while (discontinuity && !reachedEof) {
534 ok = pthread_cond_wait(&cond, &mutex);
535 assert(0 == ok);
536 }
537 ok = pthread_mutex_unlock(&mutex);
538 assert(0 == ok);
539 }
540
541 }
542