1、apple 的hls方案,采用ffmpeg转码的ts流,播放时会渐渐变得音画不同步,sohu源的处理办法是每隔5分钟加一次discontinue标签,但是这个标签会导致原生播放器重启,表现得有点卡。
解决办法:针对这种情况,改造播放器,不让重启,直接读取下一个流是比较好的办法。但是ffmpeg处理hls的播放存在其它一些问题:1、每遇到discontinue时,显示的播放时长会清零;2、只能在第一个discontinue前进行拖放。本文针对这个问题,对ffmpeg进行改造,使对hls源更优雅的适配。
ffmpeg 解决hls拖放的问题_weixin_34194087的博客-CSDN博客
2、由于discontinue的pts导致播放无法继续
记录一次解决EXT-X-DISCONTINUITY PTS错误的历程 - 简书
3、M3U8分段视频在seekTo的时候需要很长时间才能播放
M3U8分段视频在seekTo的时候需要很长时间才能播放,这时间跟拖动距离有关,怎样才能缩短时间?https://github.com/Bilibili/ijkplayer/issues/2874#
我这几天首先仔细研究了ijkplayer和ffmpeg, 基本把播放和seek逻辑搞清楚了。就像 @0ct0cat 说的seek是没有问题的,hls成功找到了正确的url,而且进行了读取,但是视频仍然无法播放。(分析了好几天,回到了你的结论,明白了逻辑才明白你的)。
按照这个结论继续分析,发现搜狐的M3U8带有EXT-X-DISCONTINUITY标签,如果seek在下一个EXT-X-DISCONTINUITY 之前随意seek都不会出现问题,但seek超出下一个EXT-X-DISCONTINUITY就会无法播放,是格式变化导致的吗?
继续分析hls对M3U8进行解析(应该没理解错,哈哈): 每个EXT-X-DISCONTINUITY划分为一个playlist, 每个EXTINF为一个segment,一个playlist有多个segment. Playlist 中cur_seq_no是当前使用的segment的序号。通过增加log,并分析发现seek时使用的cur_seq_no是正确的。所选的playlist也没有问题,进一步说明seek位置没有问题,理论上playlist的选择没有问题应该是可以播放的,难道分配的解析器有问题?
未完待续...
遇到这个问题主要是视频源里有#EXT-X-DISCONTINUITY这个标签,ffmpeg目前对这个标签是不支持的,可以参考:#5419 (HLS EXT-X-DISCONTINUITY tag is not supported) – FFmpeg 。如果使用官方原生的ffmpeg,带这个标签的视频是无法播放的。
但是ijkplayer对ffpmeg做了处理,使得ijkplayer可以支持这个标签,所以现在可以播放,但ijkplayer并没有添加带这个标签的视频seek的处理,所以导致seek时会长时间卡主,这应该是个Bug。
产生这个问题的原因是:
#EXT-X-DISCONTINUITY标签后面的视频的pts和标签之前的视频是不连续的,而ffmpeg在seek时,会去将要seek的timestamp与当前包的pts区间进行对比,来查看查找的是否是当前包,但由于pts不连续的问题,会导致seek的timestamp总是大于包的pts区间,所以到会导致一直卡着,无法播放。
我目前是这样修复的,测试了seek没问题:
首先在hls.c文件里添加一个函数,用于查找当前packet之前的视频的总时长,用于得出正确的pts:
static int find_timestamp_in_seq_no( struct playlist *pls,int64_t *timestamp, int seq_no)
{
int i;
*timestamp=0;
for (i = 0; i < seq_no; i++) {
*timestamp += pls->segments[i]->duration ;
}
return 0;
}
并且在hls_read_packet函数中声明一个变量,来保存这个时长:
int64_t timestamp = AV_NOPTS_VALUE;//add
然后在hls_read_packet函数中的这段代码中插入这个函数的调用:
for (i = 0; i < c->n_playlists; i++) {
struct playlist *pls = c->playlists[i];
find_timestamp_in_seq_no(pls,×tamp,pls->cur_seq_no);//add
/* Make sure we've got one buffered packet from each open playlist
* stream */
if (pls->needed && !pls->pkt.data) {
最后在计算seek的timestamp是否在当前packet的区间的代码上加上这个时长:
ts_diff = timestamp + av_rescale_rnd(pls->pkt.dts, AV_TIME_BASE,
tb.den, AV_ROUND_DOWN) -
pls->seek_timestamp;//edit
if (ts_diff >= 0 && (pls->seek_flags & AVSEEK_FLAG_ANY ||
pls->pkt.flags & AV_PKT_FLAG_KEY)) {
pls->seek_timestamp = AV_NOPTS_VALUE;
break;
}
这样即可正确的seek,不会一直在这个循环中查询了。
ijkplayer官方修改
ffmpeg 9e9d67d5489be7403017b9279d33334a03835601 vformat/hls: fix duration
avformat/hls: fix duration
diff --git a/libavformat/hls.c b/libavformat/hls.c
index 9542ce4a72..6eab642477 100644
--- a/libavformat/hls.c
+++ b/libavformat/hls.c
@@ -2,6 +2,7 @@
* Apple HTTP Live Streaming demuxer
* Copyright (c) 2010 Martin Storsjo
* Copyright (c) 2013 Anssi Hannula
+ * Copyright (c) 2011 Cedirc Fung ([email protected])
*
* This file is part of FFmpeg.
*
@@ -65,7 +66,9 @@ enum KeyType {
};
struct segment {
+ int64_t previous_duration;
int64_t duration;
+ int64_t start_time;
int64_t url_offset;
int64_t size;
char *url;
@@ -668,7 +671,7 @@ static int parse_playlist(HLSContext *c, const char *url,
struct playlist *pls, AVIOContext *in)
{
int ret = 0, is_segment = 0, is_variant = 0;
- int64_t duration = 0;
+ int64_t duration = 0, previous_duration1 = 0, previous_duration = 0, total_duration = 0;
enum KeyType key_type = KEY_NONE;
uint8_t iv[16] = "";
int has_iv = 0;
@@ -783,6 +786,8 @@ static int parse_playlist(HLSContext *c, const char *url,
} else if (av_strstart(line, "#EXT-X-ENDLIST", &ptr)) {
if (pls)
pls->finished = 1;
+ } else if (av_strstart(line, "#EXT-X-DISCONTINUITY", &ptr)) {
+ previous_duration = previous_duration1;
} else if (av_strstart(line, "#EXTINF:", &ptr)) {
is_segment = 1;
duration = atof(ptr) * AV_TIME_BASE;
@@ -815,6 +820,10 @@ static int parse_playlist(HLSContext *c, const char *url,
ret = AVERROR(ENOMEM);
goto fail;
}
+ previous_duration1 += duration;
+ seg->previous_duration = previous_duration;
+ seg->start_time = total_duration;
+ total_duration += duration;
seg->duration = duration;
seg->key_type = key_type;
if (has_iv) {
@@ -2050,6 +2059,28 @@ static int hls_read_packet(AVFormatContext *s, AVPacket *pkt)
}
}
+ if (c->playlists[minplaylist]->finished) {
+ struct playlist *pls = c->playlists[minplaylist];
+ int seq_no = pls->cur_seq_no - pls->start_seq_no;
+ if (seq_no < pls->n_segments && s->streams[pkt->stream_index]) {
+ struct segment *seg = pls->segments[seq_no];
+ int64_t pred = av_rescale_q(seg->previous_duration,
+ AV_TIME_BASE_Q,
+ s->streams[pkt->stream_index]->time_base);
+ int64_t max_ts = av_rescale_q(seg->start_time + seg->duration,
+ AV_TIME_BASE_Q,
+ s->streams[pkt->stream_index]->time_base);
+ /* EXTINF duration is not precise enough */
+ max_ts += 2 * AV_TIME_BASE;
+ if (s->start_time > 0) {
+ max_ts += av_rescale_q(s->start_time,
+ AV_TIME_BASE_Q,
+ s->streams[pkt->stream_index]->time_base);
+ }
+ if (pkt->dts != AV_NOPTS_VALUE && pkt->dts + pred < max_ts) pkt->dts += pred;
+ if (pkt->pts != AV_NOPTS_VALUE && pkt->pts + pred < max_ts) pkt->pts += pred;
+ }
+ }
return 0;
}
return AVERROR_EOF;
播放M3u8的视频的时候,最好:
1.将max-buffer-size探测的长度尽可能的缩小
2.enable-accurate-seek关闭这个属性