native-media-jni.c revision ac8c7318e1d7ec1358bbf924e1bc2cee45b44fc6
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_ANDROIDBUFFERQUEUE, XA_IID_STREAMINFORMATION}; 367 368 369 // create media player 370 res = (*engineEngine)->CreateMediaPlayer(engineEngine, &playerObj, &dataSrc, 371 NULL, &audioSnk, &imageVideoSink, NULL, NULL, 372 NB_MAXAL_INTERFACES /*XAuint32 numInterfaces*/, 373 iidArray /*const XAInterfaceID *pInterfaceIds*/, 374 required /*const XAboolean *pInterfaceRequired*/); 375 assert(XA_RESULT_SUCCESS == res); 376 377 // release the Java string and UTF-8 378 (*env)->ReleaseStringUTFChars(env, filename, utf8); 379 380 // realize the player 381 res = (*playerObj)->Realize(playerObj, XA_BOOLEAN_FALSE); 382 assert(XA_RESULT_SUCCESS == res); 383 384 // get the play interface 385 res = (*playerObj)->GetInterface(playerObj, XA_IID_PLAY, &playerPlayItf); 386 assert(XA_RESULT_SUCCESS == res); 387 388 // get the stream information interface (for video size) 389 res = (*playerObj)->GetInterface(playerObj, XA_IID_STREAMINFORMATION, &playerStreamInfoItf); 390 assert(XA_RESULT_SUCCESS == res); 391 392 // get the volume interface 393 res = (*playerObj)->GetInterface(playerObj, XA_IID_VOLUME, &playerVolItf); 394 assert(XA_RESULT_SUCCESS == res); 395 396 // get the Android buffer queue interface 397 res = (*playerObj)->GetInterface(playerObj, XA_IID_ANDROIDBUFFERQUEUE, &playerBQItf); 398 assert(XA_RESULT_SUCCESS == res); 399 400 // specify which events we want to be notified of 401 res = (*playerBQItf)->SetCallbackEventsMask(playerBQItf, XA_ANDROIDBUFFERQUEUEEVENT_PROCESSED); 402 403 // register the callback from which OpenMAX AL can retrieve the data to play 404 res = (*playerBQItf)->RegisterCallback(playerBQItf, AndroidBufferQueueCallback, NULL); 405 assert(XA_RESULT_SUCCESS == res); 406 407 // we want to be notified of the video size once it's found, so we register a callback for that 408 res = (*playerStreamInfoItf)->RegisterStreamChangeCallback(playerStreamInfoItf, 409 StreamChangeCallback, NULL); 410 411 // enqueue the initial buffers 412 if (!enqueueInitialBuffers(JNI_FALSE)) { 413 return JNI_FALSE; 414 } 415 416 // prepare the player 417 res = (*playerPlayItf)->SetPlayState(playerPlayItf, XA_PLAYSTATE_PAUSED); 418 assert(XA_RESULT_SUCCESS == res); 419 420 // set the volume 421 res = (*playerVolItf)->SetVolumeLevel(playerVolItf, 0);//-300); 422 assert(XA_RESULT_SUCCESS == res); 423 424 // start the playback 425 res = (*playerPlayItf)->SetPlayState(playerPlayItf, XA_PLAYSTATE_PLAYING); 426 assert(XA_RESULT_SUCCESS == res); 427 428 return JNI_TRUE; 429} 430 431 432// set the playing state for the streaming media player 433void Java_com_example_nativemedia_NativeMedia_setPlayingStreamingMediaPlayer(JNIEnv* env, 434 jclass clazz, jboolean isPlaying) 435{ 436 XAresult res; 437 438 // make sure the streaming media player was created 439 if (NULL != playerPlayItf) { 440 441 // set the player's state 442 res = (*playerPlayItf)->SetPlayState(playerPlayItf, isPlaying ? 443 XA_PLAYSTATE_PLAYING : XA_PLAYSTATE_PAUSED); 444 assert(XA_RESULT_SUCCESS == res); 445 446 } 447 448} 449 450 451// shut down the native media system 452void Java_com_example_nativemedia_NativeMedia_shutdown(JNIEnv* env, jclass clazz) 453{ 454 // destroy streaming media player object, and invalidate all associated interfaces 455 if (playerObj != NULL) { 456 (*playerObj)->Destroy(playerObj); 457 playerObj = NULL; 458 playerPlayItf = NULL; 459 playerBQItf = NULL; 460 playerStreamInfoItf = NULL; 461 playerVolItf = NULL; 462 } 463 464 // destroy output mix object, and invalidate all associated interfaces 465 if (outputMixObject != NULL) { 466 (*outputMixObject)->Destroy(outputMixObject); 467 outputMixObject = NULL; 468 } 469 470 // destroy engine object, and invalidate all associated interfaces 471 if (engineObject != NULL) { 472 (*engineObject)->Destroy(engineObject); 473 engineObject = NULL; 474 engineEngine = NULL; 475 } 476 477 // close the file 478 if (file != NULL) { 479 fclose(file); 480 file = NULL; 481 } 482 483 // make sure we don't leak native windows 484 if (theNativeWindow != NULL) { 485 ANativeWindow_release(theNativeWindow); 486 theNativeWindow = NULL; 487 } 488} 489 490 491// set the surface 492void Java_com_example_nativemedia_NativeMedia_setSurface(JNIEnv *env, jclass clazz, jobject surface) 493{ 494 // obtain a native window from a Java surface 495 theNativeWindow = ANativeWindow_fromSurface(env, surface); 496} 497 498 499// set the surface texture 500void Java_com_example_nativemedia_NativeMedia_setSurfaceTexture(JNIEnv *env, jclass clazz, 501 jobject surfaceTexture) 502{ 503 // obtain a native window from a Java surface texture 504 theNativeWindow = ANativeWindow_fromSurfaceTexture(env, surfaceTexture); 505} 506 507 508// rewind the streaming media player 509void Java_com_example_nativemedia_NativeMedia_rewindStreamingMediaPlayer(JNIEnv *env, jclass clazz) 510{ 511 XAresult res; 512 513 // make sure the streaming media player was created 514 if (NULL != playerBQItf && NULL != file) { 515 // first wait for buffers currently in queue to be drained 516 int ok; 517 ok = pthread_mutex_lock(&mutex); 518 assert(0 == ok); 519 discontinuity = JNI_TRUE; 520 // wait for discontinuity request to be observed by buffer queue callback 521 // FIXME sorry, can't rewind after EOS 522 while (discontinuity && !reachedEof) { 523 ok = pthread_cond_wait(&cond, &mutex); 524 assert(0 == ok); 525 } 526 ok = pthread_mutex_unlock(&mutex); 527 assert(0 == ok); 528 } 529 530} 531