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

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

wptr33 2025-05-16 16:45 1 浏览

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”

相关推荐

MySQL合集-innobackupex在线备份及恢复(全量和增量)

Xtrabackup是由percona开发的一个开源软件,它是innodb热备工具ibbackup(收费的商业软件)的一个开源替代品。Xtrabackup由个部分组成:xtrabackup和innob...

MySQL合集-单机容器化

MySQL单机容器化mkdir-p/opt/mysql/{data,etc}cpmy.cnf/opt/mysql/etc#dockersearchmysqldockerpullm...

MySQL合集-小版本升级指南

下载最新的mysqlwgethttps://cdn.mysql.com//Downloads/MySQL-5.7/mysql-5.7.38-linux-glibc2.12-x86_64.tar.gz...

Mysql 数据库运维方案

前言...

如果忘记了 WAMP 中本机数据库(MySQL)的密码,该怎么办?

如果忘记了WAMP中本机数据库(MySQL)的密码,可以通过以下步骤来重置:停止MySQL服务:打开WAMP,点击“停止所有服务”,或者右键点击WAMP图标,在菜单中选择“MySQL...

Linux服务器日常巡检脚本分享

Linux系统日常巡检脚本,巡检内容包含了,磁盘,...

在 SpringBoot 中设计一个订单号生成系统,原理,架构与实战

在SpringBoot中设计一个订单号生成系统,原理,架构与实战首先,我得考虑订单号的基本要求。通常订单号需要唯一性,不能重复,否则会出大问题。然后可能还要有一定的可读性,比如包含日期、时间或者业...

K8S官方java客户端之七:patch操作

欢迎访问我的GitHubhttps://github.com/zq2599/blog_demos内容:所有原创文章分类汇总及配套源码,涉及Java、Docker、Kubernetes、DevOPS等;...

浅谈Kubernetes CRD和Operator的原理和使用方法

总结CRD的全称是CustomResourceDefinition,是Kubernetes为提高可扩展性,让开发者去自定义资源(如Deployment,StatefulSet等)的一种方法....

kubernetes实用操作:kubectl命令行工具使用全面总结

kubectl作为客户端CLI工具,可以让用户通过命令行对Kubernetes集群进行操作。本节对kubectl的子命令和用法进行详细说明。kubectl用法概述kubectl[command][...

ceph rbd块存储挂载及文件存储建立

cephrbd块存储挂载及文件存储建立一、rbd块存储挂载1创建一个OSDpool...

odps sql中常用的时间处理方法

1、获取当前时间selectgetdate();2、获取昨天(字符串格式)selectto_char(dateadd(getdate(),-1,'dd'),'yyyymmd...

每天一个 Python 库:datetime 模块全攻略,时间操作太丝滑!

在日常开发中,时间处理是绕不开的一块,比如:...

时序异常检测工具:ADTK

1adtk简介智能运维AIOps的数据基本上都是...

又一批长事务,P0故障谁来背锅?

最近几周,发生过多起因为事务问题引起的服务报错。现象为...