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