2091 lines
76 KiB
C++
2091 lines
76 KiB
C++
|
|
||
|
#include <semaphore.h>
|
||
|
#include <pthread.h>
|
||
|
#include "demuxComponent.h"
|
||
|
#include "awMessageQueue.h"
|
||
|
#include "CdxParser.h" //* parser library in "LIBRARY/DEMUX/PARSER/include/"
|
||
|
#include "CdxStream.h" //* parser library in "LIBRARY/DEMUX/STREAM/include/"
|
||
|
#include "player.h" //* player library in "LIBRARY/PLAYER/"
|
||
|
#include "cache.h"
|
||
|
#include "log.h"
|
||
|
#include "demoConfig.h"
|
||
|
|
||
|
//* demux status, same with the awplayer.
|
||
|
static const int DEMUX_STATUS_IDLE = 0; //* the very beginning status.
|
||
|
static const int DEMUX_STATUS_INITIALIZED = 1<<0; //* after data source set.
|
||
|
static const int DEMUX_STATUS_PREPARING = 1<<1; //* when preparing.
|
||
|
static const int DEMUX_STATUS_PREPARED = 1<<2; //* after parser is opened and media info get.
|
||
|
static const int DEMUX_STATUS_STARTED = 1<<3; //* parsing and sending data.
|
||
|
static const int DEMUX_STATUS_PAUSED = 1<<4; //* sending job paused.
|
||
|
static const int DEMUX_STATUS_STOPPED = 1<<5; //* parser closed.
|
||
|
static const int DEMUX_STATUS_COMPLETE = 1<<6; //* all data parsed.
|
||
|
|
||
|
//* command id, same with the awplayer.
|
||
|
static const int DEMUX_COMMAND_SET_SOURCE = 0x101;
|
||
|
static const int DEMUX_COMMAND_PREPARE = 0x104;
|
||
|
static const int DEMUX_COMMAND_START = 0x105;
|
||
|
static const int DEMUX_COMMAND_PAUSE = 0x106;
|
||
|
static const int DEMUX_COMMAND_STOP = 0x107;
|
||
|
static const int DEMUX_COMMAND_QUIT = 0x109;
|
||
|
static const int DEMUX_COMMAND_SEEK = 0x10a;
|
||
|
static const int DEMUX_COMMAND_CLEAR = 0x10b;
|
||
|
static const int DEMUX_COMMAND_CANCEL_PREPARE = 0x10c;
|
||
|
static const int DEMUX_COMMAND_CANCEL_SEEK = 0x10d;
|
||
|
static const int DEMUX_COMMAND_READ = 0x10e;
|
||
|
|
||
|
//* cache start play size and max buffer size.
|
||
|
static const int CACHE_START_PLAY_SIZE = 128*1024;
|
||
|
static const int CACHE_MAX_BUFFER_SIZE = 20*1024*1024;
|
||
|
|
||
|
static void* DemuxThread(void* arg);
|
||
|
static void* CacheThread(void* arg);
|
||
|
#if CONFIG_OS == OPTION_OS_ANDROID
|
||
|
static int setDataSourceFields(CdxDataSourceT* source, char* uri, KeyedVector<String8,String8>* pHeader);
|
||
|
#else
|
||
|
static int setDataSourceFields(CdxDataSourceT* source, char* uri, map<string,string>* pHeader);
|
||
|
#endif
|
||
|
static void clearDataSourceFields(CdxDataSourceT* source);
|
||
|
static int setMediaInfo(MediaInfo* pMediaInfo, CdxMediaInfoT* pInfoFromParser);
|
||
|
static void clearMediaInfo(MediaInfo* pMediaInfo);
|
||
|
static int PlayerBufferOverflow(Player* p);
|
||
|
static int PlayerBufferUnderflow(Player* p);
|
||
|
|
||
|
typedef struct DemuxCompContext_t
|
||
|
{
|
||
|
int eStatus;
|
||
|
int bLiveStream; //* live streaming from network.
|
||
|
int bFileStream; //* local media file.
|
||
|
int bVodStream; //* vod stream from network.
|
||
|
|
||
|
//* data source.
|
||
|
int nSourceType; //* url or fd or IStreamSource.
|
||
|
CdxDataSourceT source;
|
||
|
MediaInfo mediaInfo;
|
||
|
|
||
|
pthread_t threadId;
|
||
|
AwMessageQueue* mq;
|
||
|
|
||
|
CdxParserT* pParser;
|
||
|
CdxStreamT* pStream;
|
||
|
Player* pPlayer;
|
||
|
DemuxCallback callback;
|
||
|
void* pUserData;
|
||
|
int nCacheStatReportIntervalMs;
|
||
|
|
||
|
pthread_mutex_t mutex;
|
||
|
sem_t semSetDataSource;
|
||
|
sem_t semStart;
|
||
|
sem_t semStop;
|
||
|
sem_t semQuit;
|
||
|
sem_t semClear;
|
||
|
sem_t semCancelPrepare;
|
||
|
sem_t semCancelSeek;
|
||
|
|
||
|
int nSetDataSourceReply;
|
||
|
int nStartReply;
|
||
|
int nStopReply;
|
||
|
|
||
|
pthread_t cacheThreadId;
|
||
|
AwMessageQueue* mqCache;
|
||
|
sem_t semCache;
|
||
|
int nCacheReply;
|
||
|
StreamCache* pCache;
|
||
|
int bBufferring;
|
||
|
int bEOS;
|
||
|
int bIOError;
|
||
|
|
||
|
int bCancelPrepare;
|
||
|
int bCancelSeek;
|
||
|
int bSeeking;
|
||
|
int bStopping;
|
||
|
|
||
|
}DemuxCompContext;
|
||
|
|
||
|
|
||
|
DemuxComp* DemuxCompCreate(void)
|
||
|
{
|
||
|
DemuxCompContext* d;
|
||
|
|
||
|
d = (DemuxCompContext*)malloc(sizeof(DemuxCompContext));
|
||
|
if(d == NULL)
|
||
|
{
|
||
|
loge("malloc memory fail.");
|
||
|
return NULL;
|
||
|
}
|
||
|
memset(d, 0, sizeof(DemuxCompContext));
|
||
|
|
||
|
d->nCacheStatReportIntervalMs = 1000;
|
||
|
|
||
|
pthread_mutex_init(&d->mutex, NULL);
|
||
|
sem_init(&d->semSetDataSource, 0, 0);
|
||
|
sem_init(&d->semStart, 0, 0);
|
||
|
sem_init(&d->semStop, 0, 0);
|
||
|
sem_init(&d->semQuit, 0, 0);
|
||
|
sem_init(&d->semClear, 0, 0);
|
||
|
sem_init(&d->semCancelPrepare, 0, 0);
|
||
|
sem_init(&d->semCancelSeek, 0, 0);
|
||
|
sem_init(&d->semCache, 0, 0);
|
||
|
|
||
|
d->mq = AwMessageQueueCreate(64);
|
||
|
if(d->mq == NULL)
|
||
|
{
|
||
|
loge("AwMessageQueueCreate() return fail.");
|
||
|
pthread_mutex_destroy(&d->mutex);
|
||
|
sem_destroy(&d->semSetDataSource);
|
||
|
sem_destroy(&d->semStart);
|
||
|
sem_destroy(&d->semStop);
|
||
|
sem_destroy(&d->semQuit);
|
||
|
sem_destroy(&d->semClear);
|
||
|
sem_destroy(&d->semCancelPrepare);
|
||
|
sem_destroy(&d->semCancelSeek);
|
||
|
sem_destroy(&d->semCache);
|
||
|
free(d);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
d->mqCache = AwMessageQueueCreate(64);
|
||
|
if(d->mqCache == NULL)
|
||
|
{
|
||
|
loge("AwMessageQueueCreate() return fail.");
|
||
|
AwMessageQueueDestroy(d->mq);
|
||
|
pthread_mutex_destroy(&d->mutex);
|
||
|
sem_destroy(&d->semSetDataSource);
|
||
|
sem_destroy(&d->semStart);
|
||
|
sem_destroy(&d->semStop);
|
||
|
sem_destroy(&d->semQuit);
|
||
|
sem_destroy(&d->semClear);
|
||
|
sem_destroy(&d->semCancelPrepare);
|
||
|
sem_destroy(&d->semCancelSeek);
|
||
|
sem_destroy(&d->semCache);
|
||
|
free(d);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
if(pthread_create(&d->threadId, NULL, DemuxThread, (void*)d) != 0)
|
||
|
{
|
||
|
loge("can not create thread for demux component.");
|
||
|
AwMessageQueueDestroy(d->mq);
|
||
|
AwMessageQueueDestroy(d->mqCache);
|
||
|
pthread_mutex_destroy(&d->mutex);
|
||
|
sem_destroy(&d->semSetDataSource);
|
||
|
sem_destroy(&d->semStart);
|
||
|
sem_destroy(&d->semStop);
|
||
|
sem_destroy(&d->semQuit);
|
||
|
sem_destroy(&d->semClear);
|
||
|
sem_destroy(&d->semCancelPrepare);
|
||
|
sem_destroy(&d->semCancelSeek);
|
||
|
free(d);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
d->pCache = StreamCacheCreate();
|
||
|
StreamCacheSetSize(d->pCache, CACHE_START_PLAY_SIZE, CACHE_MAX_BUFFER_SIZE);
|
||
|
|
||
|
return (DemuxComp*)d;
|
||
|
}
|
||
|
|
||
|
|
||
|
void DemuxCompDestroy(DemuxComp* d)
|
||
|
{
|
||
|
void* status;
|
||
|
|
||
|
AwMessage msg;
|
||
|
DemuxCompContext* demux;
|
||
|
|
||
|
demux = (DemuxCompContext*)d;
|
||
|
|
||
|
//* send a quit message.
|
||
|
demux->bStopping = 1;
|
||
|
setMessage(&msg, DEMUX_COMMAND_QUIT, (uintptr_t)&demux->semQuit);
|
||
|
AwMessageQueuePostMessage(demux->mq, &msg);
|
||
|
SemTimedWait(&demux->semQuit, -1);
|
||
|
|
||
|
pthread_join(demux->threadId, &status);
|
||
|
|
||
|
StreamCacheDestroy(demux->pCache);
|
||
|
|
||
|
if(demux->mq != NULL)
|
||
|
AwMessageQueueDestroy(demux->mq);
|
||
|
|
||
|
if(demux->mqCache != NULL)
|
||
|
AwMessageQueueDestroy(demux->mqCache);
|
||
|
|
||
|
pthread_mutex_destroy(&demux->mutex);
|
||
|
sem_destroy(&demux->semSetDataSource);
|
||
|
sem_destroy(&demux->semStart);
|
||
|
sem_destroy(&demux->semStop);
|
||
|
sem_destroy(&demux->semQuit);
|
||
|
sem_destroy(&demux->semClear);
|
||
|
sem_destroy(&demux->semCancelPrepare);
|
||
|
sem_destroy(&demux->semCancelSeek);
|
||
|
sem_destroy(&demux->semCache);
|
||
|
free(demux);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
void DemuxCompClear(DemuxComp* d) //* clear the data source, like just created.
|
||
|
{
|
||
|
AwMessage msg;
|
||
|
DemuxCompContext* demux;
|
||
|
|
||
|
demux = (DemuxCompContext*)d;
|
||
|
|
||
|
//* send clear message.
|
||
|
demux->bStopping = 1;
|
||
|
setMessage(&msg, DEMUX_COMMAND_CLEAR, (uintptr_t)&demux->semClear);
|
||
|
AwMessageQueuePostMessage(demux->mq, &msg);
|
||
|
SemTimedWait(&demux->semClear, -1);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
#if CONFIG_OS == OPTION_OS_ANDROID
|
||
|
int DemuxCompSetUrlSource(DemuxComp* d, const char* pUrl, const KeyedVector<String8, String8>* pHeaders)
|
||
|
#else
|
||
|
int DemuxCompSetUrlSource(DemuxComp* d, const char* pUrl, const map<string, string>* pHeaders)
|
||
|
#endif
|
||
|
{
|
||
|
AwMessage msg;
|
||
|
DemuxCompContext* demux;
|
||
|
|
||
|
demux = (DemuxCompContext*)d;
|
||
|
|
||
|
//* send a set data source message.
|
||
|
setMessage(&msg,
|
||
|
DEMUX_COMMAND_SET_SOURCE, //* message id.
|
||
|
(uintptr_t)&demux->semSetDataSource, //* params[0] = &semSetDataSource.
|
||
|
(uintptr_t)&demux->nSetDataSourceReply, //* params[1] = &nSetDataSourceReply.
|
||
|
SOURCE_TYPE_URL, //* params[2] = SOURCE_TYPE_URL.
|
||
|
(uintptr_t)pUrl, //* params[3] = pUrl.
|
||
|
(uintptr_t)pHeaders); //* params[4] = KeyedVector<String8, String8>* pHeaders;
|
||
|
AwMessageQueuePostMessage(demux->mq, &msg);
|
||
|
SemTimedWait(&demux->semSetDataSource, -1);
|
||
|
|
||
|
return demux->nSetDataSourceReply;
|
||
|
}
|
||
|
|
||
|
|
||
|
int DemuxCompSetFdSource(DemuxComp* d, int fd, int64_t nOffset, int64_t nLength)
|
||
|
{
|
||
|
AwMessage msg;
|
||
|
DemuxCompContext* demux;
|
||
|
|
||
|
demux = (DemuxCompContext*)d;
|
||
|
|
||
|
//* send a set data source message.
|
||
|
setMessage(&msg,
|
||
|
DEMUX_COMMAND_SET_SOURCE, //* message id.
|
||
|
(uintptr_t)&demux->semSetDataSource, //* params[0] = &semSetDataSource.
|
||
|
(uintptr_t)&demux->nSetDataSourceReply, //* params[1] = &nSetDataSourceReply.
|
||
|
SOURCE_TYPE_FD, //* params[2] = SOURCE_TYPE_FD.
|
||
|
fd, //* params[3] = fd.
|
||
|
(uintptr_t)(nOffset>>32), //* params[4] = high 32 bits of offset.
|
||
|
(uintptr_t)(nOffset & 0xffffffff), //* params[5] = low 32 bits of offset.
|
||
|
(uintptr_t)(nLength>>32), //* params[6] = high 32 bits of length.
|
||
|
(uintptr_t)(nLength & 0xffffffff)); //* params[7] = low 32 bits of length.
|
||
|
AwMessageQueuePostMessage(demux->mq, &msg);
|
||
|
SemTimedWait(&demux->semSetDataSource, -1);
|
||
|
|
||
|
return demux->nSetDataSourceReply;
|
||
|
}
|
||
|
|
||
|
|
||
|
int DemuxCompSetPlayer(DemuxComp* d, Player* player)
|
||
|
{
|
||
|
DemuxCompContext* demux;
|
||
|
demux = (DemuxCompContext*)d;
|
||
|
demux->pPlayer = player;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
int DemuxCompSetCallback(DemuxComp* d, DemuxCallback callback, void* pUserData)
|
||
|
{
|
||
|
DemuxCompContext* demux;
|
||
|
demux = (DemuxCompContext*)d;
|
||
|
demux->callback = callback;
|
||
|
demux->pUserData = pUserData;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
int DemuxCompPrepareAsync(DemuxComp* d)
|
||
|
{
|
||
|
AwMessage msg;
|
||
|
DemuxCompContext* demux;
|
||
|
|
||
|
demux = (DemuxCompContext*)d;
|
||
|
|
||
|
demux->bCancelPrepare = 0; //* clear this flag, you can set this flag to make the parser quit preparing.
|
||
|
demux->eStatus = DEMUX_STATUS_PREPARING;
|
||
|
|
||
|
//* send a prepare message.
|
||
|
setMessage(&msg, DEMUX_COMMAND_PREPARE);
|
||
|
AwMessageQueuePostMessage(demux->mq, &msg);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
int DemuxCompCancelPrepare(DemuxComp* d) //* should call back DEMUX_PREPARE_FINISH message.
|
||
|
{
|
||
|
AwMessage msg;
|
||
|
DemuxCompContext* demux;
|
||
|
|
||
|
demux = (DemuxCompContext*)d;
|
||
|
|
||
|
demux->bCancelPrepare = 1; //* set this flag to make the parser quit preparing.
|
||
|
|
||
|
//* send a prepare.
|
||
|
setMessage(&msg,
|
||
|
DEMUX_COMMAND_CANCEL_PREPARE, //* message id.
|
||
|
(uintptr_t)&demux->semCancelPrepare, //* params[0] = &semCancelPrepare.
|
||
|
0); //* no reply.
|
||
|
AwMessageQueuePostMessage(demux->mq, &msg);
|
||
|
SemTimedWait(&demux->semCancelPrepare, -1);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
MediaInfo* DemuxCompGetMediaInfo(DemuxComp* d)
|
||
|
{
|
||
|
DemuxCompContext* demux;
|
||
|
MediaInfo* mi;
|
||
|
MediaInfo* myMediaInfo;
|
||
|
int i;
|
||
|
VideoStreamInfo* pVideoStreamInfo;
|
||
|
AudioStreamInfo* pAudioStreamInfo;
|
||
|
SubtitleStreamInfo* pSubtitleStreamInfo;
|
||
|
int nCodecSpecificDataLen;
|
||
|
char* pCodecSpecificData;
|
||
|
|
||
|
demux = (DemuxCompContext*)d;
|
||
|
|
||
|
myMediaInfo = &demux->mediaInfo;
|
||
|
mi = (MediaInfo*)malloc(sizeof(MediaInfo));
|
||
|
if(mi == NULL)
|
||
|
{
|
||
|
loge("can not alloc memory for media info.");
|
||
|
return NULL;
|
||
|
}
|
||
|
memset(mi, 0, sizeof(MediaInfo));
|
||
|
mi->nFileSize = myMediaInfo->nFileSize;
|
||
|
mi->nDurationMs = myMediaInfo->nDurationMs;
|
||
|
mi->eContainerType = myMediaInfo->eContainerType;
|
||
|
mi->bSeekable = myMediaInfo->bSeekable;
|
||
|
|
||
|
logv("video stream num = %d, video stream info = %p", myMediaInfo->nVideoStreamNum, myMediaInfo->pVideoStreamInfo);
|
||
|
|
||
|
if(myMediaInfo->nVideoStreamNum > 0)
|
||
|
{
|
||
|
pVideoStreamInfo = (VideoStreamInfo*)malloc(sizeof(VideoStreamInfo)*myMediaInfo->nVideoStreamNum);
|
||
|
if(pVideoStreamInfo == NULL)
|
||
|
{
|
||
|
loge("can not alloc memory for media info.");
|
||
|
clearMediaInfo(mi);
|
||
|
return NULL;
|
||
|
}
|
||
|
memset(pVideoStreamInfo, 0, sizeof(VideoStreamInfo)*myMediaInfo->nVideoStreamNum);
|
||
|
mi->pVideoStreamInfo = pVideoStreamInfo;
|
||
|
|
||
|
for(i=0; i<myMediaInfo->nVideoStreamNum; i++)
|
||
|
{
|
||
|
pVideoStreamInfo = &mi->pVideoStreamInfo[i];
|
||
|
memcpy(pVideoStreamInfo, &myMediaInfo->pVideoStreamInfo[i], sizeof(VideoStreamInfo));
|
||
|
|
||
|
pCodecSpecificData = pVideoStreamInfo->pCodecSpecificData;
|
||
|
nCodecSpecificDataLen = pVideoStreamInfo->nCodecSpecificDataLen;
|
||
|
pVideoStreamInfo->pCodecSpecificData = NULL;
|
||
|
pVideoStreamInfo->nCodecSpecificDataLen = 0;
|
||
|
|
||
|
if(pCodecSpecificData != NULL && nCodecSpecificDataLen > 0)
|
||
|
{
|
||
|
pVideoStreamInfo->pCodecSpecificData = (char*)malloc(nCodecSpecificDataLen);
|
||
|
if(pVideoStreamInfo->pCodecSpecificData == NULL)
|
||
|
{
|
||
|
loge("can not alloc memory for media info.");
|
||
|
clearMediaInfo(mi);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
memcpy(pVideoStreamInfo->pCodecSpecificData, pCodecSpecificData, nCodecSpecificDataLen);
|
||
|
pVideoStreamInfo->nCodecSpecificDataLen = nCodecSpecificDataLen;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
mi->nVideoStreamNum = myMediaInfo->nVideoStreamNum;
|
||
|
}
|
||
|
|
||
|
logv("video stream num = %d, video stream info = %p", mi->nVideoStreamNum, mi->pVideoStreamInfo);
|
||
|
|
||
|
if(myMediaInfo->nAudioStreamNum > 0)
|
||
|
{
|
||
|
pAudioStreamInfo = (AudioStreamInfo*)malloc(sizeof(AudioStreamInfo)*myMediaInfo->nAudioStreamNum);
|
||
|
if(pAudioStreamInfo == NULL)
|
||
|
{
|
||
|
loge("can not alloc memory for media info.");
|
||
|
clearMediaInfo(mi);
|
||
|
return NULL;
|
||
|
}
|
||
|
memset(pAudioStreamInfo, 0, sizeof(AudioStreamInfo)*myMediaInfo->nAudioStreamNum);
|
||
|
mi->pAudioStreamInfo = pAudioStreamInfo;
|
||
|
|
||
|
for(i=0; i<myMediaInfo->nAudioStreamNum; i++)
|
||
|
{
|
||
|
pAudioStreamInfo = &mi->pAudioStreamInfo[i];
|
||
|
memcpy(pAudioStreamInfo, &myMediaInfo->pAudioStreamInfo[i], sizeof(AudioStreamInfo));
|
||
|
|
||
|
pCodecSpecificData = pAudioStreamInfo->pCodecSpecificData;
|
||
|
nCodecSpecificDataLen = pAudioStreamInfo->nCodecSpecificDataLen;
|
||
|
pAudioStreamInfo->pCodecSpecificData = NULL;
|
||
|
pAudioStreamInfo->nCodecSpecificDataLen = 0;
|
||
|
|
||
|
if(pCodecSpecificData != NULL && nCodecSpecificDataLen > 0)
|
||
|
{
|
||
|
pAudioStreamInfo->pCodecSpecificData = (char*)malloc(nCodecSpecificDataLen);
|
||
|
if(pAudioStreamInfo->pCodecSpecificData == NULL)
|
||
|
{
|
||
|
loge("can not alloc memory for media info.");
|
||
|
clearMediaInfo(mi);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
memcpy(pAudioStreamInfo->pCodecSpecificData, pCodecSpecificData, nCodecSpecificDataLen);
|
||
|
pAudioStreamInfo->nCodecSpecificDataLen = nCodecSpecificDataLen;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
mi->nAudioStreamNum = myMediaInfo->nAudioStreamNum;
|
||
|
}
|
||
|
|
||
|
if(myMediaInfo->nSubtitleStreamNum > 0)
|
||
|
{
|
||
|
pSubtitleStreamInfo = (SubtitleStreamInfo*)malloc(sizeof(SubtitleStreamInfo)*myMediaInfo->nSubtitleStreamNum);
|
||
|
if(pSubtitleStreamInfo == NULL)
|
||
|
{
|
||
|
loge("can not alloc memory for media info.");
|
||
|
clearMediaInfo(mi);
|
||
|
return NULL;
|
||
|
}
|
||
|
memset(pSubtitleStreamInfo, 0, sizeof(SubtitleStreamInfo)*myMediaInfo->nSubtitleStreamNum);
|
||
|
mi->pSubtitleStreamInfo = pSubtitleStreamInfo;
|
||
|
|
||
|
for(i=0; i<myMediaInfo->nSubtitleStreamNum; i++)
|
||
|
{
|
||
|
pSubtitleStreamInfo = &mi->pSubtitleStreamInfo[i];
|
||
|
memcpy(pSubtitleStreamInfo, &myMediaInfo->pSubtitleStreamInfo[i], sizeof(SubtitleStreamInfo));
|
||
|
|
||
|
//* parser only process imbedded subtitle stream in media file.
|
||
|
pSubtitleStreamInfo->pUrl = NULL;
|
||
|
pSubtitleStreamInfo->fd = -1;
|
||
|
pSubtitleStreamInfo->fdSub = -1;
|
||
|
}
|
||
|
|
||
|
mi->nSubtitleStreamNum = myMediaInfo->nSubtitleStreamNum;
|
||
|
}
|
||
|
|
||
|
return mi;
|
||
|
}
|
||
|
|
||
|
|
||
|
int DemuxCompStart(DemuxComp* d)
|
||
|
{
|
||
|
AwMessage msg;
|
||
|
DemuxCompContext* demux;
|
||
|
|
||
|
demux = (DemuxCompContext*)d;
|
||
|
|
||
|
if(demux->eStatus == DEMUX_STATUS_STARTED ||
|
||
|
demux->eStatus == DEMUX_STATUS_COMPLETE)
|
||
|
{
|
||
|
logv("demux component already in started or complete status.");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if(pthread_equal(pthread_self(), demux->threadId))
|
||
|
{
|
||
|
//* called from demux callback to awplayer.
|
||
|
if(demux->bSeeking)
|
||
|
{
|
||
|
demux->eStatus = DEMUX_STATUS_STARTED;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
//* send a start message.
|
||
|
setMessage(&msg,
|
||
|
DEMUX_COMMAND_START, //* message id.
|
||
|
(uintptr_t)&demux->semStart, //* params[0] = &SemStart.
|
||
|
(uintptr_t)&demux->nStartReply); //* params[1] = &nStartReply.
|
||
|
AwMessageQueuePostMessage(demux->mq, &msg);
|
||
|
SemTimedWait(&demux->semStart, -1);
|
||
|
return demux->nStartReply;
|
||
|
}
|
||
|
|
||
|
|
||
|
int DemuxCompStop(DemuxComp* d) //* close the data source, must call prepare again to restart.
|
||
|
{
|
||
|
AwMessage msg;
|
||
|
DemuxCompContext* demux;
|
||
|
|
||
|
demux = (DemuxCompContext*)d;
|
||
|
|
||
|
demux->bStopping = 1;
|
||
|
if(demux->pParser != NULL)
|
||
|
CdxParserForceStop(demux->pParser); //* quit from reading or seeking.
|
||
|
|
||
|
//* send a start message.
|
||
|
setMessage(&msg,
|
||
|
DEMUX_COMMAND_STOP, //* message id.
|
||
|
(uintptr_t)&demux->semStop, //* params[0] = &mSemStart.
|
||
|
(uintptr_t)&demux->nStopReply); //* params[1] = &mStartReply.
|
||
|
AwMessageQueuePostMessage(demux->mq, &msg);
|
||
|
SemTimedWait(&demux->semStop, -1);
|
||
|
return demux->nStopReply;
|
||
|
}
|
||
|
|
||
|
|
||
|
int DemuxCompPause(DemuxComp* d) //* no pause status in demux component, return OK immediately.
|
||
|
{
|
||
|
DemuxCompContext* demux;
|
||
|
|
||
|
demux = (DemuxCompContext*)d;
|
||
|
if(demux->eStatus != DEMUX_STATUS_STARTED)
|
||
|
{
|
||
|
logw("invalid pause operation, demux component not in started status.");
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
//* currently the demux component has no pause status, it will keep sending data until stopped.
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
int DemuxCompGetStatus(DemuxComp* d)
|
||
|
{
|
||
|
DemuxCompContext* demux;
|
||
|
demux = (DemuxCompContext*)d;
|
||
|
return demux->eStatus;
|
||
|
}
|
||
|
|
||
|
|
||
|
int DemuxCompSeekTo(DemuxComp* d, int mSec)
|
||
|
{
|
||
|
AwMessage msg;
|
||
|
DemuxCompContext* demux;
|
||
|
|
||
|
demux = (DemuxCompContext*)d;
|
||
|
|
||
|
demux->bCancelSeek = 0;
|
||
|
demux->bSeeking = 1;
|
||
|
if(demux->pParser != NULL && demux->eStatus == DEMUX_STATUS_STARTED)
|
||
|
CdxParserForceStop(demux->pParser); //* quit from reading.
|
||
|
|
||
|
//* send a start message.
|
||
|
setMessage(&msg, DEMUX_COMMAND_SEEK, 0, 0, mSec);
|
||
|
AwMessageQueuePostMessage(demux->mq, &msg);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
int DemuxCompCancelSeek(DemuxComp* d) //* should not call back DEMUX_SEEK_FINISH message.
|
||
|
{
|
||
|
AwMessage msg;
|
||
|
DemuxCompContext* demux;
|
||
|
|
||
|
demux = (DemuxCompContext*)d;
|
||
|
|
||
|
demux->bCancelSeek = 1;
|
||
|
if(demux->pParser != NULL)
|
||
|
CdxParserForceStop(demux->pParser);
|
||
|
|
||
|
//* send a prepare.
|
||
|
setMessage(&msg, DEMUX_COMMAND_CANCEL_SEEK, (uintptr_t)&demux->semCancelSeek); //* no reply.
|
||
|
AwMessageQueuePostMessage(demux->mq, &msg);
|
||
|
SemTimedWait(&demux->semCancelSeek, -1);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
int DemuxCompSetCacheStatReportInterval(DemuxComp* d, int ms)
|
||
|
{
|
||
|
DemuxCompContext* demux;
|
||
|
demux = (DemuxCompContext*)d;
|
||
|
demux->nCacheStatReportIntervalMs = ms;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
int DemuxCompSetCacheSize(DemuxComp* d, int nStartPlaySize, int nMaxBufferSize)
|
||
|
{
|
||
|
DemuxCompContext* demux;
|
||
|
demux = (DemuxCompContext*)d;
|
||
|
StreamCacheSetSize(demux->pCache, nStartPlaySize, nMaxBufferSize);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
static void* DemuxThread(void* arg)
|
||
|
{
|
||
|
AwMessage msg;
|
||
|
AwMessage newMsg;
|
||
|
int ret;
|
||
|
sem_t* pReplySem;
|
||
|
int* pReplyValue;
|
||
|
DemuxCompContext* demux;
|
||
|
|
||
|
demux = (DemuxCompContext*)arg;
|
||
|
|
||
|
while(1)
|
||
|
{
|
||
|
if(AwMessageQueueGetMessage(demux->mq, &msg) < 0)
|
||
|
{
|
||
|
loge("get message fail.");
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
process_message:
|
||
|
pReplySem = (sem_t*)msg.params[0];
|
||
|
pReplyValue = (int*)msg.params[1];
|
||
|
|
||
|
if(msg.messageId == DEMUX_COMMAND_SET_SOURCE)
|
||
|
{
|
||
|
logv("process message DEMUX_COMMAND_SET_SOURCE.");
|
||
|
|
||
|
demux->nSourceType = (int)msg.params[2];
|
||
|
|
||
|
if(demux->nSourceType == SOURCE_TYPE_URL)
|
||
|
{
|
||
|
//* data source of url path.
|
||
|
char* uri;
|
||
|
#if CONFIG_OS == OPTION_OS_ANDROID
|
||
|
KeyedVector<String8, String8>* pHeaders;
|
||
|
#else
|
||
|
map<string, string>* pHeaders;
|
||
|
#endif
|
||
|
|
||
|
uri = (char*)msg.params[3];
|
||
|
#if CONFIG_OS == OPTION_OS_ANDROID
|
||
|
pHeaders = (KeyedVector<String8, String8>*)msg.params[4];
|
||
|
#else
|
||
|
pHeaders = (map<string, string>*)msg.params[4];
|
||
|
#endif
|
||
|
|
||
|
if(setDataSourceFields(&demux->source, uri, pHeaders) == 0)
|
||
|
{
|
||
|
demux->eStatus = DEMUX_STATUS_INITIALIZED;
|
||
|
if(pReplyValue != NULL)
|
||
|
*pReplyValue = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
demux->eStatus = DEMUX_STATUS_IDLE;
|
||
|
if(pReplyValue != NULL)
|
||
|
*pReplyValue = -1;
|
||
|
}
|
||
|
|
||
|
if(pReplySem != NULL)
|
||
|
sem_post(pReplySem);
|
||
|
continue;
|
||
|
}
|
||
|
else if(demux->nSourceType == SOURCE_TYPE_FD)
|
||
|
{
|
||
|
//* data source is a file descriptor.
|
||
|
int fd;
|
||
|
int64_t nOffset;
|
||
|
int64_t nLength;
|
||
|
char str[128];
|
||
|
|
||
|
clearDataSourceFields(&demux->source);
|
||
|
|
||
|
fd = msg.params[3];
|
||
|
nOffset = msg.params[4];
|
||
|
nOffset<<=32;
|
||
|
nOffset |= msg.params[5];
|
||
|
nLength = msg.params[6];
|
||
|
nLength<<=32;
|
||
|
nLength |= msg.params[7];
|
||
|
|
||
|
sprintf(str, "fd://%d?offset=%lld&length=%lld", fd, nOffset, nLength);
|
||
|
demux->source.uri = strdup(str);
|
||
|
if(demux->source.uri != NULL)
|
||
|
{
|
||
|
demux->eStatus = DEMUX_STATUS_INITIALIZED;
|
||
|
if(pReplyValue != NULL)
|
||
|
*pReplyValue = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
loge("can not dump string to represent fd source.");
|
||
|
demux->eStatus = DEMUX_STATUS_IDLE;
|
||
|
if(pReplyValue != NULL)
|
||
|
*pReplyValue = -1;
|
||
|
}
|
||
|
|
||
|
if(pReplySem != NULL)
|
||
|
sem_post(pReplySem);
|
||
|
continue;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//* data source of IStringSource interface.
|
||
|
//* not supported in linux demo.
|
||
|
exit(1);
|
||
|
}
|
||
|
} //* end DEMUX_COMMAND_SET_SOURCE.
|
||
|
else if(msg.messageId == DEMUX_COMMAND_PREPARE)
|
||
|
{
|
||
|
int flags;
|
||
|
|
||
|
logv("process message DEMUX_COMMAND_PREPARE.");
|
||
|
|
||
|
if(demux->pParser != NULL)
|
||
|
{
|
||
|
//* should not run here, pParser should be NULL under INITIALIZED or STOPPED status.
|
||
|
logw("demux->pParser != NULL when DEMUX_COMMAND_PREPARE message received.");
|
||
|
CdxParserClose(demux->pParser);
|
||
|
demux->pParser = NULL;
|
||
|
}
|
||
|
|
||
|
flags = 0;
|
||
|
#if DEMO_CONFIG_DISABLE_SUBTITLE
|
||
|
flags |= DISABLE_SUBTITLE;
|
||
|
#endif
|
||
|
#if DEMO_CONFIG_DISABLE_AUDIO
|
||
|
flags |= DISABLE_AUDIO;
|
||
|
#endif
|
||
|
#if DEMO_CONFIG_DISABLE_VIDEO
|
||
|
flags |= DISABLE_VIDEO;
|
||
|
#endif
|
||
|
#if DEMO_CONFIG_DISALBE_MULTI_AUDIO
|
||
|
flags |= MUTIL_AUDIO;
|
||
|
#endif
|
||
|
ret = CdxParserPrepare(&demux->source, flags, &demux->mutex, &demux->bCancelPrepare,
|
||
|
&demux->pParser, &demux->pStream, NULL, NULL);
|
||
|
if(ret == 0)
|
||
|
{
|
||
|
CdxMediaInfoT parserMediaInfo;
|
||
|
memset(&parserMediaInfo, 0, sizeof(CdxMediaInfoT));
|
||
|
CdxParserGetMediaInfo(demux->pParser, &parserMediaInfo);
|
||
|
setMediaInfo(&demux->mediaInfo, &parserMediaInfo);
|
||
|
demux->mediaInfo.eContainerType = (enum ECONTAINER)demux->pParser->type;
|
||
|
|
||
|
demux->bEOS = 0;
|
||
|
demux->bIOError = 0;
|
||
|
|
||
|
if(demux->nSourceType == SOURCE_TYPE_URL)
|
||
|
{
|
||
|
if(strncasecmp(demux->source.uri, "file://", 7) == 0)
|
||
|
demux->bFileStream = 1;
|
||
|
else if(demux->mediaInfo.nDurationMs == 0) //* also demux->mediaInfo.nFileSize == 0.
|
||
|
demux->bLiveStream = 1;
|
||
|
else
|
||
|
demux->bVodStream = 1;
|
||
|
}
|
||
|
else if(demux->nSourceType == SOURCE_TYPE_FD)
|
||
|
demux->bFileStream = 1;
|
||
|
else
|
||
|
demux->bLiveStream = 1; //* treat IStreamSource(miracast) as a live stream.
|
||
|
|
||
|
if(demux->bFileStream == 0) //* start the cache trhead for netstream.
|
||
|
{
|
||
|
if(pthread_create(&demux->cacheThreadId, NULL, CacheThread, (void*)demux) == 0)
|
||
|
{
|
||
|
//* send a fetch message to start the cache loop.
|
||
|
setMessage(&newMsg, DEMUX_COMMAND_START);
|
||
|
AwMessageQueuePostMessage(demux->mqCache, &newMsg);
|
||
|
}
|
||
|
else
|
||
|
demux->cacheThreadId = 0;
|
||
|
}
|
||
|
|
||
|
demux->eStatus = DEMUX_STATUS_PREPARED;
|
||
|
demux->callback(demux->pUserData, DEMUX_NOTIFY_PREPARED, 0);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
demux->eStatus = DEMUX_STATUS_INITIALIZED;
|
||
|
if(demux->bCancelPrepare)
|
||
|
demux->callback(demux->pUserData, DEMUX_NOTIFY_PREPARED, (void*)DEMUX_ERROR_USER_CANCEL);
|
||
|
else
|
||
|
demux->callback(demux->pUserData, DEMUX_NOTIFY_PREPARED, (void*)DEMUX_ERROR_IO);
|
||
|
}
|
||
|
|
||
|
continue;
|
||
|
} //* end DEMUX_COMMAND_PREPARE.
|
||
|
else if(msg.messageId == DEMUX_COMMAND_START)
|
||
|
{
|
||
|
logv("process message DEMUX_COMMAND_START.");
|
||
|
|
||
|
if(demux->eStatus != DEMUX_STATUS_PREPARED)
|
||
|
{
|
||
|
loge("demux not in prepared or paused status when DEMUX_COMMAND_START message received.");
|
||
|
if(pReplyValue != NULL)
|
||
|
*pReplyValue = -1;
|
||
|
if(pReplySem != NULL)
|
||
|
sem_post(pReplySem);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
demux->eStatus = DEMUX_STATUS_STARTED;
|
||
|
//* send a read message to start the read loop.
|
||
|
setMessage(&newMsg, DEMUX_COMMAND_READ);
|
||
|
AwMessageQueuePostMessage(demux->mq, &newMsg);
|
||
|
if(pReplyValue != NULL)
|
||
|
*pReplyValue = 0;
|
||
|
if(pReplySem != NULL)
|
||
|
sem_post(pReplySem);
|
||
|
continue;
|
||
|
} //* end DEMUX_COMMAND_START
|
||
|
else if(msg.messageId == DEMUX_COMMAND_STOP)
|
||
|
{
|
||
|
logv("process message DEMUX_COMMAND_STOP.");
|
||
|
|
||
|
//* stop the cache thread.
|
||
|
if(demux->cacheThreadId != 0)
|
||
|
{
|
||
|
void* status;
|
||
|
setMessage(&newMsg, DEMUX_COMMAND_QUIT, (uintptr_t)&demux->semCache);
|
||
|
AwMessageQueuePostMessage(demux->mqCache, &newMsg);
|
||
|
SemTimedWait(&demux->semCache, -1);
|
||
|
pthread_join(demux->cacheThreadId, &status);
|
||
|
demux->cacheThreadId = 0;
|
||
|
}
|
||
|
|
||
|
if(demux->pParser != NULL)
|
||
|
{
|
||
|
CdxParserClose(demux->pParser);
|
||
|
demux->pParser = NULL;
|
||
|
}
|
||
|
|
||
|
demux->eStatus = DEMUX_STATUS_STOPPED;
|
||
|
demux->bStopping = 0;
|
||
|
if(pReplyValue != NULL)
|
||
|
*pReplyValue = 0;
|
||
|
if(pReplySem != NULL)
|
||
|
sem_post(pReplySem);
|
||
|
continue;
|
||
|
} //* end DEMUX_COMMAND_STOP.
|
||
|
else if(msg.messageId == DEMUX_COMMAND_QUIT || msg.messageId == DEMUX_COMMAND_CLEAR)
|
||
|
{
|
||
|
logv("process message DEMUX_COMMAND_QUIT or DEMUX_COMMAND_CLEAR.");
|
||
|
|
||
|
//* stop the cache thread if it is not stopped yet.
|
||
|
if(demux->cacheThreadId != 0)
|
||
|
{
|
||
|
void* status;
|
||
|
setMessage(&newMsg, DEMUX_COMMAND_QUIT, (uintptr_t)&demux->semCache);
|
||
|
AwMessageQueuePostMessage(demux->mqCache, &newMsg);
|
||
|
SemTimedWait(&demux->semCache, -1);
|
||
|
pthread_join(demux->cacheThreadId, &status);
|
||
|
demux->cacheThreadId = 0;
|
||
|
}
|
||
|
|
||
|
if(demux->pParser != NULL)
|
||
|
{
|
||
|
CdxParserClose(demux->pParser);
|
||
|
demux->pParser = NULL;
|
||
|
}
|
||
|
|
||
|
clearDataSourceFields(&demux->source);
|
||
|
demux->eStatus = DEMUX_STATUS_IDLE;
|
||
|
demux->bStopping = 0;
|
||
|
|
||
|
if(pReplyValue != NULL)
|
||
|
*pReplyValue = 0;
|
||
|
if(pReplySem != NULL)
|
||
|
sem_post(pReplySem);
|
||
|
if(msg.messageId == DEMUX_COMMAND_QUIT)
|
||
|
break; //* quit the thread.
|
||
|
|
||
|
continue;
|
||
|
} //* end DEMUX_COMMAND_QUIT or DEMUX_COMMAND_CLEAR.
|
||
|
else if(msg.messageId == DEMUX_COMMAND_SEEK)
|
||
|
{
|
||
|
int mSeekTimeMs;
|
||
|
int params[2];
|
||
|
|
||
|
logv("process message DEMUX_COMMAND_SEEK.");
|
||
|
|
||
|
mSeekTimeMs = msg.params[2];
|
||
|
if(demux->pParser != NULL)
|
||
|
{
|
||
|
//* flush the cache.
|
||
|
if(demux->cacheThreadId != 0)
|
||
|
{
|
||
|
setMessage(&newMsg, DEMUX_COMMAND_PAUSE, (uintptr_t)&demux->semCache);
|
||
|
AwMessageQueuePostMessage(demux->mqCache, &newMsg);
|
||
|
SemTimedWait(&demux->semCache, -1);
|
||
|
setMessage(&newMsg, DEMUX_COMMAND_SEEK, (uintptr_t)&demux->semCache);
|
||
|
AwMessageQueuePostMessage(demux->mqCache, &newMsg);
|
||
|
SemTimedWait(&demux->semCache, -1);
|
||
|
}
|
||
|
|
||
|
if(demux->bCancelSeek == 0 && demux->bStopping == 0)
|
||
|
{
|
||
|
ret = CdxParserClrForceStop(demux->pParser);
|
||
|
if(ret < 0)
|
||
|
{
|
||
|
logw("CdxParserClrForceStop fail, ret(%d)", ret);
|
||
|
}
|
||
|
}
|
||
|
ret = CdxParserSeekTo(demux->pParser, ((int64_t)mSeekTimeMs) * 1000);
|
||
|
if(ret == 0)
|
||
|
{
|
||
|
params[0] = 0;
|
||
|
params[1] = mSeekTimeMs;
|
||
|
demux->callback(demux->pUserData, DEMUX_NOTIFY_SEEK_FINISH, (void*)params);
|
||
|
|
||
|
demux->bSeeking = 0;
|
||
|
demux->bEOS = 0;
|
||
|
demux->bIOError = 0;
|
||
|
|
||
|
//* send a flush message to the cache thread.
|
||
|
if(demux->cacheThreadId != 0)
|
||
|
{
|
||
|
setMessage(&newMsg, DEMUX_COMMAND_START, (uintptr_t)&demux->semCache);
|
||
|
AwMessageQueuePostMessage(demux->mqCache, &newMsg);
|
||
|
SemTimedWait(&demux->semCache, -1);
|
||
|
}
|
||
|
if(demux->eStatus == DEMUX_STATUS_COMPLETE)
|
||
|
{
|
||
|
demux->eStatus = DEMUX_STATUS_STARTED;
|
||
|
}
|
||
|
|
||
|
if(demux->eStatus == DEMUX_STATUS_STARTED)
|
||
|
{
|
||
|
setMessage(&newMsg, DEMUX_COMMAND_READ); //* send read message to start reading loop.
|
||
|
AwMessageQueuePostMessage(demux->mq, &newMsg);
|
||
|
}
|
||
|
|
||
|
if(pReplyValue != NULL)
|
||
|
*pReplyValue = 0;
|
||
|
if(pReplySem != NULL)
|
||
|
sem_post(pReplySem);
|
||
|
continue;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
loge("CdxParserSeekTo() return fail");
|
||
|
demux->eStatus = DEMUX_STATUS_COMPLETE; //* set to complete status to stop reading.
|
||
|
demux->bSeeking = 0;
|
||
|
if(demux->bCancelSeek == 1 || demux->bStopping == 1)
|
||
|
params[0] = DEMUX_ERROR_USER_CANCEL;
|
||
|
else
|
||
|
params[0] = DEMUX_ERROR_IO;
|
||
|
params[1] = mSeekTimeMs;
|
||
|
demux->callback(demux->pUserData, DEMUX_NOTIFY_SEEK_FINISH, (void*)params);
|
||
|
|
||
|
if(pReplyValue != NULL)
|
||
|
*pReplyValue = -1;
|
||
|
if(pReplySem != NULL)
|
||
|
sem_post(pReplySem);
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
params[0] = DEMUX_ERROR_UNKNOWN;
|
||
|
params[1] = mSeekTimeMs;
|
||
|
demux->bSeeking = 0;
|
||
|
demux->callback(demux->pUserData, DEMUX_NOTIFY_SEEK_FINISH, (void*)params);
|
||
|
|
||
|
if(pReplyValue != NULL)
|
||
|
*pReplyValue = -1;
|
||
|
if(pReplySem != NULL)
|
||
|
sem_post(pReplySem);
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
else if(msg.messageId == DEMUX_COMMAND_CANCEL_PREPARE)
|
||
|
{
|
||
|
logv("process message DEMUX_COMMAND_CANCEL_PREPARE.");
|
||
|
|
||
|
demux->bCancelPrepare = 0;
|
||
|
if(pReplyValue != NULL)
|
||
|
*pReplyValue = 0;
|
||
|
if(pReplySem != NULL)
|
||
|
sem_post(pReplySem);
|
||
|
continue;
|
||
|
}
|
||
|
else if(msg.messageId == DEMUX_COMMAND_CANCEL_SEEK)
|
||
|
{
|
||
|
logv("process message DEMUX_COMMAND_CANCEL_SEEK.");
|
||
|
demux->bCancelSeek = 0;
|
||
|
if(pReplyValue != NULL)
|
||
|
*pReplyValue = 0;
|
||
|
if(pReplySem != NULL)
|
||
|
sem_post(pReplySem);
|
||
|
continue;
|
||
|
}
|
||
|
else if(msg.messageId == DEMUX_COMMAND_READ)
|
||
|
{
|
||
|
logv("process message DEMUX_COMMAND_READ.");
|
||
|
|
||
|
if(demux->eStatus != DEMUX_STATUS_STARTED)
|
||
|
{
|
||
|
logw("demux component not in started status, ignore read message.");
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if(demux->cacheThreadId != 0)
|
||
|
{
|
||
|
//**************************************************************
|
||
|
//* read data from cache.
|
||
|
//**************************************************************
|
||
|
|
||
|
if(demux->bBufferring)
|
||
|
{
|
||
|
//* the player is paused and caching stream data.
|
||
|
|
||
|
logv("buffering, wait...");
|
||
|
//* wait some time for caching.
|
||
|
ret = AwMessageQueueTryGetMessage(demux->mq, &msg, 100); //* wait for 100ms if no message come.
|
||
|
if(ret == 0) //* new message come, quit loop to process.
|
||
|
goto process_message;
|
||
|
|
||
|
//* check whether data in cache is enough for play.
|
||
|
if(StreamCacheDataEnough(demux->pCache) || demux->bEOS || demux->bIOError)
|
||
|
{
|
||
|
logv("detect data enough, notify BUFFER_END.");
|
||
|
demux->bBufferring = 0;
|
||
|
demux->callback(demux->pUserData, DEMUX_NOTIFY_BUFFER_END, NULL);
|
||
|
}
|
||
|
|
||
|
//* post a read message to continue the reading job.
|
||
|
setMessage(&newMsg, DEMUX_COMMAND_READ);
|
||
|
AwMessageQueuePostMessage(demux->mq, &newMsg);
|
||
|
continue;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//* check whether cache underflow.
|
||
|
if(StreamCacheUnderflow(demux->pCache))
|
||
|
{
|
||
|
logv("detect cache data underflow.");
|
||
|
//* cache underflow, if not eos, we need to notify pausing,
|
||
|
//* otherwise we need to notify complete.
|
||
|
if(demux->bEOS)
|
||
|
{
|
||
|
//* end of stream, notify complete.
|
||
|
logv("detect eos, notify EOS.");
|
||
|
demux->callback(demux->pUserData, DEMUX_NOTIFY_EOS, 0);
|
||
|
demux->eStatus = DEMUX_STATUS_COMPLETE;
|
||
|
continue;
|
||
|
}
|
||
|
else if(demux->bIOError)
|
||
|
{
|
||
|
logv("detect io error, notify IOERROR.");
|
||
|
//* end of stream, notify complete.
|
||
|
demux->callback(demux->pUserData, DEMUX_NOTIFY_IOERROR, 0);
|
||
|
continue;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//* no data in cache, check whether player hold enough data,
|
||
|
//* if not, we need to notify pausing to wait for caching
|
||
|
//* more data for player.
|
||
|
if(PlayerBufferUnderflow(demux->pPlayer))
|
||
|
{
|
||
|
logv("detect player data underflow, notify BUFFER_START.");
|
||
|
demux->bBufferring = 1;
|
||
|
demux->callback(demux->pUserData, DEMUX_NOTIFY_BUFFER_START, NULL);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//* wait some time for caching.
|
||
|
ret = AwMessageQueueTryGetMessage(demux->mq, &msg, 50); //* wait for 50ms if no message come.
|
||
|
if(ret == 0) //* new message come, quit loop to process.
|
||
|
goto process_message;
|
||
|
}
|
||
|
|
||
|
//* post a read message to continue the reading job.
|
||
|
setMessage(&newMsg, DEMUX_COMMAND_READ);
|
||
|
AwMessageQueuePostMessage(demux->mq, &newMsg);
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//* there is some data in cache for player.
|
||
|
//* if data in player is not too much, send it to player,
|
||
|
//* otherwise, just keep it in the cache.
|
||
|
if(PlayerBufferOverflow(demux->pPlayer))
|
||
|
{
|
||
|
logv("detect player data overflow.");
|
||
|
//* too much data in player, wait some time.
|
||
|
ret = AwMessageQueueTryGetMessage(demux->mq, &msg, 200); //* wait for 200ms if no message come.
|
||
|
if(ret == 0) //* new message come, quit loop to process.
|
||
|
goto process_message;
|
||
|
|
||
|
//* post a read message to continue the reading job.
|
||
|
setMessage(&newMsg, DEMUX_COMMAND_READ);
|
||
|
AwMessageQueuePostMessage(demux->mq, &newMsg);
|
||
|
continue;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//*************************************
|
||
|
//* send data from cache to player.
|
||
|
//*************************************
|
||
|
CacheNode* node;
|
||
|
enum EMEDIATYPE ePlayerMediaType;
|
||
|
MediaStreamDataInfo streamDataInfo;
|
||
|
int nStreamIndex;
|
||
|
void* pBuf0;
|
||
|
void* pBuf1;
|
||
|
int nBufSize0;
|
||
|
int nBufSize1;
|
||
|
|
||
|
//********************************
|
||
|
//* 1. get one frame from cache.
|
||
|
//********************************
|
||
|
node = StreamCacheNextFrame(demux->pCache);
|
||
|
if(node == NULL)
|
||
|
{
|
||
|
loge("Cache not underflow but cannot get stream frame, shouldn't run here.");
|
||
|
abort();
|
||
|
}
|
||
|
|
||
|
//********************************
|
||
|
//* 2. request buffer from player.
|
||
|
//********************************
|
||
|
if(node->eMediaType == CDX_MEDIA_VIDEO)
|
||
|
{
|
||
|
ePlayerMediaType = MEDIA_TYPE_VIDEO;
|
||
|
nStreamIndex = (node->nFlags&MINOR_STREAM)==0 ? 0 : 1;
|
||
|
}
|
||
|
else if(node->eMediaType == CDX_MEDIA_AUDIO)
|
||
|
{
|
||
|
ePlayerMediaType = MEDIA_TYPE_AUDIO;
|
||
|
nStreamIndex = node->nStreamIndex;
|
||
|
}
|
||
|
else if(node->eMediaType == CDX_MEDIA_SUBTITLE)
|
||
|
{
|
||
|
ePlayerMediaType = MEDIA_TYPE_SUBTITLE;
|
||
|
nStreamIndex = node->nStreamIndex;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
loge("media type from parser not valid, should not run here, abort().");
|
||
|
abort();
|
||
|
}
|
||
|
|
||
|
if(ePlayerMediaType == MEDIA_TYPE_VIDEO || ePlayerMediaType == MEDIA_TYPE_AUDIO)
|
||
|
{
|
||
|
while(1)
|
||
|
{
|
||
|
ret = PlayerRequestStreamBuffer(demux->pPlayer,
|
||
|
node->nLength,
|
||
|
&pBuf0,
|
||
|
&nBufSize0,
|
||
|
&pBuf1,
|
||
|
&nBufSize1,
|
||
|
ePlayerMediaType,
|
||
|
nStreamIndex);
|
||
|
if(ret<0 || (nBufSize0+nBufSize1)<node->nLength)
|
||
|
{
|
||
|
logi("waiting for stream buffer.");
|
||
|
//* no buffer, try to wait sometime.
|
||
|
ret = AwMessageQueueTryGetMessage(demux->mq, &msg, 200); //* wait for 200ms if no message come.
|
||
|
if(ret == 0) //* new message come, quit loop to process.
|
||
|
goto process_message;
|
||
|
}
|
||
|
else
|
||
|
break; //* get buffer ok.
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//* request buffer from text decoder.
|
||
|
//* TODO.
|
||
|
loge("do not support subtitle yet, abort().");
|
||
|
abort();
|
||
|
}
|
||
|
|
||
|
//**********************************************
|
||
|
//* 3. copy data to player's buffer and submit.
|
||
|
//**********************************************
|
||
|
if(node->nLength > nBufSize0)
|
||
|
{
|
||
|
memcpy(pBuf0, node->pData, nBufSize0);
|
||
|
memcpy(pBuf1, node->pData + nBufSize0, node->nLength-nBufSize0);
|
||
|
}
|
||
|
else
|
||
|
memcpy(pBuf0, node->pData, node->nLength);
|
||
|
|
||
|
streamDataInfo.pData = (char*)pBuf0;
|
||
|
streamDataInfo.nLength = node->nLength;
|
||
|
streamDataInfo.nPts = node->nPts;
|
||
|
streamDataInfo.nPcr = -1;
|
||
|
streamDataInfo.bIsFirstPart = 1;
|
||
|
streamDataInfo.bIsLastPart = 1;
|
||
|
|
||
|
PlayerSubmitStreamData(demux->pPlayer, &streamDataInfo, ePlayerMediaType, nStreamIndex);
|
||
|
|
||
|
StreamCacheFlushOneFrame(demux->pCache);
|
||
|
|
||
|
//* post a read message to continue the reading job.
|
||
|
setMessage(&newMsg, DEMUX_COMMAND_READ);
|
||
|
AwMessageQueuePostMessage(demux->mq, &newMsg);
|
||
|
continue;
|
||
|
} //* end if(PlayerBufferOverflow(...)){}else {}
|
||
|
} //* end if(StreamCacheUnderflow(...)){}else {}
|
||
|
} //* end if(demux->bBufferring){}else {}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//**************************************************************
|
||
|
//* read data directly from parser.
|
||
|
//**************************************************************
|
||
|
|
||
|
CdxPacketT packet;
|
||
|
enum EMEDIATYPE ePlayerMediaType;
|
||
|
MediaStreamDataInfo streamDataInfo;
|
||
|
int nStreamIndex;
|
||
|
|
||
|
//* if data in player is not too much, send it to player,
|
||
|
//* otherwise don't read.
|
||
|
if(PlayerBufferOverflow(demux->pPlayer))
|
||
|
{
|
||
|
//* too much data in player, wait some time.
|
||
|
ret = AwMessageQueueTryGetMessage(demux->mq, &msg, 200); //* wait for 200ms if no message come.
|
||
|
if(ret == 0) //* new message come, quit loop to process.
|
||
|
goto process_message;
|
||
|
|
||
|
//* post a read message to continue the reading job.
|
||
|
setMessage(&newMsg, DEMUX_COMMAND_READ);
|
||
|
AwMessageQueuePostMessage(demux->mq, &newMsg);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
//* 1. get data type.
|
||
|
if(CdxParserPrefetch(demux->pParser, &packet) != 0)
|
||
|
{
|
||
|
if(demux->bStopping == 0 && demux->bSeeking == 0)
|
||
|
{
|
||
|
int err = CdxParserGetStatus(demux->pParser);
|
||
|
|
||
|
if(err == PSR_IO_ERR)
|
||
|
{
|
||
|
demux->bIOError = 1;
|
||
|
demux->callback(demux->pUserData, DEMUX_NOTIFY_IOERROR, 0);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
demux->bEOS = 1;
|
||
|
demux->callback(demux->pUserData, DEMUX_NOTIFY_EOS, 0);
|
||
|
demux->eStatus = DEMUX_STATUS_COMPLETE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
//* 2. request buffer from player.
|
||
|
if(packet.type == CDX_MEDIA_VIDEO)
|
||
|
{
|
||
|
ePlayerMediaType = MEDIA_TYPE_VIDEO;
|
||
|
nStreamIndex = (packet.flags&MINOR_STREAM)==0 ? 0 : 1;
|
||
|
}
|
||
|
else if(packet.type == CDX_MEDIA_AUDIO)
|
||
|
{
|
||
|
ePlayerMediaType = MEDIA_TYPE_AUDIO;
|
||
|
nStreamIndex = packet.streamIndex;
|
||
|
}
|
||
|
else if(packet.type == CDX_MEDIA_SUBTITLE)
|
||
|
{
|
||
|
ePlayerMediaType = MEDIA_TYPE_SUBTITLE;
|
||
|
nStreamIndex = packet.streamIndex;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
loge("media type from parser not valid, should not run here, abort().");
|
||
|
abort();
|
||
|
}
|
||
|
|
||
|
if(ePlayerMediaType == MEDIA_TYPE_VIDEO || ePlayerMediaType == MEDIA_TYPE_AUDIO || ePlayerMediaType == MEDIA_TYPE_SUBTITLE)
|
||
|
{
|
||
|
while(1)
|
||
|
{
|
||
|
if((!DEMO_CONFIG_DISABLE_VIDEO && ePlayerMediaType == MEDIA_TYPE_VIDEO) ||
|
||
|
(!DEMO_CONFIG_DISABLE_AUDIO && ePlayerMediaType == MEDIA_TYPE_AUDIO) ||
|
||
|
(!DEMO_CONFIG_DISABLE_SUBTITLE && ePlayerMediaType == MEDIA_TYPE_SUBTITLE))
|
||
|
{
|
||
|
ret = PlayerRequestStreamBuffer(demux->pPlayer,
|
||
|
packet.length,
|
||
|
&packet.buf,
|
||
|
&packet.buflen,
|
||
|
&packet.ringBuf,
|
||
|
&packet.ringBufLen,
|
||
|
ePlayerMediaType,
|
||
|
nStreamIndex);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//* allocate a buffer to read uncare media data and skip it.
|
||
|
packet.buf = malloc(packet.length);
|
||
|
if(packet.buf != NULL)
|
||
|
{
|
||
|
packet.buflen = packet.length;
|
||
|
packet.ringBuf = NULL;
|
||
|
packet.ringBufLen = 0;
|
||
|
ret = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
packet.buflen = 0;
|
||
|
packet.ringBuf = NULL;
|
||
|
packet.ringBufLen = 0;
|
||
|
ret = -1;
|
||
|
}
|
||
|
}
|
||
|
if(ret<0 || (packet.buflen+packet.ringBufLen)<packet.length)
|
||
|
{
|
||
|
logi("waiting for stream buffer.");
|
||
|
//* no buffer, try to wait sometime.
|
||
|
ret = AwMessageQueueTryGetMessage(demux->mq, &msg, 200); //* wait for 200ms if no message come.
|
||
|
if(ret == 0) //* new message come, quit loop to process.
|
||
|
goto process_message;
|
||
|
}
|
||
|
else
|
||
|
break; //* get buffer ok.
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//* request buffer from text decoder.
|
||
|
//* TODO.
|
||
|
loge("do not support subtitle yet, abort().");
|
||
|
abort();
|
||
|
}
|
||
|
|
||
|
//* 3. read data to buffer and submit.
|
||
|
ret = CdxParserRead(demux->pParser, &packet);
|
||
|
if(ret == 0)
|
||
|
{
|
||
|
if((!DEMO_CONFIG_DISABLE_VIDEO && ePlayerMediaType == MEDIA_TYPE_VIDEO) ||
|
||
|
(!DEMO_CONFIG_DISABLE_AUDIO && ePlayerMediaType == MEDIA_TYPE_AUDIO) ||
|
||
|
(!DEMO_CONFIG_DISABLE_SUBTITLE && ePlayerMediaType == MEDIA_TYPE_SUBTITLE))
|
||
|
{
|
||
|
streamDataInfo.pData = (char*)packet.buf;
|
||
|
streamDataInfo.nLength = packet.length;
|
||
|
streamDataInfo.nPts = packet.pts;
|
||
|
streamDataInfo.nPcr = -1;
|
||
|
streamDataInfo.bIsFirstPart = 1;
|
||
|
streamDataInfo.bIsLastPart = 1;
|
||
|
PlayerSubmitStreamData(demux->pPlayer, &streamDataInfo, ePlayerMediaType, nStreamIndex);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//* skip the media data.
|
||
|
free(packet.buf);
|
||
|
}
|
||
|
|
||
|
//* post a read message to continue the reading job after message processed.
|
||
|
setMessage(&newMsg, DEMUX_COMMAND_READ);
|
||
|
AwMessageQueuePostMessage(demux->mq, &newMsg);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
logw("read data from parser return fail.");
|
||
|
if(demux->bStopping == 0 && demux->bSeeking == 0)
|
||
|
{
|
||
|
int err = CdxParserGetStatus(demux->pParser);
|
||
|
|
||
|
if(err == PSR_IO_ERR)
|
||
|
{
|
||
|
demux->bIOError = 1;
|
||
|
demux->callback(demux->pUserData, DEMUX_NOTIFY_IOERROR, 0);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
demux->bEOS = 1;
|
||
|
demux->callback(demux->pUserData, DEMUX_NOTIFY_EOS, 0);
|
||
|
demux->eStatus = DEMUX_STATUS_COMPLETE;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
logw("unknow message with id %d, ignore.", msg.messageId);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
|
||
|
static void* CacheThread(void* arg)
|
||
|
{
|
||
|
AwMessage msg;
|
||
|
int ret;
|
||
|
sem_t* pReplySem;
|
||
|
int* pReplyValue;
|
||
|
DemuxCompContext* demux;
|
||
|
int eCacheStatus;
|
||
|
|
||
|
demux = (DemuxCompContext*)arg;
|
||
|
eCacheStatus = DEMUX_STATUS_STOPPED;
|
||
|
|
||
|
while(1)
|
||
|
{
|
||
|
if(AwMessageQueueGetMessage(demux->mqCache, &msg) < 0)
|
||
|
{
|
||
|
loge("get message fail.");
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
cache_process_message:
|
||
|
pReplySem = (sem_t*)msg.params[0];
|
||
|
pReplyValue = (int*)msg.params[1];
|
||
|
|
||
|
if(msg.messageId == DEMUX_COMMAND_START)
|
||
|
{
|
||
|
logv("cache thread process message DEMUX_COMMAND_START.");
|
||
|
|
||
|
eCacheStatus = DEMUX_STATUS_STARTED;
|
||
|
setMessage(&msg, DEMUX_COMMAND_READ);
|
||
|
AwMessageQueuePostMessage(demux->mqCache, &msg);
|
||
|
if(pReplyValue != NULL)
|
||
|
*pReplyValue = 0;
|
||
|
if(pReplySem != NULL)
|
||
|
sem_post(pReplySem);
|
||
|
continue;
|
||
|
} //* end DEMUX_COMMAND_START
|
||
|
else if(msg.messageId == DEMUX_COMMAND_PAUSE || msg.messageId == DEMUX_COMMAND_STOP)
|
||
|
{
|
||
|
logv("cache thread process message DEMUX_COMMAND_PAUSE or DEMUX_COMMAND_STOP.");
|
||
|
|
||
|
eCacheStatus = DEMUX_STATUS_STOPPED;
|
||
|
if(pReplyValue != NULL)
|
||
|
*pReplyValue = 0;
|
||
|
if(pReplySem != NULL)
|
||
|
sem_post(pReplySem);
|
||
|
continue;
|
||
|
} //* end DEMUX_COMMAND_STOP.
|
||
|
else if(msg.messageId == DEMUX_COMMAND_QUIT)
|
||
|
{
|
||
|
logv("cache thread process message DEMUX_COMMAND_QUIT.");
|
||
|
StreamCacheFlushAll(demux->pCache);
|
||
|
if(pReplyValue != NULL)
|
||
|
*pReplyValue = 0;
|
||
|
if(pReplySem != NULL)
|
||
|
sem_post(pReplySem);
|
||
|
|
||
|
break; //* quit the thread.
|
||
|
|
||
|
} //* end DEMUX_COMMAND_QUIT.
|
||
|
else if(msg.messageId == DEMUX_COMMAND_SEEK)
|
||
|
{
|
||
|
logv("cache thread process message DEMUX_COMMAND_SEEK.");
|
||
|
|
||
|
StreamCacheFlushAll(demux->pCache);
|
||
|
|
||
|
if(pReplyValue != NULL)
|
||
|
*pReplyValue = 0;
|
||
|
if(pReplySem != NULL)
|
||
|
sem_post(pReplySem);
|
||
|
continue;
|
||
|
}
|
||
|
else if(msg.messageId == DEMUX_COMMAND_READ)
|
||
|
{
|
||
|
if(eCacheStatus != DEMUX_STATUS_STARTED)
|
||
|
continue;
|
||
|
|
||
|
logi("cache thread process message DEMUX_COMMAND_READ.");
|
||
|
|
||
|
if(StreamCacheOverflow(demux->pCache))
|
||
|
{
|
||
|
//* wait some time for cache buffer.
|
||
|
ret = AwMessageQueueTryGetMessage(demux->mqCache, &msg, 200); //* wait for 200ms if no message come.
|
||
|
if(ret == 0) //* new message come, quit loop to process.
|
||
|
goto cache_process_message;
|
||
|
|
||
|
//* post a read message to continue the reading job after message processed.
|
||
|
setMessage(&msg, DEMUX_COMMAND_READ);
|
||
|
AwMessageQueuePostMessage(demux->mqCache, &msg);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//**************************************************************
|
||
|
//* read data directly from parser.
|
||
|
//**************************************************************
|
||
|
|
||
|
CdxPacketT packet;
|
||
|
CacheNode node;
|
||
|
|
||
|
//* 1. get data type.
|
||
|
if(CdxParserPrefetch(demux->pParser, &packet) != 0)
|
||
|
{
|
||
|
logw("prefetch fail.");
|
||
|
if(demux->bStopping == 0 && demux->bSeeking == 0)
|
||
|
{
|
||
|
int err = CdxParserGetStatus(demux->pParser);
|
||
|
|
||
|
if(err == PSR_IO_ERR)
|
||
|
demux->bIOError = 1;
|
||
|
else
|
||
|
demux->bEOS = 1;
|
||
|
}
|
||
|
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
//* 2. request cache buffer.
|
||
|
while(1)
|
||
|
{
|
||
|
node.pData = (unsigned char*)malloc(packet.length);
|
||
|
if(node.pData == NULL)
|
||
|
{
|
||
|
logw("allocate memory for cache node fail, waiting for memory.");
|
||
|
//* no free memory, try to wait sometime.
|
||
|
ret = AwMessageQueueTryGetMessage(demux->mqCache, &msg, 200); //* wait for 200ms if no message come.
|
||
|
if(ret == 0) //* new message come, quit loop to process.
|
||
|
goto cache_process_message;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
packet.buf = node.pData;
|
||
|
packet.buflen = packet.length;
|
||
|
packet.ringBuf = NULL;
|
||
|
packet.ringBufLen = 0;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//* 3. read data to buffer and submit.
|
||
|
ret = CdxParserRead(demux->pParser, &packet);
|
||
|
if(ret == 0)
|
||
|
{
|
||
|
node.pNext = NULL;
|
||
|
node.nLength = packet.length;
|
||
|
node.eMediaType = packet.type;
|
||
|
node.nStreamIndex = packet.streamIndex;
|
||
|
node.nFlags = packet.flags;
|
||
|
node.nPts = packet.pts;
|
||
|
node.nPcr = -1;
|
||
|
node.bIsFirstPart = 1;
|
||
|
node.bIsLastPart = 1;
|
||
|
|
||
|
StreamCacheAddOneFrame(demux->pCache, &node);
|
||
|
|
||
|
//* post a read message to continue the reading job after message processed.
|
||
|
setMessage(&msg, DEMUX_COMMAND_READ);
|
||
|
AwMessageQueuePostMessage(demux->mqCache, &msg);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
logw("read data from parser return fail.");
|
||
|
if(node.pData != NULL)
|
||
|
free(node.pData);
|
||
|
|
||
|
if(demux->bStopping == 0 && demux->bSeeking == 0)
|
||
|
{
|
||
|
int err = CdxParserGetStatus(demux->pParser);
|
||
|
|
||
|
if(err == PSR_IO_ERR)
|
||
|
demux->bIOError = 1;
|
||
|
else
|
||
|
demux->bEOS = 1;
|
||
|
}
|
||
|
}
|
||
|
continue;
|
||
|
} //* end if(StreamCacheOverflow(demux->pCache)) {} else {}
|
||
|
} //* end DEMUX_COMMAND_READ.
|
||
|
} //* end while(1).
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
|
||
|
static void clearDataSourceFields(CdxDataSourceT* source)
|
||
|
{
|
||
|
CdxHttpHeaderFieldsT* pHttpHeaders;
|
||
|
int i;
|
||
|
int nHeaderSize;
|
||
|
|
||
|
if(source->uri != NULL)
|
||
|
{
|
||
|
free(source->uri);
|
||
|
source->uri = NULL;
|
||
|
}
|
||
|
|
||
|
if(source->extraDataType == EXTRA_DATA_HTTP_HEADER &&
|
||
|
source->extraData != NULL)
|
||
|
{
|
||
|
pHttpHeaders = (CdxHttpHeaderFieldsT*)source->extraData;
|
||
|
nHeaderSize = pHttpHeaders->num;
|
||
|
|
||
|
for(i=0; i<nHeaderSize; i++)
|
||
|
{
|
||
|
if(pHttpHeaders->pHttpHeader[i].key != NULL)
|
||
|
free((void*)pHttpHeaders->pHttpHeader[i].key);
|
||
|
if(pHttpHeaders->pHttpHeader[i].val != NULL)
|
||
|
free((void*)pHttpHeaders->pHttpHeader[i].val);
|
||
|
}
|
||
|
|
||
|
free(pHttpHeaders->pHttpHeader);
|
||
|
free(pHttpHeaders);
|
||
|
source->extraData = NULL;
|
||
|
source->extraDataType = EXTRA_DATA_UNKNOWN;
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
#if CONFIG_OS == OPTION_OS_ANDROID
|
||
|
static int setDataSourceFields(CdxDataSourceT* source, char* uri, KeyedVector<String8,String8>* pHeaders)
|
||
|
#else
|
||
|
static int setDataSourceFields(CdxDataSourceT* source, char* uri, map<string,string>* pHeaders)
|
||
|
#endif
|
||
|
{
|
||
|
CdxHttpHeaderFieldsT* pHttpHeaders;
|
||
|
int i;
|
||
|
int nHeaderSize;
|
||
|
|
||
|
clearDataSourceFields(source);
|
||
|
|
||
|
if(uri != NULL)
|
||
|
{
|
||
|
//* check whether ths uri has a scheme.
|
||
|
if(strstr(uri, "://") != NULL)
|
||
|
{
|
||
|
source->uri = strdup(uri);
|
||
|
if(source->uri == NULL)
|
||
|
{
|
||
|
loge("can not dump string of uri.");
|
||
|
return -1;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
source->uri = (char*)malloc(strlen(uri)+8);
|
||
|
if(source->uri == NULL)
|
||
|
{
|
||
|
loge("can not dump string of uri.");
|
||
|
return -1;
|
||
|
}
|
||
|
sprintf(source->uri, "file://%s", uri);
|
||
|
}
|
||
|
|
||
|
if(pHeaders != NULL && (!strncasecmp("http://", uri, 7) || !strncasecmp("https://", uri, 8)))
|
||
|
{
|
||
|
#if CONFIG_OS == OPTION_OS_ANDROID
|
||
|
String8 key;
|
||
|
String8 value;
|
||
|
char* str;
|
||
|
|
||
|
i = pHeaders->indexOfKey(String8("x-hide-urls-from-log"));
|
||
|
if(i >= 0)
|
||
|
pHeaders->removeItemsAt(i);
|
||
|
#else
|
||
|
string key;
|
||
|
string value;
|
||
|
char* str;
|
||
|
map<string, string>::iterator it;
|
||
|
|
||
|
it = pHeaders->find(string("x-hide-urls-from-log"));
|
||
|
if(it != pHeaders->end())
|
||
|
pHeaders->erase(it);
|
||
|
#endif
|
||
|
nHeaderSize = pHeaders->size();
|
||
|
if(nHeaderSize > 0)
|
||
|
{
|
||
|
pHttpHeaders = (CdxHttpHeaderFieldsT*)malloc(sizeof(CdxHttpHeaderFieldsT));
|
||
|
if(pHttpHeaders == NULL)
|
||
|
{
|
||
|
loge("can not malloc memory for http header.");
|
||
|
clearDataSourceFields(source);
|
||
|
return -1;
|
||
|
}
|
||
|
memset(pHttpHeaders, 0, sizeof(CdxHttpHeaderFieldsT));
|
||
|
pHttpHeaders->num = nHeaderSize;
|
||
|
|
||
|
pHttpHeaders->pHttpHeader = (CdxHttpHeaderFieldT*)malloc(sizeof(CdxHttpHeaderFieldT)*nHeaderSize);
|
||
|
if(pHttpHeaders->pHttpHeader == NULL)
|
||
|
{
|
||
|
loge("can not malloc memory for http header.");
|
||
|
free(pHttpHeaders);
|
||
|
clearDataSourceFields(source);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
source->extraData = (void*)pHttpHeaders;
|
||
|
source->extraDataType = EXTRA_DATA_HTTP_HEADER;
|
||
|
|
||
|
#if CONFIG_OS == OPTION_OS_ANDROID
|
||
|
for(i=0; i<nHeaderSize; i++)
|
||
|
#else
|
||
|
i = 0;
|
||
|
for(it=pHeaders->begin(); it!=pHeaders->end(); ++it)
|
||
|
#endif
|
||
|
{
|
||
|
#if CONFIG_OS == OPTION_OS_ANDROID
|
||
|
key = pHeaders->keyAt(i);
|
||
|
value = pHeaders->valueAt(i);
|
||
|
str = (char*)key.string();
|
||
|
#else
|
||
|
key = it->first;
|
||
|
value = it->second;
|
||
|
str = (char*)key.c_str();
|
||
|
#endif
|
||
|
if(str != NULL)
|
||
|
{
|
||
|
pHttpHeaders->pHttpHeader[i].key = (const char*)strdup(str);
|
||
|
if(pHttpHeaders->pHttpHeader[i].key == NULL)
|
||
|
{
|
||
|
loge("can not dump string of http header.");
|
||
|
clearDataSourceFields(source);
|
||
|
return -1;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
pHttpHeaders->pHttpHeader[i].key = NULL;
|
||
|
|
||
|
#if CONFIG_OS == OPTION_OS_ANDROID
|
||
|
str = (char*)value.string();
|
||
|
#else
|
||
|
str = (char*)value.c_str();
|
||
|
#endif
|
||
|
if(str != NULL)
|
||
|
{
|
||
|
pHttpHeaders->pHttpHeader[i].val = (const char*)strdup(str);
|
||
|
if(pHttpHeaders->pHttpHeader[i].val == NULL)
|
||
|
{
|
||
|
loge("can not dump string of http header.");
|
||
|
clearDataSourceFields(source);
|
||
|
return -1;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
pHttpHeaders->pHttpHeader[i].val = NULL;
|
||
|
|
||
|
#if CONFIG_OS != OPTION_OS_ANDROID
|
||
|
i++;
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
static int setMediaInfo(MediaInfo* pMediaInfo, CdxMediaInfoT* pInfoFromParser)
|
||
|
{
|
||
|
int i;
|
||
|
int nStreamCount;
|
||
|
VideoStreamInfo* pVideoStreamInfo;
|
||
|
AudioStreamInfo* pAudioStreamInfo;
|
||
|
SubtitleStreamInfo* pSubtitleStreamInfo;
|
||
|
int nCodecSpecificDataLen;
|
||
|
char* pCodecSpecificData;
|
||
|
|
||
|
clearMediaInfo(pMediaInfo);
|
||
|
|
||
|
pMediaInfo->nDurationMs = pInfoFromParser->program[0].duration;
|
||
|
pMediaInfo->nFileSize = pInfoFromParser->fileSize;
|
||
|
pMediaInfo->bSeekable = pInfoFromParser->bSeekable; //* TODO, parser should give this flag.
|
||
|
|
||
|
nStreamCount = pInfoFromParser->program[0].videoNum;
|
||
|
logv("video stream count = %d", nStreamCount);
|
||
|
if(nStreamCount > 0)
|
||
|
{
|
||
|
pVideoStreamInfo = (VideoStreamInfo*)malloc(sizeof(VideoStreamInfo)*nStreamCount);
|
||
|
if(pVideoStreamInfo == NULL)
|
||
|
{
|
||
|
loge("can not alloc memory for media info.");
|
||
|
return -1;
|
||
|
}
|
||
|
memset(pVideoStreamInfo, 0, sizeof(VideoStreamInfo)*nStreamCount);
|
||
|
|
||
|
pMediaInfo->pVideoStreamInfo = pVideoStreamInfo;
|
||
|
|
||
|
for(i=0; i<nStreamCount; i++)
|
||
|
{
|
||
|
pVideoStreamInfo = &pMediaInfo->pVideoStreamInfo[i];
|
||
|
memcpy(pVideoStreamInfo, &pInfoFromParser->program[0].video[i], sizeof(VideoStreamInfo));
|
||
|
|
||
|
pCodecSpecificData = pVideoStreamInfo->pCodecSpecificData;
|
||
|
nCodecSpecificDataLen = pVideoStreamInfo->nCodecSpecificDataLen;
|
||
|
pVideoStreamInfo->pCodecSpecificData = NULL;
|
||
|
pVideoStreamInfo->nCodecSpecificDataLen = 0;
|
||
|
|
||
|
if(pCodecSpecificData != NULL && nCodecSpecificDataLen > 0)
|
||
|
{
|
||
|
pVideoStreamInfo->pCodecSpecificData = (char*)malloc(nCodecSpecificDataLen);
|
||
|
if(pVideoStreamInfo->pCodecSpecificData == NULL)
|
||
|
{
|
||
|
loge("can not alloc memory for media info.");
|
||
|
clearMediaInfo(pMediaInfo);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
memcpy(pVideoStreamInfo->pCodecSpecificData, pCodecSpecificData, nCodecSpecificDataLen);
|
||
|
pVideoStreamInfo->nCodecSpecificDataLen = nCodecSpecificDataLen;
|
||
|
}
|
||
|
|
||
|
logv("the %dth video stream info.", i);
|
||
|
logv(" codec: %d.", pVideoStreamInfo->eCodecFormat);
|
||
|
logv(" width: %d.", pVideoStreamInfo->nWidth);
|
||
|
logv(" height: %d.", pVideoStreamInfo->nHeight);
|
||
|
logv(" frame rate: %d.", pVideoStreamInfo->nFrameRate);
|
||
|
logv(" aspect ratio: %d.", pVideoStreamInfo->nAspectRatio);
|
||
|
logv(" is 3D: %s.", pVideoStreamInfo->bIs3DStream ? "true" : "false");
|
||
|
logv(" codec specific data size: %d.", pVideoStreamInfo->nCodecSpecificDataLen);
|
||
|
}
|
||
|
|
||
|
pMediaInfo->nVideoStreamNum = nStreamCount;
|
||
|
}
|
||
|
|
||
|
//* copy audio stream info.
|
||
|
nStreamCount = pInfoFromParser->program[0].audioNum;
|
||
|
if(nStreamCount > 0)
|
||
|
{
|
||
|
pAudioStreamInfo = (AudioStreamInfo*)malloc(sizeof(AudioStreamInfo)*nStreamCount);
|
||
|
if(pAudioStreamInfo == NULL)
|
||
|
{
|
||
|
clearMediaInfo(pMediaInfo);
|
||
|
loge("can not alloc memory for media info.");
|
||
|
return -1;
|
||
|
}
|
||
|
memset(pAudioStreamInfo, 0, sizeof(AudioStreamInfo)*nStreamCount);
|
||
|
pMediaInfo->pAudioStreamInfo = pAudioStreamInfo;
|
||
|
|
||
|
for(i=0; i<nStreamCount; i++)
|
||
|
{
|
||
|
pAudioStreamInfo = &pMediaInfo->pAudioStreamInfo[i];
|
||
|
memcpy(pAudioStreamInfo, &pInfoFromParser->program[0].audio[i], sizeof(AudioStreamInfo));
|
||
|
|
||
|
pCodecSpecificData = pAudioStreamInfo->pCodecSpecificData;
|
||
|
nCodecSpecificDataLen = pAudioStreamInfo->nCodecSpecificDataLen;
|
||
|
pAudioStreamInfo->pCodecSpecificData = NULL;
|
||
|
pAudioStreamInfo->nCodecSpecificDataLen = 0;
|
||
|
|
||
|
if(pCodecSpecificData != NULL && nCodecSpecificDataLen > 0)
|
||
|
{
|
||
|
pAudioStreamInfo->pCodecSpecificData = (char*)malloc(nCodecSpecificDataLen);
|
||
|
if(pAudioStreamInfo->pCodecSpecificData == NULL)
|
||
|
{
|
||
|
loge("can not alloc memory for media info.");
|
||
|
clearMediaInfo(pMediaInfo);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
memcpy(pAudioStreamInfo->pCodecSpecificData, pCodecSpecificData, nCodecSpecificDataLen);
|
||
|
pAudioStreamInfo->nCodecSpecificDataLen = nCodecSpecificDataLen;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pMediaInfo->nAudioStreamNum = nStreamCount;
|
||
|
}
|
||
|
|
||
|
//* copy subtitle stream info.
|
||
|
nStreamCount = pInfoFromParser->program[0].subtitleNum;
|
||
|
if(nStreamCount > 0)
|
||
|
{
|
||
|
pSubtitleStreamInfo = (SubtitleStreamInfo*)malloc(sizeof(SubtitleStreamInfo)*nStreamCount);
|
||
|
if(pSubtitleStreamInfo == NULL)
|
||
|
{
|
||
|
clearMediaInfo(pMediaInfo);
|
||
|
loge("can not alloc memory for media info.");
|
||
|
return -1;
|
||
|
}
|
||
|
memset(pSubtitleStreamInfo, 0, sizeof(SubtitleStreamInfo)*nStreamCount);
|
||
|
pMediaInfo->pSubtitleStreamInfo = pSubtitleStreamInfo;
|
||
|
|
||
|
for(i=0; i<nStreamCount; i++)
|
||
|
{
|
||
|
pSubtitleStreamInfo = &pMediaInfo->pSubtitleStreamInfo[i];
|
||
|
memcpy(pSubtitleStreamInfo, &pInfoFromParser->program[0].subtitle[i], sizeof(SubtitleStreamInfo));
|
||
|
pSubtitleStreamInfo->bExternal = 0;
|
||
|
pSubtitleStreamInfo->pUrl = NULL;
|
||
|
pSubtitleStreamInfo->fd = -1;
|
||
|
pSubtitleStreamInfo->fdSub = -1;
|
||
|
}
|
||
|
|
||
|
pMediaInfo->nSubtitleStreamNum = nStreamCount;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
static void clearMediaInfo(MediaInfo* pMediaInfo)
|
||
|
{
|
||
|
int i;
|
||
|
VideoStreamInfo* pVideoStreamInfo;
|
||
|
AudioStreamInfo* pAudioStreamInfo;
|
||
|
|
||
|
if(pMediaInfo->nVideoStreamNum > 0)
|
||
|
{
|
||
|
for(i=0; i<pMediaInfo->nVideoStreamNum; i++)
|
||
|
{
|
||
|
pVideoStreamInfo = &pMediaInfo->pVideoStreamInfo[i];
|
||
|
if(pVideoStreamInfo->pCodecSpecificData != NULL &&
|
||
|
pVideoStreamInfo->nCodecSpecificDataLen > 0)
|
||
|
{
|
||
|
free(pVideoStreamInfo->pCodecSpecificData);
|
||
|
pVideoStreamInfo->pCodecSpecificData = NULL;
|
||
|
pVideoStreamInfo->nCodecSpecificDataLen = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
free(pMediaInfo->pVideoStreamInfo);
|
||
|
pMediaInfo->pVideoStreamInfo = NULL;
|
||
|
pMediaInfo->nVideoStreamNum = 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
if(pMediaInfo->nAudioStreamNum > 0)
|
||
|
{
|
||
|
for(i=0; i<pMediaInfo->nAudioStreamNum; i++)
|
||
|
{
|
||
|
pAudioStreamInfo = &pMediaInfo->pAudioStreamInfo[i];
|
||
|
if(pAudioStreamInfo->pCodecSpecificData != NULL &&
|
||
|
pAudioStreamInfo->nCodecSpecificDataLen > 0)
|
||
|
{
|
||
|
free(pAudioStreamInfo->pCodecSpecificData);
|
||
|
pAudioStreamInfo->pCodecSpecificData = NULL;
|
||
|
pAudioStreamInfo->nCodecSpecificDataLen = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
free(pMediaInfo->pAudioStreamInfo);
|
||
|
pMediaInfo->pAudioStreamInfo = NULL;
|
||
|
pMediaInfo->nAudioStreamNum = 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
if(pMediaInfo->nSubtitleStreamNum > 0)
|
||
|
{
|
||
|
free(pMediaInfo->pSubtitleStreamInfo);
|
||
|
pMediaInfo->pSubtitleStreamInfo = NULL;
|
||
|
pMediaInfo->nSubtitleStreamNum = 0;
|
||
|
}
|
||
|
|
||
|
pMediaInfo->nFileSize = 0;
|
||
|
pMediaInfo->nDurationMs = 0;
|
||
|
pMediaInfo->eContainerType = CONTAINER_TYPE_UNKNOWN;
|
||
|
pMediaInfo->bSeekable = 0;
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
static int PlayerBufferOverflow(Player* p)
|
||
|
{
|
||
|
int bVideoOverflow;
|
||
|
int bAudioOverflow;
|
||
|
#if !DEMO_CONFIG_DISABLE_VIDEO
|
||
|
int nPictureNum;
|
||
|
int nFrameDuration;
|
||
|
int64_t nVideoCacheTime;
|
||
|
#endif
|
||
|
int nPcmDataSize;
|
||
|
int nSampleRate;
|
||
|
int nChannelCount;
|
||
|
int nBitsPerSample;
|
||
|
int nStreamDataSize;
|
||
|
int nBitrate;
|
||
|
int64_t nAudioCacheTime;
|
||
|
|
||
|
bVideoOverflow = 1;
|
||
|
bAudioOverflow = 1;
|
||
|
#if !DEMO_CONFIG_DISABLE_VIDEO
|
||
|
if(PlayerHasVideo(p))
|
||
|
{
|
||
|
nPictureNum = PlayerGetValidPictureNum(p);
|
||
|
nFrameDuration = PlayerGetVideoFrameDuration(p);
|
||
|
nStreamDataSize = PlayerGetVideoStreamDataSize(p);
|
||
|
nBitrate = PlayerGetVideoBitrate(p);
|
||
|
|
||
|
nVideoCacheTime = nPictureNum*nFrameDuration;
|
||
|
|
||
|
if(nBitrate > 0)
|
||
|
nVideoCacheTime += ((int64_t)nStreamDataSize)*8*1000*1000/nBitrate;
|
||
|
|
||
|
if(nVideoCacheTime <= 2000000) //* cache more than 2 seconds of data.
|
||
|
bVideoOverflow = 0;
|
||
|
|
||
|
logi("picNum = %d, frameDuration = %d, dataSize = %d, bitrate = %d, bVideoOverflow = %d",
|
||
|
nPictureNum, nFrameDuration, nStreamDataSize, nBitrate, bVideoOverflow);
|
||
|
}
|
||
|
#endif
|
||
|
if(PlayerHasAudio(p))
|
||
|
{
|
||
|
nPcmDataSize = PlayerGetAudioPcmDataSize(p);
|
||
|
nStreamDataSize = PlayerGetAudioStreamDataSize(p);
|
||
|
nBitrate = PlayerGetAudioBitrate(p);
|
||
|
PlayerGetAudioParam(p, &nSampleRate, &nChannelCount, &nBitsPerSample);
|
||
|
|
||
|
nAudioCacheTime = 0;
|
||
|
|
||
|
if(nSampleRate != 0 && nChannelCount != 0 && nBitsPerSample != 0)
|
||
|
{
|
||
|
nAudioCacheTime += ((int64_t)nPcmDataSize)*8*1000*1000/(nSampleRate*nChannelCount*nBitsPerSample);
|
||
|
}
|
||
|
|
||
|
if(nBitrate > 0)
|
||
|
nAudioCacheTime += ((int64_t)nStreamDataSize)*8*1000*1000/nBitrate;
|
||
|
|
||
|
if(nAudioCacheTime <= 2000000) //* cache more than 2 seconds of data.
|
||
|
bAudioOverflow = 0;
|
||
|
|
||
|
logi("nPcmDataSize = %d, nStreamDataSize = %d, nBitrate = %d, nAudioCacheTime = %lld, bAudioOverflow = %d",
|
||
|
nPcmDataSize, nStreamDataSize, nBitrate, nAudioCacheTime, bAudioOverflow);
|
||
|
}
|
||
|
|
||
|
return bVideoOverflow && bAudioOverflow;
|
||
|
}
|
||
|
|
||
|
|
||
|
static int PlayerBufferUnderflow(Player* p)
|
||
|
{
|
||
|
int bVideoUnderflow;
|
||
|
int bAudioUnderFlow;
|
||
|
|
||
|
bVideoUnderflow = 0;
|
||
|
bAudioUnderFlow = 0;
|
||
|
#if !DEMO_CONFIG_DISABLE_VIDEO
|
||
|
if(PlayerHasVideo(p))
|
||
|
{
|
||
|
int nPictureNum;
|
||
|
int nStreamFrameNum;
|
||
|
|
||
|
nPictureNum = PlayerGetValidPictureNum(p);
|
||
|
nStreamFrameNum = PlayerGetVideoStreamFrameNum(p);
|
||
|
if(nPictureNum == 0 && nStreamFrameNum == 0)
|
||
|
bVideoUnderflow = 1;
|
||
|
|
||
|
logi("nPictureNum = %d, nStreamFrameNum = %d, bVideoUnderflow = %d",
|
||
|
nPictureNum, nStreamFrameNum, bVideoUnderflow);
|
||
|
}
|
||
|
#endif
|
||
|
if(PlayerHasAudio(p))
|
||
|
{
|
||
|
int nStreamDataSize;
|
||
|
int nPcmDataSize;
|
||
|
int nCacheTime;
|
||
|
|
||
|
nStreamDataSize = PlayerGetAudioStreamDataSize(p);
|
||
|
nPcmDataSize = PlayerGetAudioPcmDataSize(p);
|
||
|
nCacheTime = 0;
|
||
|
if(nCacheTime == 0 && nPcmDataSize == 0 && nStreamDataSize == 0)
|
||
|
bAudioUnderFlow = 1;
|
||
|
|
||
|
logi("nStreamDataSize = %d, nPcmDataSize = %d, nCacheTime = %d, bAudioUnderFlow = %d",
|
||
|
nStreamDataSize, nPcmDataSize, nCacheTime, bAudioUnderFlow);
|
||
|
}
|
||
|
|
||
|
return bVideoUnderflow | bAudioUnderFlow;
|
||
|
}
|