抱歉,你查看的文章不存在

FFmpeg总结(十四)FFmpeg如何解析直播点播m3u8

看下直播m3u8结构:

#EXTM3U
#EXT-X-MEDIA-SEQUENCE:3918
#EXT-X-TARGETDURATION:10
#EXTINF:10,
2017071806/1500358480.ts?type=hls_live_slice
#EXTINF:10,
2017071806/1500358490.ts?type=hls_live_slice
#EXTINF:10,
2017071806/1500358500.ts?type=hls_live_slice
#EXTINF:10,
2017071806/1500358510.ts?type=hls_live_slice
#EXTINF:10,
2017071806/1500358520.ts?type=hls_live_slice
#EXTINF:10,
2017071806/1500358535.ts?type=hls_live_slice
#EXTINF:10,
2017071806/1500358545.ts?type=hls_live_slice
#EXTINF:10,
2017071806/1500358555.ts?type=hls_live_slice
#EXTINF:10,
2017071806/1500358565.ts?type=hls_live_slice
#EXTINF:10,
2017071806/1500358575.ts?type=hls_live_slice

看下点播m3u8结构:

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-TARGETDURATION:19
#EXT-X-PLAYLIST-TYPE:VOD
#EXTINF:9.800,
00_d0024036a7j.320086.1.ts?index=0&start=0&end=9800&brs=0&bre=1509827&ver=4
#EXTINF:10.480,
01_d0024036a7j.320086.1.ts?index=1&start=9800&end=20280&brs=1509828&bre=3828055&ver=4
#EXTINF:9.960,
02_d0024036a7j.320086.1.ts?index=2&start=20280&end=30240&brs=3828056&bre=6248743&ver=4
#EXTINF:9.520,
03_d0024036a7j.320086.1.ts?index=3&start=30240&end=39760&brs=6248744&bre=8234399&ver=4
#EXTINF:9.920,
04_d0024036a7j.320086.1.ts?index=4&start=39760&end=49680&brs=8234400&bre=10619931&ver=4
#EXTINF:9.960,
05_d0024036a7j.320086.1.ts?index=5&start=49680&end=59640&brs=10619932&bre=13400639&ver=4
#EXTINF:9.720,
06_d0024036a7j.320086.1.ts?index=6&start=59640&end=69360&brs=13400640&bre=16777683&ver=4
#EXTINF:10.880,
07_d0024036a7j.320086.1.ts?index=7&start=69360&end=80240&brs=16777684&bre=19713867&ver=4
...省略
#EXTINF:9.960,
029_d0024036a7j.320086.2.ts?index=29&start=295520&end=305480&brs=0&bre=1287611&ver=4
#EXTINF:9.040,
...省略
#EXTINF:7.240,
0315_d0024036a7j.320086.11.ts?index=315&start=3181360&end=3188600&brs=30080752&bre=31204615&ver=4
#EXT-X-ENDLIST

区别:

  • 1、 每个TS分片时间,通过标签EXTINF,后面有的是float型,有的是int型,一般为10s(也有不会10s的),最后如果没有10s,就取对应时间。
  • 2、不是所有点播m3u8中都有EXT-X-PLAYLIST-TYPE,表明类型。最直接区分就是直播m3u8没有EXT-X-ENDLIST标签,因为是实时流,自然不会有结束,否则就是点播流了。

FFmpeg是如何解析直播,点播的HLS?在\libavformat\hlsproto.c中,就是实现步骤,先打开m3u8文件,然后parse。

static int hls_open(URLContext *h, const char *uri, int flags)
{
    HLSContext *s = h->priv_data;
    int ret, i;
    const char *nested_url;
    if (flags & AVIO_FLAG_WRITE)
        return AVERROR(ENOSYS);
    h->is_streamed = 1;
    if (av_strstart(uri, "hls+", &nested_url)) {
        av_strlcpy(s->playlisturl, nested_url, sizeof(s->playlisturl));
    } else if (av_strstart(uri, "hls://", &nested_url)) {
        av_log(h, AV_LOG_ERROR,
               "No nested protocol specified. Specify e.g. hls+http://%s\n",
               nested_url);
        ret = AVERROR(EINVAL);
        goto fail;
    } else {
        av_log(h, AV_LOG_ERROR, "Unsupported url %s\n", uri);
        ret = AVERROR(EINVAL);
        goto fail;
    }
    av_log(h, AV_LOG_WARNING,
           "Using the hls protocol is discouraged, please try using the "
           "hls demuxer instead. The hls demuxer should be more complete "
           "and work as well as the protocol implementation. (If not, "
           "please report it.) To use the demuxer, simply use %s as url.\n",
           s->playlisturl);
    if ((ret = parse_playlist(h, s->playlisturl)) < 0)
        goto fail;
    if (s->n_segments == 0 && s->n_variants > 0) {
        int max_bandwidth = 0, maxvar = -1;
        for (i = 0; i < s->n_variants; i++) {
            if (s->variants[i]->bandwidth > max_bandwidth || i == 0) {
                max_bandwidth = s->variants[i]->bandwidth;
                maxvar = i;
            }
        }
        av_strlcpy(s->playlisturl, s->variants[maxvar]->url,
                   sizeof(s->playlisturl));
        if ((ret = parse_playlist(h, s->playlisturl)) < 0)
            goto fail;
    }
    if (s->n_segments == 0) {
        av_log(h, AV_LOG_WARNING, "Empty playlist\n");
        ret = AVERROR(EIO);
        goto fail;
    }
    s->cur_seq_no = s->start_seq_no;
    if (!s->finished && s->n_segments >= 3)
        s->cur_seq_no = s->start_seq_no + s->n_segments - 3;
    return 0;
fail:
    hls_close(h);
    return ret;
}

解析playlist中的ts流

static int parse_playlist(URLContext *h, const char *url)
{
    HLSContext *s = h->priv_data;
    AVIOContext *in;
    int ret = 0, is_segment = 0, is_variant = 0, bandwidth = 0;
    int64_t duration = 0;
    char line[1024];
    const char *ptr;
    if ((ret = ffio_open_whitelist(&in, url, AVIO_FLAG_READ,
                                   &h->interrupt_callback, NULL,
                                   h->protocol_whitelist, h->protocol_blacklist)) < 0)
        return ret;
    read_chomp_line(in, line, sizeof(line));
    if (strcmp(line, "#EXTM3U")) {
        ret = AVERROR_INVALIDDATA;
        goto fail;
    }
    free_segment_list(s);
    s->finished = 0;
    while (!avio_feof(in)) {
        read_chomp_line(in, line, sizeof(line));
        if (av_strstart(line, "#EXT-X-STREAM-INF:", &ptr)) {
            struct variant_info info = {{0}};
            is_variant = 1;
            ff_parse_key_value(ptr, (ff_parse_key_val_cb) handle_variant_args,
                               &info);
            bandwidth = atoi(info.bandwidth);
        } else if (av_strstart(line, "#EXT-X-TARGETDURATION:", &ptr)) {
            s->target_duration = atoi(ptr) * AV_TIME_BASE;
        } else if (av_strstart(line, "#EXT-X-MEDIA-SEQUENCE:", &ptr)) {
            s->start_seq_no = atoi(ptr);
        } else if (av_strstart(line, "#EXT-X-ENDLIST", &ptr)) {
            s->finished = 1;
        } else if (av_strstart(line, "#EXTINF:", &ptr)) {
            is_segment = 1;
            duration = atof(ptr) * AV_TIME_BASE;
        } else if (av_strstart(line, "#", NULL)) {
            continue;
        } else if (line[0]) {
            if (is_segment) {
                struct segment *seg = av_malloc(sizeof(struct segment));
                if (!seg) {
                    ret = AVERROR(ENOMEM);
                    goto fail;
                }
                seg->duration = duration;
                ff_make_absolute_url(seg->url, sizeof(seg->url), url, line);
                dynarray_add(&s->segments, &s->n_segments, seg);
                is_segment = 0;
            } else if (is_variant) {
                struct variant *var = av_malloc(sizeof(struct variant));
                if (!var) {
                    ret = AVERROR(ENOMEM);
                    goto fail;
                }
                var->bandwidth = bandwidth;
                ff_make_absolute_url(var->url, sizeof(var->url), url, line);
                dynarray_add(&s->variants, &s->n_variants, var);
                is_variant = 0;
            }
        }
    }
    s->last_load_time = av_gettime_relative();
fail:
    avio_close(in);
    return ret;
}

原文发布于微信公众号 - 何俊林(DriodDeveloper)

原文发表时间:2017-07-19

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

编辑于

码农突围

0 篇文章93 人订阅

相关文章

来自专栏西安软件开发

软件公司对软件著作权保护及侵权司法实践中的常见判断标准及辩驳论据

软件开发工作中经常要研究参考他人已存在的软件,所以软件著作权侵权可能就在不经意之间发生了,因此软件公司进行软件开发时需要十分谨慎,以免误入他人权利的领地。从软件...

14930
来自专栏飞雪无情的博客

微信小游戏跳一跳为什么这么火?

某天晚上刚吃过饭。 正靠在沙发上刷手机。 突然微信上一个很久不活跃的同学群闪了一下。 什么情况? 难道是哪位同学荷尔蒙分泌过多, 要对当年暗恋的对象来一段深情告...

8820
来自专栏镁客网

「产品」AutoBot Eye行车记录仪,明明可以靠颜值却偏偏要凭实力

13920
来自专栏数据和云

兴趣驱动 成就卓越 - 访Oracle开发大师苏旭晖先生

苏旭晖,网名 newkid ITPUB开发版资深版主,SQL开发专家 编辑手记:感谢苏旭晖先生授权我们独家转载其系列精品文章,我们首先转载一篇ITPUB论坛对N...

32060
来自专栏牛客网

Java工程师:双非计算机小硕的秋招经历总结

拿到了心仪公司的offer了,自己的秋招算结束了,写个不靠谱的经历,不供参考 双非计算机小硕,目标职位java相关,学校里跟着师兄怼过几个小水项目,以下是我经历...

39490
来自专栏java一日一条

闭嘴吧,冒名顶替综合症们,我太会编程了

无论你称之为冒名顶替综合征还是简单地只是你的自请求尊功能失常,我们都有某些时刻感到像一个冒牌货,这一点,总有一天,我们都会发现。当那天来临,最易于患冒名顶替综合...

15210
来自专栏web前端教室

当你不技如人时,怎样把心仪的工作抢到手?

<!-- 最牛b的未必是最好使的 --> 找工作,难免技不如人,这很正常。但找工作毕竟不是武林大会,不是你武功最高就真的最牛。求职这事一看本事,二看缘分。有些时...

22260
来自专栏机器人网

这个机器人要为我们移居火星打前站

人类一直希望能在地球之外找到另一个适合人类居住的星球,其中火星就是一个备选项之一。当然,想要移居火星,人类无法单独做到这一点。他们需要具备百科知识、在压力下沉着...

28360
来自专栏我就是马云飞

你们期待的面经来了。

20820
来自专栏VRPinea

毒酒,抑或是佳酿?做一名为怪物们服务的调酒师感觉如何?

14750

扫码关注云+社区

领取腾讯云代金券