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 // OpenMAX AL MediaPlayer command-line player 18 19 #include <assert.h> 20 #include <pthread.h> 21 #include <stdio.h> 22 #include <stdlib.h> 23 #include <fcntl.h> 24 #include <sys/mman.h> 25 #include <sys/stat.h> 26 #include <unistd.h> 27 #include <OMXAL/OpenMAXAL.h> 28 #include <OMXAL/OpenMAXAL_Android.h> 29 #include "nativewindow.h" 30 31 #define MPEG2TS_PACKET_SIZE 188 // MPEG-2 transport stream packet size in bytes 32 #define PACKETS_PER_BUFFER 20 // Number of MPEG-2 transport stream packets per buffer 33 34 #define NB_BUFFERS 2 // Number of buffers in Android buffer queue 35 36 // MPEG-2 transport stream packet 37 typedef struct { 38 char data[MPEG2TS_PACKET_SIZE]; 39 } MPEG2TS_Packet; 40 41 // Globals shared between main thread and buffer queue callback 42 MPEG2TS_Packet *packets; 43 size_t totalPackets; // total number of packets in input file 44 size_t numPackets; // number of packets to play, defaults to totalPackets - firstPacket 45 size_t curPacket; // current packet index 46 size_t discPacket; // discontinuity packet index, defaults to no discontinuity requested 47 size_t afterDiscPacket; // packet index to switch to after the discontinuity 48 size_t firstPacket; // first packet index to be played, defaults to zero 49 size_t lastPacket; // last packet index to be played 50 size_t formatPacket; // format change packet index, defaults to no format change requested 51 XAmillisecond seekPos = XA_TIME_UNKNOWN; // seek to this position initially 52 int pauseMs = -1; // pause after this many ms into playback 53 XAboolean forceCallbackFailure = XA_BOOLEAN_FALSE; // force callback failures occasionally 54 XAboolean sentEOS = XA_BOOLEAN_FALSE; // whether we have enqueued EOS yet 55 56 // These are extensions to OpenMAX AL 1.0.1 values 57 58 #define PREFETCHSTATUS_UNKNOWN ((XAuint32) 0) 59 #define PREFETCHSTATUS_ERROR ((XAuint32) (-1)) 60 61 // Mutex and condition shared with main program to protect prefetch_status 62 63 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 64 static pthread_cond_t cond = PTHREAD_COND_INITIALIZER; 65 XAuint32 prefetch_status = PREFETCHSTATUS_UNKNOWN; 66 67 /* used to detect errors likely to have occured when the OpenMAX AL framework fails to open 68 * a resource, for instance because a file URI is invalid, or an HTTP server doesn't respond. 69 */ 70 #define PREFETCHEVENT_ERROR_CANDIDATE \ 71 (XA_PREFETCHEVENT_STATUSCHANGE | XA_PREFETCHEVENT_FILLLEVELCHANGE) 72 73 // stream event change callback 74 void streamEventChangeCallback(XAStreamInformationItf caller, XAuint32 eventId, 75 XAuint32 streamIndex, void *pEventData, void *pContext) 76 { 77 // context parameter is specified as NULL and is unused here 78 assert(NULL == pContext); 79 switch (eventId) { 80 case XA_STREAMCBEVENT_PROPERTYCHANGE: 81 printf("XA_STREAMCBEVENT_PROPERTYCHANGE on stream index %u, pEventData %p\n", streamIndex, 82 pEventData); 83 break; 84 default: 85 printf("Unknown stream event ID %u\n", eventId); 86 break; 87 } 88 } 89 90 // prefetch status callback 91 void prefetchStatusCallback(XAPrefetchStatusItf caller, void *pContext, XAuint32 event) 92 { 93 // pContext is unused here, so we pass NULL 94 assert(pContext == NULL); 95 XApermille level = 0; 96 XAresult result; 97 result = (*caller)->GetFillLevel(caller, &level); 98 assert(XA_RESULT_SUCCESS == result); 99 XAuint32 status; 100 result = (*caller)->GetPrefetchStatus(caller, &status); 101 assert(XA_RESULT_SUCCESS == result); 102 if (event & XA_PREFETCHEVENT_FILLLEVELCHANGE) { 103 printf("PrefetchEventCallback: Buffer fill level is = %d\n", level); 104 } 105 if (event & XA_PREFETCHEVENT_STATUSCHANGE) { 106 printf("PrefetchEventCallback: Prefetch Status is = %u\n", status); 107 } 108 XAuint32 new_prefetch_status; 109 if ((PREFETCHEVENT_ERROR_CANDIDATE == (event & PREFETCHEVENT_ERROR_CANDIDATE)) 110 && (level == 0) && (status == XA_PREFETCHSTATUS_UNDERFLOW)) { 111 printf("PrefetchEventCallback: Error while prefetching data, exiting\n"); 112 new_prefetch_status = PREFETCHSTATUS_ERROR; 113 } else if (event == XA_PREFETCHEVENT_STATUSCHANGE) { 114 new_prefetch_status = status; 115 } else { 116 return; 117 } 118 int ok; 119 ok = pthread_mutex_lock(&mutex); 120 assert(ok == 0); 121 prefetch_status = new_prefetch_status; 122 ok = pthread_cond_signal(&cond); 123 assert(ok == 0); 124 ok = pthread_mutex_unlock(&mutex); 125 assert(ok == 0); 126 } 127 128 // playback event callback 129 void playEventCallback(XAPlayItf caller, void *pContext, XAuint32 event) 130 { 131 // pContext is unused here, so we pass NULL 132 assert(NULL == pContext); 133 134 XAresult result; 135 XAmillisecond position; 136 result = (*caller)->GetPosition(caller, &position); 137 assert(XA_RESULT_SUCCESS == result); 138 139 if (XA_PLAYEVENT_HEADATEND & event) { 140 printf("XA_PLAYEVENT_HEADATEND current position=%u ms\n", position); 141 } 142 143 if (XA_PLAYEVENT_HEADATNEWPOS & event) { 144 printf("XA_PLAYEVENT_HEADATNEWPOS current position=%u ms\n", position); 145 } 146 147 if (XA_PLAYEVENT_HEADATMARKER & event) { 148 printf("XA_PLAYEVENT_HEADATMARKER current position=%u ms\n", position); 149 } 150 } 151 152 // Android buffer queue callback 153 XAresult bufferQueueCallback( 154 XAAndroidBufferQueueItf caller, 155 void *pCallbackContext, 156 void *pBufferContext, 157 void *pBufferData, 158 XAuint32 dataSize, 159 XAuint32 dataUsed, 160 const XAAndroidBufferItem *pItems, 161 XAuint32 itemsLength) 162 { 163 XAPlayItf playerPlay = (XAPlayItf) pCallbackContext; 164 // enqueue the .ts data directly from mapped memory, so ignore the empty buffer pBufferData 165 if (curPacket <= lastPacket) { 166 static const XAAndroidBufferItem discontinuity = {XA_ANDROID_ITEMKEY_DISCONTINUITY, 0}; 167 static const XAAndroidBufferItem eos = {XA_ANDROID_ITEMKEY_EOS, 0}; 168 static const XAAndroidBufferItem formatChange = {XA_ANDROID_ITEMKEY_FORMAT_CHANGE, 0}; 169 const XAAndroidBufferItem *items; 170 XAuint32 itemSize; 171 // compute number of packets to be enqueued in this buffer 172 XAuint32 packetsThisBuffer = lastPacket - curPacket; 173 if (packetsThisBuffer > PACKETS_PER_BUFFER) { 174 packetsThisBuffer = PACKETS_PER_BUFFER; 175 } 176 // last packet? this should only happen once 177 if (curPacket == lastPacket) { 178 if (sentEOS) { 179 printf("buffer completion callback after EOS\n"); 180 return XA_RESULT_SUCCESS; 181 } 182 printf("sending EOS\n"); 183 items = &eos; 184 itemSize = sizeof(eos); 185 sentEOS = XA_BOOLEAN_TRUE; 186 // discontinuity requested? 187 } else if (curPacket == discPacket) { 188 printf("sending discontinuity at packet %zu, then resuming at packet %zu\n", discPacket, 189 afterDiscPacket); 190 items = &discontinuity; 191 itemSize = sizeof(discontinuity); 192 curPacket = afterDiscPacket; 193 // format change requested? 194 } else if (curPacket == formatPacket) { 195 printf("sending format change"); 196 items = &formatChange; 197 itemSize = sizeof(formatChange); 198 // pure data with no items 199 } else { 200 items = NULL; 201 itemSize = 0; 202 } 203 XAresult result; 204 // enqueue the optional data and optional items; there is always at least one or the other 205 assert(packetsThisBuffer > 0 || itemSize > 0); 206 result = (*caller)->Enqueue(caller, NULL, &packets[curPacket], 207 sizeof(MPEG2TS_Packet) * packetsThisBuffer, items, itemSize); 208 assert(XA_RESULT_SUCCESS == result); 209 curPacket += packetsThisBuffer; 210 // display position periodically 211 if (curPacket % 1000 == 0) { 212 XAmillisecond position; 213 result = (*playerPlay)->GetPosition(playerPlay, &position); 214 assert(XA_RESULT_SUCCESS == result); 215 printf("Position after enqueueing packet %zu: %u ms\n", curPacket, position); 216 } 217 } 218 if (forceCallbackFailure && (curPacket % 1230 == 0)) { 219 return (XAresult) curPacket; 220 } else { 221 return XA_RESULT_SUCCESS; 222 } 223 } 224 225 // convert a domain type to string 226 static const char *domainToString(XAuint32 domain) 227 { 228 switch (domain) { 229 case 0: // FIXME There's a private declaration '#define XA_DOMAINTYPE_CONTAINER 0' in src/data.h 230 // but we don't have access to it. Plan to file a bug with Khronos about this symbol. 231 return "media container"; 232 #define _(x) case x: return #x; 233 _(XA_DOMAINTYPE_AUDIO) 234 _(XA_DOMAINTYPE_VIDEO) 235 _(XA_DOMAINTYPE_IMAGE) 236 _(XA_DOMAINTYPE_TIMEDTEXT) 237 _(XA_DOMAINTYPE_MIDI) 238 _(XA_DOMAINTYPE_VENDOR) 239 _(XA_DOMAINTYPE_UNKNOWN) 240 #undef _ 241 default: 242 return "unknown"; 243 } 244 } 245 246 // main program 247 int main(int argc, char **argv) 248 { 249 const char *prog = argv[0]; 250 int i; 251 252 XAboolean abq = XA_BOOLEAN_FALSE; // use AndroidBufferQueue, default is URI 253 XAboolean looping = XA_BOOLEAN_FALSE; 254 for (i = 1; i < argc; ++i) { 255 const char *arg = argv[i]; 256 if (arg[0] != '-') 257 break; 258 switch (arg[1]) { 259 case 'a': 260 abq = XA_BOOLEAN_TRUE; 261 break; 262 case 'c': 263 forceCallbackFailure = XA_BOOLEAN_TRUE; 264 break; 265 case 'd': 266 discPacket = atoi(&arg[2]); 267 break; 268 case 'D': 269 afterDiscPacket = atoi(&arg[2]); 270 break; 271 case 'f': 272 firstPacket = atoi(&arg[2]); 273 break; 274 case 'F': 275 formatPacket = atoi(&arg[2]); 276 break; 277 case 'l': 278 looping = XA_BOOLEAN_TRUE; 279 break; 280 case 'n': 281 numPackets = atoi(&arg[2]); 282 break; 283 case 'p': 284 pauseMs = atoi(&arg[2]); 285 break; 286 case 's': 287 seekPos = atoi(&arg[2]); 288 break; 289 default: 290 fprintf(stderr, "%s: unknown option %s\n", prog, arg); 291 break; 292 } 293 } 294 295 // check that exactly one URI was specified 296 if (argc - i != 1) { 297 fprintf(stderr, "usage: %s [-a] [-c] [-d#] [-D#] [-f#] [-F#] [-l] [-n#] [-p#] [-s#] uri\n", 298 prog); 299 fprintf(stderr, " -a Use Android buffer queue to supply data, default is URI\n"); 300 fprintf(stderr, " -c Force callback to return an error randomly, for debugging only\n"); 301 fprintf(stderr, " -d# Packet index to insert a discontinuity, default is none\n"); 302 fprintf(stderr, " -D# Packet index to switch to after the discontinuity\n"); 303 fprintf(stderr, " -f# First packet index, defaults to 0\n"); 304 fprintf(stderr, " -F# Packet index to insert a format change, default is none\n"); 305 fprintf(stderr, " -l Enable looping, for URI only\n"); 306 fprintf(stderr, " -n# Number of packets to enqueue\n"); 307 fprintf(stderr, " -p# Pause playback for 5 seconds after this many milliseconds\n"); 308 fprintf(stderr, " -s# Seek position in milliseconds, for URI only\n"); 309 return EXIT_FAILURE; 310 } 311 const char *uri = argv[i]; 312 313 // for AndroidBufferQueue, interpret URI as a filename and open 314 int fd = -1; 315 if (abq) { 316 fd = open(uri, O_RDONLY); 317 if (fd < 0) { 318 perror(uri); 319 goto close; 320 } 321 int ok; 322 struct stat statbuf; 323 ok = fstat(fd, &statbuf); 324 if (ok < 0) { 325 perror(uri); 326 goto close; 327 } 328 if (!S_ISREG(statbuf.st_mode)) { 329 fprintf(stderr, "%s: not an ordinary file\n", uri); 330 goto close; 331 } 332 void *ptr; 333 ptr = mmap(NULL, statbuf.st_size, PROT_READ, MAP_FILE | MAP_PRIVATE, fd, (off_t) 0); 334 if (ptr == MAP_FAILED) { 335 perror(uri); 336 goto close; 337 } 338 size_t filelen = statbuf.st_size; 339 if ((filelen % MPEG2TS_PACKET_SIZE) != 0) { 340 fprintf(stderr, "%s: warning file length %zu is not a multiple of %d\n", uri, filelen, 341 MPEG2TS_PACKET_SIZE); 342 } 343 packets = (MPEG2TS_Packet *) ptr; 344 totalPackets = filelen / MPEG2TS_PACKET_SIZE; 345 printf("%s has %zu total packets\n", uri, totalPackets); 346 if (firstPacket >= totalPackets) { 347 fprintf(stderr, "-f%zu ignored\n", firstPacket); 348 firstPacket = 0; 349 } 350 if (numPackets == 0) { 351 numPackets = totalPackets - firstPacket; 352 } else if (firstPacket + numPackets > totalPackets) { 353 fprintf(stderr, "-n%zu ignored\n", numPackets); 354 numPackets = totalPackets - firstPacket; 355 } 356 lastPacket = firstPacket + numPackets; 357 if (discPacket != 0 && (discPacket < firstPacket || discPacket >= lastPacket)) { 358 fprintf(stderr, "-d%zu ignored\n", discPacket); 359 discPacket = 0; 360 } 361 if (afterDiscPacket < firstPacket || afterDiscPacket >= lastPacket) { 362 fprintf(stderr, "-D%zu ignored\n", afterDiscPacket); 363 afterDiscPacket = 0; 364 } 365 if (formatPacket != 0 && (formatPacket < firstPacket || formatPacket >= lastPacket)) { 366 fprintf(stderr, "-F%zu ignored\n", formatPacket); 367 formatPacket = 0; 368 } 369 } 370 371 ANativeWindow *nativeWindow; 372 373 XAresult result; 374 XAObjectItf engineObject; 375 376 // create engine 377 result = xaCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL); 378 assert(XA_RESULT_SUCCESS == result); 379 result = (*engineObject)->Realize(engineObject, XA_BOOLEAN_FALSE); 380 assert(XA_RESULT_SUCCESS == result); 381 XAEngineItf engineEngine; 382 result = (*engineObject)->GetInterface(engineObject, XA_IID_ENGINE, &engineEngine); 383 assert(XA_RESULT_SUCCESS == result); 384 385 // create output mix 386 XAObjectItf outputMixObject; 387 result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, NULL, NULL); 388 assert(XA_RESULT_SUCCESS == result); 389 result = (*outputMixObject)->Realize(outputMixObject, XA_BOOLEAN_FALSE); 390 assert(XA_RESULT_SUCCESS == result); 391 392 // configure media source 393 XADataLocator_URI locUri; 394 locUri.locatorType = XA_DATALOCATOR_URI; 395 locUri.URI = (XAchar *) uri; 396 XADataFormat_MIME fmtMime; 397 fmtMime.formatType = XA_DATAFORMAT_MIME; 398 if (abq) { 399 fmtMime.mimeType = (XAchar *) XA_ANDROID_MIME_MP2TS; 400 fmtMime.containerType = XA_CONTAINERTYPE_MPEG_TS; 401 } else { 402 fmtMime.mimeType = NULL; 403 fmtMime.containerType = XA_CONTAINERTYPE_UNSPECIFIED; 404 } 405 XADataLocator_AndroidBufferQueue locABQ; 406 locABQ.locatorType = XA_DATALOCATOR_ANDROIDBUFFERQUEUE; 407 locABQ.numBuffers = NB_BUFFERS; 408 XADataSource dataSrc; 409 if (abq) { 410 dataSrc.pLocator = &locABQ; 411 } else { 412 dataSrc.pLocator = &locUri; 413 } 414 dataSrc.pFormat = &fmtMime; 415 416 // configure audio sink 417 XADataLocator_OutputMix locOM; 418 locOM.locatorType = XA_DATALOCATOR_OUTPUTMIX; 419 locOM.outputMix = outputMixObject; 420 XADataSink audioSnk; 421 audioSnk.pLocator = &locOM; 422 audioSnk.pFormat = NULL; 423 424 // configure video sink 425 nativeWindow = getNativeWindow(); 426 XADataLocator_NativeDisplay locND; 427 locND.locatorType = XA_DATALOCATOR_NATIVEDISPLAY; 428 locND.hWindow = nativeWindow; 429 locND.hDisplay = NULL; 430 XADataSink imageVideoSink; 431 imageVideoSink.pLocator = &locND; 432 imageVideoSink.pFormat = NULL; 433 434 // create media player 435 XAObjectItf playerObject; 436 XAInterfaceID ids[4] = {XA_IID_STREAMINFORMATION, XA_IID_PREFETCHSTATUS, XA_IID_SEEK, 437 XA_IID_ANDROIDBUFFERQUEUESOURCE}; 438 XAboolean req[4] = {XA_BOOLEAN_TRUE, XA_BOOLEAN_TRUE, XA_BOOLEAN_FALSE, XA_BOOLEAN_TRUE}; 439 result = (*engineEngine)->CreateMediaPlayer(engineEngine, &playerObject, &dataSrc, NULL, 440 &audioSnk, nativeWindow != NULL ? &imageVideoSink : NULL, NULL, NULL, abq ? 4 : 3, ids, 441 req); 442 assert(XA_RESULT_SUCCESS == result); 443 444 // realize the player 445 result = (*playerObject)->Realize(playerObject, XA_BOOLEAN_FALSE); 446 assert(XA_RESULT_SUCCESS == result); 447 448 // get the play interface 449 XAPlayItf playerPlay; 450 result = (*playerObject)->GetInterface(playerObject, XA_IID_PLAY, &playerPlay); 451 assert(XA_RESULT_SUCCESS == result); 452 453 if (abq) { 454 455 // get the Android buffer queue interface 456 XAAndroidBufferQueueItf playerAndroidBufferQueue; 457 result = (*playerObject)->GetInterface(playerObject, XA_IID_ANDROIDBUFFERQUEUESOURCE, 458 &playerAndroidBufferQueue); 459 assert(XA_RESULT_SUCCESS == result); 460 461 // register the buffer queue callback 462 result = (*playerAndroidBufferQueue)->RegisterCallback(playerAndroidBufferQueue, 463 bufferQueueCallback, (void *) playerPlay); 464 assert(XA_RESULT_SUCCESS == result); 465 result = (*playerAndroidBufferQueue)->SetCallbackEventsMask(playerAndroidBufferQueue, 466 XA_ANDROIDBUFFERQUEUEEVENT_PROCESSED); 467 assert(XA_RESULT_SUCCESS == result); 468 469 // set the player's state to paused, to start prefetching 470 printf("start early prefetch\n"); 471 result = (*playerPlay)->SetPlayState(playerPlay, XA_PLAYSTATE_PAUSED); 472 assert(XA_RESULT_SUCCESS == result); 473 474 // enqueue the initial buffers until buffer queue is full 475 XAuint32 packetsThisBuffer; 476 for (curPacket = firstPacket; curPacket < lastPacket; curPacket += packetsThisBuffer) { 477 // handle the unlikely case of a very short .ts 478 packetsThisBuffer = lastPacket - curPacket; 479 if (packetsThisBuffer > PACKETS_PER_BUFFER) { 480 packetsThisBuffer = PACKETS_PER_BUFFER; 481 } 482 result = (*playerAndroidBufferQueue)->Enqueue(playerAndroidBufferQueue, NULL, 483 &packets[curPacket], MPEG2TS_PACKET_SIZE * packetsThisBuffer, NULL, 0); 484 if (XA_RESULT_BUFFER_INSUFFICIENT == result) { 485 printf("Enqueued initial %zu packets in %zu buffers\n", curPacket - firstPacket, 486 (curPacket - firstPacket + PACKETS_PER_BUFFER - 1) / PACKETS_PER_BUFFER); 487 break; 488 } 489 assert(XA_RESULT_SUCCESS == result); 490 } 491 492 } 493 494 // get the stream information interface 495 XAStreamInformationItf playerStreamInformation; 496 result = (*playerObject)->GetInterface(playerObject, XA_IID_STREAMINFORMATION, 497 &playerStreamInformation); 498 assert(XA_RESULT_SUCCESS == result); 499 500 // register the stream event change callback 501 result = (*playerStreamInformation)->RegisterStreamChangeCallback(playerStreamInformation, 502 streamEventChangeCallback, NULL); 503 assert(XA_RESULT_SUCCESS == result); 504 505 // get the prefetch status interface 506 XAPrefetchStatusItf playerPrefetchStatus; 507 result = (*playerObject)->GetInterface(playerObject, XA_IID_PREFETCHSTATUS, 508 &playerPrefetchStatus); 509 assert(XA_RESULT_SUCCESS == result); 510 511 // register prefetch status callback 512 result = (*playerPrefetchStatus)->RegisterCallback(playerPrefetchStatus, prefetchStatusCallback, 513 NULL); 514 assert(XA_RESULT_SUCCESS == result); 515 result = (*playerPrefetchStatus)->SetCallbackEventsMask(playerPrefetchStatus, 516 XA_PREFETCHEVENT_FILLLEVELCHANGE | XA_PREFETCHEVENT_STATUSCHANGE); 517 assert(XA_RESULT_SUCCESS == result); 518 519 // get the seek interface for seeking and/or looping 520 if (looping || seekPos != XA_TIME_UNKNOWN) { 521 XASeekItf playerSeek; 522 result = (*playerObject)->GetInterface(playerObject, XA_IID_SEEK, &playerSeek); 523 assert(XA_RESULT_SUCCESS == result); 524 if (seekPos != XA_TIME_UNKNOWN) { 525 result = (*playerSeek)->SetPosition(playerSeek, seekPos, XA_SEEKMODE_ACCURATE); 526 if (XA_RESULT_FEATURE_UNSUPPORTED == result) { 527 fprintf(stderr, "-s%u (seek to initial position) is unsupported\n", seekPos); 528 } else { 529 assert(XA_RESULT_SUCCESS == result); 530 } 531 } 532 if (looping) { 533 result = (*playerSeek)->SetLoop(playerSeek, XA_BOOLEAN_TRUE, (XAmillisecond) 0, 534 XA_TIME_UNKNOWN); 535 if (XA_RESULT_FEATURE_UNSUPPORTED) { 536 fprintf(stderr, "-l (looping) is unsupported\n"); 537 } else { 538 assert(XA_RESULT_SUCCESS == result); 539 } 540 } 541 } 542 543 // register play event callback 544 result = (*playerPlay)->RegisterCallback(playerPlay, playEventCallback, NULL); 545 assert(XA_RESULT_SUCCESS == result); 546 result = (*playerPlay)->SetCallbackEventsMask(playerPlay, 547 XA_PLAYEVENT_HEADATEND | XA_PLAYEVENT_HEADATMARKER | XA_PLAYEVENT_HEADATNEWPOS); 548 assert(XA_RESULT_SUCCESS == result); 549 550 // set a marker 551 result = (*playerPlay)->SetMarkerPosition(playerPlay, 5000); 552 assert(XA_RESULT_SUCCESS == result); 553 554 // set position update period 555 result = (*playerPlay)->SetPositionUpdatePeriod(playerPlay, 2000); 556 assert(XA_RESULT_SUCCESS == result); 557 558 // get the position before prefetch 559 XAmillisecond position; 560 result = (*playerPlay)->GetPosition(playerPlay, &position); 561 assert(XA_RESULT_SUCCESS == result); 562 printf("Position before prefetch: %u ms\n", position); 563 564 // get the duration before prefetch 565 XAmillisecond duration; 566 result = (*playerPlay)->GetDuration(playerPlay, &duration); 567 assert(XA_RESULT_SUCCESS == result); 568 if (XA_TIME_UNKNOWN == duration) 569 printf("Duration before prefetch: unknown as expected\n"); 570 else 571 printf("Duration before prefetch: %.1f (surprise!)\n", duration / 1000.0f); 572 573 // set the player's state to paused, to start prefetching 574 printf("start prefetch\n"); 575 result = (*playerPlay)->SetPlayState(playerPlay, XA_PLAYSTATE_PAUSED); 576 assert(XA_RESULT_SUCCESS == result); 577 578 // wait for prefetch status callback to indicate either sufficient data or error 579 pthread_mutex_lock(&mutex); 580 while (prefetch_status == PREFETCHSTATUS_UNKNOWN) { 581 pthread_cond_wait(&cond, &mutex); 582 } 583 pthread_mutex_unlock(&mutex); 584 if (prefetch_status == PREFETCHSTATUS_ERROR) { 585 fprintf(stderr, "Error during prefetch, exiting\n"); 586 goto destroyRes; 587 } 588 589 // get the position after prefetch 590 result = (*playerPlay)->GetPosition(playerPlay, &position); 591 assert(XA_RESULT_SUCCESS == result); 592 printf("Position after prefetch: %u ms\n", position); 593 594 // get duration again, now it should be known for the file source or unknown for TS 595 result = (*playerPlay)->GetDuration(playerPlay, &duration); 596 assert(XA_RESULT_SUCCESS == result); 597 if (duration == XA_TIME_UNKNOWN) { 598 printf("Duration after prefetch: unknown (expected for TS, unexpected for file)\n"); 599 } else { 600 printf("Duration after prefetch: %u ms (expected for file, unexpected for TS)\n", duration); 601 } 602 603 // query for media container information 604 result = (*playerStreamInformation)->QueryMediaContainerInformation(playerStreamInformation, 605 NULL); 606 assert(XA_RESULT_PARAMETER_INVALID == result); 607 XAMediaContainerInformation mediaContainerInformation; 608 // this verifies it is filling in all the fields 609 memset(&mediaContainerInformation, 0x55, sizeof(XAMediaContainerInformation)); 610 result = (*playerStreamInformation)->QueryMediaContainerInformation(playerStreamInformation, 611 &mediaContainerInformation); 612 assert(XA_RESULT_SUCCESS == result); 613 printf("Media container information:\n"); 614 printf(" containerType = %u\n", mediaContainerInformation.containerType); 615 printf(" mediaDuration = %u\n", mediaContainerInformation.mediaDuration); 616 printf(" numStreams = %u\n", mediaContainerInformation.numStreams); 617 618 // Now query for each the streams. Note that stream indices go up to and including 619 // mediaContainerInformation.numStreams, because stream 0 is the container itself, 620 // while stream 1 to mediaContainerInformation.numStreams are the contained streams. 621 XAuint32 numStreams = mediaContainerInformation.numStreams; 622 XAuint32 streamIndex; 623 for (streamIndex = 0; streamIndex <= mediaContainerInformation.numStreams; ++streamIndex) { 624 XAuint32 domain; 625 XAuint16 nameSize; 626 XAchar name[64]; 627 printf("stream[%u]:\n", streamIndex); 628 if (streamIndex == 0) { 629 result = (*playerStreamInformation)->QueryStreamType(playerStreamInformation, 630 streamIndex, &domain); 631 assert(XA_RESULT_PARAMETER_INVALID == result); 632 result = (*playerStreamInformation)->QueryStreamInformation(playerStreamInformation, 633 streamIndex, &mediaContainerInformation); 634 //assert(XA_RESULT_PARAMETER_INVALID == result); 635 nameSize = sizeof(name); 636 result = (*playerStreamInformation)->QueryStreamName(playerStreamInformation, 637 streamIndex, &nameSize, name); 638 //assert(XA_RESULT_PARAMETER_INVALID == result); 639 continue; 640 } 641 result = (*playerStreamInformation)->QueryStreamType(playerStreamInformation, streamIndex, 642 NULL); 643 assert(XA_RESULT_PARAMETER_INVALID == result); 644 domain = 12345; 645 result = (*playerStreamInformation)->QueryStreamType(playerStreamInformation, streamIndex, 646 &domain); 647 assert(XA_RESULT_SUCCESS == result); 648 printf(" QueryStreamType: domain = 0x%X (%s)\n", domain, domainToString(domain)); 649 nameSize = sizeof(name); 650 result = (*playerStreamInformation)->QueryStreamName(playerStreamInformation, streamIndex, 651 &nameSize, name); 652 #if 0 653 assert(XA_RESULT_SUCCESS == result); 654 assert(sizeof(name) >= nameSize); 655 if (sizeof(name) != nameSize) { 656 assert('\0' == name[nameSize]); 657 } 658 printf(" QueryStreamName: nameSize=%u, name=\"%.*s\"\n", nameSize, nameSize, name); 659 result = (*playerStreamInformation)->QueryStreamInformation(playerStreamInformation, 660 streamIndex, NULL); 661 assert(XA_RESULT_PARAMETER_INVALID == result); 662 #endif 663 664 printf(" QueryStreamInformation:\n"); 665 switch (domain) { 666 #if 0 667 case 0: // FIXME container 668 result = (*playerStreamInformation)->QueryStreamInformation(playerStreamInformation, 669 streamIndex, &mediaContainerInformation); 670 assert(XA_RESULT_SUCCESS == result); 671 printf(" containerType = %u (1=unspecified)\n", 672 mediaContainerInformation.containerType); 673 printf(" mediaDuration = %u\n", mediaContainerInformation.mediaDuration); 674 printf(" numStreams = %u\n", mediaContainerInformation.numStreams); 675 break; 676 #endif 677 case XA_DOMAINTYPE_AUDIO: { 678 XAAudioStreamInformation audioStreamInformation; 679 memset(&audioStreamInformation, 0x55, sizeof(XAAudioStreamInformation)); 680 result = (*playerStreamInformation)->QueryStreamInformation(playerStreamInformation, 681 streamIndex, &audioStreamInformation); 682 assert(XA_RESULT_PARAMETER_INVALID == result); 683 printf(" codecId = %u\n", audioStreamInformation.codecId); 684 printf(" channels = %u\n", audioStreamInformation.channels); 685 printf(" sampleRate = %u\n", audioStreamInformation.sampleRate); 686 printf(" bitRate = %u\n", audioStreamInformation.bitRate); 687 printf(" langCountry = \"%s\"\n", audioStreamInformation.langCountry); 688 printf(" duration = %u\n", audioStreamInformation.duration); 689 } break; 690 case XA_DOMAINTYPE_VIDEO: { 691 XAVideoStreamInformation videoStreamInformation; 692 result = (*playerStreamInformation)->QueryStreamInformation(playerStreamInformation, 693 streamIndex, &videoStreamInformation); 694 assert(XA_RESULT_SUCCESS == result); 695 printf(" codecId = %u\n", videoStreamInformation.codecId); 696 printf(" width = %u\n", videoStreamInformation.width); 697 printf(" height = %u\n", videoStreamInformation.height); 698 printf(" frameRate = %u\n", videoStreamInformation.frameRate); 699 printf(" bitRate = %u\n", videoStreamInformation.bitRate); 700 printf(" duration = %u\n", videoStreamInformation.duration); 701 } break; 702 case XA_DOMAINTYPE_IMAGE: { 703 XAImageStreamInformation imageStreamInformation; 704 result = (*playerStreamInformation)->QueryStreamInformation(playerStreamInformation, 705 streamIndex, &imageStreamInformation); 706 assert(XA_RESULT_SUCCESS == result); 707 printf(" codecId = %u\n", imageStreamInformation.codecId); 708 printf(" width = %u\n", imageStreamInformation.width); 709 printf(" height = %u\n", imageStreamInformation.height); 710 printf(" presentationDuration = %u\n", imageStreamInformation.presentationDuration); 711 } break; 712 case XA_DOMAINTYPE_TIMEDTEXT: { 713 XATimedTextStreamInformation timedTextStreamInformation; 714 result = (*playerStreamInformation)->QueryStreamInformation(playerStreamInformation, 715 streamIndex, &timedTextStreamInformation); 716 assert(XA_RESULT_SUCCESS == result); 717 printf(" layer = %u\n", timedTextStreamInformation.layer); 718 printf(" width = %u\n", timedTextStreamInformation.width); 719 printf(" height = %u\n", timedTextStreamInformation.height); 720 printf(" tx = %u\n", timedTextStreamInformation.tx); 721 printf(" ty = %u\n", timedTextStreamInformation.ty); 722 printf(" bitrate = %u\n", timedTextStreamInformation.bitrate); 723 printf(" langCountry = \"%s\"\n", timedTextStreamInformation.langCountry); 724 printf(" duration = %u\n", timedTextStreamInformation.duration); 725 } break; 726 case XA_DOMAINTYPE_MIDI: { 727 XAMIDIStreamInformation midiStreamInformation; 728 result = (*playerStreamInformation)->QueryStreamInformation(playerStreamInformation, 729 streamIndex, &midiStreamInformation); 730 assert(XA_RESULT_SUCCESS == result); 731 printf(" channels = %u\n", midiStreamInformation.channels); 732 printf(" tracks = %u\n", midiStreamInformation.tracks); 733 printf(" bankType = %u\n", midiStreamInformation.bankType); 734 printf(" langCountry = \"%s\"\n", midiStreamInformation.langCountry); 735 printf(" duration = %u\n", midiStreamInformation.duration); 736 } break; 737 case XA_DOMAINTYPE_VENDOR: { 738 XAVendorStreamInformation vendorStreamInformation; 739 result = (*playerStreamInformation)->QueryStreamInformation(playerStreamInformation, 740 streamIndex, &vendorStreamInformation); 741 assert(XA_RESULT_SUCCESS == result); 742 printf(" VendorStreamInfo = %p\n", vendorStreamInformation.VendorStreamInfo); 743 } break; 744 case XA_DOMAINTYPE_UNKNOWN: { 745 // "It is not possible to query Information for streams identified as 746 // XA_DOMAINTYPE_UNKNOWN, any attempt to do so shall return a result of 747 // XA_RESULT_CONTENT_UNSUPPORTED." 748 char big[256]; 749 result = (*playerStreamInformation)->QueryStreamInformation(playerStreamInformation, 750 streamIndex, &big); 751 assert(XA_RESULT_CONTENT_UNSUPPORTED == result); 752 } break; 753 default: 754 break; 755 } 756 757 } 758 // Try one more stream index beyond the valid range 759 XAuint32 domain; 760 result = (*playerStreamInformation)->QueryStreamType(playerStreamInformation, streamIndex, 761 &domain); 762 assert(XA_RESULT_PARAMETER_INVALID == result); 763 XATimedTextStreamInformation big; 764 result = (*playerStreamInformation)->QueryStreamInformation(playerStreamInformation, 765 streamIndex, &big); 766 assert(XA_RESULT_PARAMETER_INVALID == result); 767 768 printf("QueryActiveStreams:\n"); 769 result = (*playerStreamInformation)->QueryActiveStreams(playerStreamInformation, NULL, NULL); 770 assert(XA_RESULT_PARAMETER_INVALID == result); 771 XAuint32 numStreams1 = 0x12345678; 772 result = (*playerStreamInformation)->QueryActiveStreams(playerStreamInformation, &numStreams1, 773 NULL); 774 assert(XA_RESULT_SUCCESS == result); 775 printf(" numStreams = %u\n", numStreams1); 776 XAboolean *activeStreams = calloc(numStreams1 + 1, sizeof(XAboolean)); 777 assert(NULL != activeStreams); 778 printf(" active stream(s) ="); 779 XAuint32 numStreams2 = numStreams1; 780 result = (*playerStreamInformation)->QueryActiveStreams(playerStreamInformation, &numStreams2, 781 activeStreams); 782 assert(XA_RESULT_SUCCESS == result); 783 assert(numStreams2 == numStreams1); 784 for (streamIndex = 0; streamIndex <= numStreams1; ++streamIndex) { 785 if (activeStreams[streamIndex]) 786 printf(" %u", streamIndex); 787 } 788 printf("\n"); 789 790 // SetActiveStream is untested 791 792 // start playing 793 printf("starting to play\n"); 794 result = (*playerPlay)->SetPlayState(playerPlay, XA_PLAYSTATE_PLAYING); 795 assert(XA_RESULT_SUCCESS == result); 796 797 // continue playing until end of media 798 for (;;) { 799 XAuint32 status; 800 result = (*playerPlay)->GetPlayState(playerPlay, &status); 801 assert(XA_RESULT_SUCCESS == result); 802 if (status == XA_PLAYSTATE_PAUSED) 803 break; 804 assert(status == XA_PLAYSTATE_PLAYING); 805 usleep(100000); 806 if (pauseMs >= 0) { 807 result = (*playerPlay)->GetPosition(playerPlay, &position); 808 assert(XA_RESULT_SUCCESS == result); 809 if (position >= pauseMs) { 810 printf("Pausing for 5 seconds at position %u\n", position); 811 result = (*playerPlay)->SetPlayState(playerPlay, XA_PLAYSTATE_PAUSED); 812 assert(XA_RESULT_SUCCESS == result); 813 sleep(5); 814 // FIXME clear ABQ queue here 815 result = (*playerPlay)->SetPlayState(playerPlay, XA_PLAYSTATE_PLAYING); 816 assert(XA_RESULT_SUCCESS == result); 817 pauseMs = -1; 818 } 819 } 820 } 821 822 // wait a bit more in case of additional callbacks 823 printf("end of media\n"); 824 sleep(3); 825 826 // get final position 827 result = (*playerPlay)->GetPosition(playerPlay, &position); 828 assert(XA_RESULT_SUCCESS == result); 829 printf("Position at end: %u ms\n", position); 830 831 // get duration again, now it should be known 832 result = (*playerPlay)->GetDuration(playerPlay, &duration); 833 assert(XA_RESULT_SUCCESS == result); 834 if (duration == XA_TIME_UNKNOWN) { 835 printf("Duration at end: unknown\n"); 836 } else { 837 printf("Duration at end: %u ms\n", duration); 838 } 839 840 destroyRes: 841 842 // destroy the player 843 (*playerObject)->Destroy(playerObject); 844 845 // destroy the output mix 846 (*outputMixObject)->Destroy(outputMixObject); 847 848 // destroy the engine 849 (*engineObject)->Destroy(engineObject); 850 851 #if 0 852 if (nativeWindow != NULL) { 853 ANativeWindow_release(nativeWindow); 854 } 855 #endif 856 857 close: 858 if (fd >= 0) { 859 (void) close(fd); 860 } 861 862 disposeNativeWindow(); 863 864 return EXIT_SUCCESS; 865 } 866