Home | History | Annotate | Download | only in sandbox
      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