Ijkplayer: M3U8分段视频在seekTo的时候需要很长时间才能播放,这时间跟拖动距离有关,怎样才能缩短时间?

Created on 22 Mar 2017  ·  44Comments  ·  Source: bilibili/ijkplayer

这是我用的M3U8地址:http://hot.vrs.sohu.com/ipad1479474_4601450223274_4680795.m3u8?plat=17&prod=ad

现象:1.播放整个的大视频seekTo时,没有问题,很快就可以继续播放。
2.对M3U8地址视频使用seekTo时可能出现两种情况:1.缓冲很长时间后可以播放 2.缓冲一段时间后视频暂停,点击播放视频从头开始播放。
3.如果seekTo的位置已经预缓冲完成则seekTo可以很快播放。
4.通过Log及播放信息可以看出,缓冲很长时间这个过程中,一直有数据进行下载,且速度并不慢。

请教各位大神帮忙分析下,这个问题很头疼,现在都是用这中分段的视频,缓冲时间过长太影响体验,谢谢了!

Most helpful comment

遇到这个问题主要是视频源里有#EXT-X-DISCONTINUITY这个标签,ffmpeg目前对这个标签是不支持的,可以参考:https://trac.ffmpeg.org/ticket/5419#comment:17 。如果使用官方原生的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,不会一直在这个循环中查询了。

All 44 comments

maybe you need modify the ffmpeg.make the probe size smaller.

the default probe is 5s. too long

使用IJKFFMoviePlayerController的话,我也碰到这问题,后来我直接改用IJKAVMoviePlayerController来显示,拖动进度就很快了,看了下IJKFFMoviePlayerController的日志,拖动的时候,会一个个ts请求下去,直到找到需要的ts,这样不慢才怪,不知道有什么法子直接定位到所需要的ts

有sample url吗

IJKAVMoviePlayerController use the ios defalut mediaplayer. not ffplay.
if yout want use ffplay,pls use IJKFFMoviePlayerController.
when the play seek to position.Reduced the probe size make it fast.

What's the prope size? Can it be modified when the video playing? I have both m3u8 and other video format like MP4. Is it will effect the MP4 playing?

发自网易邮箱大师
在2017年03月27日 10:23,test 写道:

IJKAVMoviePlayerController use the ios defalut mediaplayer. not ffplay.
if yout want use ffplay,pls use IJKFFMoviePlayerController.
when the play seek to position.Reduced the probe size make it fast.


You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub, or mute the thread.

Hi 0ct0cat,
I checked the codes of ijkplayer, IJKFFMoviePlayerController is used in IOS. How to resolve my problem on android?

there is a member probesize in AVFormatContext ,you can set it's value to control the demuxer probe size.
this method can use on android & ios

how to set probesize ,and where can i set it's value?

just like this
pls->ctx->probesize = 32 * 1024;
in hls.c
hls_read_header

thank you

I haved tried this, and i make pls->ctx->probesize = 4 * 1024. But it is not helpful. Does it work?

Hi 0ct0cat,
Could you try my Video address? I has tried a lot of ways to do it, but still can not resolved it. I have to use Vitamio as a temporary resolution.

@drivedreams
i test this stream,it's can't seek.
maybe there is a problem in mpegts.c.
after seek, hls.c work normal,it's can find right url to read and down ts data.
but the av_read_frame stop work.i think it's demux's bug.

Why do the bilibili app not have this problem? Do you have any idea to resolve it?

发自网易邮箱大师
在2017年04月07日 16:13,test 写道:

@drivedreams
i test this stream,it's can't seek.
maybe there is a problem in mpegts.c.
after seek, hls.c work normal,it's can find right url to read and down ts data.
but the av_read_frame stop work.i think it's demux's bug.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.

@drivedreams
this is not ijk's problem.
maybe sohu split the ts stream paramter have some problem.

Maybe sohu url is not standard. But vitamio player can play it without that problem? I can't modify sohu. It's aware

@drivedreams 解决了么,我也遇到这个样的问题,怎么解决的?

没有,可能需要更改源码,没时间研究。我查看搜狐的m m3u8格式应该是没有问题的。

@drivedreams 我本地文件也是这样的,不知道什么原因

Now, I test playing with IjkExoMediaPlayer. It can seek without the above problem. But I do not know whether it will cause any other problem.

这个是因为视频源的dts时间戳远小于seek时间戳,导致一直在找合适的分片,可以在hls_read_packet中使用分片查找的方式解决该问题

@kandynan
你的意思是修改ffmpeg源码重新编译吗,可以通过setoption之类的方法实现吗,不知道改动源码会不会导致其他问题。

我现在这么该暂时还没有发现问题

在 2017-04-25 13:15:35,"ZhangHaihai" notifications@github.com 写道:

@kandynan
你的意思是修改ffmpeg源码重新编译吗,可以通过setoption之类的方法实现吗,不知道改动源码会不会导致其他问题。


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.

@kandynan
我尝试修改了hls_read_packet 但是没解决,你知道怎么修改吗,给个思路也行

问题还没解决,我把我最近几天的分析写出来,大家帮忙分析下

  • 我这几天首先仔细研究了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目前对这个标签是不支持的,可以参考:https://trac.ffmpeg.org/ticket/5419#comment:17 。如果使用官方原生的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,不会一直在这个循环中查询了。

@CloudIAU 感谢回复,最近一直忙别的项目没看视频。 有时间按照您的方法修改下。还有就是发现ios 研究起来方便些 因为方便调试

@CloudIAU 按照你的修改后Seek正常了

@raymond1012 是否应该把这个bug 上面的解决方案已经验证了

@raymond1012 是不是应该把上面的解决方案应用到源码中,这样可以关闭很多issue

能提供下sample url吗

@Android4MediaPlayer 我提供的Url现在都是动态变化的。给你了也很快就会失效不具备代表性。这个BUG应该对于所有的点播的M3U8视频都都会出现,而且是必现的。随便找个M3U8测试下呗。

@CloudIAU 你好, 我按照你说的方案添加了以后(我将ffplay里边的几个hls.c都做了修改), 使用编译好的ijk播放视频的时候只要快进就崩溃.信息如下:
c80117f4-e257-4761-afc1-c04b83ff094d
请问哪里出了问题, 万分感谢!!!

@yeshibuzhong 是不是 seq_no >= pls->n_segments?

@Android4MediaPlayer @CloudIAU
ffmpeg 9e9d67d5489be7403017b9279d33334a03835601 vformat/hls: fix duration
这个提交有一些问题:

        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;
            }
        }

上面这段代码目的是为了在有 #EXT-X-DISCONTINUITY 修复pts,加上 previous_duration。
1,上面修复pts代码应该放在 av_read_frame 后面,即

                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);
                    }
                    // 修复pts代码应该放在这里,否则 seek 到下一个 #EXT-X-DISCONTINUITY pts 会陷入死循环
                    if (pls->pkt.pts != AV_NOPTS_VALUE)
                        pkt_ts =  pls->pkt.pts;
                    else if (pls->pkt.dts != AV_NOPTS_VALUE)
                        pkt_ts =  pls->pkt.dts;
                    else
                        pkt_ts = AV_NOPTS_VALUE;


                    c->first_timestamp = s->start_time != AV_NOPTS_VALUE ? s->start_time : 0;

                }

                if (pls->seek_timestamp == AV_NOPTS_VALUE)
                    break;

                if (pls->seek_stream_index < 0 ||

另外一个问题就是为什么要做这个判断 seq_no < pls->n_segments?
这边测试 发现 seg no 没有对上,看过去像是 pls->cur_seq_no 已经+1, open 了新的 ts文件, 但是 av_read_frame 返回的还是 上一个 ts文件的最后一个包。这个是不是因为 read_data 读取buf 在ffmpeg io 中被缓存了?日志如下:

 E/IJKMEDIA: 6666 pkt->pts=-9223372036854775808, pkt->dts=-9223372036854775808, seq_no=23, previous_duration=300002000,
 E/IJKMEDIA: 6666 pkt->pts=-9223372036854775808, pkt->dts=-9223372036854775808, seq_no=23, previous_duration=300002000,
 E/IJKMEDIA: 6666 pkt->pts=-9223372036854775808, pkt->dts=-9223372036854775808, seq_no=23, previous_duration=300002000,
 E/IJKMEDIA: 6666 pkt->pts=-9223372036854775808, pkt->dts=-9223372036854775808, seq_no=23, previous_duration=300002000,
 E/IJKMEDIA: 6666 pkt->pts=-9223372036854775808, pkt->dts=-9223372036854775808, seq_no=23, previous_duration=300002000,
 E/IJKMEDIA: 6666 cur_seq_no=24
 I/IJKMEDIA: Opening 'http://vipgdbgp.vod.tv.itc.cn/m3u8?&start=0&end=5.32&k=hWODtfkIlB6OWhcsWFvHbD168Vebz89bDEWNoL8VP6kRDEKNPFXUyYbS0pbcWOoGyG
tUJlzSwdoSwdomNGopwGopxVoSrGoSrBhRODOpCCqmvm0pvUhRYRzSwWj9lvzSvUqCPCJSX3Tkk7oL1gqpxmqK3KJvsUTka5qkkBxvqfomkCo
ding
 I/lebotv.danmaku.ijk.media.player.IjkMediaPlayer: onNativeInvoke 1
 I/IJKMEDIA: Hit DNS cache hostname = vipgdbgp.vod.tv.itc.cn
 I/lebotv.danmaku.ijk.media.player.IjkMediaPlayer: onNativeInvoke 131073
 I/lebotv.danmaku.ijk.media.player.IjkMediaPlayer: onNativeInvoke 131074
 I/lebotv.danmaku.ijk.media.player.IjkMediaPlayer: onNativeInvoke 131073
 I/lebotv.danmaku.ijk.media.player.IjkMediaPlayer: onNativeInvoke 131074
 I/lebotv.danmaku.ijk.media.player.IjkMediaPlayer: onNativeInvoke 2

这里刚刚好是遇到#EXT-X-DISCONTINUITY,打开下一个 ts文件
pts 27907200 明显是没有修复成功

 E/IJKMEDIA: 6666 pkt->pts=27907200, pkt->dts=27903600, seq_no=24, previous_duration=600004000,
 E/IJKMEDIA: 6666 pkt->pts=54914760, pkt->dts=54907560, seq_no=24, previous_duration=600004000,
 I/hpplay-java: [main]:PhoenixPlayer:onBufferingUpdate percent: 24
 E/IJKMEDIA: 6666 pkt->pts=54929160, pkt->dts=54911160, seq_no=24, previous_duration=600004000,
 E/IJKMEDIA: 6666 pkt->pts=54921960, pkt->dts=54914760, seq_no=24, previous_duration=600004000,
 E/IJKMEDIA: 6666 pkt->pts=54918360, pkt->dts=54918360, seq_no=24, previous_duration=600004000,

@drivedreams 你好, 我按照CloudIAU方法, 快进的时候回崩溃掉, 我用的是ijk0.88 最新版, 请问你知道怎么回事吗?
上边有我的崩溃信息

@CloudIAU 大佬这段代码有pull 到源码上么

各位大佬,这个问题解决了吗,现在测试的情况来看,拖放的时候加载依然缓慢,比mp4格式的拖放要慢很多

@chenzh2017,一样的,遇到了,mp4播放不会慢,但是用么m3u8播放就会加长,让后循环播放不得行

@CloudIAU 按照你的修改后Seek正常了

你好,请问iOS上怎么解决这个问题

niubility

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Tan-zy picture Tan-zy  ·  4Comments

xuluming picture xuluming  ·  4Comments

launam picture launam  ·  3Comments

lingchen52 picture lingchen52  ·  3Comments

yuraoff picture yuraoff  ·  3Comments