详细讲解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解析相关的结构。
在开启下面的调试时,需要把推流,流媒体服务器搭建好。可以参考前面的文章。
在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故障谁来背锅?
-
最近几周,发生过多起因为事务问题引起的服务报错。现象为...
- 一周热门
-
-
C# 13 和 .NET 9 全知道 :13 使用 ASP.NET Core 构建网站 (1)
-
因果推断Matching方式实现代码 因果推断模型
-
git pull命令使用实例 git pull--rebase
-
面试官:git pull是哪两个指令的组合?
-
git fetch 和git pull 的异同 git中fetch和pull的区别
-
git 执行pull错误如何撤销 git pull fail
-
git pull 和git fetch 命令分别有什么作用?二者有什么区别?
-
git pull 之后本地代码被覆盖 解决方案
-
还可以这样玩?Git基本原理及各种骚操作,涨知识了
-
git命令之pull git.pull
-
- 最近发表
- 标签列表
-
- git pull (33)
- git fetch (35)
- mysql insert (35)
- mysql distinct (37)
- concat_ws (36)
- java continue (36)
- jenkins官网 (37)
- mysql 子查询 (37)
- python元组 (33)
- mybatis 分页 (35)
- vba split (37)
- redis watch (34)
- python list sort (37)
- nvarchar2 (34)
- mysql not null (36)
- hmset (35)
- python telnet (35)
- python readlines() 方法 (36)
- munmap (35)
- docker network create (35)
- redis 集合 (37)
- python sftp (37)
- setpriority (34)
- c语言 switch (34)
- git commit (34)