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