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