前言
CedarX是全志科技开源的多媒体SDK,其解码的调用基于自研的解码器接口,向上可对接MediaPlayer接口。本文记录与分析其源代码中对于C语言方面的代码技术的应用,仅作记录与借鉴。
源码参考于https://github.com/FREEWING-JP/OrangePi_CedarX
问题
知道如何创建一个Parser,也了解到一个Parser有哪些接口,那么问题来了,譬如,FLV解析器的内部变量与对象放在哪里,因为CdxParserT定义中只有一个CdxParserTypeE 和一个CdxParserOpsS。
//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;
};
CdxContainerOf与Impl的搭配使用
我们看__CdxFlvParserCreate函数。在创建Parser时,创建了一个CdxFlvParserImplT 对象,如果一切顺利,返回的是CdxFlvParserImplT 对象的成员base,其类型就是CdxParserT。
static CdxParserT *__CdxFlvParserCreate(CdxStreamT *stream, cdx_uint32 flags)
{
CdxFlvParserImplT *impl;
cdx_int32 result;
impl = FlvInit(&result);
CDX_FORCE_CHECK(impl);
if(result < 0)
{
CDX_LOGE("Initiate flv file parser lib module error.");
goto failure;
}
impl->base.ops = &flvParserOps;
impl->stream = stream;
impl->nVidPtsOffset = 0;
impl->nAudPtsOffset = 0;
impl->nSubPtsOffset = 0;
impl->hasSyncVideo = 0;
impl->hasSyncAudio = 0;
impl->bFirstVidFrm = 1;
impl->curChunkInfo.nChkType = 0;
impl->curChunkInfo.nTotSize = 0;
impl->curChunkInfo.nReadSize = 0;
pthread_mutex_init(&impl->lock, NULL);
pthread_cond_init(&impl->cond, NULL);
impl->mStatus = CDX_FLV_INITIALIZED;
impl->mErrno = PSR_INVALID;
return &impl->base;
failure:
if(impl)
{
__CdxFlvParserClose(&impl->base);
impl = NULL;
}
return NULL;
}
我们看一下CdxFlvParserImplT 的定义。可见,关于FLV Parser内部实现的成员,都在这个CdxFlvParserImplT 对象中。那么,其他接口的内部实现,如何获取到这个CdxFlvParserImplT 对象,毕竟给到每个接口的参数,并没有直接看到CdxFlvParserImplT类型参数,只看到CdxParserT类型参数。
typedef struct CdxFlvParserImplS
{
CdxParserT base;//关键点,包含CdxParserT对象,名字为base
CdxStreamT *stream;
CdxPacketT pkt;
void *privData;
cdx_int32 exitFlag;
cdx_int32 mErrno;
cdx_int32 mStatus;
cdx_int32 flags;
cdx_bool hasVideo;
cdx_bool hasAudio;
cdx_bool hasSubTitle;
cdx_int8 bFirstVidFrm;
cdx_int8 bDiscardAud; // 1:discard, 0:transport
cdx_int8 videoStreamIndex;
cdx_int8 audioStreamIndex;
cdx_int8 subTitleStreamIndex;
cdx_uint32 totalFrames;
cdx_uint32 pictureNum;
cdx_uint32 nPreFRSpeed; //previous ff/rr speed, for dynamic adjust
cdx_uint32 nFRSpeed; //fast forward and fast backward speed,
//multiple of normal speed
cdx_uint32 nFRPicShowTime; //picture show time under fast forward and backward
cdx_uint32 nFRPicCnt; //picture count under ff/rr, for check if need delay
cdx_uint32 nVidPtsOffset; //video time offset
cdx_uint32 nAudPtsOffset; //audio time offset
cdx_uint32 nSubPtsOffset; //subtitle time offset
cdx_int8 hasSyncVideo; //flag, mark that if has sync video
cdx_int8 hasSyncAudio; //flag, mark that if has sync audio
cdx_uint32 startPos;
cdx_int64 fileSize;
cdx_int64 firstVideoTagPos; //for xunlei 265 seek to first keyframe.
AudioStreamInfo aFormat;
VideoStreamInfo vFormat;
SubtitleStreamInfo tFormat;
VideoStreamInfo tempVformat;
cdx_uint8 *tempBuf;
cdx_uint32 tempBufLen;
FlvChunkInfoT curChunkInfo; //current chunk information
cdx_int32 h265_vps_sps_pps_state; //3: read vps 2: read sps 1: read pps,
//0: back seek , -1: invalid
cdx_uint32 h265_start_pos_stored;
cdx_uint32 h265_data_size_stored;
pthread_mutex_t lock;
pthread_cond_t cond;
cdx_int32 bNoAvcSequenceHeader;
}CdxFlvParserImplT;
我们看一下__CdxFlvParserInit接口,内部如何获取到impl对象。
static int __CdxFlvParserInit(CdxParserT *parser)
{
cdx_int32 result, ret = 0;
CdxFlvParserImplT *impl;
CDX_CHECK(parser);
impl = CdxContainerOf(parser, CdxFlvParserImplT, base);//关键点
result = FlvOpen(impl);
if(result < 0)
{
CDX_LOGE("Open flv failed.");
impl->mErrno = PSR_OPEN_FAIL;
ret = -1;
goto __exit;
}
CDX_LOGI("read flv head finish.");
impl->mErrno = PSR_OK;
ret = 0;
__exit:
pthread_mutex_lock(&impl->lock);
impl->mStatus = CDX_FLV_IDLE;
pthread_mutex_unlock(&impl->lock);
pthread_cond_signal(&impl->cond);
return ret;
}
关键点就在于CdxContainerOf,实质与Linux内核的container_of宏一致。通过传参parser找到CdxFlvParserImplT 类型的impl,继而获取到其他内部变量与结构体对象!由此,CdxParserT 的定义并不需要包含具体的Parser的成员对象,每个具体的Parser在创建的时候,会创建自己的ParserImplT,并将CdxParserT 作为ParserImplT的第一个成员,名字为base,这样,CdxParserT 就与ParserImplT关联到一起,通过CdxParserT 就可以找到ParserImplT!
对于Linux内核的container_of宏,这个宏的作用其实很简单,就是通过一个容器(结构体)中某个成员的指针得到指向这个容器(结构体)的指针,简单的说就是通过成员找容器,在这个案例中,就是通过CdxParserT 就可以找到CdxFlvParserImplT!
关于container_of的解释,可以参考以下文章。
Linux内核container_of详解(图解)
container of()函数简介