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