FFmpeg Muxer HLS

使用FFmpeg命令来研究它对HLS协议的支持程度是最好的方法:

ffmpeg -h muxer=hls

Muxer HLS

Muxer hls [Apple HTTP Live Streaming]:Common extensions: m3u8.Default video codec: h264.Default audio codec: aac.Default subtitle codec: webvtt.

这里面告诉我们,FFmpeg中的Muxer hls实际上是对于Apple HTTP Live Streaming的一种实现(HLS,全称HTTP Live Streaming,是Apple公司发布的协议),这里明确说明了HLS只是一种封装格式而与编码无关。

默认的文件扩展名为m3u8​,我们在浏览器中观看动漫、电影的时候,可以使用工具去查看它里面的链接。最终,你大概率会发现这样一种m3u8​文件的访问链接。

FFmpeg的hls muxer默认支持的视频、音频和字幕的编码格式分别是:h264​、aac​和webvtt​。这也就意味着如果我们想要对其他编码格式的音频或者视频进行HLS封装,那么就需要显式地去指定这些编码格式。需要注意的是,这些其他的编码格式需要是HLS协议支持的编码格式。

假设我们需要对一个MP4文件进行HLS切片,更准确地说是将MP$的封装格式转换成HLS的封装格式,只不过HLS这个封装格式是由多个音视频文件和一个M3U8(该文件在HLS协议中被称为Media Playlist,用作指导这些切片后的音视频如何播放)组成的。

例子

多的不说,我这里找一个MP4文件,使用FFmpeg将其转换成hls的封装格式,看看会出现什么样的结果。

这里我使用ffprobe查看该MP4文件的编码,来明确是否需要进行转码操作:

Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'test_input.mp4':Metadata:major_brand     : isomminor_version   : 512compatible_brands: isomiso2avc1mp41encoder         : Lavf60.16.100Duration: 00:00:14.82, start: 0.000000, bitrate: 2662 kb/sStream #0:0[0x1](und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv, bt709, progressive), 1920x1200 [SAR 1:1 DAR 8:5], 2489 kb/s, 60 fps, 60 tbr, 15360 tbn (default)Metadata:handler_name    : VideoHandlervendor_id       : [0][0][0][0]Stream #0:1[0x2](und): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 159 kb/s (default)Metadata:handler_name    : SoundHandlervendor_id       : [0][0][0][0]

你可以看到,视频编码h264​和音频编码aac​都是默认的,这意味着我们不需要转码。

因此,我们可以使用以下命令来进行转封装:

mkdir output & ffmpeg -i test_input.mp4 -c copy -f hls output/index.m3u8

使用命令来查看output​目录中的内容:

> tree output
output
├── index.m3u8
├── index0.ts
├── index1.ts
├── index2.ts
└── index3.ts> cat output/index.m3u8
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:4
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:4.166667,
index0.ts
#EXTINF:4.166667,
index1.ts
#EXTINF:4.166667,
index2.ts
#EXTINF:2.366667,
index3.ts
#EXT-X-ENDLIST

此刻,output​目录中的所有内容,组成了HLS协议的封装格式,虽然它们由多个文件组成。

但是,当需要切片的文件变大时,index.m3u8​的内容会和实际的切片数量对不上,原因就是:默认的hls_list_size​的值为5​。因此,index.m3u8文件中只会记录最新的5个切片。使用-hls_list_size​能够自己指定这个值。

选项

额外指定一些选项,让HLS的切片符合你的需求。

start_number

设置开始的序列号,默认从0开始。这里我们设置序列号为1进行切片,那么产生的结果为:

> mkdir output & ffmpeg -i test_input.mp4 -c copy -f hls -start_number 1 output/index.m3u8
> tree output
output
├── index.m3u8
├── index1.ts
├── index2.ts
├── index3.ts
└── index4.ts

注意,生成的切片文件名的序列号和m3u8文件中的序列号是对应的。

hls_time

指定切片的时间长度,单位为秒,默认值为2,类型是float​。

> cat output/index.m3u8
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:4
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:4.166667,
index0.ts
#EXTINF:4.166667,
index1.ts
#EXTINF:4.166667,
index2.ts
#EXTINF:2.366667,
index3.ts
#EXT-X-ENDLIST

#EXTINF​就是切片的时间长度,这里切片的时间长度为4.166667​,这和默认值2​不相符。这是什么原因导致的呢?我猜测,这可能是因为HLS对于每个切片的关键帧具有某种要求,导致了最终的切片时间按照原本视频的关键帧分布来进行切片。

使用-force_key_frames "expr:gte(t,n_forced*2)"​来让视频GOP大小为2秒,以此让切片能够按照我们所设置的参数运行:

$ ffmpeg -i test_input.mp4 \ 
> -f hls \ 
> -force_key_frames "expr:gte(t,n_forced*2)" \
> -hls_time 2 \
> output/index.m3u8$ cat output/index.m3u8
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:2
#EXT-X-MEDIA-SEQUENCE:3
#EXTINF:2.000000,
index3.ts
#EXTINF:2.000000,
index4.ts
#EXTINF:2.000000,
index5.ts
#EXTINF:2.000000,
index6.ts
#EXTINF:0.866667,
index7.ts
#EXT-X-ENDLIST

这证明我们的猜测是正确的。注意,由于-force_key_frames​选项改变了原本的视频帧,因此不能够指定-c copy​(如果指定,则会导致我们无法对原本的视频编码做出任何改变,-force_key_frames​就会失效)。

你可以看到上面只记录了5个切片,3 4 5 6 7​,而我们的切片明明是从0开始的,并且0 1 2​确实存在于output目录中,但却没有被m3u8文件记录,这就关系到-hls_list_size​的使用了

hls_list_size

前面说了,由于m3u8​这个Media Playlist只会记录最新的几个切片,这可能会导致播放错误。该选项默认值是5,当切片多于5个时,你就要考虑将其设置地大一些,防止切片错误。

hls_base_url

你可以指定切片的基础路径,比如:http://xxx.com/​。这样浏览器可以通过读取m3u8​文件,然后通过网络来访问这些切片。

比如:

> ffmpeg -i test_input.mp4 -c copy -f hls \
> -hls_base_url "http://www.aderversa.com/" \
> output/index.m3u8> cat output/index.m3u8
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:4
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:4.166667,
http://www.aderversa.com/index0.ts
#EXTINF:4.166667,
http://www.aderversa.com/index1.ts
#EXTINF:4.166667,
http://www.aderversa.com/index2.ts
#EXTINF:2.316667,
http://www.aderversa.com/index3.ts
#EXT-X-ENDLIST

该选项通常和服务器配合使用。你设置了一个URL,然后浏览器可以通过从服务器获取m3u8​文件和视频切片文件。

远程播放HLS视频

在前面,我们已经知道了,HLS封装格式会生成以下文件:

output
├── index.m3u8
├── index0.ts
├── index1.ts
├── index2.ts
└── index3.ts

播放器拿到m3u8文件,它应该能够按照HLS协议并参考m3u8上的内容自主获取视频切片并播放。

这里我使用Qt6.6的QMediaPlayer​来播放m3u8文件(这样方便理解,实际上M3U8不包含视频数据,它只含有该怎样播放的信息)。

如何播放呢?抓住一个关键点:只要QMediaPlayer​拿到了M3U8文件,它就能够依照该M3U8文件播放上面的资源,而我们是不需要了解它具体如何播放的,因为播放器的开发者为我们完成了这部分工作。我们唯一需要关注的就是:如何让QMediaPlayer​获取到这份M3U8文件。

这里我在网上随便找一个M3U8文件的链接(随便找个不是很正规的视频网站一般都能够找到,查看其HTML代码你就能够发现隐藏在其中的M3U8文件):

具体是什么URL我就不放出来了。

我们可以发现该播放器需要一个M3U8的URL才能够播放。

我们将url=​后面的链接命名为VideoURL​,方便后续说明。

这里我们在浏览器中请求VideoURL​,看看会发生什么?结果就是,浏览器给我们下载了一个M3U8文件。

我们使用以下Qt6.6中的代码来播放VideoURL​,看看能否播放成功:

int main(int argc, char *argv[])
{QApplication a(argc, argv);QMediaPlayer player;player.setSource(QUrl("https://play.modujx11.com/20250104/pZZhNChc/index.m3u8"));QVideoWidget video_widget;player.setVideoOutput(&video_widget);video_widget.show();QAudioOutput audio_output;player.setAudioOutput(&audio_output);player.play();return a.exec();
}

结果是,播放成功了。

这说明了什么呢?说明了只要服务器能够提供一个接口,让浏览器能够通过访问该接口URL下载到M3U8文件,且M3U8中的资源是可以被浏览器访问到的,那么实现了HLS协议的播放器应该就能播放该M3U8文件。

利用上面实验出来的特性搭建视频平台的一些猜想

那么,如果我们的应用程序想要通过HLS协议实现视频远程播放的功能,首先客户端需要有M3U8文件并进行播放的能力;而服务器只需要提供下载M3U8文件和下载M3U8文件中对应的切片文件的接口即可。

若应用程序能够播放的视频是服务器端规定好的,用户无法上传任何视频,那么我们在服务器端完全可以自己在相应的文件夹下使用FFmpeg命令来慢慢进行切片。

若用户可在应用程序中上传视频,那么上传完成之后,服务器端如果不追求性能和定制化,我个人认为直接调用FFmpeg的命令行程序来完成HLS的切片是没有问题的。如果用户上传的视频的编码或者封装格式不合适,那么要么禁止用户上传这类视频;要么就在后端慢慢进行转码,若同一时间有大量转码的视频,那么对于性能的消耗将是灾难性的,因此大部分应用程序都不会允许用户将格式不合适的视频直接上传到服务器端,而是让用户自行找方法转码,转码完成后再发送到服务器端。

用户若是能够上传视频,如果应用程序具有一定的用户基数,那么上传的视频数量大概率是会逐渐增加的,对服务器的性能要求也会逐渐提高(不管是空间上还是时间上)。

应用程序可能一开始就是将这些视频开放给所有用户的,用户可以通过客户端来访问或自己上传的、或别人上传的视频数据。我们可能需要使用数据库存业务数据 + 文件系统存视频数据 + 数据库和文件系统之间存在某种数据上的联系,以此数据存储为基础构建服务器,然后客户端/前端再基于服务器的接口构建符合需求的交互界面。

实现一个简单的服务器来验证猜想

首先,在SpringBoot中实现这样一个Controller,用来下载服务器上的文件:

@Controller
public class FileDownloadController {private static final String FILE_DIR = "/videos";@GetMapping("/download/{dirname}/{filename:.+}")@ResponseBodypublic ResponseEntity<Resource> downloadFile(@PathVariable("dirname") String dirname,@PathVariable("filename") String filename) {String path = System.getProperty("user.dir") + FILE_DIR + "/" + dirname;Path media_list_path = Paths.get(path).resolve(filename).normalize();try {UrlResource resource =  new UrlResource(media_list_path.toUri());if (resource.exists() || resource.isReadable()) {return ResponseEntity.ok().contentType(MediaType.APPLICATION_OCTET_STREAM).header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"").body(resource);}else {return ResponseEntity.notFound().build();}}catch (Exception e) {e.printStackTrace();return ResponseEntity.status(500).build();}}
}

接着,我们在指定的/videos​目录下创建好HLS封装的文件:

> ffmpeg -i hls_video.mp4 -f hls \
> -hls_base_url \  
> http://127.0.0.1:8080/download/hlstest1/ \
> -c copy \ 
> -hls_list_size 1000 \
> hlstest1/index.m3u8> tree .
.
├── hls_video.mp4
└── hlstest1├── index.m3u8├── index0.ts├── index1.ts...

大致内容如上所示,然后在替换Qt原本代码中的URL成http://localhost:8080/download/hlstest1/index.m3u8​,播放成功。

我这里的服务器运行在本机中,所以ip为localhost​。

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

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

相关文章

Docker Desktop 构建java8基础镜像jdk安装配置失效解决

Docker Desktop 构建java8基础镜像jdk安装配置失效解决 文章目录 1.问题2.解决方法3.总结 1.问题 之前的好几篇文章中分享了在Linux(centOs上)和windows10上使用docker和docker Desktop环境构建java8的最小jre基础镜像&#xff0c;前几天我使用Docker Desktop环境重新构建了一个…

Node.js——fs(文件系统)模块

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;正逐渐往全干发展 &#x1f4c3;个人状态&#xff1a; 研发工程师&#xff0c;现效力于中国工业软件事业 &#x1f680;人生格言&#xff1a; 积跬步…

Microsoft Azure Cosmos DB:全球分布式、多模型数据库服务

目录 前言1. Azure Cosmos DB 简介1.1 什么是 Azure Cosmos DB&#xff1f;1.2 核心技术特点 2. 数据模型与 API 支持2.1 文档存储&#xff08;Document Store&#xff09;2.2 图数据库&#xff08;Graph DBMS&#xff09;2.3 键值存储&#xff08;Key-Value Store&#xff09;…

2025年华为OD上机考试真题(Java)——整数对最小和

题目&#xff1a; 给定两个整数数组array1、array2&#xff0c;数组元素按升序排列。假设从array1、array2中分别取出一个元素可构成一对元素&#xff0c;现在需要取出k对元素&#xff0c;并对取出的所有元素求和&#xff0c;计算和的最小值。 注意&#xff1a;两对元素如果对应…

7 分布式定时任务调度框架

先简单介绍下分布式定时任务调度框架的使用场景和功能和架构&#xff0c;然后再介绍世面上常见的产品 我们在大型的复杂的系统下&#xff0c;会有大量的跑批&#xff0c;定时任务的功能&#xff0c;如果在独立的子项目中单独去处理这些任务&#xff0c;随着业务的复杂度的提高…

网络安全 | 网络安全法规:GDPR、CCPA与中国网络安全法

网络安全 | 网络安全法规&#xff1a;GDPR、CCPA与中国网络安全法 一、前言二、欧盟《通用数据保护条例》&#xff08;GDPR&#xff09;2.1 背景2.2 主要内容2.3 特点2.4 实施效果与影响 三、美国《加利福尼亚州消费者隐私法案》&#xff08;CCPA&#xff09;3.1 背景3.2 主要内…

“AI智能陪练培训服务系统,让学习更轻松、更高效

大家好&#xff0c;我是资深产品经理小李&#xff0c;今天咱们来侃侃一个新兴的教育辅助工具——AI智能陪练培训服务系统。这个系统可谓是教育培训行业的一股新势力&#xff0c;它究竟有什么神奇之处呢&#xff1f;下面我就跟大家伙儿好好聊聊。 一、什么是AI智能陪练培训服务系…

notebook主目录及pip镜像源修改

目录 一、notebook主目录修改二、pip镜像源修改 一、notebook主目录修改 在使用Jupyter Notebook进行数据分析时&#xff0c;生成的.ipynb文件默认会保存在Jupyter的主目录中。通常情况下&#xff0c;系统会将Jupyter的主目录设置为系统的文档目录&#xff0c;而文档目录通常位…

如何利用百炼智能体编排应用轻松搭建智能AI旅游助手?

各位小伙伴儿&#xff0c;好哈&#xff01; 在上一篇文章《5分钟基于阿里云百炼平台搭建专属智能AI机器人》中我们体验了如何利用阿里云百炼平台的智能体应用搭建专属智能机器人。 它的配置过程相对简单&#xff0c;其“对话式”的输出形式也十分直观&#xff0c;非常适合初学…

计算机视觉目标检测-DETR网络

目录 摘要abstractDETR目标检测网络详解二分图匹配和损失函数 DETR总结总结 摘要 DETR&#xff08;DEtection TRansformer&#xff09;是由Facebook AI提出的一种基于Transformer架构的端到端目标检测方法。它通过将目标检测建模为集合预测问题&#xff0c;摒弃了锚框设计和非…

【Vim Masterclass 笔记09】S06L22:Vim 核心操作训练之 —— 文本的搜索、查找与替换操作(第一部分)

文章目录 S06L22 Search, Find, and Replace - Part One1 从光标位置起&#xff0c;正向定位到当前行的首个字符 b2 从光标位置起&#xff0c;反向查找某个字符3 重复上一次字符查找操作4 定位到目标字符的前一个字符5 单字符查找与 Vim 命令的组合6 跨行查找某字符串7 Vim 的增…

springboot 默认的 mysql 驱动版本

本案例以 springboot 3.1.12 版本为例 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.1.12</version><relativePath/> </parent> 点击 spring-…

计算机网络(二)——物理层和数据链路层

一、物理层 1.作用 实现相信计算机节点之间比特流的透明传输&#xff0c;尽可能屏蔽具体传输介质和物理设备的差异。 2.数据传输单位 比特。 3.相关通信概念 ①信源和信宿&#xff1a;即信号的发送方和接收方。 ②数据&#xff1a;即信息的实体&#xff0c;比如图像、视频等&am…

sql server cdc漏扫数据

SQL Server的CDC指的是“变更数据捕获”&#xff08;Change Data Capture&#xff09;。这是SQL Server数据库提供的一项功能&#xff0c;能够跟踪并记录对数据库表中数据所做的更改。这些更改包括插入、更新和删除操作。CDC可以捕获这些变更的详细信息&#xff0c;并使这些信息…

AI数字人+文旅:打造数字文旅新名片

在数字化浪潮的推动下&#xff0c;人工智能技术正以前所未有的速度渗透到我们生活的每一个角落。特别是在文化和旅游领域&#xff0c;AI数字人的出现&#xff0c;不仅为传统文旅产业注入了新的活力&#xff0c;也为游客带来了全新的体验。 肇庆AI数字人——星湖 “星湖”是肇…

做一个 简单的Django 《股票自选助手》显示 用akshare 库(A股数据获取)

图&#xff1a; 股票自选助手 这是一个基于 Django 开发的 A 股自选股票信息查看系统。系统使用 akshare 库获取实时股票数据&#xff0c;支持添加、删除和更新股票信息。 功能特点 支持添加自选股票实时显示股票价格和涨跌幅一键更新所有股票数据支持删除不需要的股票使用中…

Protobuf编码规则详解

Protobuf编码规则详解 1 Message 结构1.1 tag1.1.1 字段编号(field_num)1.1.2 传输类型(wire_type) 1.2 字段顺序1.3 默认值 2 编码2.1 Varint编码2.1.1 Varint编码过程2.1.2解码过程2.1.3 存储2.1.4 小结2.2 有符号整数(sint32和sint64)编码的问题与zigzag优化 3 编码实践3.1测…

【docker】exec /entrypoint.sh: no such file or directory

dockerfile生成的image 报错内容&#xff1a; exec /entrypoint.sh: no such file or directory查看文件正常在此路径&#xff0c;但是就是报错没找到。 可能是因为sh文件的换行符使用了win的。

计算机的错误计算(二百零七)

摘要 利用两个数学大模型计算 arccot(0.125664e2)的值&#xff0c;结果保留16位有效数字。 实验表明&#xff0c;它们的输出中分别仅含有3位和1位正确数字。 例1. 计算 arccot(0.125664e2)的值&#xff0c;结果保留16位有效数字。 下面是与一个数学解题器的对话。 以上为与…

MCANet: 基于多模态字幕感知的大语言模型训练无关视频异常检测

目录 摘要01 引言02 相关工作2.1 视频异常检测2.2 基于视频的大语言模型&#xff08;VLLMs&#xff09; 03 方法论3.1 问题定义3.2 MCANet3.3 图像字幕分支3.4 音频字幕分支3.5 基于LLM的异常评分3.6 视频-文本分数优化 04 实验4.1 数据集和评估指标4.2 实现细节4.3 定性结果4.…