关于HLS的discontinue出现问题

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的时候需要很长时间才能播放,这时间跟拖动距离有关,怎样才能缩短时间?icon-default.png?t=N7T8https://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,&timestamp,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 (wolfplanet@gmail.com)
  *
  * 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关闭这个属性

【开发踩坑】EXT-X-DISCONTINUITY 合并多个 m3u8 文件到一个 m3u8_#ext-x-discontinuity-CSDN博客

背景
因业务需求,需要将多个 m3u8 文件合并成一个。解析 m3u8 文件以后,发现一个文件分为 3 部分,第一部分是 #EXTM3U 这些文件头,第二部分是 ts 后缀的视频分片文件,第三部分就是 #EXT-X-ENDLIST 结尾。所以我的思路就是将多个 m3u8 文件的 ts 都提取出来合并到一起,然后加上头和结尾。

问题
合并出来的文件能正常播放,但是每次第一个视频文件播放完以后就不自动播放第二个视频了。

原因
原因是拼接的 2 个 ts 文件的时间不连续了,所以不会自动续播。

解决方案
在两个拼接的视频文件之间加上 #EXT-X-DISCONTINUITY。如下所示:

#EXTINF:6
https:xxxx.ts
#EXT-X-DISCONTINUITY // 两断不连续的视频中间加上 #EXT-X-DISCONTINUITY tag
#EXTINF:6
https:xxxx.ts
1
2
3
4
5
参考资料
https://stackoverflow.com/questions/64842167/can-i-merge-multiple-m3u8-files-into-one-m3u8-file
https://datatracker.ietf.org/doc/html/draft-pantos-http-live-streaming-23#section-4.3.2.3

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值