目录
1 dtdemux介绍
2 demux接口说明
3 demuxer添加步骤
上篇文章,介绍了如何在dtplayer框架下添加一个新的stream,后面又添加了封装ffmpeg的stream,使得dtplayer支持的stream类型大大增加。
而且用户添加demuxer的时候测试也方便了很多(未添加stream_ffmpeg之前只能测试本地文件,现在已经支持部分网络流)。
本篇主要介绍下dtdemuxer,主要的接口等,还会以aac demuxer为例介绍如何在dtplayer框架下添加一个新的demuxer。
1 dtdemux模块介绍
dtdemux模块在整个播放框架中起着至关重要的作用,主要工作是对stream过来的流进行解析,向上为player模块提供完整帧数据。
与dtdemuxer相关的文件主要在dtdemux目录下,主要有:
dtdemuxer_api.c dtdemux模块对外提供的api
dtdemuxer.c 中间模块,向上支持对外接口,向下管理实际的demuxer
demuxer/*.c 实际demuxer的实现,当然最重要的是demuxer_ffmpeg.c
先介绍下对外的接口,可参考dtdemuxer_api.h 头文件中的声明
int dtdemuxer_open (void **priv, dtdemuxer_para_t * para, void *parent);
dt_media_info_t *dtdemuxer_get_media_info (void *priv);
int dtdemuxer_read_frame (void *priv, dt_av_frame_t * frame);
int dtdemuxer_seekto (void *priv, int timestamp);
int dtdemuxer_close (void *priv);
接口非常简单,简单介绍下
dtdemuxer_open 初始化demuxer,申请资源等,同时在此接口中会open stream
dtdemuxer_get_media_info 获取流信息,包括audio track , video track 等信息
dtdemuxer_read_frame 读取一帧数据
dtdemuxer_seekto 实现seek操作,player等获取到seek命令后实际执行的是demuxer模块
dtdemuxer_close 播放结束时调用close进行资源的释放当
在看下dtdemuxer.h中提供的接口
void demuxer_register_all();
int demuxer_open (dtdemuxer_context_t * dem_ctx);
int demuxer_read_frame (dtdemuxer_context_t * dem_ctx, dt_av_frame_t * frame);
int demuxer_seekto (dtdemuxer_context_t * dem_ctx, int timestamp);
int demuxer_close (dtdemuxer_context_t * dem_ctx);
基本与对外的接口是一致的,dtdemuxer负责管理实际的demuxer操作,对外的接口都是通过封装dtdemuxer的方法来实现的,具体可参考实际代码
这里就不介绍了。
下面介绍下实际的工作流程
dtdemux模块主要是由dtplayer模块进行管理,由dtplayer的方法负责调用。这里根据实际代码看下:
1.1 register
使用之前需要先注册
入口在dtplayer/dtplayer_api.c
int dtplayer_init (void **player_priv, dtplayer_para_t * para)
{
int ret = 0;
if (!para)
return -1;
player_register_all();
......
*player_priv = dtp_ctx;
return 0;
}
->dtplayer/dtplayer.c
void player_register_all(){ stream_register_all(); demuxer_register_all(); audio_register_all(); video_register_all();}
->dtdemux/dtdemuxer.c
void demuxer_register_all ()
{
REGISTER_DEMUXER (AAC, aac);
REGISTER_DEMUXER (FFMPEG, ffmpeg);
}
主要有两个demuxer, aac(可作为例子)以及ffmpeg,注册之后就可以在demuxer_select中选择并工作了。
1.2 dtdemuxer_open
入口:dtplayer.c
int player_init (dtplayer_context_t * dtp_ctx)
{
int ret = 0;
pthread_t tid;
set_player_status (dtp_ctx, PLAYER_STATUS_INIT_ENTER);
dt_info (TAG, "[%s:%d] START PLAYER INIT\n", __FUNCTION__, __LINE__);
dtp_ctx->file_name = dtp_ctx->player_para.file_name;
dtp_ctx->update_cb = dtp_ctx->player_para.update_cb;
/* init server */
player_server_init (dtp_ctx);
dtdemuxer_para_t demux_para;
demux_para.file_name = dtp_ctx->file_name;
ret = dtdemuxer_open (&dtp_ctx->demuxer_priv, &demux_para, dtp_ctx);
if (ret < 0)
{
ret = -1;
goto ERR1;
}
dtp_ctx->media_info = dtdemuxer_get_media_info (dtp_ctx->demuxer_priv); ......
}
这里在player_init会执行两个操作 open 以及 get_medie_info 分别代表初始化以及获取媒体信息,进入dtdemuxer看下实现
int dtdemuxer_open (void **priv, dtdemuxer_para_t * para, void *parent)
{
dtdemuxer_context_t *dem_ctx = (dtdemuxer_context_t *) malloc (sizeof (dtdemuxer_context_t));
if (!dem_ctx)
{
dt_error (TAG, "demuxer context malloc failed \n");
return -1;
}
memset (dem_ctx, 0, sizeof (dtdemuxer_context_t));
dem_ctx->file_name = para->file_name;
if (demuxer_open (dem_ctx) == -1)
{
dt_error (TAG, "demuxer context open failed \n");
free(dem_ctx);
return -1;
}
*priv = (void *) dem_ctx;
dem_ctx->parent = parent;
dt_info (TAG, "demuxer context open success \n");
return 0;
}
这里open主要是申请dtdemuxer_context_t结构体变量,传递参数(文件名),并调用dtdemuxer.c中的demuxer_open方法,这里只讲入口
细节读者请自行阅读代码。
dt_media_info_t *dtdemuxer_get_media_info (void *priv)
{
dtdemuxer_context_t *dem_ctx = (dtdemuxer_context_t *) priv;
return &(dem_ctx->media_info);
}
再看dtdemuxer_get_media_info,这里只是直接返回了保存在dtdemuxer_context_t中的mediainfo变量,原因是在open的时候 已经完成了对文件的解析
并将解析结果存放在了dem_ctx->media_info结构体中。
1.3 dtdemuxer_read_frame & dtdemuxer_seekto
当初始化完成后,dtdemuxer便可以提供服务了,主要是提供数据读取和seek的功能
read_frame的发起在dtplayer_io.c中,dtplayer会单独启动一个线程进行数据的读取,简化的代码如下:
static void *player_io_thread (dtplayer_context_t * dtp_ctx)
{
io_loop_t *io_ctl = &dtp_ctx->io_loop;
dt_av_frame_t frame;
int frame_valid = 0;
int ret = 0;
do
{
usleep (10000);
ret = player_read_frame (dtp_ctx, &frame);
ret = player_write_frame (dtp_ctx, &frame);
}
while (1);
QUIT:
dt_info (TAG, "io thread quit ok\n");
pthread_exit (NULL);
return 0;
}
在播放器启动后,启动此线程后dtplayer就通过player_read_frame从dtdemux中读取数据并将数据通过player_write_frame写入到dtport模块中保存起来
static int player_read_frame (dtplayer_context_t * dtp_ctx, dt_av_frame_t * frame)
{
return dtdemuxer_read_frame (dtp_ctx->demuxer_priv, frame);
}
实际就是通过dtdemuxer_read_frame 对外接口来完成的,而对外接口(dtdemuxer_api.c中)则是通过dtdemuxer.c中的内部实现完成
【dtdemuxer_api.c】
int dtdemuxer_read_frame (void *priv, dt_av_frame_t * frame)
{
dtdemuxer_context_t *dem_ctx = (dtdemuxer_context_t *) priv;
return demuxer_read_frame (dem_ctx, frame);
}
[dtdemuxer.c]
int demuxer_read_frame (dtdemuxer_context_t * dem_ctx, dt_av_frame_t * frame)
{
demuxer_wrapper_t *wrapper = dem_ctx->demuxer;
return wrapper->read_frame (wrapper, frame);
}
从代码可以看出,实际工作的是dtdemuxer/demuxer/*.c等实际的demuxer来完成的。
这里不再跟进了,具体的细节读者应该也不会关心。
seek的原理及流程与read_frame非常类似,理解了read_frame相信读者阅读seek流程也不会有困难。
最后的dtdemuxer_close就不说了,作用是释放资源。
下面介绍下实际demuxer对应的接口
2 demux接口说明
移植demuxer最主要的是实现一个结构体及其函数指针
【dtdemux/dtdemuxer.h】
typedef enum{
DEMUXER_INVALID = -1,
DEMUXER_AAC,
DEMUXER_FFMPEG,
DEMUXER_UNSUPPORT,
}demuxer_format_t;
typedef struct demuxer_wrapper
{
char *name;
int id;
int (*probe) (struct demuxer_wrapper *wrapper,void *parent);
int (*open) (struct demuxer_wrapper * wrapper);
int (*read_frame) (struct demuxer_wrapper * wrapper, dt_av_frame_t * frame);
int (*setup_info) (struct demuxer_wrapper * wrapper, dt_media_info_t * info);
int (*seek_frame) (struct demuxer_wrapper * wrapper, int timestamp);
int (*close) (struct demuxer_wrapper * wrapper);
void *demuxer_priv; // point to priv context
void *parent; // point to parent, dtdemuxer_context_t
struct demuxer_wrapper *next;
} demuxer_wrapper_t;
其中demuxer_format_t是支持的demuxer, 也就是对应demuxer_wrapper_t中的id成员
后面介绍写接口含义
probe: 检测函数,与ffmpeg中的probe含义一致
open: 初始化,并完成文件参数解析
setup_info: 组装信息到dt_media_info_t ,demuxer中的信息并不能直接为dtplayer所用,
需要构造dtplayer自己的文件参数信息,也就是dt_medie_info_t变量。理由很简单,
比如之前用ffmpeg中的libavformat作为demuxer,那么可以使用avformatcontext来保存信息
但dtplayer支持去除ffmpeg依赖,因此需要有个统一的接口来实现。
read_frame:读取一帧数据,这里移植demuxer的时候需要注意这里必须返回完整帧
seek_frame: seek到timestamp (单位是秒)
close: 播放结束,释放资源
demuxer_priv: demuxer的私有信息
parent: 这里代表dtdemuxer_context_t
3 demuxer添加步骤
代码位置:添加的demuxer代码需统一存放在dtdemux/demuxer/下,方便管理
这里以aac demuxer为例,介绍添加一个demuxer的具体示例
代码位置:dtdemux/demuxer/demuxer_aac.c
3.1 定义结构体
首先是定义一个demuxer_wrapper_t的结构体,并将其链接到dtdemuxer的全局链表中
demuxer_wrapper_t demuxer_aac = {
.name = "aac demuxer",
.id = DEMUXER_AAC,
.probe = demuxer_aac_probe,
.open = demuxer_aac_open,
.read_frame = demuxer_aac_read_frame,
.setup_info = demuxer_aac_setup_info,
.seek_frame = demuxer_aac_seek_frame,
.close = demuxer_aac_close
};
建立结构提变量与dtstream类似需要按照一定的规则,必须是demuxer_** 格式
这里每添加一个新的demuxer,需要定义一个ID,统一定义的位置在dtdemux/dtdemuxer.h之前见过,再贴一次
typedef enum{
DEMUXER_INVALID = -1,
DEMUXER_AAC,
DEMUXER_FFMPEG,
DEMUXER_UNSUPPORT,
}demuxer_format_t;
现在只实现了aac 和ffmpeg的支持
3.2 在注册方法中加入demuxer
[dtdemux/dtdemuxer.c]
#define REGISTER_DEMUXER(X,x) \
if(ENABLE_DEMUXER_##X) \
{ \
extern demuxer_wrapper_t demuxer_##x; \
register_demuxer(&demuxer_##x); \
}
static demuxer_wrapper_t *g_demuxer = NULL;
static void register_demuxer (demuxer_wrapper_t * wrapper)
{
demuxer_wrapper_t **p;
p = &g_demuxer;
while (*p != NULL)
p = &((*p)->next);
*p = wrapper;
wrapper->next = NULL;
}
void demuxer_register_all ()
{
REGISTER_DEMUXER (AAC, aac);
REGISTER_DEMUXER (FFMPEG, ffmpeg);
}
比较简单即将新的demuxer挂载到全局链表: g_demuxer中
下面dtdemuxer模块会自动调用probe等选择对应的demuxer
static int demuxer_select (dtdemuxer_context_t * dem_ctx)
{
if (!g_demuxer)
return -1;
int score = 0;
demuxer_wrapper_t *entry = g_demuxer;
while(entry != NULL)
{
score = (entry)->probe(entry,dem_ctx);
if(score == 1)
break;
entry = entry->next;
}
if(!entry)
return -1;
dem_ctx->demuxer = entry;
dt_info(TAG,"SELECT DEMUXER:%s \n",entry->name);
return 0;
}
3.3 实现demuxer_wrapper_t的各个部分的功能
实现demuxer的所有接口,可直接参考aac的实现
3.4 配置
最后一步是配置编译系统
首先是将源文件加到编译系统中,这里是makefile中添加一行
SRCS_COMMON-$(DT_DEMUXER) +=dtdemux/demuxer/demuxer_aac.c
由于aac的添加没有以来其他库,因此默认是enable的
再有就是配置config.mk,这里由于aac默认enable因此不需要添加开关
这里说明一下: 当添加的模块需要以来第三方库,那么请一定在config.mk中添加开关控制,因为用户的机器可能没有安装此库,导致运行或者安装失败
至此一个demuxer就添加成功了。
下面会介绍如何在dtplayer框架下添加一个audio decoder, 会以faad为例。
源代码地址:https://github.com/peterfuture/dtplayer
联系开发者:[email protected]
blog: http://blog.csdn.net/u011350110
开源中国地址:http://www.oschina.net/p/dtplayer
由于后面随着开发的进行文章会进行细节的更新,因此为了保证读者随时读到最新的内容,文章禁止转载,多谢大家支持。