You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
866 lines
35 KiB
866 lines
35 KiB
/*
|
|
* 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 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;
|
|
}
|