目录
1 dtstream介绍
2 dtstream接口说明
3 dtstream添加步骤
stream模块和demuxer模块是播放器的数据入口,本篇主要介绍如何添加一个新的stream类型,下篇会介绍如何添加一个新的demuxer。
1 dtstream 介绍
dtstream的主要作用是依据传入的参数. 建立与源的连接,并为dtdemuxer模块提供服务,主要接口有read seek等,
在设计之初由于ffmpeg中demuxer和stream结合的比较紧密,本来是没有这个模块的,后来考虑到整个播放器的模块化以及后续的扩展性
添加了dtstream的模块,用户可以直接在dtstream框架下添加新的stream,如添加一些ffmpeg本身不支持的stream类型: rtsp 等
但由于还未提供dtdemuxer中的demuxer_ffmpeg直接使用dtsteam的接口,后面会添加此功能。
与dtstream模块相关的源代码主要在dtstream目录下,主要有
dtstream_api.c 对外接口
dtstream.c 对下层stream实现的封装,包括选择stream、调用对应stream的方法等
stream/*.c 实际stream的实现,如file rtsp http等
下面介绍下dtstream的代码执行过程。
1.1 register
在播放器开头的初始化阶段,会register所有的stream demuxer decoder render等,具体代码在dtplayer/dtplayer.c中
void player_register_all()
{
stream_register_all();
demuxer_register_all();
audio_register_all();
video_register_all();
}
具体实现在dtstream/dtstream.c中
static void stream_register_all ()
{
REGISTER_STREAM (FILE, file);
}
由于现在只添加了一个file类型,因此只注册了一个
注册过程也很简单,类似ffmpeg中的av_register_all,就是将所有的stream挂载到一个全局的链表g_stream中
1.2 dtstream_open
dtstream是依托dtdemux而存在的,为dtdemux提供服务,因此后续的控制等都由dtdemux来发起,代码在dtdemux/dtdemuxer.c:
int demuxer_open (dtdemuxer_context_t * dem_ctx)
{
int ret = 0;
/* open stream */
dtstream_para_t para;
para.stream_name = dem_ctx->file_name;
ret = dtstream_open(&dem_ctx->stream_priv,¶,dem_ctx);
if(ret != DTERROR_NONE)
{
dt_error (TAG, "stream open failed \n");
//return -1;
}
else
{
//open stream failed, then we use ffmpeg only
int64_t old_pos = dtstream_tell(dem_ctx->stream_priv);
ret = buf_init(&dem_ctx->probe_buf,PROBE_BUF_SIZE);
if(ret < 0)
return -1;
ret = dtstream_read(dem_ctx->stream_priv,dem_ctx->probe_buf.data,PROBE_BUF_SIZE);
if(ret <= 0)
return -1;
dem_ctx->probe_buf.level = ret;
dtstream_seek(dem_ctx->stream_priv,old_pos,SEEK_SET);
}
......
return 0;
}
可以看到在demuxer_open的实现中会构造并初始化dtstream_para_t结构体变量,并调用dtstream_open来初始化dtstream模块
【dtstream/dtstream_api.c】
int dtstream_open (void **priv, dtstream_para_t * para, void *parent)
{
dtstream_context_t *ctx = (dtstream_context_t *)malloc(sizeof(dtstream_context_t));
if(!ctx)
{
dt_error(TAG,"STREAM CTX MALLOC FAILED \n");
return -1;
}
memset (ctx, 0, sizeof (dtstream_context_t));
ctx->stream_name = para->stream_name;
if(stream_open(ctx) == -1)
{
dt_error(TAG,"STREAM CONTEXT OPEN FAILED \n");
free(ctx);
*priv = NULL;
return -1;
}
*priv = (void *)ctx;
ctx->parent = parent;
dt_info(TAG,"STREAM CTX OPEN SUCCESS\n");
return 0;
}
简单说下,初始化首先构造一个context,之后是选择stream,并调用stream_open
stream_open就是实际stream的封装,这里就不展开了,比较简单。
初始化成功后,基本上就可以为dtdemux提供服务了,剩下的接口大部分播放器都是一致的,read seek skip close等
这里后面介绍实际的stream的时候再说
2 dtstream api介绍
dtstream_api.c dtstream.c可以认为是一个对外,一个对内,对外提供dtstream接口
对内管理内部的各个stream,并在初始化的时候选择合适的stream提供实际的服务。
而这里只介绍 一个典型的stream应该实现哪些接口
在dtstream模块中,主要是实现一个stream_wrapper_t的结构体,定义:
【dtstream/dtstream.h】
typedef struct stream_wrapper
{
char *name;
int id;
int (*open) (struct stream_wrapper * wrapper,char *stream_name);
int64_t (*tell) (struct stream_wrapper * wrapper);
int (*read) (struct stream_wrapper * wrapper, uint8_t *buf,int len);
int (*seek) (struct stream_wrapper * wrapper, int64_t pos, int whence);
int (*close) (struct stream_wrapper * wrapper);
void *stream_priv; // point to priv context
void *parent; // point to parent, dtstream_context_t
stream_ctrl_t info;
struct stream_wrapper *next;
} stream_wrapper_t;
主要是函数指针,各个函数的含义为
open 初始化
tell 返回当前读取位置
read 在当前位置读取len大小的数据到buf中,返回实际读取的数据量
seek 在流中seek,whence和pos的定义与read的系统调用定义一致
close 关闭stream,释放内存等
各个成员含义为:
name: stream名称
id: stream id
stream_priv 实际stream可定义自己独有的结构保存在此成员中
parent:上一级管理员,这里为dtstream_context_t
info:一些控制信息,如eof等,这样判断eof就不必到实际的stream中,提高了效率
next: 所有的stream都挂载在一条链表中,这里next指的当前stream的下一个,在register的时候会用到
3 dtstream 添加步骤
这里以刚添加的stream_file为例,介绍如何在dtstream框架下添加一个新的stream
具体源文件在dtstream/stream/stream_file.c
注意这里不讲细节,只讲如何添加一个stream并在dtplayer中跑起来
3.1 定义结构体
首先是定义一个stream_wrapper_t的结构体,并将其链接到dtstream的全局链表中
stream_wrapper_t stream_file = {
.name = "File",
.id = STREAM_FILE,
.open = stream_file_open,
.read = stream_file_read,
.seek = stream_file_seek,
.close = stream_file_close,
};
这里可以看下示例建立的结构提变量需要按照一定的明明规则,后面会讲回身么,必须是stream_** 格式
这里每添加一个新的stream,需要定义一个ID,统一定义的位置在dtstream/dtstream.h
typedef enum{
STREAM_INVALID = -1,
STREAM_FILE,
STREAM_FFMPEG,
STREAM_UNSUPPORT
}stream_format_t;
现在只实现了file ,下一步会添加ffmpe封装
3.2 在注册方法中加入file
代码在dtstream/dtstream.c中
static void stream_register_all (){ REGISTER_STREAM (FILE, file);}
看下宏实现
#define REGISTER_STREAM(X,x) \ if(ENABLE_STREAM_##X) \ { \ extern stream_wrapper_t stream_##x; \ register_stream(&stream_##x); \ }
之前说stream命名需按照规则,作用就在此处,在注册的时候是按照统一的模式进行注册的,
参数FILE 主要是构造成ENABLE_STREAM_FILE来判断在编译的时候是否配置成可用,如果不可用,则不注册
参数file主要是引用实际的变量,即stream_file调用registere_stream进行注册
static stream_wrapper_t *g_stream = NULL;
static void register_stream (stream_wrapper_t * wrapper)
{
stream_wrapper_t **p;
p = &g_stream;
while (*p != NULL)
p = &((*p)->next);
*p = wrapper;
wrapper->next = NULL;
} static int stream_select (dtstream_context_t * stm_ctx) { if (!g_stream) return -1; stm_ctx->stream = g_stream; // select the only one return 0; }
实现也比较简单,就是挂载在g_stream的链表中,挂载好后,就可以通过stream_select选择对应的stream了
选择好了之后后面调用dtstream_api的接口时,就会路由到实际注册的stream的实现或者通过封装相应的实现来完成。
3.3 实现stream_wrapper_t的各个部分的功能
这部分就不展开了,但凡自己添加stream的同仁都知道要实现什么样的接口。
实在不清楚的可以邮件我:)
3.4 配置
最后一步是配置编译系统
首先是将源文件加到编译系统中,这里是makefile中添加一行
#dtstream
SRCS_COMMON-$(DT_STREAM) +=dtstream/dtstream_api.c
SRCS_COMMON-$(DT_STREAM) +=dtstream/dtstream.c
+ SRCS_COMMON-$(DT_STREAM_FILE) +=dtstream/stream/stream_file.c
这样在编译整个项目的时候才能编译到你添加的源文件
再有就是配置config.mk,这里主要就是两部分
+DT_CFLAGS += -DENABLE_STREAM_FILE=1 +DT_STREAM_FILE=yes
其中第一个是用于注册的时候用,第二个是上面Makfile中配置将代码加入编译
至此一个stream就添加成功了。
最后介绍下stream选取的思路, 后面会添加比较多的stream,选择的依据是,最先匹配优先,当都不匹配的时候会选择ffmpeg。
由于后面随着开发的进行文章会进行细节的更新,因此为了保证读者随时读到最新的内容,文章禁止转载,多谢大家支持。
dtplayer如何添加stream,布布扣,bubuko.com
原文:http://blog.csdn.net/u011350110/article/details/22093145