/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // OpenMAX AL MediaPlayer command-line player #include <assert.h> #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <sys/mman.h> #include <sys/stat.h> #include <unistd.h> #include <OMXAL/OpenMAXAL.h> #include <OMXAL/OpenMAXAL_Android.h> #include "nativewindow.h" #define MPEG2TS_PACKET_SIZE 188 // MPEG-2 transport stream packet size in bytes #define PACKETS_PER_BUFFER 20 // Number of MPEG-2 transport stream packets per buffer #define NB_BUFFERS 2 // Number of buffers in Android buffer queue // MPEG-2 transport stream packet typedef struct { char data[MPEG2TS_PACKET_SIZE]; } MPEG2TS_Packet; // Globals shared between main thread and buffer queue callback MPEG2TS_Packet *packets; size_t totalPackets; // total number of packets in input file size_t numPackets; // number of packets to play, defaults to totalPackets - firstPacket size_t curPacket; // current packet index size_t discPacket; // discontinuity packet index, defaults to no discontinuity requested size_t afterDiscPacket; // packet index to switch to after the discontinuity size_t firstPacket; // first packet index to be played, defaults to zero size_t lastPacket; // last packet index to be played size_t formatPacket; // format change packet index, defaults to no format change requested XAmillisecond seekPos = XA_TIME_UNKNOWN; // seek to this position initially int pauseMs = -1; // pause after this many ms into playback XAboolean forceCallbackFailure = XA_BOOLEAN_FALSE; // force callback failures occasionally XAboolean sentEOS = XA_BOOLEAN_FALSE; // whether we have enqueued EOS yet // These are extensions to OpenMAX AL 1.0.1 values #define PREFETCHSTATUS_UNKNOWN ((XAuint32) 0) #define PREFETCHSTATUS_ERROR ((XAuint32) (-1)) // Mutex and condition shared with main program to protect prefetch_status static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t cond = PTHREAD_COND_INITIALIZER; XAuint32 prefetch_status = PREFETCHSTATUS_UNKNOWN; /* used to detect errors likely to have occured when the OpenMAX AL framework fails to open * a resource, for instance because a file URI is invalid, or an HTTP server doesn't respond. */ #define PREFETCHEVENT_ERROR_CANDIDATE \ (XA_PREFETCHEVENT_STATUSCHANGE | XA_PREFETCHEVENT_FILLLEVELCHANGE) // stream event change callback void streamEventChangeCallback(XAStreamInformationItf caller __unused, XAuint32 eventId, XAuint32 streamIndex, void *pEventData, void *pContext) { // context parameter is specified as NULL and is unused here assert(NULL == pContext); switch (eventId) { case XA_STREAMCBEVENT_PROPERTYCHANGE: printf("XA_STREAMCBEVENT_PROPERTYCHANGE on stream index %u, pEventData %p\n", streamIndex, pEventData); break; default: printf("Unknown stream event ID %u\n", eventId); break; } } // prefetch status callback void prefetchStatusCallback(XAPrefetchStatusItf caller, void *pContext, XAuint32 event) { // pContext is unused here, so we pass NULL assert(pContext == NULL); XApermille level = 0; XAresult result; result = (*caller)->GetFillLevel(caller, &level); assert(XA_RESULT_SUCCESS == result); XAuint32 status; result = (*caller)->GetPrefetchStatus(caller, &status); assert(XA_RESULT_SUCCESS == result); if (event & XA_PREFETCHEVENT_FILLLEVELCHANGE) { printf("PrefetchEventCallback: Buffer fill level is = %d\n", level); } if (event & XA_PREFETCHEVENT_STATUSCHANGE) { printf("PrefetchEventCallback: Prefetch Status is = %u\n", status); } XAuint32 new_prefetch_status; if ((PREFETCHEVENT_ERROR_CANDIDATE == (event & PREFETCHEVENT_ERROR_CANDIDATE)) && (level == 0) && (status == XA_PREFETCHSTATUS_UNDERFLOW)) { printf("PrefetchEventCallback: Error while prefetching data, exiting\n"); new_prefetch_status = PREFETCHSTATUS_ERROR; } else if (event == XA_PREFETCHEVENT_STATUSCHANGE) { new_prefetch_status = status; } else { return; } int ok; ok = pthread_mutex_lock(&mutex); assert(ok == 0); prefetch_status = new_prefetch_status; ok = pthread_cond_signal(&cond); assert(ok == 0); ok = pthread_mutex_unlock(&mutex); assert(ok == 0); } // playback event callback void playEventCallback(XAPlayItf caller, void *pContext, XAuint32 event) { // pContext is unused here, so we pass NULL assert(NULL == pContext); XAresult result; XAmillisecond position; result = (*caller)->GetPosition(caller, &position); assert(XA_RESULT_SUCCESS == result); if (XA_PLAYEVENT_HEADATEND & event) { printf("XA_PLAYEVENT_HEADATEND current position=%u ms\n", position); } if (XA_PLAYEVENT_HEADATNEWPOS & event) { printf("XA_PLAYEVENT_HEADATNEWPOS current position=%u ms\n", position); } if (XA_PLAYEVENT_HEADATMARKER & event) { printf("XA_PLAYEVENT_HEADATMARKER current position=%u ms\n", position); } } // Android buffer queue callback XAresult bufferQueueCallback( XAAndroidBufferQueueItf caller, void *pCallbackContext, void *pBufferContext __unused, void *pBufferData __unused, XAuint32 dataSize __unused, XAuint32 dataUsed __unused, const XAAndroidBufferItem *pItems __unused, XAuint32 itemsLength __unused) { XAPlayItf playerPlay = (XAPlayItf) pCallbackContext; // enqueue the .ts data directly from mapped memory, so ignore the empty buffer pBufferData if (curPacket <= lastPacket) { static const XAAndroidBufferItem discontinuity = {XA_ANDROID_ITEMKEY_DISCONTINUITY, 0}; static const XAAndroidBufferItem eos = {XA_ANDROID_ITEMKEY_EOS, 0}; static const XAAndroidBufferItem formatChange = {XA_ANDROID_ITEMKEY_FORMAT_CHANGE, 0}; const XAAndroidBufferItem *items; XAuint32 itemSize; // compute number of packets to be enqueued in this buffer XAuint32 packetsThisBuffer = lastPacket - curPacket; if (packetsThisBuffer > PACKETS_PER_BUFFER) { packetsThisBuffer = PACKETS_PER_BUFFER; } // last packet? this should only happen once if (curPacket == lastPacket) { if (sentEOS) { printf("buffer completion callback after EOS\n"); return XA_RESULT_SUCCESS; } printf("sending EOS\n"); items = &eos; itemSize = sizeof(eos); sentEOS = XA_BOOLEAN_TRUE; // discontinuity requested? } else if (curPacket == discPacket) { printf("sending discontinuity at packet %zu, then resuming at packet %zu\n", discPacket, afterDiscPacket); items = &discontinuity; itemSize = sizeof(discontinuity); curPacket = afterDiscPacket; // format change requested? } else if (curPacket == formatPacket) { printf("sending format change"); items = &formatChange; itemSize = sizeof(formatChange); // pure data with no items } else { items = NULL; itemSize = 0; } XAresult result; // enqueue the optional data and optional items; there is always at least one or the other assert(packetsThisBuffer > 0 || itemSize > 0); result = (*caller)->Enqueue(caller, NULL, &packets[curPacket], sizeof(MPEG2TS_Packet) * packetsThisBuffer, items, itemSize); assert(XA_RESULT_SUCCESS == result); curPacket += packetsThisBuffer; // display position periodically if (curPacket % 1000 == 0) { XAmillisecond position; result = (*playerPlay)->GetPosition(playerPlay, &position); assert(XA_RESULT_SUCCESS == result); printf("Position after enqueueing packet %zu: %u ms\n", curPacket, position); } } if (forceCallbackFailure && (curPacket % 1230 == 0)) { return (XAresult) curPacket; } else { return XA_RESULT_SUCCESS; } } // convert a domain type to string static const char *domainToString(XAuint32 domain) { switch (domain) { case 0: // FIXME There's a private declaration '#define XA_DOMAINTYPE_CONTAINER 0' in src/data.h // but we don't have access to it. Plan to file a bug with Khronos about this symbol. return "media container"; #define _(x) case x: return #x; _(XA_DOMAINTYPE_AUDIO) _(XA_DOMAINTYPE_VIDEO) _(XA_DOMAINTYPE_IMAGE) _(XA_DOMAINTYPE_TIMEDTEXT) _(XA_DOMAINTYPE_MIDI) _(XA_DOMAINTYPE_VENDOR) _(XA_DOMAINTYPE_UNKNOWN) #undef _ default: return "unknown"; } } // main program int main(int argc, char **argv) { const char *prog = argv[0]; int i; XAboolean abq = XA_BOOLEAN_FALSE; // use AndroidBufferQueue, default is URI XAboolean looping = XA_BOOLEAN_FALSE; for (i = 1; i < argc; ++i) { const char *arg = argv[i]; if (arg[0] != '-') break; switch (arg[1]) { case 'a': abq = XA_BOOLEAN_TRUE; break; case 'c': forceCallbackFailure = XA_BOOLEAN_TRUE; break; case 'd': discPacket = atoi(&arg[2]); break; case 'D': afterDiscPacket = atoi(&arg[2]); break; case 'f': firstPacket = atoi(&arg[2]); break; case 'F': formatPacket = atoi(&arg[2]); break; case 'l': looping = XA_BOOLEAN_TRUE; break; case 'n': numPackets = atoi(&arg[2]); break; case 'p': pauseMs = atoi(&arg[2]); break; case 's': seekPos = atoi(&arg[2]); break; default: fprintf(stderr, "%s: unknown option %s\n", prog, arg); break; } } // check that exactly one URI was specified if (argc - i != 1) { fprintf(stderr, "usage: %s [-a] [-c] [-d#] [-D#] [-f#] [-F#] [-l] [-n#] [-p#] [-s#] uri\n", prog); fprintf(stderr, " -a Use Android buffer queue to supply data, default is URI\n"); fprintf(stderr, " -c Force callback to return an error randomly, for debugging only\n"); fprintf(stderr, " -d# Packet index to insert a discontinuity, default is none\n"); fprintf(stderr, " -D# Packet index to switch to after the discontinuity\n"); fprintf(stderr, " -f# First packet index, defaults to 0\n"); fprintf(stderr, " -F# Packet index to insert a format change, default is none\n"); fprintf(stderr, " -l Enable looping, for URI only\n"); fprintf(stderr, " -n# Number of packets to enqueue\n"); fprintf(stderr, " -p# Pause playback for 5 seconds after this many milliseconds\n"); fprintf(stderr, " -s# Seek position in milliseconds, for URI only\n"); return EXIT_FAILURE; } const char *uri = argv[i]; // for AndroidBufferQueue, interpret URI as a filename and open int fd = -1; if (abq) { fd = open(uri, O_RDONLY); if (fd < 0) { perror(uri); goto close; } int ok; struct stat statbuf; ok = fstat(fd, &statbuf); if (ok < 0) { perror(uri); goto close; } if (!S_ISREG(statbuf.st_mode)) { fprintf(stderr, "%s: not an ordinary file\n", uri); goto close; } void *ptr; ptr = mmap(NULL, statbuf.st_size, PROT_READ, MAP_FILE | MAP_PRIVATE, fd, (off_t) 0); if (ptr == MAP_FAILED) { perror(uri); goto close; } size_t filelen = statbuf.st_size; if ((filelen % MPEG2TS_PACKET_SIZE) != 0) { fprintf(stderr, "%s: warning file length %zu is not a multiple of %d\n", uri, filelen, MPEG2TS_PACKET_SIZE); } packets = (MPEG2TS_Packet *) ptr; totalPackets = filelen / MPEG2TS_PACKET_SIZE; printf("%s has %zu total packets\n", uri, totalPackets); if (firstPacket >= totalPackets) { fprintf(stderr, "-f%zu ignored\n", firstPacket); firstPacket = 0; } if (numPackets == 0) { numPackets = totalPackets - firstPacket; } else if (firstPacket + numPackets > totalPackets) { fprintf(stderr, "-n%zu ignored\n", numPackets); numPackets = totalPackets - firstPacket; } lastPacket = firstPacket + numPackets; if (discPacket != 0 && (discPacket < firstPacket || discPacket >= lastPacket)) { fprintf(stderr, "-d%zu ignored\n", discPacket); discPacket = 0; } if (afterDiscPacket < firstPacket || afterDiscPacket >= lastPacket) { fprintf(stderr, "-D%zu ignored\n", afterDiscPacket); afterDiscPacket = 0; } if (formatPacket != 0 && (formatPacket < firstPacket || formatPacket >= lastPacket)) { fprintf(stderr, "-F%zu ignored\n", formatPacket); formatPacket = 0; } } ANativeWindow *nativeWindow; XAresult result; XAObjectItf engineObject; // create engine result = xaCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL); assert(XA_RESULT_SUCCESS == result); result = (*engineObject)->Realize(engineObject, XA_BOOLEAN_FALSE); assert(XA_RESULT_SUCCESS == result); XAEngineItf engineEngine; result = (*engineObject)->GetInterface(engineObject, XA_IID_ENGINE, &engineEngine); assert(XA_RESULT_SUCCESS == result); // create output mix XAObjectItf outputMixObject; result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, NULL, NULL); assert(XA_RESULT_SUCCESS == result); result = (*outputMixObject)->Realize(outputMixObject, XA_BOOLEAN_FALSE); assert(XA_RESULT_SUCCESS == result); // configure media source XADataLocator_URI locUri; locUri.locatorType = XA_DATALOCATOR_URI; locUri.URI = (XAchar *) uri; XADataFormat_MIME fmtMime; fmtMime.formatType = XA_DATAFORMAT_MIME; if (abq) { fmtMime.mimeType = (XAchar *) XA_ANDROID_MIME_MP2TS; fmtMime.containerType = XA_CONTAINERTYPE_MPEG_TS; } else { fmtMime.mimeType = NULL; fmtMime.containerType = XA_CONTAINERTYPE_UNSPECIFIED; } XADataLocator_AndroidBufferQueue locABQ; locABQ.locatorType = XA_DATALOCATOR_ANDROIDBUFFERQUEUE; locABQ.numBuffers = NB_BUFFERS; XADataSource dataSrc; if (abq) { dataSrc.pLocator = &locABQ; } else { dataSrc.pLocator = &locUri; } dataSrc.pFormat = &fmtMime; // configure audio sink XADataLocator_OutputMix locOM; locOM.locatorType = XA_DATALOCATOR_OUTPUTMIX; locOM.outputMix = outputMixObject; XADataSink audioSnk; audioSnk.pLocator = &locOM; audioSnk.pFormat = NULL; // configure video sink nativeWindow = getNativeWindow(); XADataLocator_NativeDisplay locND; locND.locatorType = XA_DATALOCATOR_NATIVEDISPLAY; locND.hWindow = nativeWindow; locND.hDisplay = NULL; XADataSink imageVideoSink; imageVideoSink.pLocator = &locND; imageVideoSink.pFormat = NULL; // create media player XAObjectItf playerObject; XAInterfaceID ids[4] = {XA_IID_STREAMINFORMATION, XA_IID_PREFETCHSTATUS, XA_IID_SEEK, XA_IID_ANDROIDBUFFERQUEUESOURCE}; XAboolean req[4] = {XA_BOOLEAN_TRUE, XA_BOOLEAN_TRUE, XA_BOOLEAN_FALSE, XA_BOOLEAN_TRUE}; result = (*engineEngine)->CreateMediaPlayer(engineEngine, &playerObject, &dataSrc, NULL, &audioSnk, nativeWindow != NULL ? &imageVideoSink : NULL, NULL, NULL, abq ? 4 : 3, ids, req); assert(XA_RESULT_SUCCESS == result); // realize the player result = (*playerObject)->Realize(playerObject, XA_BOOLEAN_FALSE); assert(XA_RESULT_SUCCESS == result); // get the play interface XAPlayItf playerPlay; result = (*playerObject)->GetInterface(playerObject, XA_IID_PLAY, &playerPlay); assert(XA_RESULT_SUCCESS == result); if (abq) { // get the Android buffer queue interface XAAndroidBufferQueueItf playerAndroidBufferQueue; result = (*playerObject)->GetInterface(playerObject, XA_IID_ANDROIDBUFFERQUEUESOURCE, &playerAndroidBufferQueue); assert(XA_RESULT_SUCCESS == result); // register the buffer queue callback result = (*playerAndroidBufferQueue)->RegisterCallback(playerAndroidBufferQueue, bufferQueueCallback, (void *) playerPlay); assert(XA_RESULT_SUCCESS == result); result = (*playerAndroidBufferQueue)->SetCallbackEventsMask(playerAndroidBufferQueue, XA_ANDROIDBUFFERQUEUEEVENT_PROCESSED); assert(XA_RESULT_SUCCESS == result); // set the player's state to paused, to start prefetching printf("start early prefetch\n"); result = (*playerPlay)->SetPlayState(playerPlay, XA_PLAYSTATE_PAUSED); assert(XA_RESULT_SUCCESS == result); // enqueue the initial buffers until buffer queue is full XAuint32 packetsThisBuffer; for (curPacket = firstPacket; curPacket < lastPacket; curPacket += packetsThisBuffer) { // handle the unlikely case of a very short .ts packetsThisBuffer = lastPacket - curPacket; if (packetsThisBuffer > PACKETS_PER_BUFFER) { packetsThisBuffer = PACKETS_PER_BUFFER; } result = (*playerAndroidBufferQueue)->Enqueue(playerAndroidBufferQueue, NULL, &packets[curPacket], MPEG2TS_PACKET_SIZE * packetsThisBuffer, NULL, 0); if (XA_RESULT_BUFFER_INSUFFICIENT == result) { printf("Enqueued initial %zu packets in %zu buffers\n", curPacket - firstPacket, (curPacket - firstPacket + PACKETS_PER_BUFFER - 1) / PACKETS_PER_BUFFER); break; } assert(XA_RESULT_SUCCESS == result); } } // get the stream information interface XAStreamInformationItf playerStreamInformation; result = (*playerObject)->GetInterface(playerObject, XA_IID_STREAMINFORMATION, &playerStreamInformation); assert(XA_RESULT_SUCCESS == result); // register the stream event change callback result = (*playerStreamInformation)->RegisterStreamChangeCallback(playerStreamInformation, streamEventChangeCallback, NULL); assert(XA_RESULT_SUCCESS == result); // get the prefetch status interface XAPrefetchStatusItf playerPrefetchStatus; result = (*playerObject)->GetInterface(playerObject, XA_IID_PREFETCHSTATUS, &playerPrefetchStatus); assert(XA_RESULT_SUCCESS == result); // register prefetch status callback result = (*playerPrefetchStatus)->RegisterCallback(playerPrefetchStatus, prefetchStatusCallback, NULL); assert(XA_RESULT_SUCCESS == result); result = (*playerPrefetchStatus)->SetCallbackEventsMask(playerPrefetchStatus, XA_PREFETCHEVENT_FILLLEVELCHANGE | XA_PREFETCHEVENT_STATUSCHANGE); assert(XA_RESULT_SUCCESS == result); // get the seek interface for seeking and/or looping if (looping || seekPos != XA_TIME_UNKNOWN) { XASeekItf playerSeek; result = (*playerObject)->GetInterface(playerObject, XA_IID_SEEK, &playerSeek); assert(XA_RESULT_SUCCESS == result); if (seekPos != XA_TIME_UNKNOWN) { result = (*playerSeek)->SetPosition(playerSeek, seekPos, XA_SEEKMODE_ACCURATE); if (XA_RESULT_FEATURE_UNSUPPORTED == result) { fprintf(stderr, "-s%u (seek to initial position) is unsupported\n", seekPos); } else { assert(XA_RESULT_SUCCESS == result); } } if (looping) { result = (*playerSeek)->SetLoop(playerSeek, XA_BOOLEAN_TRUE, (XAmillisecond) 0, XA_TIME_UNKNOWN); if (XA_RESULT_FEATURE_UNSUPPORTED) { fprintf(stderr, "-l (looping) is unsupported\n"); } else { assert(XA_RESULT_SUCCESS == result); } } } // register play event callback result = (*playerPlay)->RegisterCallback(playerPlay, playEventCallback, NULL); assert(XA_RESULT_SUCCESS == result); result = (*playerPlay)->SetCallbackEventsMask(playerPlay, XA_PLAYEVENT_HEADATEND | XA_PLAYEVENT_HEADATMARKER | XA_PLAYEVENT_HEADATNEWPOS); assert(XA_RESULT_SUCCESS == result); // set a marker result = (*playerPlay)->SetMarkerPosition(playerPlay, 5000); assert(XA_RESULT_SUCCESS == result); // set position update period result = (*playerPlay)->SetPositionUpdatePeriod(playerPlay, 2000); assert(XA_RESULT_SUCCESS == result); // get the position before prefetch XAmillisecond position; result = (*playerPlay)->GetPosition(playerPlay, &position); assert(XA_RESULT_SUCCESS == result); printf("Position before prefetch: %u ms\n", position); // get the duration before prefetch XAmillisecond duration; result = (*playerPlay)->GetDuration(playerPlay, &duration); assert(XA_RESULT_SUCCESS == result); if (XA_TIME_UNKNOWN == duration) printf("Duration before prefetch: unknown as expected\n"); else printf("Duration before prefetch: %.1f (surprise!)\n", duration / 1000.0f); // set the player's state to paused, to start prefetching printf("start prefetch\n"); result = (*playerPlay)->SetPlayState(playerPlay, XA_PLAYSTATE_PAUSED); assert(XA_RESULT_SUCCESS == result); // wait for prefetch status callback to indicate either sufficient data or error pthread_mutex_lock(&mutex); while (prefetch_status == PREFETCHSTATUS_UNKNOWN) { pthread_cond_wait(&cond, &mutex); } pthread_mutex_unlock(&mutex); if (prefetch_status == PREFETCHSTATUS_ERROR) { fprintf(stderr, "Error during prefetch, exiting\n"); goto destroyRes; } // get the position after prefetch result = (*playerPlay)->GetPosition(playerPlay, &position); assert(XA_RESULT_SUCCESS == result); printf("Position after prefetch: %u ms\n", position); // get duration again, now it should be known for the file source or unknown for TS result = (*playerPlay)->GetDuration(playerPlay, &duration); assert(XA_RESULT_SUCCESS == result); if (duration == XA_TIME_UNKNOWN) { printf("Duration after prefetch: unknown (expected for TS, unexpected for file)\n"); } else { printf("Duration after prefetch: %u ms (expected for file, unexpected for TS)\n", duration); } // query for media container information result = (*playerStreamInformation)->QueryMediaContainerInformation(playerStreamInformation, NULL); assert(XA_RESULT_PARAMETER_INVALID == result); XAMediaContainerInformation mediaContainerInformation; // this verifies it is filling in all the fields memset(&mediaContainerInformation, 0x55, sizeof(XAMediaContainerInformation)); result = (*playerStreamInformation)->QueryMediaContainerInformation(playerStreamInformation, &mediaContainerInformation); assert(XA_RESULT_SUCCESS == result); printf("Media container information:\n"); printf(" containerType = %u\n", mediaContainerInformation.containerType); printf(" mediaDuration = %u\n", mediaContainerInformation.mediaDuration); printf(" numStreams = %u\n", mediaContainerInformation.numStreams); // Now query for each the streams. Note that stream indices go up to and including // mediaContainerInformation.numStreams, because stream 0 is the container itself, // while stream 1 to mediaContainerInformation.numStreams are the contained streams. XAuint32 numStreams = mediaContainerInformation.numStreams; XAuint32 streamIndex; for (streamIndex = 0; streamIndex <= mediaContainerInformation.numStreams; ++streamIndex) { XAuint32 domain; XAuint16 nameSize; XAchar name[64]; printf("stream[%u]:\n", streamIndex); if (streamIndex == 0) { result = (*playerStreamInformation)->QueryStreamType(playerStreamInformation, streamIndex, &domain); assert(XA_RESULT_PARAMETER_INVALID == result); result = (*playerStreamInformation)->QueryStreamInformation(playerStreamInformation, streamIndex, &mediaContainerInformation); //assert(XA_RESULT_PARAMETER_INVALID == result); nameSize = sizeof(name); result = (*playerStreamInformation)->QueryStreamName(playerStreamInformation, streamIndex, &nameSize, name); //assert(XA_RESULT_PARAMETER_INVALID == result); continue; } result = (*playerStreamInformation)->QueryStreamType(playerStreamInformation, streamIndex, NULL); assert(XA_RESULT_PARAMETER_INVALID == result); domain = 12345; result = (*playerStreamInformation)->QueryStreamType(playerStreamInformation, streamIndex, &domain); assert(XA_RESULT_SUCCESS == result); printf(" QueryStreamType: domain = 0x%X (%s)\n", domain, domainToString(domain)); nameSize = sizeof(name); result = (*playerStreamInformation)->QueryStreamName(playerStreamInformation, streamIndex, &nameSize, name); #if 0 assert(XA_RESULT_SUCCESS == result); assert(sizeof(name) >= nameSize); if (sizeof(name) != nameSize) { assert('\0' == name[nameSize]); } printf(" QueryStreamName: nameSize=%u, name=\"%.*s\"\n", nameSize, nameSize, name); result = (*playerStreamInformation)->QueryStreamInformation(playerStreamInformation, streamIndex, NULL); assert(XA_RESULT_PARAMETER_INVALID == result); #endif printf(" QueryStreamInformation:\n"); switch (domain) { #if 0 case 0: // FIXME container result = (*playerStreamInformation)->QueryStreamInformation(playerStreamInformation, streamIndex, &mediaContainerInformation); assert(XA_RESULT_SUCCESS == result); printf(" containerType = %u (1=unspecified)\n", mediaContainerInformation.containerType); printf(" mediaDuration = %u\n", mediaContainerInformation.mediaDuration); printf(" numStreams = %u\n", mediaContainerInformation.numStreams); break; #endif case XA_DOMAINTYPE_AUDIO: { XAAudioStreamInformation audioStreamInformation; memset(&audioStreamInformation, 0x55, sizeof(XAAudioStreamInformation)); result = (*playerStreamInformation)->QueryStreamInformation(playerStreamInformation, streamIndex, &audioStreamInformation); assert(XA_RESULT_PARAMETER_INVALID == result); printf(" codecId = %u\n", audioStreamInformation.codecId); printf(" channels = %u\n", audioStreamInformation.channels); printf(" sampleRate = %u\n", audioStreamInformation.sampleRate); printf(" bitRate = %u\n", audioStreamInformation.bitRate); printf(" langCountry = \"%s\"\n", audioStreamInformation.langCountry); printf(" duration = %u\n", audioStreamInformation.duration); } break; case XA_DOMAINTYPE_VIDEO: { XAVideoStreamInformation videoStreamInformation; result = (*playerStreamInformation)->QueryStreamInformation(playerStreamInformation, streamIndex, &videoStreamInformation); assert(XA_RESULT_SUCCESS == result); printf(" codecId = %u\n", videoStreamInformation.codecId); printf(" width = %u\n", videoStreamInformation.width); printf(" height = %u\n", videoStreamInformation.height); printf(" frameRate = %u\n", videoStreamInformation.frameRate); printf(" bitRate = %u\n", videoStreamInformation.bitRate); printf(" duration = %u\n", videoStreamInformation.duration); } break; case XA_DOMAINTYPE_IMAGE: { XAImageStreamInformation imageStreamInformation; result = (*playerStreamInformation)->QueryStreamInformation(playerStreamInformation, streamIndex, &imageStreamInformation); assert(XA_RESULT_SUCCESS == result); printf(" codecId = %u\n", imageStreamInformation.codecId); printf(" width = %u\n", imageStreamInformation.width); printf(" height = %u\n", imageStreamInformation.height); printf(" presentationDuration = %u\n", imageStreamInformation.presentationDuration); } break; case XA_DOMAINTYPE_TIMEDTEXT: { XATimedTextStreamInformation timedTextStreamInformation; result = (*playerStreamInformation)->QueryStreamInformation(playerStreamInformation, streamIndex, &timedTextStreamInformation); assert(XA_RESULT_SUCCESS == result); printf(" layer = %u\n", timedTextStreamInformation.layer); printf(" width = %u\n", timedTextStreamInformation.width); printf(" height = %u\n", timedTextStreamInformation.height); printf(" tx = %u\n", timedTextStreamInformation.tx); printf(" ty = %u\n", timedTextStreamInformation.ty); printf(" bitrate = %u\n", timedTextStreamInformation.bitrate); printf(" langCountry = \"%s\"\n", timedTextStreamInformation.langCountry); printf(" duration = %u\n", timedTextStreamInformation.duration); } break; case XA_DOMAINTYPE_MIDI: { XAMIDIStreamInformation midiStreamInformation; result = (*playerStreamInformation)->QueryStreamInformation(playerStreamInformation, streamIndex, &midiStreamInformation); assert(XA_RESULT_SUCCESS == result); printf(" channels = %u\n", midiStreamInformation.channels); printf(" tracks = %u\n", midiStreamInformation.tracks); printf(" bankType = %u\n", midiStreamInformation.bankType); printf(" langCountry = \"%s\"\n", midiStreamInformation.langCountry); printf(" duration = %u\n", midiStreamInformation.duration); } break; case XA_DOMAINTYPE_VENDOR: { XAVendorStreamInformation vendorStreamInformation; result = (*playerStreamInformation)->QueryStreamInformation(playerStreamInformation, streamIndex, &vendorStreamInformation); assert(XA_RESULT_SUCCESS == result); printf(" VendorStreamInfo = %p\n", vendorStreamInformation.VendorStreamInfo); } break; case XA_DOMAINTYPE_UNKNOWN: { // "It is not possible to query Information for streams identified as // XA_DOMAINTYPE_UNKNOWN, any attempt to do so shall return a result of // XA_RESULT_CONTENT_UNSUPPORTED." char big[256]; result = (*playerStreamInformation)->QueryStreamInformation(playerStreamInformation, streamIndex, &big); assert(XA_RESULT_CONTENT_UNSUPPORTED == result); } break; default: break; } } // Try one more stream index beyond the valid range XAuint32 domain; result = (*playerStreamInformation)->QueryStreamType(playerStreamInformation, streamIndex, &domain); assert(XA_RESULT_PARAMETER_INVALID == result); XATimedTextStreamInformation big; result = (*playerStreamInformation)->QueryStreamInformation(playerStreamInformation, streamIndex, &big); assert(XA_RESULT_PARAMETER_INVALID == result); printf("QueryActiveStreams:\n"); result = (*playerStreamInformation)->QueryActiveStreams(playerStreamInformation, NULL, NULL); assert(XA_RESULT_PARAMETER_INVALID == result); XAuint32 numStreams1 = 0x12345678; result = (*playerStreamInformation)->QueryActiveStreams(playerStreamInformation, &numStreams1, NULL); assert(XA_RESULT_SUCCESS == result); printf(" numStreams = %u\n", numStreams1); XAboolean *activeStreams = calloc(numStreams1 + 1, sizeof(XAboolean)); assert(NULL != activeStreams); printf(" active stream(s) ="); XAuint32 numStreams2 = numStreams1; result = (*playerStreamInformation)->QueryActiveStreams(playerStreamInformation, &numStreams2, activeStreams); assert(XA_RESULT_SUCCESS == result); assert(numStreams2 == numStreams1); for (streamIndex = 0; streamIndex <= numStreams1; ++streamIndex) { if (activeStreams[streamIndex]) printf(" %u", streamIndex); } printf("\n"); // SetActiveStream is untested // start playing printf("starting to play\n"); result = (*playerPlay)->SetPlayState(playerPlay, XA_PLAYSTATE_PLAYING); assert(XA_RESULT_SUCCESS == result); // continue playing until end of media for (;;) { XAuint32 status; result = (*playerPlay)->GetPlayState(playerPlay, &status); assert(XA_RESULT_SUCCESS == result); if (status == XA_PLAYSTATE_PAUSED) break; assert(status == XA_PLAYSTATE_PLAYING); usleep(100000); if (pauseMs >= 0) { result = (*playerPlay)->GetPosition(playerPlay, &position); assert(XA_RESULT_SUCCESS == result); if ((int) position >= pauseMs) { printf("Pausing for 5 seconds at position %u\n", position); result = (*playerPlay)->SetPlayState(playerPlay, XA_PLAYSTATE_PAUSED); assert(XA_RESULT_SUCCESS == result); sleep(5); // FIXME clear ABQ queue here result = (*playerPlay)->SetPlayState(playerPlay, XA_PLAYSTATE_PLAYING); assert(XA_RESULT_SUCCESS == result); pauseMs = -1; } } } // wait a bit more in case of additional callbacks printf("end of media\n"); sleep(3); // get final position result = (*playerPlay)->GetPosition(playerPlay, &position); assert(XA_RESULT_SUCCESS == result); printf("Position at end: %u ms\n", position); // get duration again, now it should be known result = (*playerPlay)->GetDuration(playerPlay, &duration); assert(XA_RESULT_SUCCESS == result); if (duration == XA_TIME_UNKNOWN) { printf("Duration at end: unknown\n"); } else { printf("Duration at end: %u ms\n", duration); } destroyRes: // destroy the player (*playerObject)->Destroy(playerObject); // destroy the output mix (*outputMixObject)->Destroy(outputMixObject); // destroy the engine (*engineObject)->Destroy(engineObject); #if 0 if (nativeWindow != NULL) { ANativeWindow_release(nativeWindow); } #endif close: if (fd >= 0) { (void) close(fd); } disposeNativeWindow(); return EXIT_SUCCESS; }