CedarX是全志科技开源的多媒体SDK,其解码的调用基于自研的解码器接口,向上可对接MediaPlayer接口。本文记录与分析其源代码中对于C语言方面的代码技术的应用,仅作记录与借鉴。
源码参考于https://github.com/FREEWING-JP/OrangePi_CedarX
demuxComponent.c中根据source中的URL创建对应的格式解析器,譬如url为/sdcard/movie.mp4,则创建一个mp4的一个Parser,如果url是一个/sdcard/movie.mp3,则创建一个mp3的Parser。URL的后缀其实并不重要,一方面,可以根据后缀猜测这个片源是什么格式,然后加以验证,另一方面,也可以根据片源的二进制格式逐一判断,每种封装格式总一个魔数在文件开头。
//cedarx\xplayer\demuxComponent.c
int ret = CdxParserPrepare(&demux->source, flags, &demux->mutex, &demux->bCancelPrepare,
&demux->pParser, &demux->pStream, &parserContorlTask, &parserContorlTask);
该函数定义在CdxParser.c中。
//cedarx\libcore\parser\base\CdxParser.c
int CdxParserPrepare(CdxDataSourceT *source, cdx_uint32 flags, pthread_mutex_t *mutex,
cdx_bool *exit, CdxParserT **parser, CdxStreamT **stream, ContorlTask *parserTasks,
ContorlTask *streamTasks)
{
CDX_LOGD("source uri '%s'", source->uri);
//创建source对应的stream对象,如果本地文件,则创建FILE类型的stream对象
//如果是HTTP/RTMP片源,则创建对应的HTTP/RTMP流对象。
int ret = CdxStreamOpen(source, mutex, exit, stream, streamTasks);
if (ret < 0)
{
CDX_LOGE("open stream fail, uri(%s)", source->uri);
goto out;
}
//根据流对象,创建对应的格式解析器parser,如果文件格式是mp4,则创建mp4 parser
ret = CdxParserOpen(*stream, flags, mutex, exit, parser, parserTasks);
if (ret < 0)
{
CDX_LOGE("open parser fail, uri(%s)", source->uri);
goto out;
}
out:
return ret;
}
查看CdxStreamOpen的定义。
//cedarx\libcore\parser\base\CdxParser.c
int CdxParserOpen(CdxStreamT *stream, cdx_uint32 flags, pthread_mutex_t *mutex, cdx_bool *exit,
CdxParserT **parser, ContorlTask *parserTasks)
{
if(mutex)
pthread_mutex_lock(mutex);
if(exit && *exit)
{
CDX_LOGW("open parser user cancel.");
if(mutex)
pthread_mutex_unlock(mutex);
return -1;
}
//在这里创建解析器
*parser = CdxParserCreate(stream, flags);
if(mutex)
pthread_mutex_unlock(mutex);
//省略......
//初始化解析器
return CdxParserInit(*parser);
}
追踪创建解析器的代码。
//cedarx\libcore\parser\base\CdxParser.c
CdxParserT *CdxParserCreate(CdxStreamT *stream, cdx_uint32 flags)
{
cdx_char *sampleUri = NULL;
struct CdxParserNodeS *psrNode;
struct CdxParserNodeS *maxScoreNode = NULL;
cdx_uint32 score = 0, maxScore = 0;
CdxParserT *parser = NULL;
CdxStreamProbeDataT *probeDataOld = NULL;
union {
CdxStreamProbeDataT probeData;
char buf[sizeof(*probeDataOld) + sizeof(probeDataOld->uri[0])];
} probeDataUnion;
CdxStreamProbeDataT *probeData = &probeDataUnion.probeData;
/*fast guess*/
CdxStreamGetMetaData(stream, STREAM_METADATA_REDIRECT_URI, (void **)&sampleUri);
if (!sampleUri)
{
CdxStreamGetMetaData(stream, STREAM_METADATA_ACCESSIBLE_URI, (void **)&sampleUri);
}
if (!sampleUri)
{
CdxStreamGetMetaData(stream, "uri", (void **)&sampleUri);
}
//根据后缀猜是什么封装格式,譬如.mp4
probeDataOld = CdxStreamGetProbeData(stream);
probeData->buf = probeDataOld->buf;
probeData->len = probeDataOld->len;
probeData->uri[0] = sampleUri;
psrNode = ParserTypeGuess(sampleUri);
//试着创建一个mp4解析器,检测是否文件是否mp4格式,得分100分则确定是MP4
if (psrNode)
{
if (psrNode->creator->probe(probeData) == 100)
{
maxScoreNode = psrNode;
goto found;
}
}
/*fast guess not work, should ask all parser*/
//如果快速判断失效,则只能一个封装一个封装地探测了,注意这里有个全局的parserList
CdxListForEachEntry(psrNode, &parserList.list, node)
{
CDX_CHECK(psrNode->creator);
CDX_CHECK(psrNode->creator->probe);
score = psrNode->creator->probe(probeData);
if (score == 100)
{
maxScoreNode = psrNode;
goto found;
}
else if(score > maxScore)
{
maxScore = score;
maxScoreNode = psrNode;
}
}
if(!maxScoreNode)
{
CDX_LOGW("Sorry, I don't know what it is!");
return NULL;
}
found:
CDX_LOGD("Good, it's '%s'",
maxScoreNode->keyInfo->comment ? maxScoreNode->keyInfo->comment : "unknow");
//根据找到的节点创建parser
parser = maxScoreNode->creator->create(stream, flags);
parser->type = maxScoreNode->type;
CDX_LOGD("parser type(%d)", parser->type);
//返回具体的parser
return parser;
}
全局的parserList是一个链表,包含了所有支持的封装格式解析器节点。它的初始化是在main函数之前。
//cedarx\libcore\parser\base\CdxParser.c
static void AwParserInit(void) __attribute__((constructor));//初始化是在main函数之前
//注册所有的解析器,即parser,可见parser非常多,越常用的parser放置在越前边,更快探测到。
void AwParserInit(void)
{
CDX_LOGI("aw parser init...");
/* Make HLS be the first one since:
* 1. HLS is widely used
* 2. HLS parser's probe funtion is simple and reliable
* 3. Other parsers' probe function are not reliable, so m3u8 can be
* identified as other formats like TS.
*
* zhaozhili, 2016-11-21
*/
AwParserRegister(&hlsParserCtor, CDX_PARSER_HLS, &hlsKeyInfo);
AwParserRegister(&asfParserCtor, CDX_PARSER_ASF, &asfKeyInfo);
AwParserRegister(&movParserCtor, CDX_PARSER_MOV, &movKeyInfo);
AwParserRegister(&remuxParserCtor, CDX_PARSER_REMUX, &remuxKeyInfo);
AwParserRegister(&flvParserCtor, CDX_PARSER_FLV, &flvKeyInfo);
AwParserRegister(&aviParserCtor, CDX_PARSER_AVI, &aviKeyInfo);
AwParserRegister(&tsParserCtor, CDX_PARSER_TS, &tsKeyInfo);
#ifdef __ANDROID__
AwParserRegister(&dashParserCtor, CDX_PARSER_DASH, &dashKeyInfo);
AwParserRegister(&mmsParserCtor, CDX_PARSER_MMS, &mmsKeyInfo);
AwParserRegister(&mmshttpParserCtor, CDX_PARSER_MMSHTTP, &mmshttpKeyInfo);
#endif
AwParserRegister(&mkvParserCtor, CDX_PARSER_MKV, &mkvKeyInfo);
#ifdef __ANDROID__
AwParserRegister(&bdParserCtor, CDX_PARSER_BD, &bdKeyInfo);
AwParserRegister(&pmpParserCtor, CDX_PARSER_PMP, &pmpKeyInfo);
#endif
AwParserRegister(&oggParserCtor, CDX_PARSER_OGG, &oggKeyInfo);
#ifdef __ANDROID__
AwParserRegister(&m3u9ParserCtor, CDX_PARSER_M3U9, &m3u9KeyInfo);
AwParserRegister(&playlistParserCtor, CDX_PARSER_PLAYLIST, &playlistKeyInfo);
//* do not support widewine in cedarx for Android N,
//* just stagefright
#if (CONF_ANDROID_MAJOR_VER < 7)
AwParserRegister(&wvmParserCtor, CDX_PARSER_WVM, &wvmKeyInfo);
#endif
AwParserRegister(&envParserCtor, CDX_PARSER_ENV, &envKeyInfo);
#endif
AwParserRegister(&mpgParserCtor, CDX_PARSER_MPG, &mpgKeyInfo);
AwParserRegister(&apeParserCtor, CDX_PARSER_APE, &apeKeyInfo);
AwParserRegister(&flacParserCtor, CDX_PARSER_FLAC, &flacKeyInfo);
AwParserRegister(&amrParserCtor, CDX_PARSER_AMR, &amrKeyInfo);
#ifdef __ANDROID__
AwParserRegister(&atracParserCtor, CDX_PARSER_ATRAC, &atracKeyInfo);
#endif
AwParserRegister(&mp3ParserCtor, CDX_PARSER_MP3, &mp3KeyInfo);
AwParserRegister(&aacParserCtor, CDX_PARSER_AAC, &aacKeyInfo);
AwParserRegister(&wavParserCtor, CDX_PARSER_WAV, &wavKeyInfo);
#ifdef __ANDROID__
AwParserRegister(&awtsParserCtor, CDX_PARSER_AWTS, &awtsKeyInfo);
AwParserRegister(&sstrParserCtor, CDX_PARSER_SSTR, &sstrKeyInfo);
AwParserRegister(&cafParserCtor, CDX_PARSER_CAF, &cafKeyInfo);
AwParserRegister(&g729ParserCtor, CDX_PARSER_G729, &g729KeyInfo);
AwParserRegister(&dsdParserCtor, CDX_PARSER_DSD, &dsdKeyInfo);
AwParserRegister(&aiffParserCtor, CDX_PARSER_AIFF, &aiffKeyInfo);
#endif
AwParserRegister(&id3ParserCtor, CDX_PARSER_ID3, &id3KeyInfo);
CDX_LOGD("aw parser size:%d",parserList.size);
return ;
}
先不管节点是怎样的结构体对象,当找到节点后,创建对应的parser, parser = maxScoreNode->creator->create(stream, flags)
。creator的定义如下:
//cedarx\libcore\parser\include\CdxParser.h
struct CdxParserCreatorS
{
//创建parser
CdxParserT *(*create)(CdxStreamT *, cdx_uint32 /*flags*/);
//探测是否某种封装格式,返回分数值
cdx_uint32 (*probe)(CdxStreamProbeDataT *);/*return score(0-100)*/
};
CdxParserT 也就是我们要找到的parser对象。通过其Ops成员的各个函数指针,实现解析器的各个需要的接口,譬如探测数据,读取一笔数据,获取媒体信息,跳播到某个时间点,初始化与关闭等。
//cedarx\libcore\parser\include\CdxParser.h
typedef struct CdxParserS CdxParserT;
//Ops类型,函数指针,接口统一
struct CdxParserOpsS
{
cdx_int32 (*control)(CdxParserT *, cdx_int32 /* cmd */, void * /* param */);
cdx_int32 (*prefetch)(CdxParserT *, CdxPacketT * /* pkt */);
cdx_int32 (*read)(CdxParserT *, CdxPacketT * /* pkt */);
cdx_int32 (*getMediaInfo)(CdxParserT *, CdxMediaInfoT * /* MediaInfo */);
cdx_int32 (*seekTo)(CdxParserT *, cdx_int64 /* timeUs */);
cdx_uint32 (*attribute)(CdxParserT *); /*return falgs define as open's falgs*/
cdx_int32 (*getStatus)(CdxParserT *); /*return enum CdxPrserStatusE*/
cdx_int32 (*close)(CdxParserT *);
cdx_int32 (*init)(CdxParserT *);
};
//Parser的定义
struct CdxParserS
{
enum CdxParserTypeE type;
struct CdxParserOpsS *ops;
};