xaplay.c revision c3b82a293ed06001ba6d50f111608160c6065ef2
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 37typedef struct { 38 char data[MPEG2TS_PACKET_SIZE]; 39} MPEG2TS_Packet; 40 41#if 0 42// Each buffer in Android buffer queue 43typedef struct { 44 MPEG2TS_Packet packets[PACKETS_PER_BUFFER]; 45} Buffer; 46#endif 47 48// Globals shared between main thread and buffer queue callback 49MPEG2TS_Packet *packets; 50size_t numPackets; 51size_t curPacket; 52size_t discPacket; 53 54// These are extensions to OpenMAX AL 1.0.1 values 55 56#define PREFETCHSTATUS_UNKNOWN ((XAuint32) 0) 57#define PREFETCHSTATUS_ERROR ((XAuint32) (-1)) 58 59// Mutex and condition shared with main program to protect prefetch_status 60 61static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 62static pthread_cond_t cond = PTHREAD_COND_INITIALIZER; 63XAuint32 prefetch_status = PREFETCHSTATUS_UNKNOWN; 64 65/* used to detect errors likely to have occured when the OpenMAX AL framework fails to open 66 * a resource, for instance because a file URI is invalid, or an HTTP server doesn't respond. 67 */ 68#define PREFETCHEVENT_ERROR_CANDIDATE \ 69 (XA_PREFETCHEVENT_STATUSCHANGE | XA_PREFETCHEVENT_FILLLEVELCHANGE) 70 71// stream event change callback 72void streamEventChangeCallback(XAStreamInformationItf caller, XAuint32 eventId, 73 XAuint32 streamIndex, void *pEventData, void *pContext) 74{ 75 // context parameter is specified as NULL and is unused here 76 assert(NULL == pContext); 77 switch (eventId) { 78 case XA_STREAMCBEVENT_PROPERTYCHANGE: 79 printf("XA_STREAMCBEVENT_PROPERTYCHANGE on stream index %u, pEventData %p\n", streamIndex, 80 pEventData); 81 break; 82 default: 83 printf("Unknown stream event ID %u\n", eventId); 84 break; 85 } 86} 87 88// prefetch status callback 89void prefetchStatusCallback(XAPrefetchStatusItf caller, void *pContext, XAuint32 event) 90{ 91 // pContext is unused here, so we pass NULL 92 assert(pContext == NULL); 93 XApermille level = 0; 94 XAresult result; 95 result = (*caller)->GetFillLevel(caller, &level); 96 assert(XA_RESULT_SUCCESS == result); 97 XAuint32 status; 98 result = (*caller)->GetPrefetchStatus(caller, &status); 99 assert(XA_RESULT_SUCCESS == result); 100 if (event & XA_PREFETCHEVENT_FILLLEVELCHANGE) { 101 printf("PrefetchEventCallback: Buffer fill level is = %d\n", level); 102 } 103 if (event & XA_PREFETCHEVENT_STATUSCHANGE) { 104 printf("PrefetchEventCallback: Prefetch Status is = %u\n", status); 105 } 106 XAuint32 new_prefetch_status; 107 if ((PREFETCHEVENT_ERROR_CANDIDATE == (event & PREFETCHEVENT_ERROR_CANDIDATE)) 108 && (level == 0) && (status == XA_PREFETCHSTATUS_UNDERFLOW)) { 109 printf("PrefetchEventCallback: Error while prefetching data, exiting\n"); 110 new_prefetch_status = PREFETCHSTATUS_ERROR; 111 } else if (event == XA_PREFETCHEVENT_STATUSCHANGE) { 112 new_prefetch_status = status; 113 } else { 114 return; 115 } 116 int ok; 117 ok = pthread_mutex_lock(&mutex); 118 assert(ok == 0); 119 prefetch_status = new_prefetch_status; 120 ok = pthread_cond_signal(&cond); 121 assert(ok == 0); 122 ok = pthread_mutex_unlock(&mutex); 123 assert(ok == 0); 124} 125 126// playback event callback 127void playEventCallback(XAPlayItf caller, void *pContext, XAuint32 event) 128{ 129 // pContext is unused here, so we pass NULL 130 assert(NULL == pContext); 131 132 XAmillisecond position; 133 134 if (XA_PLAYEVENT_HEADATEND & event) { 135 printf("XA_PLAYEVENT_HEADATEND reached\n"); 136 //SignalEos(); 137 } 138 139 XAresult result; 140 if (XA_PLAYEVENT_HEADATNEWPOS & event) { 141 result = (*caller)->GetPosition(caller, &position); 142 assert(XA_RESULT_SUCCESS == result); 143 printf("XA_PLAYEVENT_HEADATNEWPOS current position=%ums\n", position); 144 } 145 146 if (XA_PLAYEVENT_HEADATMARKER & event) { 147 result = (*caller)->GetPosition(caller, &position); 148 assert(XA_RESULT_SUCCESS == result); 149 printf("XA_PLAYEVENT_HEADATMARKER current position=%ums\n", position); 150 } 151} 152 153// Android buffer queue callback 154XAresult bufferQueueCallback( 155 XAAndroidBufferQueueItf caller, 156 void *pCallbackContext, 157 void *pBufferContext, 158 void *pBufferData, 159 XAuint32 dataSize, 160 XAuint32 dataUsed, 161 const XAAndroidBufferItem *pItems, 162 XAuint32 itemsLength) 163{ 164 // enqueue the .ts data directly from mapped memory, so ignore the empty buffer pBufferData 165 if (curPacket <= numPackets) { 166 static const XAAndroidBufferItem discontinuity = {XA_ANDROID_ITEMKEY_DISCONTINUITY, 0}; 167 static const XAAndroidBufferItem eos = {XA_ANDROID_ITEMKEY_EOS, 0}; 168 const XAAndroidBufferItem *items; 169 XAuint32 itemSize; 170 // compute number of packets to be enqueued in this buffer 171 XAuint32 packetsThisBuffer = numPackets - curPacket; 172 if (packetsThisBuffer > PACKETS_PER_BUFFER) { 173 packetsThisBuffer = PACKETS_PER_BUFFER; 174 } 175 // last packet? this should only happen once 176 if (curPacket == numPackets) { 177 (void) write(1, "e", 1); 178 items = &eos; 179 itemSize = sizeof(eos); 180 // discontinuity requested? 181 } else if (curPacket == discPacket) { 182 printf("sending discontinuity, rewinding from beginning of stream\n"); 183 items = &discontinuity; 184 itemSize = sizeof(discontinuity); 185 curPacket = 0; 186 // pure data with no items 187 } else { 188 items = NULL; 189 itemSize = 0; 190 } 191 XAresult result; 192 // enqueue the optional data and optional items; there is always at least one or the other 193 assert(packetsThisBuffer > 0 || itemSize > 0); 194 result = (*caller)->Enqueue(caller, NULL, &packets[curPacket], 195 sizeof(MPEG2TS_Packet) * packetsThisBuffer, items, itemSize); 196 assert(XA_RESULT_SUCCESS == result); 197 curPacket += packetsThisBuffer; 198 } 199 return XA_RESULT_SUCCESS; 200} 201 202// main program 203int main(int argc, char **argv) 204{ 205 const char *prog = argv[0]; 206 int i; 207 208 XAboolean abq = XA_BOOLEAN_FALSE; // use AndroidBufferQueue, default is URI 209 XAboolean looping = XA_BOOLEAN_FALSE; 210#ifdef REINITIALIZE 211 int reinit_counter = 0; 212#endif 213 for (i = 1; i < argc; ++i) { 214 const char *arg = argv[i]; 215 if (arg[0] != '-') 216 break; 217 switch (arg[1]) { 218 case 'a': 219 abq = XA_BOOLEAN_TRUE; 220 break; 221 case 'd': 222 discPacket = atoi(&arg[2]); 223 break; 224 case 'l': 225 looping = XA_BOOLEAN_TRUE; 226 break; 227#ifdef REINITIALIZE 228 case 'r': 229 reinit_counter = atoi(&arg[2]); 230 break; 231#endif 232 default: 233 fprintf(stderr, "%s: unknown option %s\n", prog, arg); 234 break; 235 } 236 } 237 238 // check that exactly one URI was specified 239 if (argc - i != 1) { 240 fprintf(stderr, "usage: %s [-a] [-d#] [-l] uri\n", prog); 241 return EXIT_FAILURE; 242 } 243 const char *uri = argv[i]; 244 245 // for AndroidBufferQueue, interpret URI as a filename and open 246 int fd = -1; 247 if (abq) { 248 fd = open(uri, O_RDONLY); 249 if (fd < 0) { 250 perror(uri); 251 goto close; 252 } 253 int ok; 254 struct stat statbuf; 255 ok = fstat(fd, &statbuf); 256 if (ok < 0) { 257 perror(uri); 258 goto close; 259 } 260 if (!S_ISREG(statbuf.st_mode)) { 261 fprintf(stderr, "%s: not an ordinary file\n", uri); 262 goto close; 263 } 264 void *ptr; 265 ptr = mmap(NULL, statbuf.st_size, PROT_READ, MAP_FILE | MAP_PRIVATE, fd, (off_t) 0); 266 if (ptr == MAP_FAILED) { 267 perror(uri); 268 goto close; 269 } 270 size_t filelen = statbuf.st_size; 271 if ((filelen % MPEG2TS_PACKET_SIZE) != 0) { 272 fprintf(stderr, "%s: warning file length %zu is not a multiple of %d\n", uri, filelen, 273 MPEG2TS_PACKET_SIZE); 274 } 275 packets = (MPEG2TS_Packet *) ptr; 276 numPackets = filelen / MPEG2TS_PACKET_SIZE; 277 printf("%s has %zu packets\n", uri, numPackets); 278 } 279 280 ANativeWindow *nativeWindow; 281 282#ifdef REINITIALIZE 283reinitialize: ; 284#endif 285 286 XAresult result; 287 XAObjectItf engineObject; 288 289 // create engine 290 result = xaCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL); 291 assert(XA_RESULT_SUCCESS == result); 292 result = (*engineObject)->Realize(engineObject, XA_BOOLEAN_FALSE); 293 assert(XA_RESULT_SUCCESS == result); 294 XAEngineItf engineEngine; 295 result = (*engineObject)->GetInterface(engineObject, XA_IID_ENGINE, &engineEngine); 296 assert(XA_RESULT_SUCCESS == result); 297 298 // create output mix 299 XAObjectItf outputMixObject; 300 result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, NULL, NULL); 301 assert(XA_RESULT_SUCCESS == result); 302 result = (*outputMixObject)->Realize(outputMixObject, XA_BOOLEAN_FALSE); 303 assert(XA_RESULT_SUCCESS == result); 304 305 // configure media source 306 XADataLocator_URI locUri; 307 locUri.locatorType = XA_DATALOCATOR_URI; 308 locUri.URI = (XAchar *) uri; 309 XADataFormat_MIME fmtMime; 310 fmtMime.formatType = XA_DATAFORMAT_MIME; 311 if (abq) { 312 fmtMime.mimeType = (XAchar *) XA_ANDROID_MIME_MP2TS; 313 fmtMime.containerType = XA_CONTAINERTYPE_MPEG_TS; 314 } else { 315 fmtMime.mimeType = NULL; 316 fmtMime.containerType = XA_CONTAINERTYPE_UNSPECIFIED; 317 } 318 XADataLocator_AndroidBufferQueue locABQ; 319 locABQ.locatorType = XA_DATALOCATOR_ANDROIDBUFFERQUEUE; 320 locABQ.numBuffers = NB_BUFFERS; 321 XADataSource dataSrc; 322 if (abq) { 323 dataSrc.pLocator = &locABQ; 324 } else { 325 dataSrc.pLocator = &locUri; 326 } 327 dataSrc.pFormat = &fmtMime; 328 329 // configure audio sink 330 XADataLocator_OutputMix locOM; 331 locOM.locatorType = XA_DATALOCATOR_OUTPUTMIX; 332 locOM.outputMix = outputMixObject; 333 XADataSink audioSnk; 334 audioSnk.pLocator = &locOM; 335 audioSnk.pFormat = NULL; 336 337 // configure video sink 338 nativeWindow = getNativeWindow(); 339 XADataLocator_NativeDisplay locND; 340 locND.locatorType = XA_DATALOCATOR_NATIVEDISPLAY; 341 locND.hWindow = nativeWindow; 342 locND.hDisplay = NULL; 343 XADataSink imageVideoSink; 344 imageVideoSink.pLocator = &locND; 345 imageVideoSink.pFormat = NULL; 346 347 // create media player 348 XAObjectItf playerObject; 349 XAInterfaceID ids[4] = {XA_IID_STREAMINFORMATION, XA_IID_PREFETCHSTATUS, XA_IID_SEEK, 350 XA_IID_ANDROIDBUFFERQUEUESOURCE}; 351 XAboolean req[4] = {XA_BOOLEAN_TRUE, XA_BOOLEAN_TRUE, XA_BOOLEAN_TRUE, XA_BOOLEAN_TRUE}; 352 result = (*engineEngine)->CreateMediaPlayer(engineEngine, &playerObject, &dataSrc, NULL, 353 &audioSnk, nativeWindow != NULL ? &imageVideoSink : NULL, NULL, NULL, abq ? 4 : 3, ids, 354 req); 355 assert(XA_RESULT_SUCCESS == result); 356 357 // realize the player 358 result = (*playerObject)->Realize(playerObject, XA_BOOLEAN_FALSE); 359 assert(XA_RESULT_SUCCESS == result); 360 361 if (abq) { 362 363 // get the Android buffer queue interface 364 XAAndroidBufferQueueItf playerAndroidBufferQueue; 365 result = (*playerObject)->GetInterface(playerObject, XA_IID_ANDROIDBUFFERQUEUESOURCE, 366 &playerAndroidBufferQueue); 367 assert(XA_RESULT_SUCCESS == result); 368 369 // register the buffer queue callback 370 result = (*playerAndroidBufferQueue)->RegisterCallback(playerAndroidBufferQueue, 371 bufferQueueCallback, NULL); 372 assert(XA_RESULT_SUCCESS == result); 373 result = (*playerAndroidBufferQueue)->SetCallbackEventsMask(playerAndroidBufferQueue, 374 XA_ANDROIDBUFFERQUEUEEVENT_PROCESSED); 375 assert(XA_RESULT_SUCCESS == result); 376 377 // enqueue the initial buffers until buffer queue is full 378 XAuint32 packetsThisBuffer; 379 for (curPacket = 0; curPacket < numPackets; curPacket += packetsThisBuffer) { 380 // handle the unlikely case of a very short .ts 381 packetsThisBuffer = numPackets - curPacket; 382 if (packetsThisBuffer > PACKETS_PER_BUFFER) { 383 packetsThisBuffer = PACKETS_PER_BUFFER; 384 } 385 result = (*playerAndroidBufferQueue)->Enqueue(playerAndroidBufferQueue, NULL, 386 &packets[curPacket], MPEG2TS_PACKET_SIZE * packetsThisBuffer, NULL, 0); 387 if (XA_RESULT_BUFFER_INSUFFICIENT == result) { 388 printf("Enqueued initial %u packets in %u buffers\n", curPacket, curPacket / PACKETS_PER_BUFFER); 389 break; 390 } 391 assert(XA_RESULT_SUCCESS == result); 392 } 393 394 } 395 396 // get the stream information interface 397 XAStreamInformationItf playerStreamInformation; 398 result = (*playerObject)->GetInterface(playerObject, XA_IID_STREAMINFORMATION, 399 &playerStreamInformation); 400 assert(XA_RESULT_SUCCESS == result); 401 402 // register the stream event change callback 403 result = (*playerStreamInformation)->RegisterStreamChangeCallback(playerStreamInformation, 404 streamEventChangeCallback, NULL); 405 assert(XA_RESULT_SUCCESS == result); 406 407 // get the prefetch status interface 408 XAPrefetchStatusItf playerPrefetchStatus; 409 result = (*playerObject)->GetInterface(playerObject, XA_IID_PREFETCHSTATUS, 410 &playerPrefetchStatus); 411 assert(XA_RESULT_SUCCESS == result); 412 413 // register prefetch status callback 414 result = (*playerPrefetchStatus)->RegisterCallback(playerPrefetchStatus, prefetchStatusCallback, 415 NULL); 416 assert(XA_RESULT_SUCCESS == result); 417 result = (*playerPrefetchStatus)->SetCallbackEventsMask(playerPrefetchStatus, 418 XA_PREFETCHEVENT_FILLLEVELCHANGE | XA_PREFETCHEVENT_STATUSCHANGE); 419 assert(XA_RESULT_SUCCESS == result); 420 421 // get the seek interface 422 if (looping) { 423 XASeekItf playerSeek; 424 result = (*playerObject)->GetInterface(playerObject, XA_IID_SEEK, &playerSeek); 425 assert(XA_RESULT_SUCCESS == result); 426 result = (*playerSeek)->SetLoop(playerSeek, XA_BOOLEAN_TRUE, (XAmillisecond) 0, 427 XA_TIME_UNKNOWN); 428 assert(XA_RESULT_SUCCESS == result); 429 } 430 431 // get the play interface 432 XAPlayItf playerPlay; 433 result = (*playerObject)->GetInterface(playerObject, XA_IID_PLAY, &playerPlay); 434 assert(XA_RESULT_SUCCESS == result); 435 436 // register play event callback 437 result = (*playerPlay)->RegisterCallback(playerPlay, playEventCallback, NULL); 438 assert(XA_RESULT_SUCCESS == result); 439#if 0 // FIXME broken 440 result = (*playerPlay)->SetCallbackEventsMask(playerPlay, 441 XA_PLAYEVENT_HEADATEND | XA_PLAYEVENT_HEADATMARKER | XA_PLAYEVENT_HEADATNEWPOS); 442 assert(XA_RESULT_SUCCESS == result); 443#endif 444 445 // set a marker 446 result = (*playerPlay)->SetMarkerPosition(playerPlay, 10000); 447 assert(XA_RESULT_SUCCESS == result); 448 449 // set position update period 450 result = (*playerPlay)->SetPositionUpdatePeriod(playerPlay, 1000); 451 assert(XA_RESULT_SUCCESS == result); 452 453 // get the duration 454 XAmillisecond duration; 455 result = (*playerPlay)->GetDuration(playerPlay, &duration); 456 assert(XA_RESULT_SUCCESS == result); 457 if (XA_TIME_UNKNOWN == duration) 458 printf("Duration: unknown\n"); 459 else 460 printf("Duration: %.1f\n", duration / 1000.0f); 461 462 // set the player's state to paused, to start prefetching 463 printf("start prefetch\n"); 464 result = (*playerPlay)->SetPlayState(playerPlay, XA_PLAYSTATE_PAUSED); 465 assert(XA_RESULT_SUCCESS == result); 466 467 // wait for prefetch status callback to indicate either sufficient data or error 468 pthread_mutex_lock(&mutex); 469 while (prefetch_status == PREFETCHSTATUS_UNKNOWN) { 470 pthread_cond_wait(&cond, &mutex); 471 } 472 pthread_mutex_unlock(&mutex); 473 if (prefetch_status == PREFETCHSTATUS_ERROR) { 474 fprintf(stderr, "Error during prefetch, exiting\n"); 475 goto destroyRes; 476 } 477 478 // get duration again, now it should be known 479 result = (*playerPlay)->GetDuration(playerPlay, &duration); 480 assert(XA_RESULT_SUCCESS == result); 481 if (duration == XA_TIME_UNKNOWN) { 482 fprintf(stdout, "Content duration is unknown (after prefetch completed)\n"); 483 } else { 484 fprintf(stdout, "Content duration is %u ms (after prefetch completed)\n", duration); 485 } 486 487 // start playing 488 printf("starting to play\n"); 489 result = (*playerPlay)->SetPlayState(playerPlay, XA_PLAYSTATE_PLAYING); 490 assert(XA_RESULT_SUCCESS == result); 491 492 // continue playing until end of media 493 for (;;) { 494 XAuint32 status; 495 result = (*playerPlay)->GetPlayState(playerPlay, &status); 496 assert(XA_RESULT_SUCCESS == result); 497 if (status == XA_PLAYSTATE_PAUSED) 498 break; 499 assert(status == XA_PLAYSTATE_PLAYING); 500 sleep(1); 501 } 502 503 // wait a bit more in case of additional callbacks 504 printf("end of media\n"); 505 sleep(3); 506 507destroyRes: 508 509 // destroy the player 510 (*playerObject)->Destroy(playerObject); 511 512 // destroy the output mix 513 (*outputMixObject)->Destroy(outputMixObject); 514 515 // destroy the engine 516 (*engineObject)->Destroy(engineObject); 517 518#ifdef REINITIALIZE 519 if (--reinit_count > 0) { 520 prefetch_status = PREFETCHSTATUS_UNKNOWN; 521 goto reinitialize; 522 } 523#endif 524 525#if 0 526 if (nativeWindow != NULL) { 527 ANativeWindow_release(nativeWindow); 528 } 529#endif 530 531close: 532 if (fd >= 0) { 533 (void) close(fd); 534 } 535 536 return EXIT_SUCCESS; 537} 538