ffmpeg之hls实现分析

本文深入分析FFmpeg 4.0版本中HLS解复用器的实现,重点关注read_probe、read_header、read_packet、read_close和read_seek等关键函数。通过解析m3u8文件,理解HLS协议的探测、初始化、数据读取和关闭过程。
摘要由CSDN通过智能技术生成

1、引言

本文将关注于FFmpeg中的HLS相关实现,相关代码在libavformat/hls.c中(我所使用的Ffmpeg版本是4.0的),分析hls_demuxer的主要实现逻辑。

本文作为我之前的HLS综述的后续文章,也是ffmpeg框架分析的后续文章。前者介绍了HLS协议相关的理论部分,后者介绍了FFmpeg主要框架分析(本文主要关注demuxer);要是你对此感兴趣建议了解下。

2、ff_hls_demuxer的主要对外接口

#define OFFSET(x) offsetof(HLSContext, x)
static const AVOption hls_options[] = {
    {"live_start_index", "segment index to start live streams at (negative values are from the end)",
        OFFSET(live_start_index), AV_OPT_TYPE_INT, {.i64 = -3}, INT_MIN, INT_MAX, FLAGS},
    // ... HLS demuxer支持的参数,有删减
    {NULL}
};
​
static const AVClass hls_class = {
    .class_name = "hls,applehttp",
    .item_name  = av_default_item_name,
    .option     = hls_options,
    .version    = LIBAVUTIL_VERSION_INT,
};
​
AVInputFormat ff_hls_demuxer = {
    .name           = "hls,applehttp",
    .long_name      = NULL_IF_CONFIG_SMALL("Apple HTTP Live Streaming"),
    .priv_class     = &hls_class,
    .priv_data_size = sizeof(HLSContext),
    .flags          = AVFMT_NOGENSEARCH,
    .read_probe     = hls_probe, // 媒体格式探测
    .read_header    = hls_read_header, // 读取协议头并获取节目信息
    .read_packet    = hls_read_packet, // 读取音视频包
    .read_close     = hls_close, // 关闭HLS通信
    .read_seek      = hls_read_seek, // 实现HLS的seek操作
};

通常分析FFmpeg中的demuxer,我们主要关注其read_probe、read_header、read_packet、read_close、read_seek这五个函数指针所对应的实现代码。FFmpeg在实现demuxer时将其协议解析的部分完整封装了demuxer中。 hls_demuxer中主要的结构是HLSContext,定义如下:

typedef struct HLSContext {
    AVClass *class;
    AVFormatContext *ctx;
    int n_variants;
    struct variant **variants;/* master playlist中有多个variants */
    int n_playlists;
    struct playlist **playlists;/* playlists中包含一个segment的列表 */
    int n_renditions;
    struct rendition **renditions;
​
    int cur_seq_no;
    int live_start_index;
    int first_packet;
    int64_t first_timestamp;
    int64_t cur_timestamp;
    AVIOInterruptCB *interrupt_callback;
    AVDictionary *avio_opts;
    int strict_std_compliance;
    char *allowed_extensions;
    int max_reload;
    int http_persistent;
    int http_multiple;
    AVIOContext *playlist_pb;
} HLSContext;

接下来我们将逐个函数查看。

3、hls_demuxer核心接口实现

3.1 read_probe -- hls_probe

这个函数主要是HLS格式探测,其实现代码如下:

static int hls_probe(AVProbeData *p)
{
    /* HLS协议要求必须以#EXTM3U打头,并至少有下面三个字段之一存在 */
    if (strncmp(p->buf, "#EXTM3U", 7))
        return 0;
​
    if (strstr(p->buf, "#EXT-X-STREAM-INF:")     ||
        strstr(p->buf, "#EXT-X-TARGETDURATION:") ||
        strstr(p->buf, "#EXT-X-MEDIA-SEQUENCE:"))
        return AVPROBE_SCORE_MAX;
    return 0;
}

这段代码逻辑比较简单,都是关于字符串匹配的处理,也是按照HLS协议规定的要求进行媒体格式探测的。

3.2 read_header -- hls_read_header

hls_read_header将是比较复杂的处理逻辑,因为这里涉及到master playlist解析、节目信息获取,同时初始化hls解析相关的结构。这个函数实现有300行左右。我们将分为三部分:playlist解析、hls相关初始化、提取节目信息。

3.2.1 playlist解析

hls_read_header第一部分代码如下:

static int hls_read_header(AVFormatContext *s)
{
    HLSContext *c = s->priv_data;
    int ret = 0, i;
​
    c->ctx                = s;
    c->interrupt_callback = &s->interrupt_callback;
    c->strict_std_compliance = s->strict_std_compliance;
    c->first_packet = 1;
    c->first_timestamp = AV_NOPTS_VALUE;
    c->cur_timestamp = AV_NOPTS_VALUE;
    
    /* 解析m3u8 */
    if ((ret = parse_playlist(c, s->url, NULL, s->pb)) < 0)
        goto fail;
​
    if (c->n_variants == 0) {
        av_log(NULL, AV_LOG_WARNING, "Empty playlist\n");
        ret = AVERROR_EOF;
        goto fail;
    }
    /* 对于master playlist,逐个解析其中的playlist */
    if (c->n_playlists > 1 || c->playlists[0]->n_segments == 0) {
        for (i = 0; i < c->n_playlists; i++) {
            struct playlist *pls = c->playlists[i];
            if ((ret = parse_playlist(c, pls->url, pls, NULL)) < 0)
                goto fail;
        }
    }
    /* 必须有至少一个variant */
    if (c->variants[0]->playlists[0]->n_segments == 0) {
        av_log(NULL, AV_LOG_WARNING, "Empty playlist\n");
        ret = AVERROR_EOF;
        goto fail;
    }
    // ... 部分代码,有删减

从上面代码来看最主要的逻辑就是调用parse_playlist函数来解析m3u8文件。解析部分主要参考HLS协议就可以。其代码如下:

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;
    char line[MAX_URL_SIZE];
    const char *ptr;
    int64_t seg_offset = 0;
    int64_t seg_size = -1;
    uint8_t *new_url = NULL;
    struct variant_info variant_info;
    char tmp_str[MAX_URL_SIZE];
    struct segment *cur_init_section = NULL;
​
    if (!in) { /* 创建用于HTTP请求的AVIO */
        AVDictionary *opts = NULL;
        av_dict_copy(&opts, c->avio_opts, 0);
​
        ret = c->ctx->io_open(c->ctx, &in, url, AVIO_FLAG_READ, &opts);
        av_dict_free(&opts);
        if (ret < 0)
            return ret;
    }
    /* HTTP-URL重定向 */
    if (av_opt_get(in, "location", AV_OPT_SEARCH_CHILDREN, &new_url) >= 0)
        url = new_url;
​
    ff_get_chomp_line(in, line, sizeof(line));
    if (strcmp(line, "#EXTM3U")) {/* HLS协议标志起始头 */
        ret = AVERROR_INVALIDDATA;
        goto fail;
    }
    /* 释放已经存在的pls及segment */
    if (pls) {
        free_segment_list(pls);
        pls->finished = 0;
        pls->type = PLS_TYPE_UNSPECIFIED;
    }/* 以下是具体协议的解析 */
    while (!avio_feof(in)) {
        ff_get_chomp_line(in, line, sizeof(line));
        if (av_strstart(line, "#EXT-X-STREAM-INF:", &ptr)) {
            is_variant = 1;
            memset(&variant_info, 0, sizeof(variant_info));
            ff_parse_key_value(ptr, (ff_parse_key_val_cb) handle_variant_args,
                               &variant_info);
        } else if (av_strstart(line, "#EXT-X-MEDIA:", &ptr)) {
            struct rendition_info info = {
  {0}};
            ff_parse_key_value(ptr, (ff_parse_key_val_cb) handle_rendition_args,
                               &info);
            new_rendition(c, &info, url);
        } else if (av_strstart(line, "#EXT-X-TARGETDURATION:", &ptr)) {
            ret = ensure_playlist(c, &pls, url);
            if (ret < 0)
                goto fail; /* 最大分片时长 */
            pls-&
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值