Home | History | Annotate | Download | only in jni
      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
    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
    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
    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
    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
    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
    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
    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
    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
    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