OK,来领教一下ffmpeg中如何实现流媒体的seek功能吧,这里仍然只是对mms协议、asf格式的流媒体seek代码的分析,至于其他种种对于有开源精神的大家来说花点时间去读一读吧!
这次不多说废话,从API开始,seek功能在ffmpeg中对外的api是av_seek_frame()函数,此函数中并未实现对mms协议流媒体的seek,那么我们主要的修改自然就集中在这里.
首先看一下seek的三个标志:
#define AVSEEK_FLAG_BACKWARD 1 ///< seek backward
#define AVSEEK_FLAG_BYTE 2 ///< seeking based on position in bytes
#define AVSEEK_FLAG_ANY 4 ///< seek to any frame, even non keyframes
恩,看了英文注释仍然有点迷糊?实际上前两种是对于seek到keyframes的情况,而最后一种的英文注释则很好理解.也就是说,AVSEEK_FLAG_BACKWARD表示如果seek的当前位置不是keyframes,那么回退至当前位置的keyframes,如果此标志不置位那就前进到当前位置的keyframes.AVSEEK_FLAG_BYTE表示seek的标志按bytes拖进.这三种标志的代码中在详细看.
int av_seek_frame(AVFormatContext *s, int stream_index, int64_t timestamp, int flags)
{
int ret;
AVStream *st;
av_read_frame_flush(s); //清空播放缓冲器的函数,既然seek了就没必要还残留seek之前的播放桢.
if(flags & AVSEEK_FLAG_BYTE) //位置按bytes位seek,区别于按时间戳的seek.
return av_seek_frame_byte(s, stream_index, timestamp, flags);
if(stream_index < 0){ //对默认流进行seek,我这里将stream_index置为了-1.
stream_index= av_find_default_stream_index(s);
if(stream_index < 0)
return -1;
st= s->streams[stream_index];
/* timestamp for default must be expressed in AV_TIME_BASE units */
timestamp = av_rescale(timestamp, st->time_base.den, AV_TIME_BASE * (int64_t)st->time_base.num); //时间戳的调整,调整时基同时保证不越界.
}
st= s->streams[stream_index];
/* first, we try the format specific seek */
if (s->iformat->read_seek) //如果iformat中存在read_seek函数,调用此函数,我们将会在这里进入seek的function.
ret = s->iformat->read_seek(s, stream_index, timestamp, flags);
else
ret = -1;
if (ret >= 0) {
return 0;
}
if(s->iformat->read_timestamp)
return av_seek_frame_binary(s, stream_index, timestamp, flags);
else
return av_seek_frame_generic(s, stream_index, timestamp, flags);
}
这里插话说一下这三个结构之间的关联:AVFormatContext、ByteIOContext以及URLContext.此三个结构决定了ffmpeg的构架.首先来看一下AVFormatContext.我就不贴结构的全部了,我们主要关注于这三者直接的联系以及这三者之间不可或缺的帮凶们.
AVFormatContext包含三个重要的结构:AVInputFormat、AVOutputFormat以及 ByteIOContext.前两者顾名思义:输入文件的格式(我们这里就是asf格式)、输出文件格式.那么ByteIOContext就是完成他们两者之间转换的桥梁.也就是说通过调用ByteIOContext结构中的回调函数,完成了我们拿到了数据包(不论是本地的还是流媒体).那么URLContext与ByteIOContext呢?同样,URLContext被包含在了ByteIOContext中,可是我们在ByteIOContext中并未见到对URLContext的包含啊?注意这里对ByteIOContext这个结构的初始化,函数是init_put_byte(),而ByteIOContext结构中的void *opaque指针指向的正是URLContext结构.而URLContext结构中的URLProtocol结构则指明了我们的流媒体采用的是什么样的协议.
理清出了三个结构的关系,我们再次回到代码中,代码在这里ret = s->iformat->read_seek(s, stream_index, timestamp, flags);进入执行主要的seek功能,上面说过了s->iformat是AVInputFormat结构,好了我们去这个结构中找一找我们需要的read_seek函数.前面已经说过了 这里指针对asf格式的视频文件,那么我们试着去asf.c中看一看.
asf.c中有如下的结构体初始化:
AVInputFormat asf_demuxer = {
"asf",
"asf format",
sizeof(ASFContext),
asf_probe,
asf_read_header,
asf_read_packet,
asf_read_close,
asf_read_seek,
asf_read_pts,
};
这就是我们要找的挂接回调函数的结构了,当然我们目前仍然还未与mms协议扯上关系,但是我们已经接近了.来看看asf_read_seek的实现:
static int asf_read_seek(AVFormatContext *s, int stream_index, int64_t pts, int flags)
{
ASFContext *asf = s->priv_data;
AVStream *st = s->streams[stream_index];
int64_t pos;
int index;
if (asf->packet_size <= 0)
return -1;
/* Try using the protocol's read_seek if available */
if(s->pb) { //上面已经提及了s->pb是ByteIOContext结构,此结构是与URLcontext的联系
int ret = av_url_read_fseek(s->pb, stream_index, pts, flags); //这个函数对我实现的seek至关重要,但是如果你并未实现此函数中的调用(稍后讲到),也
并无关系,因为此函数之后的代码会帮你完成从时间戳到pos的转换并完成seek.
if(ret >= 0)
asf_reset_header(s); //seek成功,重置asf文件缓冲区头部
if (ret != AVERROR(ENOSYS))
return ret;
}
if (!asf->index_read)
asf_build_simple_index(s, stream_index); //此函数用于寻找asf格式文件中的simple_index部分,关于asf格式我在下面小提一下
具体的请自行查找资料
if(!(asf->index_read && st->index_entries)){
if(av_seek_frame_binary(s, stream_index, pts, flags)<0)
return -1;
}else{
index= av_index_search_timestamp(st, pts, flags); //根据时间戳来寻找index,一会介绍asf格式时一目了然.
if(index<0)
return -1;
/* find the position */
pos = st->index_entries[index].pos;
pts = st->index_entries[index].timestamp;av_log(NULL, AV_LOG_DEBUG, "SEEKTO: %"PRId64"\n", pos);
url_fseek(s->pb, pos, SEEK_SET);
}
asf_reset_header(s);
return 0;
}
这里在补充一下asf格式,我这里只是提及一下,不涉及深入分析(我也没有深入分析,没那个必要).关于asf格式的文档可以在微软的官方网站下载,地址是
由于msn的blog十分不方便贴图,我还是以代码来说明一下asf的格式.asf有三个主要的objects: Header object、Data object以及Index object. Sample index就被包含在Index object中.每一个object都有一个Guid,可以依据Guid来判断当前包是属于什么样的object.结合代码来看,在asf_build_simple_index()函数中:
static void asf_build_simple_index(AVFormatContext *s, int stream_index)
{
GUID1 g;
ASFContext *asf = s->priv_data;
int64_t gsize, itime;
int64_t pos, current_pos, index_pts;
int i;
int pct,ict;
long ret = 100;
current_pos = url_ftell(s->pb); //得到当前播放的位置,注意你的URLprotocol中要实现seek函数,在后面会讲到
ret = url_fseek(s->pb, asf->data_object_offset + asf->data_object_size, SEEK_SET); //seek到index object的位置.
asf->data_object_offset + asf->data_object_size这
里表示: asf->data_object_offset数据包的offset实际上
就是一个asf文件头的大小, asf->data_object_size数据包
object的大小,相加之后也就偏移到了文件末尾index object的位置
get_guid(s->pb,
&g);
//开始读取数据包,并完成比对guid,查看是否是index object的位置
if (!memcmp(&g, &index_guid, sizeof(GUID1))) { //后面的代码比较好理解,我就不一一注释了
gsize = get_le64(s->pb);
get_guid(s->pb, &g);
itime=get_le64(s->pb);
pct=get_le32(s->pb);
ict=get_le32(s->pb);
av_log(NULL, AV_LOG_DEBUG, "itime:0x%"PRIx64", pct:%d, ict:%d\n",itime,pct,ict);
for (i=0;i int pktnum=get_le32(s->pb);
int pktct =get_le16(s->pb);
av_log(NULL, AV_LOG_DEBUG, "pktnum:%d, pktct:%d\n", pktnum, pktct);
pos=s->data_offset + asf->packet_size*(int64_t)pktnum;
index_pts=av_rescale(itime, i, 10000);
av_add_index_entry(s->streams[stream_index], pos, index_pts, asf->packet_size, 0, AVINDEX_KEYFRAME);
}
asf->index_read= 1;
}
url_fseek(s->pb, current_pos, SEEK_SET);
}
但是我上面就说过,我这里并未采用从index object中寻找index进行seek的实现.因为我每次读出来之后去比对guid都发现我读到的是数据包而不是index object.目前原因不明.所以我采用了直接使用协议去实现,也就是上面提到的函数int ret = av_url_read_fseek(s->pb, stream_index, pts, flags).这个函数进入之后就会是进入了mms协议层的调用,由于出了seek同时还有pause的实现以及超时等问题,我们还需要一章blog来阐述!!
阅读(3124) | 评论(0) | 转发(0) |