hslenc.c 代码提纲挈领分析 - 指南

news/2025/10/4 9:40:22/文章来源:https://www.cnblogs.com/yxysuanfa/p/19125326

author: hjjdebug
date: 2025年 09月 28日 星期日 17:48:41 CST
descrip: hslenc.c 代码提纲挈领分析


文章目录

  • 1. 前言
    • 1.1 运行方式:
    • 1.2: 执行结果
    • 1.3. 目的. 想在test.m3u8 文件中加点自己的东西.
  • 2: 我想搞懂以下问题.
    • 2.1: 是先打开m3u8文件, 还是先打开切片文件例如test0.ts?
    • 2.2 问: pb 中都保存了什么呢?
    • 2.3: 切片文件例如test0.ts 数据是怎样被写入的?
    • 2.4 AVOutputFormat对象指针为什么可以直接转换为FFOutputFormat 对象指针?
    • 2.5 hls 中有一个重要概念VariantStream. 它是什么?
    • 2.6 AVIOContext 中的 write_packet 指针是何时赋值的?
  • 3 test.m3u8 文件是在哪里打开的?
  • 4. hls_write_packet 把数据写到了哪里? 为什么写了很多次包才调用到打开test.m3u8.tmp 文件?

1. 前言

被研究的程序. doc/example/transcode 程序.
这里分析的是ffmpeg6.1.1的版本, 在ubuntu24 下调试的.

1.1 运行方式:

命令行参数:
./transcode mp2.ts ts/test.m3u8
我们把一个ts 流, 按m3u8 格式重新编码后来输出.

1.2: 执行结果

我们看到, 在ts 目录下生成了test.m3u8 文件及 一系列切片文件.

$ls ts
test0.ts  test1.ts  test2.ts  test3.ts  test4.ts  test.m3u8
$ cat test.m3u8
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:2
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:2.400000,
test0.ts
#EXTINF:1.920000,
test1.ts
#EXTINF:1.920000,
test2.ts
#EXTINF:1.920000,
test3.ts
#EXTINF:1.800000,
test4.ts
#EXT-X-ENDLIST

1.3. 目的. 想在test.m3u8 文件中加点自己的东西.

难点: 在hlsenc.c 中观摩了一天,不是半天. 竟然还是懵懵懂懂. 菜啊,看不懂.
文件的打开已经看不到fopen, 文件写入也看不到fwrite, 这些是底层的接口,
上层就是另一种接口了,它封装了协议,封装了URL. 原则上应该更简单. 但实际上却不是.
单这一个文件就3500行代码, 很难理解. 这里不能copy过来一行行标注,那还是不得要领.
下面就要送根拐杖了, 帮助我们理解代码.

2: 我想搞懂以下问题.

test.m3u8 文件是在哪里打开的? 又是在哪里写入的? 在哪里关闭的?
切片文件例如 test0.ts, test1.ts 又是在哪里打开,写入和关闭的?
下面按执行顺序来依次解释.

先忽略掉 hsl_init() 及 hls_write_header() 函数, 这两个函数是avformat_write_header()调用的.
与创建test.m3u8 没有直接关系. 而且代码很多,开始时不易看懂.

  1. hls_init(AVFormatContext *s) 是由mux.c 调用的, 通过FFOutputFormat 对象 ff_hls_muxer 来调用
    其调用栈:
0 in hls_init of libavformat/hlsenc.c:3264
1 in init_muxer of libavformat/mux.c:408     // 通过 s->oformat->init(), s->oformat就是ff_hls_muxer
2 in avformat_init_output of libavformat/mux.c:504
3 in avformat_write_header of libavformat/mux.c:529

hls_write_header 的调用栈也是一样,通过ff_hls_muxer 对象来调用
2. hls_write_header(AVFormatContext *s) …```

0 in hls_write_header of libavformat/hlsenc.c:2617
1 in avformat_write_header of libavformat/mux.c:536 //通过s->oformat->write_header

虽然它们也很关键,但只是做了一些初始化工作,即完善了外层AVFormatContext 对象的一些参数及
内层HLSContext 对象的一些参数. 对m3u8文件的生成没有直接关系.

libavformat/hlsenc.c 文件中, 打开文件的接口是
int hlsenc_io_open(AVFormat s, AVIOContextpb, char *filename, AVDictionary **options);

2.1: 是先打开m3u8文件, 还是先打开切片文件例如test0.ts?

答, 先打开和写入切片文件.
此时调用栈:
(gdb) info args
s = 0x555555624340
pb = 0x555555633be0
filename = 0x55555566d080 “ts/test0.ts”
options = 0x7fffffffd6f0
0 in hlsenc_io_open of libavformat/hlsenc.c:295
1 in hls_write_packet of libavformat/hlsenc.c:2926
2 in write_packet of libavformat/mux.c:818
3 in interleaved_write_packet of libavformat/mux.c:1238
4 in write_packet_common of libavformat/mux.c:1264
5 in write_packets_common of libavformat/mux.c:1333
6 in av_interleaved_write_frame of libavformat/mux.c:1404
7 in encode_write_frame of transcode.c:498
8 in filter_encode_write_frame of transcode.c:539
9 in main of transcode.c:610

可见它的库调用入口是av_interleaved_write_frame,
其底层对象ff_hls_muxer的接口时 hls_write_packet
文件内函数是hlsenc_io_open
hlsenc_io_open()
通过调用s->io_open 来打开filename, 保存结果到pb.
s->io_open(s, pb, filename, AVIO_FLAG_WRITE, options);
上面的函数s->io_open 作为基础接口,就不向下分析了. 意思时AVFormatContext s,
打开 filename, 结果春入pb.

2.2 问: pb 中都保存了什么呢?

pb 是一个AVIOContext 指针, 它包含一个opaque 指针, 实际是URLContext *
当AVIO 需要底层操作时,会传递这个指针.
URLContext 中包含一个char *filename, 保存了文件名.
另外还有2个重要成员: prot 指针, 对于此例它指向ff_file_protocol
及 priv_data 指针. 对于此例它指向一个FileContext *, 当调用prot中的函数时,
传递这个私有的context, 这个context中, 被打开的文件描述符fd 就保存于此.
这就是分层管理的概念, 也是面向对象的概念.
对象就是一个结构体, 对象有嵌套的概念,对象可以包含对象(或对象指针).
指针使得对象可以呈现树状结构,使得对象可以很复杂.
搞懂了底层数据结构,才能看懂底层程序.
如果我们不关心底层程序, 层次化管理, 也使我们可以不关心底层实现,而只关心接口.扯的有点远了.

2.3: 切片文件例如test0.ts 数据是怎样被写入的?

方式很多了,例如:
av_write_frame(oc, NULL); /* Flush any buffered data */
avio_flush(oc->pb);
avio_write(vs->out, vs->init_buffer, range_length);
ret = flush_dynbuf(vs, &range_length);

具体怎么写的,还是有点难查的.
用AVIOContext 对象打开的文件, 怎样进行读写?
采用一点逆向的技巧, 如果设置函数断点fwrite, 不行,会中断在av_log中.
另一个函数断点. libavformat/file.c 中有file_write() 函数
我们抓到了它的函数调用栈,
判定出它的书写函数在本文件中使用的是flush_dynbuf(核心函数). 返回长度range_length,
调用栈如下, 对你关注的flush_dynbuf 进行浏览, 理解其工作原理.
0 in file_write of libavformat/file.c:153
1 in retry_transfer_wrapper of libavformat/avio.c:364
2 in ffurl_write2 of libavformat/avio.c:425
3 in writeout of libavformat/aviobuf.c:186
4 in flush_buffer of libavformat/aviobuf.c:214
5 in avio_write of libavformat/aviobuf.c:263
6 in flush_dynbuf of libavformat/hlsenc.c:607 // *************** 重点
7 in hls_write_packet of libavformat/hlsenc.c:2939
8 in write_packet of libavformat/mux.c:818
9 in interleaved_write_packet of libavformat/mux.c:1238
10 in write_packet_common of libavformat/mux.c:1264
11 in write_packets_common of libavformat/mux.c:1333
12 in av_interleaved_write_frame of libavformat/mux.c:1404
13 in encode_write_frame of transcode.c:498
14 in filter_encode_write_frame of transcode.c:539
15 in main of transcode.c:610

// 函数代码不多,但意义却很重要, 重点标注一下.

static int flush_dynbuf(VariantStream* vs, int* range_length)
{
AVFormatContext* ctx = vs->avf;
//健壮性判别
if (!ctx->pb) return AVERROR(EINVAL);
// flush, 把编码器所有的frame 刷出去
av_write_frame(ctx, NULL);
// write out to file, 关闭动态缓冲区,并返回固定缓冲区地址temp_buffer和大小range_length
*range_length = avio_close_dyn_buf(ctx->pb, &vs->temp_buffer);
ctx->pb = NULL;
//把内容写到缓冲区,缓冲区满会写到文件.
avio_write(vs->out, vs->temp_buffer, *range_length);
//刷出AVIOContext, 把缓存中所余内容全部写入到文件
avio_flush(vs->out);
// re-open buffer, 重新打开动态缓冲区
return avio_open_dyn_buf(&ctx->pb);
}

再上一层:

int hls_write_packet(AVFormatContext* s, AVPacket* pkt)
{
......
if(能够在此切分,并且时间也到了)
{
ret = hlsenc_io_open(s, &vs->out, filename, &options); //代开文件
ret = flush_dynbuf(vs, &range_length); //写数据
ret = hlsenc_io_close(s, &vs->out, filename);//关闭文件
}
......
}

可见切片文件是在一定条件下(video I frame and slice time over), 打开文件,书写数据,关闭文件

输出文件对象是AVFormatContext s, 它有一个AVOutputFormat 对象->oformat,

2.4 AVOutputFormat对象指针为什么可以直接转换为FFOutputFormat 对象指针?

ffofmt(s->oformat)
ffofmat() 函数是如下定义的,实际上就是一个强制指针类型转换. 是父类转换为子类!!

static inline const FFOutputFormat *ffofmt(const AVOutputFormat *fmt)
{
return (const FFOutputFormat*)fmt;
}

当我打开hlsenc.c 文件, 发现定义的对象本来就是FFOutputFormat 对象. 这才一下恍然大悟!

const FFOutputFormat ff_hls_muxer = {
.p.name = "hls",
.p.long_name = NULL_IF_CONFIG_SMALL("Apple HTTP Live Streaming"),
.p.extensions = "m3u8",
.p.audio_codec = AV_CODEC_ID_AAC,
.p.video_codec = AV_CODEC_ID_H264,
.p.subtitle_codec = AV_CODEC_ID_WEBVTT,
#if FF_API_ALLOW_FLUSH
.p.flags = AVFMT_NOFILE | AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH | AVFMT_NODIMENSIONS,
#else
.p.flags = AVFMT_NOFILE | AVFMT_GLOBALHEADER | AVFMT_NODIMENSIONS,
#endif
.p.priv_class = &hls_class,
//后面成员是属于子类FFOutputFormat 对象的
.flags_internal = FF_FMT_ALLOW_FLUSH,
.priv_data_size = sizeof(HLSContext),
.init = hls_init,
.write_header = hls_write_header,
.write_packet = hls_write_packet,
.write_trailer = hls_write_trailer,
.deinit = hls_deinit,
};

可见所谓的
oformat = av_guess_format(format, NULL, NULL);
其返回值地址
const AVOutputFormat *av_guess_format(const char *short_name, const char *filename,
const char *mime_type)
函数声明的返回地址是AVOutputFormat, 但实际枚举的是FFOutputFormat 地址.
所以,你以后把AVOutputFormat 指针强制转换为FFOutputFormat 地址不会出错, 因为它本来就是继承类地址.
而子类退化为父类地址是自然的. 你用的信息少,这不会出问题, 这是c++中继承的概念.
父类指针被强制转换为子类,是因为它本来就是子类指针.

2.5 hls 中有一个重要概念VariantStream. 它是什么?

它是一个对象, 把结构抄写过来也没有什么意义. 这里就节省篇幅不copy了.
它的用途是为了针对不同的码率而设置的一个对象. 多个对象意味着有多种输出.
因而会有一个VariantStream 指针和一个个数. 不过通常只有一个输出流(我们测试的是一个输出流).
它会包含一个 AVOutputFormat *oformat; 用以保存输出文件封装格式参数(muxer)
并包含一个 AVIOContext *out; 用以书写数据
还包含一个 AVFormatContext *avf; 用以保存格式,流信息,i/o操作句柄等信息

2.6 AVIOContext 中的 write_packet 指针是何时赋值的?

如果直接定位hls 中的 VarantStream 中的out 变量, 即hls->var_streams[0].out
而hls 是AVOutputFormat 中的私有数据结构, 就知道这个数据隐藏的很深了.
(gdb) p ((HLSContext *)g_ofmt_ctx->priv_data)->var_streams
$25 = (VariantStream *) 0x555555633bc0
(gdb) p ((HLSContext *)g_ofmt_ctx->priv_data)->var_streams[0].out
$26 = (AVIOContext *) 0x5555556c1380

hls->var_streams 在hls_init()->update_variant_stream_info() 函数中赋值.
而其下var_streams[0].out 是一个AVIOContext 对象. 其赋值通过下列函数.
*s = avio_alloc_context(buffer, buffer_size, h->flags & AVIO_FLAG_WRITE, h,
ffurl_read2, ffurl_write2, ffurl_seek2);
其write_packet 指针的赋值,显然是传递的参数ffurl_write2.
是的,我是通过内存断点及函数观察多重方式获取的信息.

3 test.m3u8 文件是在哪里打开的?

先打开一个test.m3u8.tmp, 以后再重命名为test.m3u8
调用栈如下: 你可以找到它的代码.
(gdb) info args
s = 0x555555624340
pb = 0x555555633be0
filename = 0x7fffffffb670 “ts/test.m3u8.tmp”
options = 0x7fffffffb628
0 in hlsenc_io_open of libavformat/hlsenc.c:295
1 in hls_window of libavformat/hlsenc.c:1777 //****在该函数中形成m3u8文件
2 in hls_write_packet of libavformat/hlsenc.c:2987
3 in write_packet of libavformat/mux.c:818
4 in interleaved_write_packet of libavformat/mux.c:1238
5 in write_packet_common of libavformat/mux.c:1264
6 in write_packets_common of libavformat/mux.c:1333
7 in av_interleaved_write_frame of libavformat/mux.c:1404
8 in encode_write_frame of transcode.c:498
9 in filter_encode_write_frame of transcode.c:539
10 in main of transcode.c:610

观察hls_window(), 赫然发现, 它调用了一堆ff_hls_write_开头的函数, 原来这些函数在其它的文件中,
是外部引用函数. 例如: ff_hls_write_playlist_head(…) 函数, 就在hlsplaylist.c文件中定义.
这个文件不大, 原来字符串向m3u8文件的输出都在这里. 不足200行代码我们还是能看懂!

怎样向文件输出的, 用avio_printf(AVIOContest *s, char *fmt, …) 函数向文件输出的.

当创建了切片文件,然后会更新m3u8文件, 通过hls_window() 调用 hlsenc_io_open, 参考上面的调用栈
打开文件, 在hls_window() 函数中调用ff_hls_开始的函数,用avio_printf 书写信息到文件中.

4. hls_write_packet 把数据写到了哪里? 为什么写了很多次包才调用到打开test.m3u8.tmp 文件?

答: 它写到了dyn_buffer中, 动态分配的内存中. 写的时间到,一次性写到文件中. 这里就不详细分析了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/926980.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Solar9月赛wp - 场

水一篇Solar9月赛 Wireshark 哥斯拉流量,webshell的密码一直变,neta可以梭到一个zip,里面是key.txt,直接追踪到最后一个返回包,拿key.txt进行爆破解密拿到flagflag{ccebdb78-4b5c-4252-b20a-0039913c5c94} HAPPY …

做电商网站商标wordpress导航栏的文件在哪里

http://www.cnblogs.com/ccblogs/p/5260949.html 一. 效果图 二. 功能介绍 支持滚动和点击选择年月。&#xff08;目前只支持设置年月的最大最小值&#xff0c;不支持整体的最大最小值&#xff09; 三. 代码 1. 在你的html中添加如下代码&#xff1a; 直接加载<body>里面…

昆明网站建设教学视频wordpress判断是否是子分类

1.遇到的问题 服务网关 | RuoYi 最近调试若依的微服务版本需要用到Sentinel这个组件&#xff0c;若依内部继承了这个组件连上即用。 Sentinel是阿里巴巴开源的限流器熔断器&#xff0c;并且带有可视化操作界面。 在日常开发中&#xff0c;限流功能时常被使用&#xff0c;用…

wordpress 网站维护wordpress的设置网址导航

概率基础——极大似然估计 引言 极大似然估计&#xff08;Maximum Likelihood Estimation&#xff0c;简称MLE&#xff09;是统计学中最常用的参数估计方法之一&#xff0c;它通过最大化样本的似然函数来估计参数值&#xff0c;以使得样本出现的概率最大化。极大似然估计在各…

网站建设开发费用预算泉州做网站优化的公司

美女姜培琳&#xff1a;传授独家心经 不同职业的面试着装技巧。 一般来说&#xff0c;职场中精英女性的装扮&#xff0c;首要应讲求端庄、稳重。人们对服饰过于花哨怪异者的工作能力、工作作风、敬业精神、生活态度等&#xff0c;都会持怀疑的态度。 其实&#x…

成都营销型网站建设价格网站构建工具

Python2.7.13 安装 说明 以下所有操作都基于centos6.9 1. Issue zlib zlib-devel是安装setuptools依赖的模块&#xff0c;需要在安装python之前先安装这两个模块 2. Install Base #基础依赖 yum -y install gcc gcc-c zlib zlib-devel openssl-devel#删除当前mysql版本 yum rem…

坂田做网站多少钱wordpress怎么建加盟网

动态规划、DFS 和回溯算法&#xff1a;二叉树问题的三种视角 在计算机科学中&#xff0c;算法是解决问题的核心。特别是对于复杂的问题&#xff0c;不同的算法可以提供不同的解决方案。在本篇博客中&#xff0c;我们将探讨三种算法&#xff1a;动态规划、深度优先搜索&#xf…

Elastic Search 安装部署最全教程(Docker)

@@docker es 安装https://blog.csdn.net/Grey_fantasy/article/details/131561847   https://blog.csdn.net/qq_33034733/article/details/130857381     https://blog.csdn.net/yueyue763184/article/details/…

大型网站开发pdf英文网站建设 江门

几个删除重复记录的SQL语句在大的数据库应用中&#xff0c;经常因为各种原因遇到重复的记录&#xff0c;造成数据的冗余和维护上的不便。1.用rowid方法2.用group by方法3.用distinct方法 1。用rowid方法据据oracle带的rowid属性&#xff0c;进行判断&#xff0c;是否存在重复,语…

详细介绍:C#多线程全家桶:从Thread到async/await

详细介绍:C#多线程全家桶:从Thread到async/await2025-10-04 09:18 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; displ…

十堰的网站建设威海建设集团招聘信息网站

要使用Django开发一个抽奖活动的后台&#xff0c;需要进行以下步骤&#xff1a; 安装Django&#xff1a;首先确保已经安装了Python和pip&#xff0c;然后使用pip安装Django库&#xff1a; pip install django 创建Django项目&#xff1a;在命令行中执行以下命令创建一个新的Dja…

详细介绍:图像分割:PyTorch从零开始实现SegFormer语义分割

详细介绍:图像分割:PyTorch从零开始实现SegFormer语义分割pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Conso…

用html5做的静态网站网站wordpress修改首页名称

一、常用的常用的生命周期钩子&#xff1a; mounted: 发送 ajax 请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】 mounted() {console.log(vm 实例被挂载之后&#xff1a;mounted);this.timer setInterval(() > {...} }beforeDestroy: 清除定时器、解绑自定…

网络营销方案seo入门到精通

考察点 大数&#xff0c;快排知识点 题目 分析 本题目给一个整型数组&#xff0c;要求他能排出来的最小的数字。这道题目我们大可以通过排列的方式枚举出所有的数字然后求一个最小的&#xff0c;只不过这种方式时间复杂度非常高。接下来我们通过举例的方式观察我们的思维和数…

深入解析:Playwright同步、异步、并行、串行执行效率比较

深入解析:Playwright同步、异步、并行、串行执行效率比较pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consola…

怎么用织梦做自己的网站苏州app开发

1 混合出版物 允许传统稿件提交或作者支付的开放获取&#xff08;OA&#xff09;稿件 2 长度 所有页面限制包括参考文献和作者简历。对于常规论文&#xff0c;接受稿件的最终版面设计完成后超出这些限制的页面&#xff0c;将收取强制性超长页面费用&#xff08;MOPC&#xf…

详细介绍:异步日志系统

详细介绍:异步日志系统2025-10-04 09:03 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; fon…

Linux基础开发工具 --- vim - 详解

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

2025十一集训——Day2模拟赛

赛时: 四个题都很可做的样子, T3好像是原??? OK T1 简单二分,切了。 调 lowerbound 那里 +-1 的细节,8:50 成功过掉大样例。 开 T2。 9:00,想到差分。 诶T2咋是原,就一黄f**k.本文来自博客园,作者:zhangxia…

完整教程:ARM Cortex-M:内存保护单元 (MPU) 发布

完整教程:ARM Cortex-M:内存保护单元 (MPU) 发布pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas"…