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
370 // created from a Surface or SurfaceTextureClient
371 (void*)theNativeWindow, // hWindow
372 // must be NULL
373 NULL // hDisplay
374 };
375 XADataSink imageVideoSink = {&loc_nd, NULL};
376
377 // declare interfaces to use
378 XAboolean required[NB_MAXAL_INTERFACES]
379 = {XA_BOOLEAN_TRUE, XA_BOOLEAN_TRUE, XA_BOOLEAN_TRUE};
380 XAInterfaceID iidArray[NB_MAXAL_INTERFACES]
381 = {XA_IID_PLAY, XA_IID_ANDROIDBUFFERQUEUESOURCE,
382 XA_IID_STREAMINFORMATION};
383
384
385 // create media player
386 res = (*engineEngine)->CreateMediaPlayer(engineEngine, &playerObj, &dataSrc,
387 NULL, &audioSnk, &imageVideoSink, NULL, NULL,
388 NB_MAXAL_INTERFACES /*XAuint32 numInterfaces*/,
389 iidArray /*const XAInterfaceID *pInterfaceIds*/,
390 required /*const XAboolean *pInterfaceRequired*/);
391 assert(XA_RESULT_SUCCESS == res);
392
393 // release the Java string and UTF-8
394 (*env)->ReleaseStringUTFChars(env, filename, utf8);
395
396 // realize the player
397 res = (*playerObj)->Realize(playerObj, XA_BOOLEAN_FALSE);
398 assert(XA_RESULT_SUCCESS == res);
399
400 // get the play interface
401 res = (*playerObj)->GetInterface(playerObj, XA_IID_PLAY, &playerPlayItf);
402 assert(XA_RESULT_SUCCESS == res);
403
404 // get the stream information interface (for video size)
405 res = (*playerObj)->GetInterface(playerObj, XA_IID_STREAMINFORMATION, &playerStreamInfoItf);
406 assert(XA_RESULT_SUCCESS == res);
407
408 // get the volume interface
409 res = (*playerObj)->GetInterface(playerObj, XA_IID_VOLUME, &playerVolItf);
410 assert(XA_RESULT_SUCCESS == res);
411
412 // get the Android buffer queue interface
413 res = (*playerObj)->GetInterface(playerObj, XA_IID_ANDROIDBUFFERQUEUESOURCE, &playerBQItf);
414 assert(XA_RESULT_SUCCESS == res);
415
416 // specify which events we want to be notified of
417 res = (*playerBQItf)->SetCallbackEventsMask(playerBQItf, XA_ANDROIDBUFFERQUEUEEVENT_PROCESSED);
418
419 // use the play interface to set up a callback for the XA_PLAYEVENT_HEADATEND event */
420 res = (*playerPlayItf)->SetCallbackEventsMask(playerPlayItf, XA_PLAYEVENT_HEADATEND);
421 assert(XA_RESULT_SUCCESS == res);
422 res = (*playerPlayItf)->RegisterCallback(playerPlayItf,
423 PlayCallback /*callback*/, NULL /*pContext*/);
424 assert(XA_RESULT_SUCCESS == res);
425
426 // register the callback from which OpenMAX AL can retrieve the data to play
427 res = (*playerBQItf)->RegisterCallback(playerBQItf, AndroidBufferQueueCallback, NULL);
428 assert(XA_RESULT_SUCCESS == res);
429
430 // we want to be notified of the video size once it's found, so we register a callback for that
431 res = (*playerStreamInfoItf)->RegisterStreamChangeCallback(playerStreamInfoItf,
432 StreamChangeCallback, NULL);
433
434 // enqueue the initial buffers
435 if (!enqueueInitialBuffers(JNI_FALSE)) {
436 return JNI_FALSE;
437 }
438
439 // prepare the player
440 res = (*playerPlayItf)->SetPlayState(playerPlayItf, XA_PLAYSTATE_PAUSED);
441 assert(XA_RESULT_SUCCESS == res);
442
443 // set the volume
444 res = (*playerVolItf)->SetVolumeLevel(playerVolItf, 0);//-300);
445 assert(XA_RESULT_SUCCESS == res);
446
447 // start the playback
448 res = (*playerPlayItf)->SetPlayState(playerPlayItf, XA_PLAYSTATE_PLAYING);
449 assert(XA_RESULT_SUCCESS == res);
450
451 return JNI_TRUE;
452 }
453
454
455 // set the playing state for the streaming media player
Java_com_example_nativemedia_NativeMedia_setPlayingStreamingMediaPlayer(JNIEnv * env,jclass clazz,jboolean isPlaying)456 void Java_com_example_nativemedia_NativeMedia_setPlayingStreamingMediaPlayer(JNIEnv* env,
457 jclass clazz, jboolean isPlaying)
458 {
459 XAresult res;
460
461 // make sure the streaming media player was created
462 if (NULL != playerPlayItf) {
463
464 // set the player's state
465 res = (*playerPlayItf)->SetPlayState(playerPlayItf, isPlaying ?
466 XA_PLAYSTATE_PLAYING : XA_PLAYSTATE_PAUSED);
467 assert(XA_RESULT_SUCCESS == res);
468
469 }
470
471 }
472
473
474 // shut down the native media system
Java_com_example_nativemedia_NativeMedia_shutdown(JNIEnv * env,jclass clazz)475 void Java_com_example_nativemedia_NativeMedia_shutdown(JNIEnv* env, jclass clazz)
476 {
477 // destroy streaming media player object, and invalidate all associated interfaces
478 if (playerObj != NULL) {
479 (*playerObj)->Destroy(playerObj);
480 playerObj = NULL;
481 playerPlayItf = NULL;
482 playerBQItf = NULL;
483 playerStreamInfoItf = NULL;
484 playerVolItf = NULL;
485 }
486
487 // destroy output mix object, and invalidate all associated interfaces
488 if (outputMixObject != NULL) {
489 (*outputMixObject)->Destroy(outputMixObject);
490 outputMixObject = NULL;
491 }
492
493 // destroy engine object, and invalidate all associated interfaces
494 if (engineObject != NULL) {
495 (*engineObject)->Destroy(engineObject);
496 engineObject = NULL;
497 engineEngine = NULL;
498 }
499
500 // close the file
501 if (file != NULL) {
502 fclose(file);
503 file = NULL;
504 }
505
506 // make sure we don't leak native windows
507 if (theNativeWindow != NULL) {
508 ANativeWindow_release(theNativeWindow);
509 theNativeWindow = NULL;
510 }
511 }
512
513
514 // set the surface
Java_com_example_nativemedia_NativeMedia_setSurface(JNIEnv * env,jclass clazz,jobject surface)515 void Java_com_example_nativemedia_NativeMedia_setSurface(JNIEnv *env, jclass clazz, jobject surface)
516 {
517 // obtain a native window from a Java surface
518 theNativeWindow = ANativeWindow_fromSurface(env, surface);
519 }
520
521
522 // rewind the streaming media player
Java_com_example_nativemedia_NativeMedia_rewindStreamingMediaPlayer(JNIEnv * env,jclass clazz)523 void Java_com_example_nativemedia_NativeMedia_rewindStreamingMediaPlayer(JNIEnv *env, jclass clazz)
524 {
525 // make sure the streaming media player was created
526 if (NULL != playerBQItf && NULL != file) {
527 // first wait for buffers currently in queue to be drained
528 int ok;
529 ok = pthread_mutex_lock(&mutex);
530 assert(0 == ok);
531 discontinuity = JNI_TRUE;
532 // wait for discontinuity request to be observed by buffer queue callback
533 // FIXME sorry, can't rewind after EOS
534 while (discontinuity && !reachedEof) {
535 ok = pthread_cond_wait(&cond, &mutex);
536 assert(0 == ok);
537 }
538 ok = pthread_mutex_unlock(&mutex);
539 assert(0 == ok);
540 }
541
542 }
543