前言
最近一个播放器项目基于ijkplayer做二次开发,是http-mp4点播,需要做精准seek,用到了avformat_seek_file接口,其他组开发人员的加密方案是:对video的2个nalu单元中间加了固定的N字节随机数据。
这里简要介绍下ffmpeg下的这个接口使用、注意事项,以及http-mp4网络流精准seek的方案。
接口介绍
int avformat_seek_file(AVFormatContext *s,
int stream_index,
int64_t min_ts,
int64_t ts,
int64_t max_ts,
int flags);
解释:
1)AVFormatContext *s,AVFormatContext实例。
2)int stream_index,流索引,但是只有在 flags 包含 AVSEEK_FLAG_FRAME 的时候才是 设置某个流的读取位置。其他情况都只是把这个流的 time_base (时间基)作为参考。
3)int64_t min_ts,跳转到的最小的时间,或时间单位,或字节单位,或帧数序号(第几帧)。
4)int64_t ts,目标seek位置,单位同上,通常填INT64_MIN即可。
5)int64_t max_ts,跳转到的最大的时间,单位同上,通常填 INT64_MAX 即可。
6)int flags,seek方式,如下:
AVSEEK_FLAG_BYTE,按字节大小进行seek。
AVSEEK_FLAG_FRAME,按帧数大小进行seek。
AVSEEK_FLAG_ANY,会seek到非IDR,解码会出现马赛克、花屏现象。
AVSEEK_FLAG_BACKWARD,往 ts 后面找最近的IDR。
本地mp4
经测试,avformat_seek_file接口,可通过将flag设置为AVSEEK_FLAG_BACKWARD,可seek到离ts最近的IDR帧(方向是ts的后面,本地明文mp4),参考示例:
avformat_seek_file(s, -1, INT64_MIN, target_ts, INT64_MAX, AVSEEK_FLAG_BACKWARD)
本地明文mp4,如果要做精准seek,则按如上方案实施即可,可找到target_ts后面最近的IDR帧,然后逐帧解码后,当视频的pts == target_ts,或者误差在指定范围内认为是精准seek即可。
经验证,若网络流是明文mp4,亦可seek到最近的IDR帧。
http-mp4
然而,对于如上方案的加密网络流,http-mp4却不能通过avformat_seek_file接口来找到离target_ts后面最近的IDR帧,可通过服务器来实现,让服务器返回最近的IDR帧。
此处,介绍一个本人自己经实践可行的方案。
// 此处指http-mp4流
static bool is_http_stream(const char* filename)
{
assert(filename);
char* pos = strstr(filename, "http://");
if (pos != NULL) {
return true;
}
pos = strstr(filename, "https://");
return pos != NULL;
}
而后,在ijkplayer的ff_ffplay.c文件read_thread方法的seek_req判断里,增加如下代码:
if (is->seek_req) {
int64_t seek_target = is->seek_pos;
if (is_http_stream(is->filename)) {
int gop_size = is->viddec.avctx->gop_size;
if (gop_size > 0) {
int64_t backward = gop_size * 1000 * 1000;
if (seek_target > backward) {
seek_target -= backward;
}
}
}
int64_t seek_min = is->seek_rel > 0 ? seek_target - is->seek_rel + 2: INT64_MIN;
int64_t seek_max = is->seek_rel < 0 ? seek_target - is->seek_rel - 2: INT64_MAX;
// FIXME the +-2 is due to rounding being not done in the correct direction in generation
// of the seek_pos/seek_rel variables
ffp_toggle_buffering(ffp, 1);
ffp_notify_msg3(ffp, FFP_MSG_BUFFERING_UPDATE, 0, 0);
ret = avformat_seek_file(is->ic, -1, seek_min, seek_target, seek_max, is->seek_flags);
关键的代码段逻辑:
1)即首先判断是否是http-mp4网络流(本地文件不走此逻辑)
2)通过VideoState结构体的Decoder类型字段viddec,找到AVCodecContext类型变量avctx
3)再通过avctx拿到本片源的gop_size
4)再将seek_target -= gop_size * 1000 * 1000,再按seek_target此值往回找IDR,可实现http-mp4的精准定位。
if (is_http_stream(is->filename)) {
int gop_size = is->viddec.avctx->gop_size;
if (gop_size > 0) {
int64_t backward = gop_size * 1000 * 1000;
if (seek_target > backward) {
seek_target -= backward;
}
}
}
注意:
1.如果gop较小,比如1s一个关键帧,则精准定位按ijkplayer的代码基本就能满足需求;
2.但本项目中,遇到了10s甚至更长的gop大小,这对精准seek提出了更高的要求,增加了精准seek的难度 ,avformat_seek_file接口无法seek到最近的IDR帧,如果您也是遇到gop较大情形,而又没有后端服务支持,可参考本精准seek方案。