Home | History | Annotate | Download | only in jni
      1 /*
      2  * Copyright (C) 2012 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 /* Original code copied from NDK Native-media sample code */
     18 
     19 #undef NDEBUG
     20 #include <assert.h>
     21 #include <jni.h>
     22 #include <pthread.h>
     23 #include <stdio.h>
     24 #include <string.h>
     25 
     26 // for __android_log_print(ANDROID_LOG_INFO, "YourApp", "formatted message");
     27 #include <android/log.h>
     28 #define TAG "NativeMedia"
     29 #define ALOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, TAG, __VA_ARGS__)
     30 #define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
     31 #define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
     32 // for native media
     33 #include <OMXAL/OpenMAXAL.h>
     34 #include <OMXAL/OpenMAXAL_Android.h>
     35 
     36 // for native window JNI
     37 #include <android/native_window_jni.h>
     38 
     39 // engine interfaces
     40 static XAObjectItf engineObject = NULL;
     41 static XAEngineItf engineEngine = NULL;
     42 
     43 // output mix interfaces
     44 static XAObjectItf outputMixObject = NULL;
     45 
     46 // streaming media player interfaces
     47 static XAObjectItf             playerObj = NULL;
     48 static XAPlayItf               playerPlayItf = NULL;
     49 static XAAndroidBufferQueueItf playerBQItf = NULL;
     50 static XAStreamInformationItf  playerStreamInfoItf = NULL;
     51 static XAVolumeItf             playerVolItf = NULL;
     52 
     53 // number of required interfaces for the MediaPlayer creation
     54 #define NB_MAXAL_INTERFACES 3 // XAAndroidBufferQueueItf, XAStreamInformationItf and XAPlayItf
     55 
     56 // video sink for the player
     57 static ANativeWindow* theNativeWindow = NULL;
     58 
     59 /**
     60  * Macro to clean-up already created stuffs and return JNI_FALSE when cond is not true
     61  */
     62 #define RETURN_ON_ASSERTION_FAILURE(cond, env, clazz)                                   \
     63         if (!(cond)) {                                                                  \
     64             ALOGE("assertion failure at file %s line %d", __FILE__, __LINE__);           \
     65             Java_android_mediastress_cts_NativeMediaActivity_shutdown((env), (clazz));  \
     66             return JNI_FALSE;                                                           \
     67         }
     68 
     69 // number of buffers in our buffer queue, an arbitrary number
     70 #define NB_BUFFERS 16
     71 
     72 // we're streaming MPEG-2 transport stream data, operate on transport stream block size
     73 #define MPEG2_TS_BLOCK_SIZE 188
     74 
     75 // number of MPEG-2 transport stream blocks per buffer, an arbitrary number
     76 #define BLOCKS_PER_BUFFER 20
     77 
     78 // determines how much memory we're dedicating to memory caching
     79 #define BUFFER_SIZE (BLOCKS_PER_BUFFER*MPEG2_TS_BLOCK_SIZE)
     80 
     81 // where we cache in memory the data to play
     82 // note this memory is re-used by the buffer queue callback
     83 char dataCache[BUFFER_SIZE * NB_BUFFERS];
     84 
     85 // handle of the file to play
     86 static FILE *file = NULL;
     87 
     88 // has the app reached the end of the file
     89 static jboolean reachedEof = JNI_FALSE;
     90 
     91 // constant to identify a buffer context which is the end of the stream to decode
     92 static const int kEosBufferCntxt = 1980; // a magic value we can compare against
     93 
     94 // for mutual exclusion between callback thread and application thread(s)
     95 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
     96 static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
     97 
     98 static jboolean enqueueInitialBuffers() {
     99 
    100     /* Fill our cache */
    101     size_t nbRead;
    102     nbRead = fread(dataCache, BUFFER_SIZE, NB_BUFFERS, file);
    103     if (nbRead <= 0) {
    104         // could be premature EOF or I/O error
    105         ALOGE("Error filling cache, exiting\n");
    106         return JNI_FALSE;
    107     }
    108     assert(1 <= nbRead && nbRead <= NB_BUFFERS);
    109     ALOGV("Initially queueing %zu buffers of %u bytes each", nbRead, BUFFER_SIZE);
    110 
    111     /* Enqueue the content of our cache before starting to play,
    112        we don't want to starve the player */
    113     size_t i;
    114     for (i = 0; i < nbRead; i++) {
    115         XAresult res = (*playerBQItf)->Enqueue(playerBQItf, NULL /*pBufferContext*/,
    116                     dataCache + i*BUFFER_SIZE, BUFFER_SIZE, NULL, 0);
    117         assert(XA_RESULT_SUCCESS == res);
    118     }
    119 
    120     return JNI_TRUE;
    121 }
    122 // AndroidBufferQueueItf callback for an audio player
    123 extern "C" XAresult AndroidBufferQueueCallback(
    124         XAAndroidBufferQueueItf caller,
    125         void *pCallbackContext,        /* input */
    126         void *pBufferContext,          /* input */
    127         void *pBufferData,             /* input */
    128         XAuint32 dataSize,             /* input */
    129         XAuint32 dataUsed,             /* input */
    130         const XAAndroidBufferItem *pItems,/* input */
    131         XAuint32 itemsLength           /* input */)
    132 {
    133     XAresult res;
    134     int ok;
    135 
    136     // pCallbackContext was specified as NULL at RegisterCallback and is unused here
    137     assert(NULL == pCallbackContext);
    138 
    139     // note there is never any contention on this mutex unless a discontinuity request is active
    140     ok = pthread_mutex_lock(&mutex);
    141     assert(0 == ok);
    142 
    143     if ((pBufferData == NULL) && (pBufferContext != NULL)) {
    144         const int processedCommand = *(int *)pBufferContext;
    145         if (kEosBufferCntxt == processedCommand) {
    146             ALOGV("EOS was processed\n");
    147             // our buffer with the EOS message has been consumed
    148             assert(0 == dataSize);
    149             goto exit;
    150         }
    151     }
    152 
    153     // pBufferData is a pointer to a buffer that we previously Enqueued
    154     assert(BUFFER_SIZE == dataSize);
    155     assert(dataCache <= (char *) pBufferData && (char *) pBufferData <
    156             &dataCache[BUFFER_SIZE * NB_BUFFERS]);
    157     assert(0 == (((char *) pBufferData - dataCache) % BUFFER_SIZE));
    158 
    159     // don't bother trying to read more data once we've hit EOF
    160     if (reachedEof) {
    161         goto exit;
    162     }
    163 
    164     size_t nbRead;
    165     // note we do call fread from multiple threads, but never concurrently
    166     nbRead = fread(pBufferData, BUFFER_SIZE, 1, file);
    167     if (nbRead > 0) {
    168         assert(1 == nbRead);
    169         res = (*caller)->Enqueue(caller, NULL /*pBufferContext*/,
    170                 pBufferData /*pData*/,
    171                 nbRead * BUFFER_SIZE /*dataLength*/,
    172                 NULL /*pMsg*/,
    173                 0 /*msgLength*/);
    174         assert(XA_RESULT_SUCCESS == res);
    175     } else {
    176         // signal EOS
    177         XAAndroidBufferItem msgEos[1];
    178         msgEos[0].itemKey = XA_ANDROID_ITEMKEY_EOS;
    179         msgEos[0].itemSize = 0;
    180         // EOS message has no parameters, so the total size of the message is the size of the key
    181         //   plus the size if itemSize, both XAuint32
    182         res = (*caller)->Enqueue(caller, (void *)&kEosBufferCntxt /*pBufferContext*/,
    183                 NULL /*pData*/, 0 /*dataLength*/,
    184                 msgEos /*pMsg*/,
    185                 // FIXME == sizeof(BufferItem)? */
    186                 sizeof(XAuint32)*2 /*msgLength*/);
    187         assert(XA_RESULT_SUCCESS == res);
    188         reachedEof = JNI_TRUE;
    189     }
    190 
    191 exit:
    192     ok = pthread_mutex_unlock(&mutex);
    193     assert(0 == ok);
    194     return XA_RESULT_SUCCESS;
    195 }
    196 
    197 // callback invoked whenever there is new or changed stream information
    198 static void StreamChangeCallback(XAStreamInformationItf caller,
    199         XAuint32 eventId,
    200         XAuint32 streamIndex,
    201         void * pEventData,
    202         void * pContext )
    203 {
    204     ALOGV("StreamChangeCallback called for stream %u", streamIndex);
    205     // pContext was specified as NULL at RegisterStreamChangeCallback and is unused here
    206     assert(NULL == pContext);
    207     switch (eventId) {
    208       case XA_STREAMCBEVENT_PROPERTYCHANGE: {
    209         /** From spec 1.0.1:
    210             "This event indicates that stream property change has occurred.
    211             The streamIndex parameter identifies the stream with the property change.
    212             The pEventData parameter for this event is not used and shall be ignored."
    213          */
    214         XAresult res;
    215         XAuint32 domain;
    216         res = (*caller)->QueryStreamType(caller, streamIndex, &domain);
    217         assert(XA_RESULT_SUCCESS == res);
    218         switch (domain) {
    219           case XA_DOMAINTYPE_VIDEO: {
    220             XAVideoStreamInformation videoInfo;
    221             res = (*caller)->QueryStreamInformation(caller, streamIndex, &videoInfo);
    222             assert(XA_RESULT_SUCCESS == res);
    223             ALOGV("Found video size %u x %u, codec ID=%u, frameRate=%u, bitRate=%u, duration=%u ms",
    224                         videoInfo.width, videoInfo.height, videoInfo.codecId, videoInfo.frameRate,
    225                         videoInfo.bitRate, videoInfo.duration);
    226           } break;
    227           default:
    228               ALOGE("Unexpected domain %u\n", domain);
    229             break;
    230         }
    231       } break;
    232       default:
    233           ALOGE("Unexpected stream event ID %u\n", eventId);
    234           break;
    235     }
    236 }
    237 
    238 // shut down the native media system
    239 // force C naming convention for JNI interfaces to avoid registering manually
    240 extern "C" void Java_android_mediastress_cts_NativeMediaActivity_shutdown(JNIEnv* env,
    241         jclass clazz)
    242 {
    243     // destroy streaming media player object, and invalidate all associated interfaces
    244     if (playerObj != NULL) {
    245         if (playerPlayItf != NULL) {
    246             (*playerPlayItf)->SetPlayState(playerPlayItf, XA_PLAYSTATE_STOPPED);
    247         }
    248         (*playerObj)->Destroy(playerObj);
    249         playerObj = NULL;
    250         playerPlayItf = NULL;
    251         playerBQItf = NULL;
    252         playerStreamInfoItf = NULL;
    253         playerVolItf = NULL;
    254     }
    255 
    256     // destroy output mix object, and invalidate all associated interfaces
    257     if (outputMixObject != NULL) {
    258         (*outputMixObject)->Destroy(outputMixObject);
    259         outputMixObject = NULL;
    260     }
    261 
    262     // destroy engine object, and invalidate all associated interfaces
    263     if (engineObject != NULL) {
    264         (*engineObject)->Destroy(engineObject);
    265         engineObject = NULL;
    266         engineEngine = NULL;
    267     }
    268 
    269     // make sure we don't leak native windows
    270     if (theNativeWindow != NULL) {
    271         ANativeWindow_release(theNativeWindow);
    272         theNativeWindow = NULL;
    273     }
    274 
    275     // close the file
    276     if (file != NULL) {
    277         fclose(file);
    278         file = NULL;
    279     }
    280 
    281 }
    282 
    283 // create the engine and output mix objects
    284 extern "C" jboolean Java_android_mediastress_cts_NativeMediaActivity_createEngine(JNIEnv* env,
    285         jclass clazz)
    286 {
    287     XAresult res;
    288 
    289     // create engine
    290     res = xaCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
    291     RETURN_ON_ASSERTION_FAILURE((XA_RESULT_SUCCESS == res), env, clazz);
    292 
    293     // realize the engine
    294     res = (*engineObject)->Realize(engineObject, XA_BOOLEAN_FALSE);
    295     RETURN_ON_ASSERTION_FAILURE((XA_RESULT_SUCCESS == res), env, clazz);
    296 
    297     // get the engine interface, which is needed in order to create other objects
    298     res = (*engineObject)->GetInterface(engineObject, XA_IID_ENGINE, &engineEngine);
    299     RETURN_ON_ASSERTION_FAILURE((XA_RESULT_SUCCESS == res), env, clazz);
    300 
    301     // create output mix
    302     res = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, NULL, NULL);
    303     RETURN_ON_ASSERTION_FAILURE((XA_RESULT_SUCCESS == res), env, clazz);
    304 
    305     // realize the output mix
    306     res = (*outputMixObject)->Realize(outputMixObject, XA_BOOLEAN_FALSE);
    307     RETURN_ON_ASSERTION_FAILURE((XA_RESULT_SUCCESS == res), env, clazz);
    308 
    309     return JNI_TRUE;
    310 }
    311 
    312 
    313 // create streaming media player
    314 extern "C" jboolean Java_android_mediastress_cts_NativeMediaActivity_createMediaPlayer(JNIEnv* env,
    315         jclass clazz, jstring fileUri)
    316 {
    317     XAresult res;
    318 
    319     // convert Java string to UTF-8
    320     const char *utf8 = env->GetStringUTFChars(fileUri, NULL);
    321     RETURN_ON_ASSERTION_FAILURE((NULL != utf8), env, clazz);
    322 
    323     RETURN_ON_ASSERTION_FAILURE(strstr(utf8, "file:///") == utf8, env, clazz);
    324 
    325     file = fopen(utf8 + 7, "rb");
    326     RETURN_ON_ASSERTION_FAILURE(file != NULL, env, clazz);
    327 
    328 
    329     // configure data source
    330     XADataLocator_AndroidBufferQueue loc_abq = { XA_DATALOCATOR_ANDROIDBUFFERQUEUE, NB_BUFFERS };
    331     XADataFormat_MIME format_mime = {
    332             XA_DATAFORMAT_MIME, XA_ANDROID_MIME_MP2TS, XA_CONTAINERTYPE_MPEG_TS };
    333     XADataSource dataSrc = {&loc_abq, &format_mime};
    334 
    335     // configure audio sink
    336     XADataLocator_OutputMix loc_outmix = { XA_DATALOCATOR_OUTPUTMIX, outputMixObject };
    337     XADataSink audioSnk = { &loc_outmix, NULL };
    338 
    339     // configure image video sink
    340     XADataLocator_NativeDisplay loc_nd = {
    341             XA_DATALOCATOR_NATIVEDISPLAY,        // locatorType
    342             // the video sink must be an ANativeWindow created from a Surface or SurfaceTexture
    343             (void*)theNativeWindow,              // hWindow
    344             // must be NULL
    345             NULL                                 // hDisplay
    346     };
    347     XADataSink imageVideoSink = {&loc_nd, NULL};
    348 
    349     // declare interfaces to use
    350     XAboolean     required[NB_MAXAL_INTERFACES]
    351                            = {XA_BOOLEAN_TRUE, XA_BOOLEAN_TRUE, XA_BOOLEAN_TRUE};
    352     XAInterfaceID iidArray[NB_MAXAL_INTERFACES]
    353                            = {XA_IID_PLAY,
    354                               XA_IID_ANDROIDBUFFERQUEUESOURCE,
    355                               XA_IID_STREAMINFORMATION};
    356 
    357     // create media player
    358     res = (*engineEngine)->CreateMediaPlayer(engineEngine, &playerObj, &dataSrc,
    359             NULL, &audioSnk, &imageVideoSink, NULL, NULL,
    360             NB_MAXAL_INTERFACES /*XAuint32 numInterfaces*/,
    361             iidArray /*const XAInterfaceID *pInterfaceIds*/,
    362             required /*const XAboolean *pInterfaceRequired*/);
    363     RETURN_ON_ASSERTION_FAILURE((XA_RESULT_SUCCESS == res), env, clazz);
    364 
    365     // release the Java string and UTF-8
    366     env->ReleaseStringUTFChars(fileUri, utf8);
    367 
    368     // realize the player
    369     res = (*playerObj)->Realize(playerObj, XA_BOOLEAN_FALSE);
    370     RETURN_ON_ASSERTION_FAILURE((XA_RESULT_SUCCESS == res), env, clazz);
    371 
    372     // get the play interface
    373     res = (*playerObj)->GetInterface(playerObj, XA_IID_PLAY, &playerPlayItf);
    374     RETURN_ON_ASSERTION_FAILURE((XA_RESULT_SUCCESS == res), env, clazz);
    375 
    376     // get the stream information interface (for video size)
    377     res = (*playerObj)->GetInterface(playerObj, XA_IID_STREAMINFORMATION, &playerStreamInfoItf);
    378     RETURN_ON_ASSERTION_FAILURE((XA_RESULT_SUCCESS == res), env, clazz);
    379 
    380     // get the volume interface
    381     res = (*playerObj)->GetInterface(playerObj, XA_IID_VOLUME, &playerVolItf);
    382     RETURN_ON_ASSERTION_FAILURE((XA_RESULT_SUCCESS == res), env, clazz);
    383 
    384     // get the Android buffer queue interface
    385     res = (*playerObj)->GetInterface(playerObj, XA_IID_ANDROIDBUFFERQUEUESOURCE, &playerBQItf);
    386     assert(XA_RESULT_SUCCESS == res);
    387 
    388     // specify which events we want to be notified of
    389     res = (*playerBQItf)->SetCallbackEventsMask(playerBQItf, XA_ANDROIDBUFFERQUEUEEVENT_PROCESSED);
    390 
    391     // register the callback from which OpenMAX AL can retrieve the data to play
    392     res = (*playerBQItf)->RegisterCallback(playerBQItf, AndroidBufferQueueCallback, NULL);
    393     assert(XA_RESULT_SUCCESS == res);
    394 
    395     // enqueue the initial buffers
    396     if (!enqueueInitialBuffers()) {
    397         return JNI_FALSE;
    398     }
    399 
    400     // we want to be notified of the video size once it's found, so we register a callback for that
    401     res = (*playerStreamInfoItf)->RegisterStreamChangeCallback(playerStreamInfoItf,
    402             StreamChangeCallback, NULL);
    403     RETURN_ON_ASSERTION_FAILURE((XA_RESULT_SUCCESS == res), env, clazz);
    404 
    405     // prepare the player
    406     res = (*playerPlayItf)->SetPlayState(playerPlayItf, XA_PLAYSTATE_PAUSED);
    407     RETURN_ON_ASSERTION_FAILURE((XA_RESULT_SUCCESS == res), env, clazz);
    408 
    409     // set the volume
    410     res = (*playerVolItf)->SetVolumeLevel(playerVolItf, 0);
    411     RETURN_ON_ASSERTION_FAILURE((XA_RESULT_SUCCESS == res), env, clazz);
    412 
    413     // start the playback
    414     res = (*playerPlayItf)->SetPlayState(playerPlayItf, XA_PLAYSTATE_PLAYING);
    415     RETURN_ON_ASSERTION_FAILURE((XA_RESULT_SUCCESS == res), env, clazz);
    416 
    417     return JNI_TRUE;
    418 }
    419 
    420 
    421 // set the playing state for the streaming media player
    422 extern "C" jboolean Java_android_mediastress_cts_NativeMediaActivity_playOrPauseMediaPlayer(
    423         JNIEnv* env, jclass clazz, jboolean play)
    424 {
    425     XAresult res;
    426 
    427     // make sure the streaming media player was created
    428     RETURN_ON_ASSERTION_FAILURE((NULL != playerPlayItf), env, clazz);
    429     // set the player's state
    430     res = (*playerPlayItf)->SetPlayState(playerPlayItf, play ?
    431         XA_PLAYSTATE_PLAYING : XA_PLAYSTATE_PAUSED);
    432     RETURN_ON_ASSERTION_FAILURE((XA_RESULT_SUCCESS == res), env, clazz);
    433     return JNI_TRUE;
    434 }
    435 
    436 // set the surface
    437 extern "C" jboolean Java_android_mediastress_cts_NativeMediaActivity_setSurface(JNIEnv *env,
    438         jclass clazz, jobject surface)
    439 {
    440     // obtain a native window from a Java surface
    441     theNativeWindow = ANativeWindow_fromSurface(env, surface);
    442     return JNI_TRUE;
    443 }
    444 
    445 
    446