• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 <string.h>
24 #include <fcntl.h>
25 #include <sys/mman.h>
26 #include <sys/stat.h>
27 #include <unistd.h>
28 #include <OMXAL/OpenMAXAL.h>
29 #include <OMXAL/OpenMAXAL_Android.h>
30 #include "nativewindow.h"
31 
32 #define MPEG2TS_PACKET_SIZE 188  // MPEG-2 transport stream packet size in bytes
33 #define PACKETS_PER_BUFFER 20    // Number of MPEG-2 transport stream packets per buffer
34 
35 #define NB_BUFFERS 2    // Number of buffers in Android buffer queue
36 
37 // MPEG-2 transport stream packet
38 typedef struct {
39     char data[MPEG2TS_PACKET_SIZE];
40 } MPEG2TS_Packet;
41 
42 // Globals shared between main thread and buffer queue callback
43 MPEG2TS_Packet *packets;
44 size_t totalPackets;    // total number of packets in input file
45 size_t numPackets;      // number of packets to play, defaults to totalPackets - firstPacket
46 size_t curPacket;       // current packet index
47 size_t discPacket;      // discontinuity packet index, defaults to no discontinuity requested
48 size_t afterDiscPacket; // packet index to switch to after the discontinuity
49 size_t firstPacket;     // first packet index to be played, defaults to zero
50 size_t lastPacket;      // last packet index to be played
51 size_t formatPacket;    // format change packet index, defaults to no format change requested
52 XAmillisecond seekPos = XA_TIME_UNKNOWN;    // seek to this position initially
53 int pauseMs = -1;       // pause after this many ms into playback
54 XAboolean forceCallbackFailure = XA_BOOLEAN_FALSE;  // force callback failures occasionally
55 XAboolean sentEOS = XA_BOOLEAN_FALSE;   // whether we have enqueued EOS yet
56 
57 // These are extensions to OpenMAX AL 1.0.1 values
58 
59 #define PREFETCHSTATUS_UNKNOWN ((XAuint32) 0)
60 #define PREFETCHSTATUS_ERROR   ((XAuint32) (-1))
61 
62 // Mutex and condition shared with main program to protect prefetch_status
63 
64 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
65 static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
66 XAuint32 prefetch_status = PREFETCHSTATUS_UNKNOWN;
67 
68 /* used to detect errors likely to have occured when the OpenMAX AL framework fails to open
69  * a resource, for instance because a file URI is invalid, or an HTTP server doesn't respond.
70  */
71 #define PREFETCHEVENT_ERROR_CANDIDATE \
72         (XA_PREFETCHEVENT_STATUSCHANGE | XA_PREFETCHEVENT_FILLLEVELCHANGE)
73 
74 // stream event change callback
streamEventChangeCallback(XAStreamInformationItf caller __unused,XAuint32 eventId,XAuint32 streamIndex,void * pEventData,void * pContext)75 void streamEventChangeCallback(XAStreamInformationItf caller __unused, XAuint32 eventId,
76         XAuint32 streamIndex, void *pEventData, void *pContext)
77 {
78     // context parameter is specified as NULL and is unused here
79     assert(NULL == pContext);
80     switch (eventId) {
81     case XA_STREAMCBEVENT_PROPERTYCHANGE:
82         printf("XA_STREAMCBEVENT_PROPERTYCHANGE on stream index %u, pEventData %p\n", streamIndex,
83                 pEventData);
84         break;
85     default:
86         printf("Unknown stream event ID %u\n", eventId);
87         break;
88     }
89 }
90 
91 // prefetch status callback
prefetchStatusCallback(XAPrefetchStatusItf caller,void * pContext,XAuint32 event)92 void prefetchStatusCallback(XAPrefetchStatusItf caller,  void *pContext, XAuint32 event)
93 {
94     // pContext is unused here, so we pass NULL
95     assert(pContext == NULL);
96     XApermille level = 0;
97     XAresult result;
98     result = (*caller)->GetFillLevel(caller, &level);
99     assert(XA_RESULT_SUCCESS == result);
100     XAuint32 status;
101     result = (*caller)->GetPrefetchStatus(caller, &status);
102     assert(XA_RESULT_SUCCESS == result);
103     if (event & XA_PREFETCHEVENT_FILLLEVELCHANGE) {
104         printf("PrefetchEventCallback: Buffer fill level is = %d\n", level);
105     }
106     if (event & XA_PREFETCHEVENT_STATUSCHANGE) {
107         printf("PrefetchEventCallback: Prefetch Status is = %u\n", status);
108     }
109     XAuint32 new_prefetch_status;
110     if ((PREFETCHEVENT_ERROR_CANDIDATE == (event & PREFETCHEVENT_ERROR_CANDIDATE))
111             && (level == 0) && (status == XA_PREFETCHSTATUS_UNDERFLOW)) {
112         printf("PrefetchEventCallback: Error while prefetching data, exiting\n");
113         new_prefetch_status = PREFETCHSTATUS_ERROR;
114     } else if (event == XA_PREFETCHEVENT_STATUSCHANGE) {
115         new_prefetch_status = status;
116     } else {
117         return;
118     }
119     int ok;
120     ok = pthread_mutex_lock(&mutex);
121     assert(ok == 0);
122     prefetch_status = new_prefetch_status;
123     ok = pthread_cond_signal(&cond);
124     assert(ok == 0);
125     ok = pthread_mutex_unlock(&mutex);
126     assert(ok == 0);
127 }
128 
129 // playback event callback
playEventCallback(XAPlayItf caller,void * pContext,XAuint32 event)130 void playEventCallback(XAPlayItf caller, void *pContext, XAuint32 event)
131 {
132     // pContext is unused here, so we pass NULL
133     assert(NULL == pContext);
134 
135     XAresult result;
136     XAmillisecond position;
137     result = (*caller)->GetPosition(caller, &position);
138     assert(XA_RESULT_SUCCESS == result);
139 
140     if (XA_PLAYEVENT_HEADATEND & event) {
141         printf("XA_PLAYEVENT_HEADATEND current position=%u ms\n", position);
142     }
143 
144     if (XA_PLAYEVENT_HEADATNEWPOS & event) {
145         printf("XA_PLAYEVENT_HEADATNEWPOS current position=%u ms\n", position);
146     }
147 
148     if (XA_PLAYEVENT_HEADATMARKER & event) {
149         printf("XA_PLAYEVENT_HEADATMARKER current position=%u ms\n", position);
150     }
151 }
152 
153 // Android buffer queue callback
bufferQueueCallback(XAAndroidBufferQueueItf caller,void * pCallbackContext,void * pBufferContext __unused,void * pBufferData __unused,XAuint32 dataSize __unused,XAuint32 dataUsed __unused,const XAAndroidBufferItem * pItems __unused,XAuint32 itemsLength __unused)154 XAresult bufferQueueCallback(
155         XAAndroidBufferQueueItf caller,
156         void *pCallbackContext,
157         void *pBufferContext __unused,
158         void *pBufferData __unused,
159         XAuint32 dataSize __unused,
160         XAuint32 dataUsed __unused,
161         const XAAndroidBufferItem *pItems __unused,
162         XAuint32 itemsLength __unused)
163 {
164     XAPlayItf playerPlay = (XAPlayItf) pCallbackContext;
165     // enqueue the .ts data directly from mapped memory, so ignore the empty buffer pBufferData
166     if (curPacket <= lastPacket) {
167         static const XAAndroidBufferItem discontinuity = {XA_ANDROID_ITEMKEY_DISCONTINUITY, 0, {}};
168         static const XAAndroidBufferItem eos = {XA_ANDROID_ITEMKEY_EOS, 0, {}};
169         static const XAAndroidBufferItem formatChange = {XA_ANDROID_ITEMKEY_FORMAT_CHANGE, 0, {}};
170         const XAAndroidBufferItem *items;
171         XAuint32 itemSize;
172         // compute number of packets to be enqueued in this buffer
173         XAuint32 packetsThisBuffer = lastPacket - curPacket;
174         if (packetsThisBuffer > PACKETS_PER_BUFFER) {
175             packetsThisBuffer = PACKETS_PER_BUFFER;
176         }
177         // last packet? this should only happen once
178         if (curPacket == lastPacket) {
179             if (sentEOS) {
180                 printf("buffer completion callback after EOS\n");
181                 return XA_RESULT_SUCCESS;
182             }
183             printf("sending EOS\n");
184             items = &eos;
185             itemSize = sizeof(eos);
186             sentEOS = XA_BOOLEAN_TRUE;
187         // discontinuity requested?
188         } else if (curPacket == discPacket) {
189             printf("sending discontinuity at packet %zu, then resuming at packet %zu\n", discPacket,
190                     afterDiscPacket);
191             items = &discontinuity;
192             itemSize = sizeof(discontinuity);
193             curPacket = afterDiscPacket;
194         // format change requested?
195         } else if (curPacket == formatPacket) {
196             printf("sending format change");
197             items = &formatChange;
198             itemSize = sizeof(formatChange);
199         // pure data with no items
200         } else {
201             items = NULL;
202             itemSize = 0;
203         }
204         XAresult result;
205         // enqueue the optional data and optional items; there is always at least one or the other
206         assert(packetsThisBuffer > 0 || itemSize > 0);
207         result = (*caller)->Enqueue(caller, NULL, &packets[curPacket],
208                 sizeof(MPEG2TS_Packet) * packetsThisBuffer, items, itemSize);
209         assert(XA_RESULT_SUCCESS == result);
210         curPacket += packetsThisBuffer;
211         // display position periodically
212         if (curPacket % 1000 == 0) {
213             XAmillisecond position;
214             result = (*playerPlay)->GetPosition(playerPlay, &position);
215             assert(XA_RESULT_SUCCESS == result);
216             printf("Position after enqueueing packet %zu: %u ms\n", curPacket, position);
217         }
218     }
219     if (forceCallbackFailure && (curPacket % 1230 == 0)) {
220         return (XAresult) curPacket;
221     } else {
222         return XA_RESULT_SUCCESS;
223     }
224 }
225 
226 // convert a domain type to string
domainToString(XAuint32 domain)227 static const char *domainToString(XAuint32 domain)
228 {
229     switch (domain) {
230     case 0: // FIXME There's a private declaration '#define XA_DOMAINTYPE_CONTAINER 0' in src/data.h
231             // but we don't have access to it. Plan to file a bug with Khronos about this symbol.
232         return "media container";
233 #define _(x) case x: return #x;
234     _(XA_DOMAINTYPE_AUDIO)
235     _(XA_DOMAINTYPE_VIDEO)
236     _(XA_DOMAINTYPE_IMAGE)
237     _(XA_DOMAINTYPE_TIMEDTEXT)
238     _(XA_DOMAINTYPE_MIDI)
239     _(XA_DOMAINTYPE_VENDOR)
240     _(XA_DOMAINTYPE_UNKNOWN)
241 #undef _
242     default:
243         return "unknown";
244     }
245 }
246 
247 // main program
main(int argc,char ** argv)248 int main(int argc, char **argv)
249 {
250     const char *prog = argv[0];
251     int i;
252 
253     XAboolean abq = XA_BOOLEAN_FALSE;   // use AndroidBufferQueue, default is URI
254     XAboolean looping = XA_BOOLEAN_FALSE;
255     for (i = 1; i < argc; ++i) {
256         const char *arg = argv[i];
257         if (arg[0] != '-')
258             break;
259         switch (arg[1]) {
260         case 'a':
261             abq = XA_BOOLEAN_TRUE;
262             break;
263         case 'c':
264             forceCallbackFailure = XA_BOOLEAN_TRUE;
265             break;
266         case 'd':
267             discPacket = atoi(&arg[2]);
268             break;
269         case 'D':
270             afterDiscPacket = atoi(&arg[2]);
271             break;
272         case 'f':
273             firstPacket = atoi(&arg[2]);
274             break;
275         case 'F':
276             formatPacket = atoi(&arg[2]);
277             break;
278         case 'l':
279             looping = XA_BOOLEAN_TRUE;
280             break;
281         case 'n':
282             numPackets = atoi(&arg[2]);
283             break;
284         case 'p':
285             pauseMs = atoi(&arg[2]);
286             break;
287         case 's':
288             seekPos = atoi(&arg[2]);
289             break;
290         default:
291             fprintf(stderr, "%s: unknown option %s\n", prog, arg);
292             break;
293         }
294     }
295 
296     // check that exactly one URI was specified
297     if (argc - i != 1) {
298         fprintf(stderr, "usage: %s [-a] [-c] [-d#] [-D#] [-f#] [-F#] [-l] [-n#] [-p#] [-s#] uri\n",
299                 prog);
300         fprintf(stderr, "    -a  Use Android buffer queue to supply data, default is URI\n");
301         fprintf(stderr, "    -c  Force callback to return an error randomly, for debugging only\n");
302         fprintf(stderr, "    -d# Packet index to insert a discontinuity, default is none\n");
303         fprintf(stderr, "    -D# Packet index to switch to after the discontinuity\n");
304         fprintf(stderr, "    -f# First packet index, defaults to 0\n");
305         fprintf(stderr, "    -F# Packet index to insert a format change, default is none\n");
306         fprintf(stderr, "    -l  Enable looping, for URI only\n");
307         fprintf(stderr, "    -n# Number of packets to enqueue\n");
308         fprintf(stderr, "    -p# Pause playback for 5 seconds after this many milliseconds\n");
309         fprintf(stderr, "    -s# Seek position in milliseconds, for URI only\n");
310         return EXIT_FAILURE;
311     }
312     const char *uri = argv[i];
313 
314     // for AndroidBufferQueue, interpret URI as a filename and open
315     int fd = -1;
316     if (abq) {
317         fd = open(uri, O_RDONLY);
318         if (fd < 0) {
319             perror(uri);
320             goto close;
321         }
322         int ok;
323         struct stat statbuf;
324         ok = fstat(fd, &statbuf);
325         if (ok < 0) {
326             perror(uri);
327             goto close;
328         }
329         if (!S_ISREG(statbuf.st_mode)) {
330             fprintf(stderr, "%s: not an ordinary file\n", uri);
331             goto close;
332         }
333         void *ptr;
334         ptr = mmap(NULL, statbuf.st_size, PROT_READ, MAP_FILE | MAP_PRIVATE, fd, (off_t) 0);
335         if (ptr == MAP_FAILED) {
336             perror(uri);
337             goto close;
338         }
339         size_t filelen = statbuf.st_size;
340         if ((filelen % MPEG2TS_PACKET_SIZE) != 0) {
341             fprintf(stderr, "%s: warning file length %zu is not a multiple of %d\n", uri, filelen,
342                     MPEG2TS_PACKET_SIZE);
343         }
344         packets = (MPEG2TS_Packet *) ptr;
345         totalPackets = filelen / MPEG2TS_PACKET_SIZE;
346         printf("%s has %zu total packets\n", uri, totalPackets);
347         if (firstPacket >= totalPackets) {
348             fprintf(stderr, "-f%zu ignored\n", firstPacket);
349             firstPacket = 0;
350         }
351         if (numPackets == 0) {
352             numPackets = totalPackets - firstPacket;
353         } else if (firstPacket + numPackets > totalPackets) {
354             fprintf(stderr, "-n%zu ignored\n", numPackets);
355             numPackets = totalPackets - firstPacket;
356         }
357         lastPacket = firstPacket + numPackets;
358         if (discPacket != 0 && (discPacket < firstPacket || discPacket >= lastPacket)) {
359             fprintf(stderr, "-d%zu ignored\n", discPacket);
360             discPacket = 0;
361         }
362         if (afterDiscPacket < firstPacket || afterDiscPacket >= lastPacket) {
363             fprintf(stderr, "-D%zu ignored\n", afterDiscPacket);
364             afterDiscPacket = 0;
365         }
366         if (formatPacket != 0 && (formatPacket < firstPacket || formatPacket >= lastPacket)) {
367             fprintf(stderr, "-F%zu ignored\n", formatPacket);
368             formatPacket = 0;
369         }
370     }
371 
372     ANativeWindow *nativeWindow;
373 
374     XAresult result;
375     XAObjectItf engineObject;
376 
377     // create engine
378     result = xaCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
379     assert(XA_RESULT_SUCCESS == result);
380     result = (*engineObject)->Realize(engineObject, XA_BOOLEAN_FALSE);
381     assert(XA_RESULT_SUCCESS == result);
382     XAEngineItf engineEngine;
383     result = (*engineObject)->GetInterface(engineObject, XA_IID_ENGINE, &engineEngine);
384     assert(XA_RESULT_SUCCESS == result);
385 
386     // create output mix
387     XAObjectItf outputMixObject;
388     result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, NULL, NULL);
389     assert(XA_RESULT_SUCCESS == result);
390     result = (*outputMixObject)->Realize(outputMixObject, XA_BOOLEAN_FALSE);
391     assert(XA_RESULT_SUCCESS == result);
392 
393     // configure media source
394     XADataLocator_URI locUri;
395     locUri.locatorType = XA_DATALOCATOR_URI;
396     locUri.URI = (XAchar *) uri;
397     XADataFormat_MIME fmtMime;
398     fmtMime.formatType = XA_DATAFORMAT_MIME;
399     if (abq) {
400         fmtMime.mimeType = (XAchar *) XA_ANDROID_MIME_MP2TS;
401         fmtMime.containerType = XA_CONTAINERTYPE_MPEG_TS;
402     } else {
403         fmtMime.mimeType = NULL;
404         fmtMime.containerType = XA_CONTAINERTYPE_UNSPECIFIED;
405     }
406     XADataLocator_AndroidBufferQueue locABQ;
407     locABQ.locatorType = XA_DATALOCATOR_ANDROIDBUFFERQUEUE;
408     locABQ.numBuffers = NB_BUFFERS;
409     XADataSource dataSrc;
410     if (abq) {
411         dataSrc.pLocator = &locABQ;
412     } else {
413         dataSrc.pLocator = &locUri;
414     }
415     dataSrc.pFormat = &fmtMime;
416 
417     // configure audio sink
418     XADataLocator_OutputMix locOM;
419     locOM.locatorType = XA_DATALOCATOR_OUTPUTMIX;
420     locOM.outputMix = outputMixObject;
421     XADataSink audioSnk;
422     audioSnk.pLocator = &locOM;
423     audioSnk.pFormat = NULL;
424 
425     // configure video sink
426     nativeWindow = getNativeWindow();
427     XADataLocator_NativeDisplay locND;
428     locND.locatorType = XA_DATALOCATOR_NATIVEDISPLAY;
429     locND.hWindow = nativeWindow;
430     locND.hDisplay = NULL;
431     XADataSink imageVideoSink;
432     imageVideoSink.pLocator = &locND;
433     imageVideoSink.pFormat = NULL;
434 
435     // create media player
436     XAObjectItf playerObject;
437     XAInterfaceID ids[4] = {XA_IID_STREAMINFORMATION, XA_IID_PREFETCHSTATUS, XA_IID_SEEK,
438             XA_IID_ANDROIDBUFFERQUEUESOURCE};
439     XAboolean req[4] = {XA_BOOLEAN_TRUE, XA_BOOLEAN_TRUE, XA_BOOLEAN_FALSE, XA_BOOLEAN_TRUE};
440     result = (*engineEngine)->CreateMediaPlayer(engineEngine, &playerObject, &dataSrc, NULL,
441             &audioSnk, nativeWindow != NULL ? &imageVideoSink : NULL, NULL, NULL, abq ? 4 : 3, ids,
442             req);
443     assert(XA_RESULT_SUCCESS == result);
444 
445     // realize the player
446     result = (*playerObject)->Realize(playerObject, XA_BOOLEAN_FALSE);
447     assert(XA_RESULT_SUCCESS == result);
448 
449     // get the play interface
450     XAPlayItf playerPlay;
451     result = (*playerObject)->GetInterface(playerObject, XA_IID_PLAY, &playerPlay);
452     assert(XA_RESULT_SUCCESS == result);
453 
454     if (abq) {
455 
456         // get the Android buffer queue interface
457         XAAndroidBufferQueueItf playerAndroidBufferQueue;
458         result = (*playerObject)->GetInterface(playerObject, XA_IID_ANDROIDBUFFERQUEUESOURCE,
459                 &playerAndroidBufferQueue);
460         assert(XA_RESULT_SUCCESS == result);
461 
462         // register the buffer queue callback
463         result = (*playerAndroidBufferQueue)->RegisterCallback(playerAndroidBufferQueue,
464                 bufferQueueCallback, (void *) playerPlay);
465         assert(XA_RESULT_SUCCESS == result);
466         result = (*playerAndroidBufferQueue)->SetCallbackEventsMask(playerAndroidBufferQueue,
467                 XA_ANDROIDBUFFERQUEUEEVENT_PROCESSED);
468         assert(XA_RESULT_SUCCESS == result);
469 
470         // set the player's state to paused, to start prefetching
471         printf("start early prefetch\n");
472         result = (*playerPlay)->SetPlayState(playerPlay, XA_PLAYSTATE_PAUSED);
473         assert(XA_RESULT_SUCCESS == result);
474 
475         // enqueue the initial buffers until buffer queue is full
476         XAuint32 packetsThisBuffer;
477         for (curPacket = firstPacket; curPacket < lastPacket; curPacket += packetsThisBuffer) {
478             // handle the unlikely case of a very short .ts
479             packetsThisBuffer = lastPacket - curPacket;
480             if (packetsThisBuffer > PACKETS_PER_BUFFER) {
481                 packetsThisBuffer = PACKETS_PER_BUFFER;
482             }
483             result = (*playerAndroidBufferQueue)->Enqueue(playerAndroidBufferQueue, NULL,
484                     &packets[curPacket], MPEG2TS_PACKET_SIZE * packetsThisBuffer, NULL, 0);
485             if (XA_RESULT_BUFFER_INSUFFICIENT == result) {
486                 printf("Enqueued initial %zu packets in %zu buffers\n", curPacket - firstPacket,
487                         (curPacket - firstPacket + PACKETS_PER_BUFFER - 1) / PACKETS_PER_BUFFER);
488                 break;
489             }
490             assert(XA_RESULT_SUCCESS == result);
491         }
492 
493     }
494 
495     // get the stream information interface
496     XAStreamInformationItf playerStreamInformation;
497     result = (*playerObject)->GetInterface(playerObject, XA_IID_STREAMINFORMATION,
498             &playerStreamInformation);
499     assert(XA_RESULT_SUCCESS == result);
500 
501     // register the stream event change callback
502     result = (*playerStreamInformation)->RegisterStreamChangeCallback(playerStreamInformation,
503             streamEventChangeCallback, NULL);
504     assert(XA_RESULT_SUCCESS == result);
505 
506     // get the prefetch status interface
507     XAPrefetchStatusItf playerPrefetchStatus;
508     result = (*playerObject)->GetInterface(playerObject, XA_IID_PREFETCHSTATUS,
509             &playerPrefetchStatus);
510     assert(XA_RESULT_SUCCESS == result);
511 
512     // register prefetch status callback
513     result = (*playerPrefetchStatus)->RegisterCallback(playerPrefetchStatus, prefetchStatusCallback,
514             NULL);
515     assert(XA_RESULT_SUCCESS == result);
516     result = (*playerPrefetchStatus)->SetCallbackEventsMask(playerPrefetchStatus,
517             XA_PREFETCHEVENT_FILLLEVELCHANGE | XA_PREFETCHEVENT_STATUSCHANGE);
518     assert(XA_RESULT_SUCCESS == result);
519 
520     // get the seek interface for seeking and/or looping
521     if (looping || seekPos != XA_TIME_UNKNOWN) {
522         XASeekItf playerSeek;
523         result = (*playerObject)->GetInterface(playerObject, XA_IID_SEEK, &playerSeek);
524         assert(XA_RESULT_SUCCESS == result);
525         if (seekPos != XA_TIME_UNKNOWN) {
526             result = (*playerSeek)->SetPosition(playerSeek, seekPos, XA_SEEKMODE_ACCURATE);
527             if (XA_RESULT_FEATURE_UNSUPPORTED == result) {
528                 fprintf(stderr, "-s%u (seek to initial position) is unsupported\n", seekPos);
529             } else {
530                 assert(XA_RESULT_SUCCESS == result);
531             }
532         }
533         if (looping) {
534             result = (*playerSeek)->SetLoop(playerSeek, XA_BOOLEAN_TRUE, (XAmillisecond) 0,
535                     XA_TIME_UNKNOWN);
536             if (XA_RESULT_FEATURE_UNSUPPORTED) {
537                 fprintf(stderr, "-l (looping) is unsupported\n");
538             } else {
539                 assert(XA_RESULT_SUCCESS == result);
540             }
541         }
542     }
543 
544     // register play event callback
545     result = (*playerPlay)->RegisterCallback(playerPlay, playEventCallback, NULL);
546     assert(XA_RESULT_SUCCESS == result);
547     result = (*playerPlay)->SetCallbackEventsMask(playerPlay,
548             XA_PLAYEVENT_HEADATEND | XA_PLAYEVENT_HEADATMARKER | XA_PLAYEVENT_HEADATNEWPOS);
549     assert(XA_RESULT_SUCCESS == result);
550 
551     // set a marker
552     result = (*playerPlay)->SetMarkerPosition(playerPlay, 5000);
553     assert(XA_RESULT_SUCCESS == result);
554 
555     // set position update period
556     result = (*playerPlay)->SetPositionUpdatePeriod(playerPlay, 2000);
557     assert(XA_RESULT_SUCCESS == result);
558 
559     // get the position before prefetch
560     XAmillisecond position;
561     result = (*playerPlay)->GetPosition(playerPlay, &position);
562     assert(XA_RESULT_SUCCESS == result);
563     printf("Position before prefetch: %u ms\n", position);
564 
565     // get the duration before prefetch
566     XAmillisecond duration;
567     result = (*playerPlay)->GetDuration(playerPlay, &duration);
568     assert(XA_RESULT_SUCCESS == result);
569     if (XA_TIME_UNKNOWN == duration)
570         printf("Duration before prefetch: unknown as expected\n");
571     else
572         printf("Duration before prefetch: %.1f (surprise!)\n", duration / 1000.0f);
573 
574     // set the player's state to paused, to start prefetching
575     printf("start prefetch\n");
576     result = (*playerPlay)->SetPlayState(playerPlay, XA_PLAYSTATE_PAUSED);
577     assert(XA_RESULT_SUCCESS == result);
578 
579     // wait for prefetch status callback to indicate either sufficient data or error
580     pthread_mutex_lock(&mutex);
581     while (prefetch_status == PREFETCHSTATUS_UNKNOWN) {
582         pthread_cond_wait(&cond, &mutex);
583     }
584     pthread_mutex_unlock(&mutex);
585     if (prefetch_status == PREFETCHSTATUS_ERROR) {
586         fprintf(stderr, "Error during prefetch, exiting\n");
587         goto destroyRes;
588     }
589 
590     // get the position after prefetch
591     result = (*playerPlay)->GetPosition(playerPlay, &position);
592     assert(XA_RESULT_SUCCESS == result);
593     printf("Position after prefetch: %u ms\n", position);
594 
595     // get duration again, now it should be known for the file source or unknown for TS
596     result = (*playerPlay)->GetDuration(playerPlay, &duration);
597     assert(XA_RESULT_SUCCESS == result);
598     if (duration == XA_TIME_UNKNOWN) {
599         printf("Duration after prefetch: unknown (expected for TS, unexpected for file)\n");
600     } else {
601         printf("Duration after prefetch: %u ms (expected for file, unexpected for TS)\n", duration);
602     }
603 
604     // query for media container information
605     result = (*playerStreamInformation)->QueryMediaContainerInformation(playerStreamInformation,
606             NULL);
607     assert(XA_RESULT_PARAMETER_INVALID == result);
608     XAMediaContainerInformation mediaContainerInformation;
609     // this verifies it is filling in all the fields
610     memset(&mediaContainerInformation, 0x55, sizeof(XAMediaContainerInformation));
611     result = (*playerStreamInformation)->QueryMediaContainerInformation(playerStreamInformation,
612             &mediaContainerInformation);
613     assert(XA_RESULT_SUCCESS == result);
614     printf("Media container information:\n");
615     printf("  containerType = %u\n", mediaContainerInformation.containerType);
616     printf("  mediaDuration = %u\n", mediaContainerInformation.mediaDuration);
617     printf("  numStreams = %u\n", mediaContainerInformation.numStreams);
618 
619     // Now query for each the streams.  Note that stream indices go up to and including
620     // mediaContainerInformation.numStreams, because stream 0 is the container itself,
621     // while stream 1 to mediaContainerInformation.numStreams are the contained streams.
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 ((int) 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