• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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