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