详细讲解HLS拉流源码分析(1)
wptr33 2025-05-16 16:45 11 浏览
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”
相关推荐
- Python自动化脚本应用与示例(python办公自动化脚本)
-
Python是编写自动化脚本的绝佳选择,因其语法简洁、库丰富且跨平台兼容性强。以下是Python自动化脚本的常见应用场景及示例,帮助你快速上手:一、常见自动化场景文件与目录操作...
- Python文件操作常用库高级应用教程
-
本文是在前面《Python文件操作常用库使用教程》的基础上,进一步学习Python文件操作库的高级应用。一、高级文件系统监控1.1watchdog库-实时文件系统监控安装与基本使用:...
- Python办公自动化系列篇之六:文件系统与操作系统任务
-
作为高效办公自动化领域的主流编程语言,Python凭借其优雅的语法结构、完善的技术生态及成熟的第三方工具库集合,已成为企业数字化转型过程中提升运营效率的理想选择。该语言在结构化数据处理、自动化文档生成...
- 14《Python 办公自动化教程》os 模块操作文件与文件夹
-
在日常工作中,我们经常会和文件、文件夹打交道,比如将服务器上指定目录下文件进行归档,或将爬虫爬取的数据根据时间创建对应的文件夹/文件,如果这些还依靠手动来进行操作,无疑是费时费力的,这时候Pyt...
- python中os模块详解(python os.path模块)
-
os模块是Python标准库中的一个模块,它提供了与操作系统交互的方法。使用os模块可以方便地执行许多常见的系统任务,如文件和目录操作、进程管理、环境变量管理等。下面是os模块中一些常用的函数和方法:...
- 21-Python-文件操作(python文件的操作步骤)
-
在Python中,文件操作是非常重要的一部分,它允许我们读取、写入和修改文件。下面将详细讲解Python文件操作的各个方面,并给出相应的示例。1-打开文件...
- 轻松玩转Python文件操作:移动、删除
-
哈喽,大家好,我是木头左!Python文件操作基础在处理计算机文件时,经常需要执行如移动和删除等基本操作。Python提供了一些内置的库来帮助完成这些任务,其中最常用的就是os模块和shutil模块。...
- Python 初学者练习:删除文件和文件夹
-
在本教程中,你将学习如何在Python中删除文件和文件夹。使用os.remove()函数删除文件...
- 引人遐想,用 Python 获取你想要的“某个人”摄像头照片
-
仅用来学习,希望给你们有提供到学习上的作用。1.安装库需要安装python3.5以上版本,在官网下载即可。然后安装库opencv-python,安装方式为打开终端输入命令行。...
- Python如何使用临时文件和目录(python目录下文件)
-
在某些项目中,有时候会有大量的临时数据,比如各种日志,这时候我们要做数据分析,并把最后的结果储存起来,这些大量的临时数据如果常驻内存,将消耗大量内存资源,我们可以使用临时文件,存储这些临时数据。使用标...
- Linux 下海量文件删除方法效率对比,最慢的竟然是 rm
-
Linux下海量文件删除方法效率对比,本次参赛选手一共6位,分别是:rm、find、findwithdelete、rsync、Python、Perl.首先建立50万个文件$testfor...
- Python 开发工程师必会的 5 个系统命令操作库
-
当我们需要编写自动化脚本、部署工具、监控程序时,熟练操作系统命令几乎是必备技能。今天就来聊聊我在实际项目中高频使用的5个系统命令操作库,这些可都是能让你效率翻倍的"瑞士军刀"。一...
- Python常用文件操作库使用详解(python文件操作选项)
-
Python生态系统提供了丰富的文件操作库,可以处理各种复杂的文件操作需求。本教程将介绍Python中最常用的文件操作库及其实际应用。一、标准库核心模块1.1os模块-操作系统接口主要功能...
- 11. 文件与IO操作(文件io和网络io)
-
本章深入探讨Go语言文件处理与IO操作的核心技术,结合高性能实践与安全规范,提供企业级解决方案。11.1文件读写11.1.1基础操作...
- Python os模块的20个应用实例(python中 import os模块用法)
-
在Python中,...
- 一周热门
-
-
C# 13 和 .NET 9 全知道 :13 使用 ASP.NET Core 构建网站 (1)
-
因果推断Matching方式实现代码 因果推断模型
-
git pull命令使用实例 git pull--rebase
-
面试官:git pull是哪两个指令的组合?
-
git 执行pull错误如何撤销 git pull fail
-
git pull 和git fetch 命令分别有什么作用?二者有什么区别?
-
git fetch 和git pull 的异同 git中fetch和pull的区别
-
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)