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 45static XAObjectItf engineObject = NULL; 46static XAEngineItf engineEngine = NULL; 47 48// output mix interfaces 49static XAObjectItf outputMixObject = NULL; 50 51// streaming media player interfaces 52static XAObjectItf playerObj = NULL; 53static XAPlayItf playerPlayItf = NULL; 54static XAAndroidBufferQueueItf playerBQItf = NULL; 55static XAStreamInformationItf playerStreamInfoItf = NULL; 56static 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 62static 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 78static char dataCache[BUFFER_SIZE * NB_BUFFERS]; 79 80// handle of the file to play 81static FILE *file; 82 83// has the app reached the end of the file 84static jboolean reachedEof = JNI_FALSE; 85 86// constant to identify a buffer context which is the end of the stream to decode 87static 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 93static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 94static pthread_cond_t cond = PTHREAD_COND_INITIALIZER; 95 96// whether a discontinuity is in progress 97static jboolean discontinuity = JNI_FALSE; 98 99static jboolean enqueueInitialBuffers(jboolean discontinuity); 100 101// AndroidBufferQueueItf callback to supply MPEG-2 TS packets to the media player 102static 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 194exit: 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 202static 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 245void 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 273static 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 %u 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 329jboolean 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 437void 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 456void 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 496void 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 504void 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