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
38typedef struct {
39    char data[MPEG2TS_PACKET_SIZE];
40} MPEG2TS_Packet;
41
42// Globals shared between main thread and buffer queue callback
43MPEG2TS_Packet *packets;
44size_t totalPackets;    // total number of packets in input file
45size_t numPackets;      // number of packets to play, defaults to totalPackets - firstPacket
46size_t curPacket;       // current packet index
47size_t discPacket;      // discontinuity packet index, defaults to no discontinuity requested
48size_t afterDiscPacket; // packet index to switch to after the discontinuity
49size_t firstPacket;     // first packet index to be played, defaults to zero
50size_t lastPacket;      // last packet index to be played
51size_t formatPacket;    // format change packet index, defaults to no format change requested
52XAmillisecond seekPos = XA_TIME_UNKNOWN;    // seek to this position initially
53int pauseMs = -1;       // pause after this many ms into playback
54XAboolean forceCallbackFailure = XA_BOOLEAN_FALSE;  // force callback failures occasionally
55XAboolean 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
64static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
65static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
66XAuint32 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
75void 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
92void 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
130void 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
154XAresult 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
227static 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
248int 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,
637streamIndex, &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,
669streamIndex, &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
840destroyRes:
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
857close:
858    if (fd >= 0) {
859        (void) close(fd);
860    }
861
862    disposeNativeWindow();
863
864    return EXIT_SUCCESS;
865}
866