百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

详细讲解HLS拉流源码分析(1)_flv拉流

zhezhongyun 2025-09-04 22:08 27 浏览

1.HLS播放流程框架

hls整个播放流程,读取数据部分,涉及到ffmpeg文件有,ffplay.c,utils.c,format.c,options.c,aviobuf.c,avio.c,hls.c,mpegts.c。整个数据读取流程如下:

(1)打开文件或实时流,avformat_open_input

(2)初始化输入,init_input

(3)根据文件格式,探测探测是否成功,av_probe_input_format2

(4)调用io_open_default

(5)调用fifo_open_whitelist

(6)调用ffurl_alloc()

(7)调用protocol 使用http协议。

(8)调用ffurl_connect,发送HTTP报文,下载M3u8文件。

(9)av_probe_input_buffer2,探测解复用ff_hls_demuxer。

(10)使用hls_read_header读取m3u8相应文件的字段信息。

(11)解析M3u8文件存储到结构体playlists中。

(12)使用ffio_init_context(read_data),其read_data是自定义函数,涉及到playlist更新问题,实际更新的是ts segment。

(13)av_probe_input_buffer2用read_data读取第0片,探测解复用是
AVInputFormat_mpegts_demuxer。

(14)用avformat_open_input打开第0片文件,如解析PMT表,就能知道使用的是什么解码器。

(15)使用av_read_frame读取。

(16)调用hls_read_packet。

(17)av_read_frame读取后,并返回相关信息。

(18)用mpegts_read_packet读取ts正真数据。

(19)实际是read返回。

(20)如果有seek,则肯定有调用avformat_seek_file,然后调用hls_read_seek。


2.解析m3u8

如果有一个播放地址
http://192.168.1.11/live/livestream.m3u8 ,其会对应一个
ff_hls_demux,如果livestream.m3u8有xxx.ts的时候,在ffmpeg源码里,那就需要ff_mpegts_demuxer(获取流信息的重要结构),对应源码位置,如下图。这个在ffmpeg源码里对应的是mpegts.c


使用http协议,去拉流,对应ffmpeg源码位置如下:

下载m3u8文件,怎样去判断是属于hls?

(1)首先,要去调用ff_hls_demuxer的,hls_probe函数。如下图:

(2)然后,去判断m3u8的文件格式,格式是否正确,源码如下图:

HLS协议要求必须以#EXTM3U打头,并至少有下面三个字段之一存在,才是属于HLS。其中

EXT-X-STREAM-INF是针对master playlist(嵌套具有可变码率的格式),#EXT-X-TARGETDURATION是针对media playlist,#EXT-X-MEDIA-SEQUENCE是针对media playlist

再看看m3u8的文件格式,都是以#EXTM3U作为文件起始,如下图。


(3)确定使用哪个demuxer后,就调用demuxer对数据进行分析。主要分析SEAUENCE-NUM,xxx.ts流等。分析就是使用ffmpeg源码的hls_read_header(读取协议头并获取节目信息)。这个函数会解析m3u8文件,并打开第一个ts文件读取信息 这里涉及到master playlist解析、节目信息获取,同时初始化hls解析相关的结构。

在开启下面的调试时,需要把推流,流媒体服务器搭建好。可以参考前面的文章。

手把手配置HLS流媒体服务器

HLS实战之Wireshark抓包分析

手把手搭建流媒体服务器详细步骤

在linux环境下,使用gdb去调试,gdb ./ffplay_g,如下图:


命令行中,设置参数:

在函数hls_read_header里,解析m3u8文件,并打开第一个ts文件读取信息 这里涉及到master playlist解析、节目信息获取,同时初始化hls解析相关的结构。打断点

源码如下,有比较关键的注释:

static int hls_read_header(AVFormatContext *s)
{
    HLSContext *c = s->priv_data;
    int ret = 0, i;
    int highest_cur_seq_no = 0;

    c->ctx                = s;
    c->interrupt_callback = &s->interrupt_callback;

    c->first_packet = 1;
    c->first_timestamp = AV_NOPTS_VALUE;
    c->cur_timestamp = AV_NOPTS_VALUE;

    if ((ret = save_avio_options(s)) < 0)
        goto fail;

    /* Some HLS servers don't like being sent the range header */
    av_dict_set(&c->avio_opts, "seekable", "0", 0);
    // 解析palylist,主要是解析m3u8
    if ((ret = parse_playlist(c, s->url, NULL, s->pb)) < 0)
        goto fail;

    if (c->n_variants == 0) {
        av_log(s, AV_LOG_WARNING, "Empty playlist\n");
        ret = AVERROR_EOF;
        goto fail;
    }
    /* If the playlist only contained playlists (Master Playlist),
     * parse each individual playlist.对于master playlist,逐个解析其中的playlist  */
    if (c->n_playlists > 1 || c->playlists[0]->n_segments == 0) {   // n_segments==0说明一定是master playlist
        for (i = 0; i < c->n_playlists; i++) {          // 如果是嵌套则要继续解析
            struct playlist *pls = c->playlists[i];
            if ((ret = parse_playlist(c, pls->url, pls, NULL)) < 0) // 比如http://example.com/hi.m3u8
                goto fail;
        }
    }

    if (c->variants[0]->playlists[0]->n_segments == 0) {
        av_log(s, AV_LOG_WARNING, "Empty segment\n");
        ret = AVERROR_EOF;
        goto fail;
    }

    /* If this isn't a live stream, calculate the total duration of the
     * stream. */
  //针对点播
    if (c->variants[0]->playlists[0]->finished) {   // finished只有解析到#EXT-X-ENDLIST"才会置为1
        int64_t duration = 0;
        for (i = 0; i < c->variants[0]->playlists[0]->n_segments; i++)
            duration += c->variants[0]->playlists[0]->segments[i]->duration;    
        s->duration = duration; // 计算点播文件的总长度
    }

    /* Associate renditions with variants  必须有至少一个variant */
    for (i = 0; i < c->n_variants; i++) {
        struct variant *var = c->variants[i];

        if (var->audio_group[0])
            add_renditions_to_variant(c, var, AVMEDIA_TYPE_AUDIO, var->audio_group);
        if (var->video_group[0])
            add_renditions_to_variant(c, var, AVMEDIA_TYPE_VIDEO, var->video_group);
        if (var->subtitles_group[0])
            add_renditions_to_variant(c, var, AVMEDIA_TYPE_SUBTITLE, var->subtitles_group);
    }

    /* Create a program for each variant */
    for (i = 0; i < c->n_variants; i++) {
        struct variant *v = c->variants[i];
        AVProgram *program;

        program = av_new_program(s, i); // 节目数量
        if (!program)
            goto fail;
        av_dict_set_int(&program->metadata, "variant_bitrate", v->bandwidth, 0);
    }

    /* Select the starting segments */
    for (i = 0; i < c->n_playlists; i++) {
        struct playlist *pls = c->playlists[i]; // 挑选playlist

        if (pls->n_segments == 0)
            continue;

        pls->cur_seq_no = select_cur_seq_no(c, pls);    // 选择起始的播放序列
        highest_cur_seq_no = FFMAX(highest_cur_seq_no, pls->cur_seq_no);
    }

    /* Open the demuxer for each playlist 对每个playlist打开其demuxer*/
    for (i = 0; i < c->n_playlists; i++) {
        struct playlist *pls = c->playlists[i];
        ff_const59 AVInputFormat *in_fmt = NULL;

        if (!(pls->ctx = avformat_alloc_context())) {   // 分配解复用器上下文
            ret = AVERROR(ENOMEM);
            goto fail;
        }

        if (pls->n_segments == 0)       // 没有分片则下一个playlist
            continue;

        pls->index  = i;        // 当前playlist编号
        pls->needed = 1;        // 有segment的才置为1
        pls->parent = s;        // 从属的父解复用器

        /*
         * If this is a live stream and this playlist looks like it is one segment
         * behind, try to sync it up so that every substream starts at the same
         * time position (so e.g. avformat_find_stream_info() will see packets from
         * all active streams within the first few seconds). This is not very generic,
         * though, as the sequence numbers are technically independent. 调整直播流的起播索引号,以保证所有playlist是同步的
         */
        if (!pls->finished && pls->cur_seq_no == highest_cur_seq_no - 1 &&
            highest_cur_seq_no < pls->start_seq_no + pls->n_segments) {
            pls->cur_seq_no = highest_cur_seq_no;
        }

        pls->read_buffer = av_malloc(INITIAL_BUFFER_SIZE);
        if (!pls->read_buffer){
            ret = AVERROR(ENOMEM);
            avformat_free_context(pls->ctx);
            pls->ctx = NULL;
            goto fail;
        }
        ffio_init_context(&pls->pb, pls->read_buffer, INITIAL_BUFFER_SIZE, 0, pls,
                          read_data, NULL, NULL);       // read_data是真正读取数据的函数
        pls->pb.seekable = 0;
        ret = av_probe_input_buffer(&pls->pb, &in_fmt, pls->segments[0]->url,
                                    NULL, 0, 0);
        if (ret < 0) {
            /* Free the ctx - it isn't initialized properly at this point,
             * so avformat_close_input shouldn't be called. If
             * avformat_open_input fails below, it frees and zeros the
             * context, so it doesn't need any special treatment like this. */
            av_log(s, AV_LOG_ERROR, "Error when loading first segment '%s'\n", pls->segments[0]->url);
            avformat_free_context(pls->ctx);
            pls->ctx = NULL;
            goto fail;
        }
        pls->ctx->pb       = &pls->pb;
        pls->ctx->io_open  = nested_io_open;
        pls->ctx->flags   |= s->flags & ~AVFMT_FLAG_CUSTOM_IO;

        if ((ret = ff_copy_whiteblacklists(pls->ctx, s)) < 0)
            goto fail;
        /* 打开流,获取其中AVStream信息 */
        ret = avformat_open_input(&pls->ctx, pls->segments[0]->url, in_fmt, NULL);  // 打开流
        if (ret < 0)
            goto fail;

        if (pls->id3_deferred_extra && pls->ctx->nb_streams == 1) {
            ff_id3v2_parse_apic(pls->ctx, &pls->id3_deferred_extra);
            avformat_queue_attached_pictures(pls->ctx);
            ff_id3v2_parse_priv(pls->ctx, &pls->id3_deferred_extra);
            ff_id3v2_free_extra_meta(&pls->id3_deferred_extra);
            pls->id3_deferred_extra = NULL;
        }

        if (pls->is_id3_timestamped == -1)
            av_log(s, AV_LOG_WARNING, "No expected HTTP requests have been made\n");

        /*
         * For ID3 timestamped raw audio streams we need to detect the packet
         * durations to calculate timestamps in fill_timing_for_id3_timestamped_stream(),
         * but for other streams we can rely on our user calling avformat_find_stream_info()
         * on us if they want to.
         */
        if (pls->is_id3_timestamped || (pls->n_renditions > 0 && pls->renditions[0]->type == AVMEDIA_TYPE_AUDIO)) {
            ret = avformat_find_stream_info(pls->ctx, NULL);    // 分析对应的音视频成分
            if (ret < 0)
                goto fail;
        }

        pls->has_noheader_flag = !!(pls->ctx->ctx_flags & AVFMTCTX_NOHEADER);

        /* Create new AVStreams for each stream in this playlist */
        ret = update_streams_from_subdemuxer(s, pls);   // 将分析出来的流成分更新给 parent demux
        if (ret < 0)
            goto fail;

        /*
         * Copy any metadata from playlist to main streams, but do not set
         * event flags.
         */
        if (pls->n_main_streams)
            av_dict_copy(&pls->main_streams[0]->metadata, pls->ctx->metadata, 0);

        add_metadata_from_renditions(s, pls, AVMEDIA_TYPE_AUDIO);
        add_metadata_from_renditions(s, pls, AVMEDIA_TYPE_VIDEO);
        add_metadata_from_renditions(s, pls, AVMEDIA_TYPE_SUBTITLE);
    }

    update_noheader_flag(s);

    return 0;
fail:
    hls_close(s);
    return ret;
}

如果能够正常启动,就需要使用命令,set scheduler-locking step(锁定线程)

查看当前调用栈,

有个重要函数,parse_playlist,源码如下,这个函数会间隔时间,一直调用。开始解析头数据会调用,后面解析数据会一直调用。这里也有个重要的结构体playlist。

观看源码部分,需要了解m3u8文件格式,可以参考这篇文章。详解m3u8协议

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;
    enum KeyType key_type = KEY_NONE;
    uint8_t iv[16] = "";
    int has_iv = 0;
    char key[MAX_URL_SIZE] = "";    // 如果有key
    char line[MAX_URL_SIZE];
    const char *ptr;
    int close_in = 0;
    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;
    int is_http = av_strstart(url, "http", NULL);   // 是不是http协议
    struct segment **prev_segments = NULL;
    int prev_n_segments = 0;
    int prev_start_seq_no = -1;
    // 第一次进来的时候 in非空,playlist_pb是空,此时是沿用in的通道
    if (is_http && !in && c->http_persistent && c->playlist_pb) {   // 如果没有打开则打开
        in = c->playlist_pb;    // 先打开http连接
        ret = open_url_keepalive(c->ctx, &c->playlist_pb, url);
        if (ret == AVERROR_EXIT) {
            return ret;
        } else if (ret < 0) {
            if (ret != AVERROR_EOF)
                av_log(c->ctx, AV_LOG_WARNING,
                    "keepalive request failed for '%s' when parsing playlist, retrying with new connection: %s\n",
                    url, av_err2str(ret));
            in = NULL;
        }
    }

    if (!in) {  /* 创建用于HTTP请求的AVIO */
        AVDictionary *opts = NULL;
        av_dict_copy(&opts, c->avio_opts, 0);

        if (c->http_persistent)
            av_dict_set(&opts, "multiple_requests", "1", 0);

        ret = c->ctx->io_open(c->ctx, &in, url, AVIO_FLAG_READ, &opts);
        av_dict_free(&opts);
        if (ret < 0)
            return ret;

        if (is_http && c->http_persistent)
            c->playlist_pb = in;        // 对应 io
        else
            close_in = 1;
    }
    /* 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;
    }

    if (pls) {      // 第一次的时候 pls是空的 // 每次更新playlist之前都先保存前一次playlist的有效信息
        prev_start_seq_no = pls->start_seq_no;  // 序列号
        prev_segments = pls->segments;      // 前一个分片
        prev_n_segments = pls->n_segments;  // 多少个分片
        pls->segments = NULL;
        pls->n_segments = 0;                
        pls->finished = 0;
        pls->type = PLS_TYPE_UNSPECIFIED;
    }
    while (!avio_feof(in)) {    // ptr指向tag :后的位置
        ff_get_chomp_line(in, line, sizeof(line));  // 读取一行
        if (av_strstart(line, "#EXT-X-STREAM-INF:", &ptr)) { // 是否嵌套m3u8
            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-KEY:", &ptr)) { // #EXT-X-KEY:表示怎么对 media segments 进行解码
            struct key_info info = {{0}};
            ff_parse_key_value(ptr, (ff_parse_key_val_cb) handle_key_args,
                               &info);
            key_type = KEY_NONE;
            has_iv = 0; 
            if (!strcmp(info.method, "AES-128"))
                key_type = KEY_AES_128;
            if (!strcmp(info.method, "SAMPLE-AES"))
                key_type = KEY_SAMPLE_AES;
            if (!strncmp(info.iv, "0x", 2) || !strncmp(info.iv, "0X", 2)) {
                ff_hex_to_data(iv, info.iv + 2);
                has_iv = 1;
            }
            av_strlcpy(key, info.uri, sizeof(key)); // 保存key信息
        } 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);    // 确保pls不为空
            if (ret < 0)
                goto fail;
            pls->target_duration = strtoll(ptr, NULL, 10) * AV_TIME_BASE;   // 解析target duration
        } else if (av_strstart(line, "#EXT-X-MEDIA-SEQUENCE:", &ptr)) { // 起始序号
            ret = ensure_playlist(c, &pls, url);
            if (ret < 0)
                goto fail;
            pls->start_seq_no = atoi(ptr);      // 记录起始序号
        } else if (av_strstart(line, "#EXT-X-PLAYLIST-TYPE:", &ptr)) {
            ret = ensure_playlist(c, &pls, url);
            if (ret < 0)
                goto fail;
            if (!strcmp(ptr, "EVENT"))
                pls->type = PLS_TYPE_EVENT; // 实时生成
            else if (!strcmp(ptr, "VOD"))
                pls->type = PLS_TYPE_VOD;   // 点播生成 m3u8 和 ts 文件
        } else if (av_strstart(line, "#EXT-X-MAP:", &ptr)) {
            struct init_section_info info = {{0}}; //定如何获取分析适用的 Media Segments 所需的 Media Initialization Section
            ret = ensure_playlist(c, &pls, url);
            if (ret < 0)
                goto fail;
            ff_parse_key_value(ptr, (ff_parse_key_val_cb) handle_init_section_args,
                               &info);
            cur_init_section = new_init_section(pls, &info, url);
            cur_init_section->key_type = key_type;
            if (has_iv) {
                memcpy(cur_init_section->iv, iv, sizeof(iv));   //如果有 IV,则将该值当成 16 个字节的 16 进制数
            } else {
                int seq = pls->start_seq_no + pls->n_segments;
                memset(cur_init_section->iv, 0, sizeof(cur_init_section->iv));
                AV_WB32(cur_init_section->iv + 12, seq);//如果没有 IV(Initialization Vector),则使用序列号作为 IV 进行编解码,将序列号的高位赋到 16 个字节的 buffer 中,左边补 0
            }

            if (key_type != KEY_NONE) {
                ff_make_absolute_url(tmp_str, sizeof(tmp_str), url, key);
                cur_init_section->key = av_strdup(tmp_str);
                if (!cur_init_section->key) {
                    av_free(cur_init_section);
                    ret = AVERROR(ENOMEM);
                    goto fail;
                }
            } else {
                cur_init_section->key = NULL;
            }

        } else if (av_strstart(line, "#EXT-X-ENDLIST", &ptr)) {
            if (pls)
                pls->finished = 1;  // 指示点播文件结束  playlist结束标志,表示VOD  
        } else if (av_strstart(line, "#EXTINF:", &ptr)) {
            is_segment = 1;
            duration   = atof(ptr) * AV_TIME_BASE;  // 获取时长
        } else if (av_strstart(line, "#EXT-X-BYTERANGE:", &ptr)) {
            seg_size = strtoll(ptr, NULL, 10);  //分片大小
            ptr = strchr(ptr, '@');
            if (ptr)
                seg_offset = strtoll(ptr+1, NULL, 10);  // 起始
        } else if (av_strstart(line, "#", NULL)) {
            av_log(c->ctx, AV_LOG_INFO, "Skip ('%s')\n", line); // 纯注释
            continue;
        } else if (line[0]) {   // livestream-422.ts 或者http://example.com/hi.m3u8
            if (is_variant) {   // 比如 http://example.com/hi.m3u8
                if (!new_variant(c, &variant_info, line, url)) {    // 添加 playlist
                    ret = AVERROR(ENOMEM);
                    goto fail;
                }
                is_variant = 0;
            }
            if (is_segment) {   // 比如livestream-422.ts
                struct segment *seg;
                if (!pls) {
                    if (!new_variant(c, 0, url, NULL)) {
                        ret = AVERROR(ENOMEM);
                        goto fail;
                    }
                    pls = c->playlists[c->n_playlists - 1];
                }
                seg = av_malloc(sizeof(struct segment));    // 添加分片segment
                if (!seg) {
                    ret = AVERROR(ENOMEM);
                    goto fail;
                }
                seg->duration = duration;       // 持续播放的时间
                seg->key_type = key_type;       // key类型
                if (has_iv) {
                    memcpy(seg->iv, iv, sizeof(iv));
                } else {
                    int seq = pls->start_seq_no + pls->n_segments;  //序号
                    memset(seg->iv, 0, sizeof(seg->iv));
                    AV_WB32(seg->iv + 12, seq);
                }

                if (key_type != KEY_NONE) {
                    ff_make_absolute_url(tmp_str, sizeof(tmp_str), url, key);
                    seg->key = av_strdup(tmp_str);
                    if (!seg->key) {
                        av_free(seg);
                        ret = AVERROR(ENOMEM);
                        goto fail;
                    }
                } else {
                    seg->key = NULL;
                }
                // 将相对的livestream-422.ts路径 拼接成绝对路径 http://111.229.231.225:8081/live/livestream-422.ts
                ff_make_absolute_url(tmp_str, sizeof(tmp_str), url, line);
                seg->url = av_strdup(tmp_str);
                if (!seg->url) {
                    av_free(seg->key);
                    av_free(seg);
                    ret = AVERROR(ENOMEM);
                    goto fail;
                }

                dynarray_add(&pls->segments, &pls->n_segments, seg);    // 增加segments
                is_segment = 0;

                seg->size = seg_size;       // 设置segment http请求的range
                if (seg_size >= 0) {
                    seg->url_offset = seg_offset;
                    seg_offset += seg_size;
                    seg_size = -1;
                } else {
                    seg->url_offset = 0;
                    seg_offset = 0;
                }

                seg->init_section = cur_init_section;
            }
        }
    }
    if (prev_segments) {    // 如果解析playlist之前还有segment
        if (pls->start_seq_no > prev_start_seq_no && c->first_timestamp != AV_NOPTS_VALUE) {
            int64_t prev_timestamp = c->first_timestamp;
            int i, diff = pls->start_seq_no - prev_start_seq_no;
            for (i = 0; i < prev_n_segments && i < diff; i++) {
                c->first_timestamp += prev_segments[i]->duration;   // 跳过时间戳?
            }
            av_log(c->ctx, AV_LOG_DEBUG, "Media sequence change (%d -> %d)"
                   " reflected in first_timestamp: %"PRId64" -> %"PRId64"\n",
                   prev_start_seq_no, pls->start_seq_no,
                   prev_timestamp, c->first_timestamp);
        } else if (pls->start_seq_no < prev_start_seq_no) {
            av_log(c->ctx, AV_LOG_WARNING, "Media sequence changed unexpectedly: %d -> %d\n",
                   prev_start_seq_no, pls->start_seq_no);       // 新的序号反而变小是不期望的
        }
        free_segment_dynarray(prev_segments, prev_n_segments);  // 释放掉之前segment,再次播放的时候也是使用新解析出来的segment来播放
        av_freep(&prev_segments);                               // 但是要注意的是,这里讲的新segment,并不是说他对应的数据一定会更新,只是说刚从m3u8解析出来的
    }
    if (pls)
        pls->last_load_time = av_gettime_relative();

fail:
    av_free(new_url);
    if (close_in)
        ff_format_io_close(c->ctx, &in);
    c->ctx->ctx_flags = c->ctx->ctx_flags & ~(unsigned)AVFMTCTX_UNSEEKABLE;
    if (!c->n_variants || !c->variants[0]->n_playlists ||
        !(c->variants[0]->playlists[0]->finished ||
          c->variants[0]->playlists[0]->type == PLS_TYPE_EVENT))
        c->ctx->ctx_flags |= AVFMTCTX_UNSEEKABLE;
    return ret;
}

如果新的playlist,可以通过函数new_playlist添加进来。

static struct playlist *new_playlist(HLSContext *c, const char *url,
                                     const char *base)
{
    struct playlist *pls = av_mallocz(sizeof(struct playlist));
    if (!pls)
        return NULL;
    reset_packet(&pls->pkt);
    ff_make_absolute_url(pls->url, sizeof(pls->url), base, url);
    pls->seek_timestamp = AV_NOPTS_VALUE;

    pls->is_id3_timestamped = -1;
    pls->id3_mpegts_timestamp = AV_NOPTS_VALUE;

    dynarray_add(&c->playlists, &c->n_playlists, pls);  // n_playlists + 1
    return pls;
}

playlist数据结构如下:

struct playlist {
    char url[MAX_URL_SIZE];
    AVIOContext pb;
    uint8_t* read_buffer;
    AVIOContext *input;         // 当前要读取的segment
    int input_read_done;
    AVIOContext *input_next;    // 对应下一个将要读取的segment
    int input_next_requested;
    AVFormatContext *parent;    // 这个将指向公用的AVFormatContext
    int index;
    AVFormatContext *ctx;       // 这个将用于解析当前playlist的所有segment
    AVPacket pkt;
    int has_noheader_flag;

    /* main demuxer streams associated with this playlist
     * indexed by the subdemuxer stream indexes */
    AVStream **main_streams;    /* 当前playlist中包含的AVStream信息 */
    int n_main_streams;         // 流成分数量

    int finished;       /* segment读取状态的相关参数 */
    enum PlaylistType type;
    int64_t target_duration;
    int start_seq_no;
    int n_segments;     /* 当前playlist中的所有segment数组 */
    struct segment **segments;
    int needed;
    int cur_seq_no;     // 当前播放序列号
    int64_t cur_seg_offset;
    int64_t last_load_time;

    /* Currently active Media Initialization Section */
    struct segment *cur_init_section;
    uint8_t *init_sec_buf;
    unsigned int init_sec_buf_size;
    unsigned int init_sec_data_len;
    unsigned int init_sec_buf_read_offset;

    char key_url[MAX_URL_SIZE]; /* HLS解密密钥对应的URL */
    uint8_t key[16];
    // 一般的HLS 码流没有ID3 tag,都是0x47 开头的标准TS流, Nuplayer 会检查标准TS流,如果不是TS 流,才会走到检查ID3 tag。
    /* ID3 timestamp handling (elementary audio streams have ID3 timestamps
     * (and possibly other ID3 tags) in the beginning of each segment) */
    int is_id3_timestamped; /* -1: not yet known */
    int64_t id3_mpegts_timestamp; /* in mpegts tb */
    int64_t id3_offset; /* in stream original tb */
    uint8_t* id3_buf; /* temp buffer for id3 parsing */
    unsigned int id3_buf_size;
    AVDictionary *id3_initial; /* data from first id3 tag */
    int id3_found; /* ID3 tag found at some point */
    int id3_changed; /* ID3 tag data has changed at some point */
    ID3v2ExtraMeta *id3_deferred_extra; /* stored here until subdemuxer is opened */

    int64_t seek_timestamp; /* seek相关的参数 */
    int seek_flags;
    int seek_stream_index; /* into subdemuxer stream array */

    /* Renditions associated with this playlist, if any.
     * Alternative rendition playlists have a single rendition associated
     * with them, and variant main Media Playlists may have
     * multiple (playlist-less) renditions associated with them. */
    int n_renditions;   /* 和当前playlist相关的Renditions(可选) */
    struct rendition **renditions;

    /* Media Initialization Sections (EXT-X-MAP) associated with this
     * playlist, if any. */
    int n_init_sections;
    struct segment **init_sections;
};


3.选择起始播放序列

可以设置从某个文件开始播放,主要是使用函数select_cur_seq_no

// 如何选择起始segment的索引
static int select_cur_seq_no(HLSContext *c, struct playlist *pls)
{
    int seq_no;
    /* 直播情况下,定期更新m3u8 */
    if (!pls->finished && !c->first_packet &&
        av_gettime_relative() - pls->last_load_time >= default_reload_interval(pls))
        /* reload the playlist since it was suspended */
        parse_playlist(c, pls->url, pls, NULL);

    /* If playback is already in progress (we are just selecting a new
     * playlist) and this is a complete file, find the matching segment
     * by counting durations. 对于非直播的情况,直接通过时长查找对应的segment索引号(seek时比较常用的逻辑)*/
    if (pls->finished && c->cur_timestamp != AV_NOPTS_VALUE) {
        find_timestamp_in_playlist(c, pls, c->cur_timestamp, &seq_no);
        return seq_no;
    }

    if (!pls->finished) {
        if (!c->first_packet && /* we are doing a segment selection during playback  播放过程中选择segment*/
            c->cur_seq_no >= pls->start_seq_no &&
            c->cur_seq_no < pls->start_seq_no + pls->n_segments)
            /* While spec 3.4.3 says that we cannot assume anything about the
             * content at the same sequence number on different playlists,
             * in practice this seems to work and doing it otherwise would
             * require us to download a segment to inspect its timestamps. */
            return c->cur_seq_no;

        /* If this is a live stream, start live_start_index segments from the
         * start or end */ /* 直播情况下,需要参考live_start_index调整下 */
        if (c->live_start_index < 0)    // 起始播放时选择的seq no; live_start_index越小 延迟越大
            return pls->start_seq_no + FFMAX(pls->n_segments + c->live_start_index, 0); // 默认live_start_index是-3,
        else
            return pls->start_seq_no + FFMIN(c->live_start_index, pls->n_segments - 1); //
    }

    /* Otherwise just start on the first segment.  其他情况直接返回起始segment索引号*/*/
    return pls->start_seq_no;
}

本篇源码分析的文章就分享到这里,后面还会有文章继续分析。欢迎关注,点赞,转发,收藏。

关于后期项目知识和相关文章,微信也同步更新,欢迎关注微信公众号“记录世界 from antonio”

相关推荐

Python入门学习记录之一:变量_python怎么用变量

写这个,主要是对自己学习python知识的一个总结,也是加深自己的印象。变量(英文:variable),也叫标识符。在python中,变量的命名规则有以下三点:>变量名只能包含字母、数字和下划线...

python变量命名规则——来自小白的总结

python是一个动态编译类编程语言,所以程序在运行前不需要如C语言的先行编译动作,因此也只有在程序运行过程中才能发现程序的问题。基于此,python的变量就有一定的命名规范。python作为当前热门...

Python入门学习教程:第 2 章 变量与数据类型

2.1什么是变量?在编程中,变量就像一个存放数据的容器,它可以存储各种信息,并且这些信息可以被读取和修改。想象一下,变量就如同我们生活中的盒子,你可以把东西放进去,也可以随时拿出来看看,甚至可以换成...

绘制学术论文中的“三线表”具体指导

在科研过程中,大家用到最多的可能就是“三线表”。“三线表”,一般主要由三条横线构成,当然在变量名栏里也可以拆分单元格,出现更多的线。更重要的是,“三线表”也是一种数据记录规范,以“三线表”形式记录的数...

Python基础语法知识--变量和数据类型

学习Python中的变量和数据类型至关重要,因为它们构成了Python编程的基石。以下是帮助您了解Python中的变量和数据类型的分步指南:1.变量:变量在Python中用于存储数据值。它们充...

一文搞懂 Python 中的所有标点符号

反引号`无任何作用。传说Python3中它被移除是因为和单引号字符'太相似。波浪号~(按位取反符号)~被称为取反或补码运算符。它放在我们想要取反的对象前面。如果放在一个整数n...

Python变量类型和运算符_python中变量的含义

别再被小名词坑哭了:Python新手常犯的那些隐蔽错误,我用同事的真实bug拆给你看我记得有一次和同事张姐一起追查一个看似随机崩溃的脚本,最后发现罪魁祸首竟然是她把变量命名成了list。说实话...

从零开始:深入剖析 Spring Boot3 中配置文件的加载顺序

在当今的互联网软件开发领域,SpringBoot无疑是最为热门和广泛应用的框架之一。它以其强大的功能、便捷的开发体验,极大地提升了开发效率,成为众多开发者构建Web应用程序的首选。而在Spr...

Python中下划线 ‘_’ 的用法,你知道几种

Python中下划线()是一个有特殊含义和用途的符号,它可以用来表示以下几种情况:1在解释器中,下划线(_)表示上一个表达式的值,可以用来进行快速计算或测试。例如:>>>2+...

解锁Shell编程:变量_shell $变量

引言:开启Shell编程大门Shell作为用户与Linux内核之间的桥梁,为我们提供了强大的命令行交互方式。它不仅能执行简单的文件操作、进程管理,还能通过编写脚本实现复杂的自动化任务。无论是...

一文学会Python的变量命名规则!_python的变量命名有哪些要求

目录1.变量的命名原则3.内置函数尽量不要做变量4.删除变量和垃圾回收机制5.结语1.变量的命名原则①由英文字母、_(下划线)、或中文开头②变量名称只能由英文字母、数字、下画线或中文字所组成。③英文字...

更可靠的Rust-语法篇-区分语句/表达式,略览if/loop/while/for

src/main.rs://函数定义fnadd(a:i32,b:i32)->i32{a+b//末尾表达式}fnmain(){leta:i3...

C++第五课:变量的命名规则_c++中变量的命名规则

变量的命名不是想怎么起就怎么起的,而是有一套固定的规则的。具体规则:1.名字要合法:变量名必须是由字母、数字或下划线组成。例如:a,a1,a_1。2.开头不能是数字。例如:可以a1,但不能起1a。3....

Rust编程-核心篇-不安全编程_rust安全性

Unsafe的必要性Rust的所有权系统和类型系统为我们提供了强大的安全保障,但在某些情况下,我们需要突破这些限制来:与C代码交互实现底层系统编程优化性能关键代码实现某些编译器无法验证的安全操作Rus...

探秘 Python 内存管理:背后的神奇机制

在编程的世界里,内存管理就如同幕后的精密操控者,确保程序的高效运行。Python作为一种广泛使用的编程语言,其内存管理机制既巧妙又复杂,为开发者们提供了便利的同时,也展现了强大的底层控制能力。一、P...