文章目录
-
hls_read_seek
-
hls_read_packet
-
精准seek
hls_read_seek
它支持的flag只有AVSEEK_FLAG_BACKWARD和AVSEEK_FLAG_ANY
如果同时存在音频和视频,stream_index要使用视频的stream,否则AVSEEK_FLAG_BACKWARD这个flag相当于无效,因为音频的每一帧都是关键帧
它的工作就是根据当前的时间戳重新选择一个适合的ts文件(URL),主要关注这两个参数cur_seq_no和seek_timestamp
当下次调用hls_read_packet的时候就会调用http读取cur_seq_no上的流,一直读到符合当前seek_timestamp和seek_flags的流才返回,所以seek之后第一次读数据会比较慢
static int hls_read_seek(AVFormatContext *s, int stream_index, int64_t timestamp, int flags)
{
HLSContext *c = s->priv_data;
struct playlist *seek_pls = NULL;
int i, seq_no;
int j;
int stream_subdemuxer_index;
int64_t first_timestamp, seek_timestamp, duration;
// hls不支持byte方式的seek,并判断是否支持seek,hls的seek状态会动态改变的
if ((flags & AVSEEK_FLAG_BYTE) || (c->ctx->ctx_flags & AVFMTCTX_UNSEEKABLE))
return AVERROR(ENOSYS);
first_timestamp = c->first_timestamp == AV_NOPTS_VALUE ? 0 : c->first_timestamp;
// 计算以time_base为倍数的时间戳
seek_timestamp = av_rescale_rnd(timestamp, AV_TIME_BASE,
s->streams[stream_index]->time_base.den,
flags & AVSEEK_FLAG_BACKWARD ?
AV_ROUND_DOWN : AV_ROUND_UP);
duration = s->duration == AV_NOPTS_VALUE ? 0 : s->duration;
if (0 < duration && duration < seek_timestamp - first_timestamp)
return AVERROR(EIO);
/* find the playlist with the specified stream */
for (i = 0; i < c->n_playlists; i++) {
struct playlist *pls = c->playlists[i];
for (j = 0; j < pls->n_main_streams; j++) {
if (pls->main_streams[j] == s->streams[stream_index]) {
seek_pls = pls;
stream_subdemuxer_index = j;
break;
}
}
}
/* check if the timestamp is valid for the playlist with the specified stream index */
if (!seek_pls || !find_timestamp_in_playlist(c, seek_pls, seek_timestamp, &seq_no))
return AVERROR(EIO);
/* set segment now so we do not need to search again below */
seek_pls->cur_seq_no = seq_no;
seek_pls->seek_stream_index = stream_subdemuxer_index;
for (i = 0; i < c->n_playlists; i++) {
/* Reset reading */
struct playlist *pls = c->playlists[i];
if (pls->input)
ff_format_io_close(pls->parent, &pls->input);
pls->input_read_done = 0;
if (pls->input_next)
ff_format_io_close(pls->parent, &pls->input_next);
pls->input_next_requested = 0;
av_packet_unref(&pls->pkt);
reset_packet(&pls->pkt);
pls->pb.eof_reached = 0;
/* Clear any buffered data */
pls->pb.buf_end = pls->pb.buf_ptr = pls->pb.buffer;
/* Reset the pos, to let the mpegts demuxer know we've seeked. */
pls->pb.pos = 0;
/* Flush the packet queue of the subdemuxer. */
ff_read_frame_flush(pls->ctx);
pls->seek_timestamp = seek_timestamp;
pls->seek_flags = flags;
if (pls != seek_pls) {
/* set closest segment seq_no for playlists not handled above */
find_timestamp_in_playlist(c, pls, seek_timestamp, &pls->cur_seq_no);
/* seek the playlist to the given position without taking
* keyframes into account since this playlist does not have the
* specified stream where we should look for the keyframes */
pls->seek_stream_index = -1;
pls->seek_flags |= AVSEEK_FLAG_ANY;
}
}
c->cur_timestamp = seek_timestamp;
return 0;
}
hls_read_packet
- 当seek之后第一帧的时间戳一定是等于或者晚于我们要期望的seek时间的
static int hls_read_packet(AVFormatContext *s, AVPacket *pkt)
{
HLSContext *c = s->priv_data;
int ret, i, minplaylist = -1;
recheck_discard_flags(s, c->first_packet);
c->first_packet = 0;
for (i = 0; i < c->n_playlists; i++) {
struct playlist *pls = c->playlists[i];
/* Make sure we've got one buffered packet from each open playlist
* stream */
if (pls->needed && !pls->pkt.data) {
while (1) {
int64_t ts_diff;
AVRational tb;
ret = av_read_frame(pls->ctx, &pls->pkt);
if (ret < 0) {
if (!avio_feof(&pls->pb) && ret != AVERROR_EOF)
return ret;
reset_packet(&pls->pkt);
break;
} else {
/* stream_index check prevents matching picture attachments etc. */
if (pls->is_id3_timestamped && pls->pkt.stream_index == 0) {
/* audio elementary streams are id3 timestamped */
fill_timing_for_id3_timestamped_stream(pls);
}
if (c->first_timestamp == AV_NOPTS_VALUE &&
pls->pkt.dts != AV_NOPTS_VALUE)
c->first_timestamp = av_rescale_q(pls->pkt.dts,
get_timebase(pls), AV_TIME_BASE_Q);
}
if (pls->seek_timestamp == AV_NOPTS_VALUE)
break;
if (pls->seek_stream_index < 0 || pls->seek_stream_index == pls->pkt.stream_index) {
if (pls->pkt.dts == AV_NOPTS_VALUE) {
pls->seek_timestamp = AV_NOPTS_VALUE;
break;
}
tb = get_timebase(pls);
// 得到当前帧的时间戳和要seek的时间戳的差值
ts_diff = av_rescale_rnd(pls->pkt.dts, AV_TIME_BASE, tb.den, AV_ROUND_DOWN) - pls->seek_timestamp;
// seek_flags就支持AVSEEK_FLAG_ANY和AV_PKT_FLAG_KEY,返回的帧一定是要晚于seek时间戳的
if (ts_diff >= 0 && (pls->seek_flags & AVSEEK_FLAG_ANY || pls->pkt.flags & AV_PKT_FLAG_KEY)) {
pls->seek_timestamp = AV_NOPTS_VALUE;
break;
}
}
av_packet_unref(&pls->pkt);
reset_packet(&pls->pkt);
}
}
/* Check if this stream has the packet with the lowest dts */
if (pls->pkt.data) {
struct playlist *minpls = minplaylist < 0 ? NULL : c->playlists[minplaylist];
if (minplaylist < 0) {
minplaylist = i;
} else {
int64_t dts = pls->pkt.dts;
int64_t mindts = minpls->pkt.dts;
if (dts == AV_NOPTS_VALUE ||
(mindts != AV_NOPTS_VALUE && compare_ts_with_wrapdetect(dts, pls, mindts, minpls) < 0))
minplaylist = i;
}
}
}
...
/* If we got a packet, return it */
if (minplaylist >= 0) {
...
return 0;
}
return AVERROR_EOF;
}
精准seek
要做精准seek必定是需要修改hls_read_packet的代码的,要让它返回我们期望的时间戳的前一个关键帧的时间戳
本文结尾底部,领取最新最全C++音视频学习提升资料,内容包括(C/C++,Linux 服务器开发,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)↓↓↓↓↓↓↓↓↓↓↓↓