解复用流程
音视频的解复用(Demultiplexing)是一个将单个输入媒体文件分离成不同音频、视频和其他数据流(如果有的话)的过程。这些单独的流之后可以被独立地读取、解码或处理。下面是FFmpeg解复用的基本流程:
-
初始化:
- 引入所有编解码器和格式器。
- 注册需要用到的设备。
- 初始化网络,如果需要处理网络流。
-
打开输入文件:
- 使用
avformat_open_input
函数来打开输入媒体文件。 - 如果需要的话,分析输入流的信息,可能会用到
avformat_find_stream_info
函数。
- 使用
-
查找流信息:
- 通过分析得到的信息来确定有哪些音/视频流。
- 找到后,可以根据需求来选择特定的流进行处理。
-
打开解码器:
- 为每一个想要处理的流找到对应的解码器。
- 使用
avcodec_find_decoder
来查找解码器。 - 使用
avcodec_open2
来打开解码器。
-
读取数据包:
- 通过
av_read_frame
来读取数据包。 - 该函数会帮你得到一个数据包,包含了解复用后的数据(可以是音频、视频或其他类型的数据)。
- 通过
-
解码数据包:
- 使用合适的解码器,调用
avcodec_send_packet
和avcodec_receive_frame
来解码数据包。 - 这样可以得到原始的音频样本或视频帧。
- 使用合适的解码器,调用
-
处理音视频数据:
- 一旦数据被解码,就可以进行各种处理,比如编码到另一种格式、保存到文件、传输等。
-
清理:
- 关闭解码器和输入文件。
- 释放所有分配的资源。
音频解码流程
音频解码是指将压缩的音频数据转换为可以再生的PCM(脉冲编码调制)数据的过程。使用FFmpeg来进行音频解码的基本步骤如下:
-
初始化FFmpeg解码器:
- 包含FFmpeg头文件。
- 调用
av_register_all()
初始化编解码器(这是在FFmpeg 4.0之前需要的,在新版本中可能不再需要)。 - 调用
avcodec_register_all()
注册所有编解码器(同样,这一步在新版本中可能不再需要)。
-
打开输入的音频流:
- 使用
avformat_open_input()
函数来读取和打开音频文件。 - 使用
avformat_find_stream_info()
函数获取流信息。
- 使用
-
查找音频流:
- 检索音频流的索引。
- 找到第一个音频流并记下它的index。
-
打开对应的解码器:
- 查找音频流对应的解解码器
avcodec_find_decoder()
。 - 使用
avcodec_open2()
函数来打开解码器。
- 查找音频流对应的解解码器
-
读取音频包解码:
- 遍历音频数据,读取音频包(AVPacket)。
- 使用
av_read_frame()
来读取。 - 检查包是否属于所需的音频流。
-
将音频包送入解码器:
- 使用
avcodec_send_packet()
将包送入解码器准备解码。
- 使用
-
从解码器读取解码后的音频帧:
- 使用
avcodec_receive_frame()
获取解码后的帧(AVFrame)。 - 继续从解码器获取所有解码后的帧直到返回EAGAIN或错误。
- 使用
-
转换音频格式 (可选):
- 如果需要,将音频数据转换成不同的格式或采样率,可以使用'libswresample'或者'libavresample'。
-
后处理 (根据需要):
- 对解码的音频进行必要的后处理,比如音量调整、混音等。
-
清理和资源释放:
- 关闭解码器。
- 关闭音频文件。
- 释放所有使用过的AVFrame和AVPacket。
- 释放编解码上下文等。
视频解码流程
FFmpeg进行视频解码的流程与音频解码流程类似,目的是将压缩的视频数据流转换成解码后的原始视频帧(通常是YUV或RGB格式)。以下是使用FFmpeg进行视频解码的基本步骤:
-
初始化FFmpeg:
- 调用
av_register_all()
来注册组件,这在新版本的FFmpeg中可能不再需要。 - 初始化网络库
avformat_network_init()
(如果需要处理网络视频流)。
- 调用
-
打开输入文件(或流):
- 使用
avformat_open_input()
去打开输入的视频文件或流,并创建一个AVFormatContext
。 - 使用
avformat_find_stream_info()
分析获取流信息,方便后面选择流。
- 使用
-
查找视频流:
- 遍历
AVFormatContext
中的streams
数组,找到视频流的索引。 - 可以通过检查流的
codecpar->codec_type
来确认是不是视频流。
- 遍历
-
找到并打开视频解码器:
- 使用
avcodec_find_decoder()
来找到对应的解码器。 - 然后创建一个
AVCodecContext
并使用avcodec_open2()
与找到的解码器关联。
- 使用
-
读取视频数据包:
- 通过
av_read_frame()
从媒体文件中读取视频数据(AVPacket
)。 - 考虑只处理我们之前记下的视频流索引对应的包。
- 通过
-
发送数据包到解码器:
- 用
avcodec_send_packet()
函数发送数据包到解码器。
- 用
-
从解码器中获取解码的视频帧:
- 使用
avcodec_receive_frame()
从解码器中获取解码后的视频帧(AVFrame
)。 - 需要循环重复此过程以获取所有解码后的帧。
- 使用
-
视频帧处理 (如有需要):
- 将解码的视频帧转换成需要的格式或进行处理,可以使用
libswscale
来进行格式转换或调整尺寸。
- 将解码的视频帧转换成需要的格式或进行处理,可以使用
-
帧率控制 (如有需要):
- 根据视频的PTS(Presentation Time Stamp)来处理帧率,确保视频按正确的速率播放。
-
清理工作:
- 释放已分配的
AVCodecContext
和AVFormatContext
。 - 释放使用过的
AVFrame
和AVPacket
。 - 关闭视频流和网络库(如果初始化了)。
- 释放已分配的
FLV 封装格式分析
FLV(Flash Video)是Adobe Systems开发的一个流行的视频文件格式,通常用于互联网上的视频流媒体服务。
FLV文件由多个部分组成,这些部分包含了视频和音频的数据以及其他元数据。以下是FLV封装格式的一些关键组成部分:
组件名称 | 组件类型 | 描述 | 备注 |
---|---|---|---|
Header | 固定 | 包含标识符、版本、以及音视频存在标志位 | 文件开始的部分 |
Previous Tag Size 0 | 固定 | 第一个Tag之前的大小,默认为0 | 用于标识第一个Tag开始 |
Tag 1 | 可重复 | 第一个音频、视频或脚本数据Tag | 按需求重复 |
Previous Tag Size 1 | 可重复 | 第一个Tag的大小,即Tag 1 | 紧跟着Tag 1 |
Tag 2 | 可重复 | 第二个音频、视频或脚本数据Tag | 按需求重复 |
Previous Tag Size 2 | 可重复 | 第二个Tag的大小,即Tag 2 | 紧跟着Tag 2 |
... | ... | 更多音频、视频或脚本数据Tag | ... |
Previous Tag Size N | 可重复 | 最后一个Tag的大小,即Tag N | 紧跟着Tag N |
EOF | 固定 | 文件结束标志(非正式,实际并无特定EOF标记,文件结束由文件系统决定) |
各Tag的具体内部结构如下:
音频Tag:
- 音频编码格式(例如AAC、MP3)
- 采样率
- 采样大小
- 声道数
- 音频数据(编码后)
视频Tag:
- 帧类型(关键帧、内部帧等)
- 视频编码格式(例如AVC即H.264、Sorenson H.263)
- 视频数据(编码后)
脚本数据Tag:
- 事件名称(例如"onMetaData")
- 数据(编码为AMF格式,通常包括视频的文件属性)
MP4 封装格式分析
MP4封装格式,正式名称为MPEG-4 Part 14 (MP4),是一种用于存储多媒体类型如音频、视频和其他数据(如字幕和静态图片)的容器格式。MP4基于Apple的QuickTime文件格式,并且遵循ISO基本媒体文件格式(ISO/IEC 14496-12)。MP4文件格式灵活且被广泛用于视频存储和流式传输。
MP4文件的结构基于一系列的box(也称为atom或container),每个box可以包含数据或者其他box。以下是MP4封装格式的一些关键部分,它们组成MP4文件的层次结构:
组件类型 | Box 类型 | 描述 |
---|---|---|
文件类型Box | ftyp |
指定文件类型和兼容性。包含编解码器和文件的兼容性信息。 |
媒体数据Box | mdat |
包含实际的媒体数据,如视频、音频片段。 |
电影Box | moov |
包含关于媒体数据和播放的全部或大部分元数据。 |
- 电影头Box | mvhd |
描述文件的全局属性,如总播放时间、创建时间和修改时间。 |
- 跟踪Box | trak |
描述一个特定的媒体序列(音频、视频、字幕),每个trak负责一个类型的流。 |
- 跟踪头 | tkhd |
指定跟踪的特性,如持续时间、宽度和高度。 |
- 媒体Box | mdia |
包含描述媒体信息的box,定义了媒体的数据结构和处理方法。 |
- 媒体头 | mdhd |
描述媒体的全局属性,如播放时长和时区信息。 |
- 处理器 | hdlr |
指示是否为视频、音频或字幕轨道。 |
- 媒体信息 | minf |
包含具体媒体数据的头信息。 |
- 视频媒体信息 | vmhd |
视频轨道的一般信息。 |
- 音频媒体信息 | smhd |
音频轨道的一般信息。 |
- 数据信息 | dinf |
包含媒体数据的定位信息。 |
- 样本表Box | stbl |
关键的索引结构,包含关于媒体数据样本的索引和位置信息,如样本描述、时间、布局。 |
- Chunk Offset Box | stco or co64 |
样本在mdat 中的位置。 |
- 样本描述Box | stsd |
描述样本的编码情况,可能包含编解码器具体信息。 |
- 时间到样本Box | stts |
关联样本的播放时间。 |
- 样本到Chunk Box | stsc |
样本与块(chunk)的映射。 |
- 样本大小Box | stsz or stz2 |
每个样本的大小。 |
- 同步样本Box | stss |
关键帧(对于视频)的索引,用于快速定位。 |
用户数据Box | udta |
可用于存储自定义元数据。 |
以上是MP4文件格式的主要结构,每个部分可能会根据实际文件包含的内容而有所不同。文件通常从ftyp
开始,指明文件类型和版本,紧随其后的是moov
,包含了媒体播放所需的所有信息,最后是mdat
,这是包含了实际媒体数据的部分。每个部分都可能包含一些其他的嵌套box,以提供必要的数据和元数据。
FLV 和 MP4 seek 有什么区别
在FFmpeg处理音视频文件的上下文中,seek 指的是跳转到特定时间点进行播放或处理。FLV(Flash Video)和MP4(MPEG-4 Part 14)作为两种不同的封装格式,它们在seek操作上存在差异,主要是因为各自的文件结构和索引信息方式导致的。
特性 | FLV格式 | MP4格式 |
---|---|---|
索引信息 | 不包含完整索引,seek主要依赖于关键帧。 | 包含详细的索引信息,如样本表,允许精确seek到特定时间点。 |
Seek精度 | 较低,因为只能跳转到最近的关键帧。 | 较高,可以准确跳转到任何帧,包括非关键帧。 |
Seek速度 | 比MP4慢,尤其在关键帧间隔较大时。 | 较快,由于有详细索引,即使文件很大也能快速seek。 |
适用场合 | 主要用于流式传输中,对seek精度要求不高时使用。 | 广泛用于各种需要高精度和高性能seek的场合。 |
关键帧依赖 | 是,seek性能和关键帧间隔密切相关。 | 否,关键帧用于提高压缩效率,但对seek的影响不大,因为索引包含所有帧的信息。 |
文件适用性 | 实时流媒体传输或简单的视频播放。 | 高质量、多功能的媒体播放和编辑,如电影和电视节目的在线分发。 |
文件结构 | 简单,便于快速开始播放流媒体数据。 | 复杂,用于存储大量关于文件结构和媒体流的元数据。 |
随机访问 | 受限,因为没有所有帧的详细索引信息。 | 支持,文件的随机访问性能优异,适合编辑和广泛的媒体处理操作。 |
格式设计理念 | 为网页视频和简单的流式传输优化,关注快速传输和播放启动。 | 为广泛媒体应用设计,包括高清视频播放、复杂编辑和多媒体集成。 |
为什么 FLV 格式能用于直播
FLV格式,全称为Flash Video,是为在线视频播放设计的一种媒体文件格式。它特别适合用于直播流媒体的几个原因如下:
-
快速启动:FLV文件的结构使得视频播放可以在下载的同时开始,这是所谓的快速启动特性。与其他格式相比,FLV格式能够更快捷地开始播放,这对于直播场景非常关键,因为观众希望尽可能迅速地接入现场直播内容。
-
关键帧索引:FLV格式在文件的头部包含了关键帧的索引,使得视频流可以直接跳到关键帧开始播放,这样可以减少缓冲时间,对于直播来说尤为重要。
-
简单的流媒体协议:FLV经常和RTMP(Real Time Messaging Protocol)一起使用,RTMP是一个专为高速传输音视频数据设计的协议。由于RTMP直接在TCP协议之上工作,它能够提供稳定的连接并维持低延迟传输,非常适合直播。
-
容器灵活性:FLV是一个容器格式,它能够封装多种编解码器处理的音视频数据,而对于直播来说,H.264视频编解码器和AAC音频编解码器是被广泛支持的组合。FLV容器可以方便地包装这些流行的编解码器输出的数据流。
-
广泛支持:过去,几乎所有的浏览器和移动设备都支持Flash Player,它可以播放FLV文件。尽管现在Flash Player的重要性已经减少,但FLV格式仍然由于其直播能力而在某些场合仍然使用。
-
低延迟性能:FLV格式配合RTMP协议能够实现相对较低的延迟,这对于直播中实时互动非常重要。
FLV 格式如何实现快速启动?
FLV格式实现快速启动的关键在于它的文件结构和流式传输特性。以下是详细的解释:
-
文件结构:
- 头部信息:FLV文件具有一个简单的头部,它标识了文件类型、版本以及是否存在音频或视频标签。
- 元数据:紧随头部之后的是元数据,这通常包含视频的总时长、宽高、帧率等信息,对于快速启动不是必须的,但当存在时可以提供更好的用户体验。
- 交错的音视频标签:FLV的主体是由音频和视频标签交错组成的,每个标签都附带了时间戳。这意味着播放器可以立即开始解码并播放接收到的数据,而不需要等待整个文件下载完成。
-
关键帧索引:
- FLV文件中的视频序列依赖关键帧(或I帧)进行快速访问。虽然对于完整的跳转功能来说关键帧索引是必需的,但对于直播和快速启动,播放器可以从接收到的任何关键帧开始播放,而无需完整的索引。
-
流式传输协议:
- FLV文件通常与RTMP(Real-Time Messaging Protocol)一起使用,这是一个为传输实时数据而优化的协议。RTMP提供了连续流的能力,即数据可以一边下载一边播放。
-
简化的解析与缓冲:
- 由于FLV是为网络播放优化的,它允许播放器在收到足够数据后立即开始播放。这种设计减少了解析和缓冲所需的时间,致力于实现低启动延迟。
-
媒体服务器的支持:
- 对于直播而言,媒体服务器通常配置成会优先发送关键帧,从而使得客户端能够尽快地开始播放视频。
FLV格式可以与哪些流式传输协议一起使用?
FLV格式主要与以下流式传输协议结合使用:
-
RTMP (Real Time Messaging Protocol):
- RTMP是Adobe Systems开发的一种设计用于高性能传输音视频流的协议。它基于TCP,能够提供稳定的连接并保持较低的延迟,是FLV格式用于直播的最常见协议。RTMP经常运用在直播平台和流媒体服务器上。
-
RTMPE (Real Time Messaging Protocol Encrypted):
- 这是RTMP的一个变种,提供了额外的安全性,通过在RTMP的基础上添加了一个加密层保护传输的内容不被截获或解码。
-
RTMPT (Real Time Messaging Protocol Tunneled):
- RTMPT是RTMP的封装,它通过HTTP进行数据传输,可用于绕过防火墙或代理服务器的限制。
-
RTMPS (Real Time Messaging Protocol Secure):
- RTMPS是RTMP通过SSL/TLS连接的安全版本,提供了比RTMPE更强的安全性,因为SSL/TLS是一个更加成熟和可靠的安全标准。
-
HTTP FLV Streaming:
- 虽然不如RTMP及其变体那样流行,但FLV格式的文件也可以通过HTTP协议进行传输。这种方式实现了所谓的HTTP伪流(HTTP pseudostreaming),允许播放器在下载进行中请求视频文件的不同部分。
为什么 MP4 不能用于直播
在音视频流媒体领域,MP4格式(基于ISO基础媒体文件格式)通常不被用于直播,其原因主要与其文件结构和处理方式有关。以下是一些详细的解释:
-
文件结构:
- MP4文件通常包含显著的'moov'原子(一个文件结构单元),它含有媒体的元数据,如关键帧索引、轨道信息、时长和其他必要信息,以便播放器可以知道如何处理文件中的数据流。在MP4文件中,'moov'原子是必需的,因此播放器必须读取这部分信息才能开始播放。
- 对于文件的下载和点播来说,通常这个'moov'原子位于文件的末尾,因此需要下载整个文件才能处理和播放。虽然通过"快速启动"处理可以将'moov'原子移到文件开始部分,但这对实时生成的内容来说依然是一个问题,因为在直播流结束之前,元数据大小和完整结构是未知的。
-
实时性:
- 直播要求流媒体具有实时性,即生产者生成的数据流可以立即被消费者消费。MP4格式设计用于存储完整的媒体文件,终端播放器在获取并处理文件头部的'moov'原子后才能播放视频。在直播的上下文中,直到直播结束,播放器才知道所有必要的元数据信息,这不利于实时播放。
-
延迟:
- 由于需要先处理'moov'原子,MP4格式会在开始播放之前引入额外的延迟,而在直播应用中,尽可能降低延迟是非常关键的。
MP4文件的'moov'原子有什么作用?
在MP4文件格式中,'moov'原子非常重要,它起到了索引和资源描述的作用。'moov'原子包含了多种类型的子原子,每个子原子都储存了流的不同类型的元数据。
以下是'moov'原子所包含的一些主要子原子及其作用:
-
mvhd (Movie Header): 这个子原子包含了整个文件的全局信息,如创建时间、修改时间、时间轴的时间单位、总时长等。
-
trak (Track): MP4文件可以包含多个'trak'原子,每个代表文件中的一条轨道。例如,一个轨道可以是视频,另一个轨道可以是音频。
-
mdia (Media): 在每个'trak'轨道中都有'mdia'原子,它负责储存该轨道的特定信息,包括'mdhd'(媒体头部),'hdlr'(处理程序参考),以及'minf'(媒体信息)。
-
mdhd (Media Header): 存储媒体(音频或视频)的时长和语言等信息。
-
hdlr (Handler Reference): 指明轨道的类型和处理方式,比如指出该轨道是用于视频、音频或字幕。
-
minf (Media Information): 包含了更详细的媒体数据类型信息,如具体视听格式、数据量和质量。
- vmhd (Video Media Header): 如果是视频轨道,会有这个子原子,包含视频的特定信息。
- smhd (Sound Media Header): 如果是音频轨道,会有这个子原子,包含音频的特定信息。
-
stbl (Sample Table): 这个子原子是'moov'原子中最复杂的部分,它定义了所有样本(sample)的详细索引信息,使解码器和播放器能找到每个样本的位置和大小以及解码它们所需的所有信息。
- stsd (Sample Description): 指定样本的格式和解码信息。
- stts (Decoding Time to Sample): 保存了样本的时间戳信息。
- stsc (Sample To Chunk): 描述了样本如何映射到数据块中。
- stsz (Sample Sizes): 包含每个样本的大小。
- stco (Chunk Offset): 指定每个数据块在文件中的位置。
- stss (Sync Sample Table): 标记关键帧,用于随机访问和编辑。
MP4 能否用来做点播
MP4格式非常适合做点播(Video on Demand, VOD)系统,原因在于其设计理念和功能特点非常适配点播服务的需求。
以下是为什么MP4格式适合用来做点播的几个理由:
-
高兼容性:
- MP4是一种广泛受支持的视频格式,几乎所有的现代浏览器、媒体播放器和设备都能够播放MP4文件。这样一来,内容提供商可以确保他们的视频可以被大多数用户访问和观看。
-
良好的视频质量:
- MP4文件使用H.264(或AVC)和H.265(或HEVC)等高效的视频编码技术,能够在较小的文件大小下提供高质量的视频,这对于保持点播内容的视觉效果同时减少存储和带宽成本是非常有利的。
-
支持多种数据流:
- MP4格式可以容纳多个数据流,如视频、音频和字幕轨道。该特性对于提供多语言选项和其他增强服务是很重要的。
-
'moov'原子提供了必要的元数据:
- 如之前所解释,'moov'原子包含了视频的元数据,这对于点播来说是必要的。它允许播放器快速定位到视频文件的任何位置进行播放,无需下载整个文件,这对于用户快速寻找视频中的特定部分非常有用。
-
支持流化:
- 通过某些处理(如使用'faststart'选项),可以将'moov'原子移到文件开始,使视频可以在下载过程中播放,达到流畅的点播体验。
-
适合长格式内容:
- MP4格式非常适合长格式内容,因为它支持高效压缩而且播放稳定、无缝,这对于点播服务中常见的长视频而言是一个优点。
H.264 NALU 分析
NALU的组织分层
H.264视频流由多个NALU组成,每个NALU有它自己的头部(Header)和有效载荷(Payload)。NALU头部包含重要的信息,例如NALU的类型。有效载荷则包含实际的编码视频数据或者其他必要的数据信息。
NALU类型
H.264定义了多种类型的NALU,比如:
- 序列参数集(Sequence Parameter Set, SPS): 包含一系列编码视频序列的参数,如图像尺寸、编码使用的配置文件和级别等信息。
- 图像参数集(Picture Parameter Set, PPS): 包含一系列编码视频图像的参数,如熵编码模式、量化参数和去块效应滤波设置等。
- IDR帧(Instantaneous Decoder Refresh): 关键帧,它确保即使在丢失之前的数据包的情况下,视频也能正确地解码。
- 非IDR帧(Non-IDR): 包括P(预测)帧和B(双向预测)帧,这些帧依赖于其他帧来进行解码。
分层结构
H.264利用多个层次来传输数据:
- 视频编码层(Video Coding Layer, VCL): VCL负责高效地表示视频内容。VCL NALU包含实际的视频数据,即压缩后的宏块(例如P帧和B帧)。
- 网络抽象层(Network Abstraction Layer, NAL): NAL的目标是提供一套在不同网络和存储媒介上传输和存储VCL数据的方法。NAL负责打包VCL NALU以及其他类型的NALU(如SPS和PPS)。
这种分层结构使得H.264非常灵活,并且能够被广泛应用于不同的系统和网络条件下。例如,它允许流传输和存储介质丢弃某些不必要的NALU来适应带宽,而不会破坏解码过程。
基于NALU的传输
在基于IP的系统中,如RTSP(实时流协议)或RTP(实时传输协议)流媒体,NALU可以适应地组织和打包,以优化网络层的传输。
对于RTP传输,ff一般会根据NALU的大小和网络环境将它们封装成RTP包。大的NALU可能会被分片到多个RTP包中,确保数据包符合传输协议的最大传输单元(MTU)。
NALU header
在H.264标准中,NALU(网络抽象层单元)头部是每个NALU的开始部分,它提供了关于该NALU的信息。每个NALU头部一般是1个字节的长度,并且包含了几个重要的字段。
这个字节的结构如下:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI| Type |
+---------------+
其中各个位代表的意义如下:
-
F(禁止位,Forbidden zero bit): 这个位应该总是设置为0。如果在传输过程中发生错误,导致这个位置1,接收方应当抛弃这个NALU。
-
NRI(NAL引用指示,NAL reference indicator): 这两位用来指示NALU的重要程度。值较高的NALU在传输和存储中被更优先考虑。例如,在网络条件差的环境下,可能会选择丢弃某些NRI值较低的非关键帧数据包。
00
:NALU不用于reference01
:NALU最低级别的reference10
或11
:NALU较高级别的reference
-
Type(类型): 这5个位指示NALU的类型。H.264中定义了多种NALU类型,典型的类型包括:
1
:非IDR图像中不采用数据分割的片(Coded slice of a non-IDR picture)5
:IDR图像的片(Coded slice of an IDR picture)6
:补充增强信息单元(SEI:Supplemental enhancement information)7
:序列参数集(SPS:Sequence parameter set)8
:图像参数集(PPS:Picture parameter set)9
:访问单元分隔符(AUD:Access unit delimiter)- 其他值定义了其他特殊类型的NALU,比如序列结束、流结束等。
NALU头部之后紧跟着有效载荷,其内容取决于NALU的类型。例如,如果NALU类型指示它是SPS,那么有效载荷将包含编码视频序列所需的参数设定信息。
NALU body
在H.264编码视频中,NALU(网络抽象层单元)体,也称为有效载荷(payload),是NALU头部之后的部分,包含了编码视频帧的实际数据。根据NALU的类型,这些数据可以表示各种不同的信息和编码元素。
以下是一些常见NALU类型及其主体内容的描述:
序列参数集(SPS)
类型号:7
SPS的主体包含了一系列编码视频序列所需的参数,比如编码使用的配置文件(profile)、级别(level)、分辨率、图像格式等。这些参数为解码器提供解码的框架。
图像参数集(PPS)
类型号:8
PPS的主体包含了一系列编码图片所需的参数,比如熵编码模式、量化参数、去块效应滤波设置等。这些参数与每个图片(比如IDR帧或者非IDR帧)相关联。
关键帧(IDR帧)
类型号:5
关键帧(Instantaneous Decoder Refresh - IDR)的主体包含了一帧完整的图像数据,这意味着IDR帧可以独立于其他帧进行解码,不需要任何额外的参考帧。在视频序列开始处或视频中的关键点,通常放置IDR帧。
非关键帧(P和B帧)
类型号:1或其他
非关键帧的主体包含相对于其他帧(参考帧)的差异数据。P帧(Predicted-picture)包含相对于之前帧的变动,而B帧(Bi-predictive-picture)则可能参照前一个和/或后一个帧的数据来进行编码。B帧是依赖于其他帧的,因此需要较少的数据来表示相同的信息,从而提供了更高的压缩比率。
补充增强信息单元(SEI)
类型号:6
SEI的主体提供了对编码视频流的额外信息,如视频的色彩范围、时钟频率、字幕数据等。它常用
AVIO 内存输入模式
在FFmpeg项目中,AVIO指的是与输入/输出相关的一个部分,AVIO standing for Audio Video Input Output。FFmpeg利用AVIO提供了一个抽象层,使开发者可以使用统一的接口处理不同来源的多媒体数据。无论数据是来自本地文件、网络流还是自定义的数据源,都可以通过AVIO的接口来读取和写入。
AVIO的作用主要包括:
-
抽象输入/输出操作:开发者可以使用AVIO定义的函数来执行开放、读、写和关闭操作,而不需要关心数据源的具体类型。FFmpeg会处理底层的细节,比如文件访问、网络协议等。
-
支持定制的数据源:如果你需要从非标准来源读取数据,如特某一设备的接口或内存中的数据结构,AVIO允许你定义自己的读/写函数。这意味着你可以完全控制数据是如何被读取或写入的。
-
缓冲管理:AVIO还负责管理缓冲,以提高数据处理的效率。通过缓冲机制,可以减少对实际数据源的读写次数,尤其是在网络环境不稳定或者读写慢的存储介质上尤为重要。
-
透明化网络协议处理:FFmpeg支持众多的网络协议,如HTTP, HTTPS, FTP, RTMP等。AVIO层处理所有这些细节,开发者仅需要提供一个URL即可,无须担心具体的协议实现。
-
错误处理:AVIO提供了错误处理机制,当读写操作失败时可以得到错误代码,这使得开发者可以对特定的错误情况做出响应。
-
无阻塞模式:AVIO支持无阻塞的读写模式,这在处理实时数据流如直播流时非常有用。
在FFmpeg中,AVIO提供了一个输入/输出抽象层,允许使用者处理音视频数据的读取和写入。其中,AVIO的内存输入模式允许直接将内存中的数据用作输入而无需通过文件系统。这在处理必须从内存而非磁盘读取的数据流时非常有用,比如直接从网络接收的数据或者需要对数据进行预处理的情况。
以下是通过FFmpeg的AVIO使用内存输入模式的基本步骤:
-
创建并初始化一个AVIOContext :
使用
avio_alloc_context
函数,你可以分配并初始化一个AVIOContext,它需要一个足够大的缓冲区来处理数据。此缓冲区通常是动态分配的。 -
填充缓冲区 :
你需要从你的数据源(例如,内存结构、网络连接等)获得音视频数据并存储到指定的缓冲区中。
-
实现读取回调 :
AVIOContext需要一个读取函数回调,在FFmpeg需要数据时会调用这个函数。你的回调函数需要从你的数据源复制数据到FFmpeg提供的缓冲区中。
-
打开输入 :
使用内存中的数据创建并打开一个
AVFormatContext
,将先前创建的AVIOContext
与之关联起来。 -
读取和解码 :
正常使用libavformat和libavcodec的API来读取和解码音视频流。
下面是一个简化的代码示例,展示了如何设置AVIOContext并准备读取函数:
cpp
// 存放内存数据的缓冲区和大小
unsigned char *buffer = ...; // 数据指针
int buffer_size = ...; // 数据大小
// AVIO读取回调函数
static int read_packet(void *opaque, uint8_t *buf, int buf_size) {
BufferData *bd = (BufferData *)opaque;
buf_size = FFMIN(buf_size, bd->size);
// 将数据从你的缓冲区复制到FFmpeg缓冲区
memcpy(buf, bd->ptr, buf_size);
bd->ptr += buf_size;
bd->size -= buf_size;
// 如果没有数据可读,返回AVERROR_EOF
if (buf_size == 0) {
return AVERROR_EOF;
}
return buf_size;
}
// 创建AVIOContext
AVIOContext *avio_ctx = NULL;
avio_ctx = avio_alloc_context(
buffer, // 内部缓冲
buffer_size, // 内部缓冲大小
0, // 写标志(0表示读取)
buffer_data, // 传递给回调函数的自定义数据
read_packet, // 读取回调函数
NULL, // 写回调函数(不需要时NULL)
NULL // 求进回调函数(不需要时NULL)
);
// 创建并初始化一个AVFormatContext,然后将AVIOContext与之关联
AVFormatContext *fmt_ctx = avformat_alloc_context();
fmt_ctx->pb = avio_ctx;
fmt_ctx->flags |= AVFMT_FLAG_CUSTOM_IO;
// ... 此处省略打开流、读取和解码的细节
音频重采样实战
音频重采样指的是改变音频信号的采样率,以适应不同的播放设备或者满足编码要求。
使用FFmpeg进行音频重采样的基本步骤如下:
- 打开源音频文件:你需要指定输入文件和相关参数。
- 设置重采样选项:包括目标采样率、音频通道数、采样格式等。
- 输出文件:指定输出文件的名称和格式。
举个例子,假设我们有一个音频文件input.wav
,我们想将其采样率从原来的44.1 kHz重采样到48 kHz,命令如下:
bash
ffmpeg -i input.wav -ar 48000 output.wav
在上面的命令中,-i
代表输入文件,-ar
用于设置重采样的目标采样率,这里是48000 Hz,output.wav
是重采样后的输出文件名。
如果需要更多高级设置,比如指定音频通道数或者采样格式,可以使用如下命令:
bash
ffmpeg -i input.wav -ac 2 -ar 48000 -sample_fmt s16 output.wav
在这个命令中,-ac
指定了音频通道数,这里是2通道,-sample_fmt
指定了采样格式,这里是16位PCM格式。当然,FFmpeg还支持更多的选项来处理音频重采样和其他音视频处理需求。
FFmpeg支持哪些音频重采样的采样格式?
当进行音频重采样时,可以使用 -sample_fmt 选项来指定期望的采样格式。以下是一些常见的采样格式:
u8
: 无符号8位PCMs16
: 有符号16位PCM,适用于CD质量的音频s32
: 有符号32位PCMflt
: 单精度浮点dbl
: 双精度浮点u8p
: 无符号8位,平面s16p
: 有符号16位,平面s32p
: 有符号32位,平面fltp
: 单精度浮点,平面dblp
: 双精度浮点,平面s64
: 有符号64位PCMs64p
: 有符号64位,平面
平面格式意味着每个通道的数据都是单独存储的,而非交错存储。平面格式主要在音频编码和混音过程中使用,它允许更方便地访问和修改单独通道的数据。
要获得你使用的 FFmpeg 版本支持的完整采样格式列表,你可以运行 ffmpeg -sample_fmts
命令:
bash
ffmpeg -sample_fmts
此命令将列出当前 FFmpeg 版本支持的所有采样格式。
重采样后的数据播放时长是否一致
重采样目的是将原始的音频或视频信号转换到一个不同的采样率。在对音频进行重采样时,如果仅改变采样率而保持数据的总样本数不变,那么播放时长理论上应该是一致的。但实际上,可能会有细微的差别,因为重采样过程中可能会引入一些处理上的延时,尤其是当重采样涉及到复杂的滤波器设计时。
例如,将一个采样率为44.1kHz的音频重采样到48kHz,如果样本数维持不变,那么每个样本表示的时间就会缩短,因此播放速率会变快,但总的播放时间不会改变。反之,如果每个样本代表的时间增长,则播放速率会变慢。
通常,采样率的转换需要按照一定的比例来改变样本数量。比方说,如果我们需要将一个44.1kHz的音频升采样到48kHz,我们需要相应地增加样本数量,以保证每秒钟的样本数匹配新的采样率,这种情况下就会延长整个音频的播放时间。
重采样可能会引入额外的处理,比如插值和滤波,这些处理可能会对音质有所影响。采用高质量的重采样算法和工具,例如FFmpeg中的libswresample
库,可以帮助减少这些影响,保证音频质量。
FFmpeg中进行重采样通常会用到如下命令行参数:
shell
ffmpeg -i input.wav -ar 48000 output.wav
其中,-ar
代表audio rate,后跟新的采样率值。这个命令会读取input.wav
文件,将其重采样到48kHz,然后将结果保存到output.wav
文件中。
重采样后 PTS 如何表示
在音视频处理中,PTS,即Presentation Time Stamp
,表示的是媒体数据(如音频帧或视频帧)应该被呈现的时间。在FFmpeg中,PTS用于确保音视频的同步播放。当进行重采样或者转码工作时,PTS 需要被重新计算以确保与媒体数据的采样率匹配。
重采样后,PTS 的计算通常遵循以下原则:
-
时间基(Time Base) : 指的是一秒钟可以分割成多少份,表示为分数形式(numerator/denominator)。例如,如果时间基是
1/1000
,那么它表示时间可以精确到毫秒级别。PTS值与时间基相乘将得到实际的呈现时间。 -
重采样前后的采样率变化:在重采样过程中,如果采样率从44.1kHz变换到48kHz,你需要重新计算PTS,因为每一个采样点表示的实际时间已经改变。
-
PTS的调整计算:在重采样后,PTS需要按照新的采样率进行调整。调整的公式大致如下:
plaintext新的PTS = 原始PTS * (原始采样率 / 新采样率)
这个公式确保了即使采样率改变,每个样本的呈现时间依然准确。
-
APT的考虑:在某些情况下,音频的PTS被称为APT(Audio Presentation Time),并且APT的调整也是必要的。由于音频的重采样可能改变样本数量,APT必须被相应地调整以反映新的播放速率。
以FFmpeg为例的命令行工具,它会自动处理重采样后的 PTS 调整。如果你在使用 FFmpeg 的 libav* 库编程,那你可能需要自己管理PTS的计算和调整。
举例说明:
shell
ffmpeg -i input.mkv -filter_complex "[0:a]aresample=48000,asetpts=N/SR/TB[a]" -map "[a]" output.mkv
aresample=48000
:将音频重采样到48kHz。asetpts=N/SR/TB
:该过滤器会根据输入流的采样率(SR)和时间基(TB)重新计算PTS(N
是样本数)。时间基(TB)通常是通过流的属性来获得,或者可以手动指定。
PTS在重采样后是否会改变?
在重采样过程中,PTS(Presentation Time Stamp)本身的值通常会发生改变,以确保音视频的同步播放不受影响。当音频的采样率发生变化时,每个采样的时间持续度也会相应变化,因此必须调整PTS,以反映新的采样间隔。
举例来说,如果你增加了音频的采样率,每个样本表示的时间会变短,为了保持音频数据在时间上的正确展现,原有的PTS需要根据新的采样频率重新计算。同理,减小采样率时,每个样本代表的时间会变长,PTS也需要相应地调整。
FFmpeg在执行重采样时经常会自动处理PTS的重新计算。这涉及到对时间基的重新计算,以便于新采样率下,每个样本都能在其正确的播放时间点被呈现。这个过程在FFmpeg里通常是透明的,不需要手动干预。
在具体实施时,如果是使用FFmpeg的命令行工具进行重采样,命令行参数如asetpts
(asetpts=N/SR/TB
)可以用来调整音频的PTS。当开发者使用FFmpeg的编程接口进行音视频开发时,可能会涉及到手动计算和调整PTS值。
视频解码后 YUV 内存对齐问题
在FFmpeg中,YUV格式的视频解码后,为了提高内存访问效率,经常涉及到内存对齐。内存对齐指的是数据在内存中的起始地址是某个数(如16、32、64等)的倍数,这样可以确保数据在物理页和缓存行中能够对齐,从而提高CPU的访问效率。
视频解码后,YUV数据通常以Y、U和V三个分量来存储,每个分量会有各自的宽度和高度。对于YUV420p格式(一种常见的YUV格式),Y分量占据的空间是U和V分量的两倍。当YUV数据需要内存对齐时,Y、U和V三个分量的行(line)宽度需要以对齐的数(对齐的基数)为单位来进行扩展。
例如,如果对齐基数是16,那么每一行的宽度会被扩展到是16的倍数。这样,如果解码后的视频的宽度不是16的倍数,实际在内存中存储的行宽度就会大于视频的宽度。对Y、U、V分量的每一行进行这样的扩展,就可以得到对齐的YUV数据。
这种对齐可以通过以下几个步骤来实施:
- 确定对齐基数:根据不同的硬件和系统规定对齐的基数,通常可以是2的幂。
- 计算对齐后的宽度和高度:将视频宽度和高度扩展到对齐基数的倍数。
- 分配内存:根据对齐后的宽度和高度为Y、U和V分量分配内存。
- 数据复制:将解码后的YUV数据复制到对齐后分配的内存中。
在FFmpeg中,AVFrame结构中的linesize数组就是用来存储每个分量对齐后的行宽度的。当你解码视频帧并得到一个AVFrame后,你可以通过linesize数组来得知每个分量的对齐后的行宽度。
如何确定视频解码后YUV数据的对齐基数?
确定视频解码后YUV数据的对齐基数通常取决于几个因素:
-
硬件要求:具体的硬件平台可能有自己的内存访问优化需求,例如,一些平台可能需要16字节对齐或者更大的对齐,以优化CPU缓存的使用。
-
操作系统:操作系统层面也可能有其标准或推荐的内存对齐数值,尤其是当使用系统级的图像处理函数时。
-
外部库或API:如果你在处理YUV数据时需要与其他图像处理库(如OpenGL, DirectX或者特定的硬件加速API)配合使用,那么这些库或API也可能规定了特定的内存对齐要求。
-
FFmpeg默认:FFmpeg通常会使用默认的对齐基数,比如16或32,这在大多数情况下都是高效的。
在FFmpeg中,你可以通过AVCodecContext结构中的alignment
字段来设置或获取对齐基数,这在初始化解码器时可以手动指定。如不指定,FFmpeg会使用其默认值。
代码示例可能是:
c
AVCodecContext *codec_ctx = ...; // 已经初始化的解码器上下文
codec_ctx->alignment = 32; // 设置为32字节对齐
音频解码后 PCM 排列格式问题
在FFmpeg中处理音频数据时,PCM(Pulse Code Modulation)数据的排列格式是一个必须关注的重要问题。PCM数据可以以多种格式出现,其中最常见的有两种排列格式:平面格式(Planar)和非平面格式(Interleaved)。
平面格式 (Planar)
在平面格式中,每个声道的数据是分开存储的,即首先存储所有采样点的第一个声道,然后是第二个声道,依此类推。对于立体声(stereo)PCM数据,如果使用平面格式,那么左声道所有的样本将会先连续存储,右声道的样本会在左声道样本之后。
例如,在一个立体声音频流中,数据排列可能会是这样的:
LLLLLLLLRRRRRRRR
其中L
表示左声道的样本,R
表示右声道的样本。
非平面格式 (Interleaved)
在非平面格式中,左右声道的样本是交错存储的。也就是说,立体声音频的每个左声道样本后会紧跟一个右声道的样本。
对于立体声PCM数据,非平面排列会是这样的:
LRLRLRLRLRLRLRLR
FFmpeg通过AVFrame
结构体来存储解码后的音频样本。其中data
数组包含指向音频样本数据的指针,linesize
数组包含每个声道的行大小(对于音频而言,通常是单个声道的大小)。
- 对于非平面格式,
AVFrame
的data[0]
将包含交错的所有声道样本。 - 对于平面格式,每个声道的样本将分别存储在
data
数组的不同索引中,例如data[0]
是第一个声道的样本,data[1]
是第二个声道的样本,等等。
要检查解码后的PCM数据是否为平面格式,可以检查AVFrame
的format
字段,结合av_sample_fmt_is_planar()
函数。这个函数如果返回非零值,则说明样本格式是平面的。
c
AVFrame *frame = ...; // 已经获取解码后的音频帧
int is_planar = av_sample_fmt_is_planar(frame->format);
AVFrame的format字段如何确定解码后的PCM数据是否为平面格式?
在FFmpeg中,AVFrame
结构体的format
字段指示的是音频样本的格式,这是一个枚举类型,它表示音频数据的具体格式,如AV_SAMPLE_FMT_S16
、AV_SAMPLE_FMT_FLTP
等。这个字段的值对应于FFmpeg中定义的AVSampleFormat
枚举。
要确定AVFrame
的format
字段值所表示的PCM数据是否为平面格式,可以使用FFmpeg提供的av_sample_fmt_is_planar()
函数。这个函数接受AVSampleFormat
枚举类型的值作为参数,并返回一个表示该格式是否为平面的整数值。
下面是如何使用这个函数的例子:
c
AVFrame *frame = ...; // 解码后的音频帧
if (av_sample_fmt_is_planar(frame->format)) {
// 格式是平面的
} else {
// 格式是非平面的(交错的)
}
如果av_sample_fmt_is_planar()
返回1,那就意味着每个声道的PCM数据是单独存储的,即该样本格式是平面的。如果返回0,那么PCM数据是交错存储的,即非平面格式。
FFmpeg中的音频格式前缀AV_SAMPLE_FMT_
后面通常会有一个后缀来表示数据的类型和布局。例如:
S16
: 表示16位整型样本,非平面格式。S16P
: 表示16位整型样本,平面格式。FLT
: 表示浮点样本,非平面格式。FLTP
: 表示浮点样本,平面格式。
因此,你可以通过format
字段所指示的样本格式后缀是否包含P
来初步判断它是否为平面格式,然后再使用av_sample_fmt_is_planar()
函数进行确认。
硬件解码 dxva2/nvdec/cuvid/qsv
在FFmpeg中硬件解码是利用特定硬件加速解码视频的过程,通常可以显著提高解码效率和性能。FFmpeg支持多种硬件解码接口,包括但不限于DXVA2(DirectX Video Acceleration 2)、NVDEC/NVCUVID(NVIDIA的解码技术)、和QSV(Intel Quick Sync Video)。
DXVA2
DXVA2是Windows上用于硬件加速视频解码的API,它是DXVA的第二个版本。DXVA2可以使用GPU来加速多种格式的视频解码,比如H.264, MPEG-2, 和VC-1。
在FFmpeg中使用DXVA2硬件解码,需要在编译FFmpeg时启用--enable-dxva2
选项。同时,在使用FFmpeg进行硬件解码时,需要选择相应的硬件加速解码器,例如h264_dxva2
。
NVDEC / NVCUVID
NVDEC是NVIDIA提供的硬件视频解码接口,起始被称为NVCUVID在Linux中使用。它支持多种编解码标准,包括但不限于H.264, HEVC, VP8/VP9, VC-1等。NVDEC可以显著提高解码性能和降低CPU占用。
在FFmpeg中使用NVDEC硬件解码时,需要在编译FFmpeg时启用--enable-nvdec
选项,并使用例如h264_cuvid
这样的解码器名称。
CUVID
CUVID是NVDEC的旧名,它是NVIDIA CUDA Video Decoding API的缩写,现已废弃,但仍有部分资料可能会提到。它是专门为NVIDIA的CUDA架构设计的视频解码库。
QSV
QSV是Intel的硬件视频解码和编码技术,它是内置在部分Intel处理器中的。QSV可以用来加速H.264, MPEG-2, VC-1和HEVC等格式的解码。
在FFmpeg中使用QSV硬件解码,需要在编译FFmpeg时启用--enable-libmfx
和--enable-qsv
选项。使用QSV,你需要选择以_qsv
结尾的解码器,例如h264_qsv
。
FFmpeg中使用硬件解码的一般步骤如下:
- 确认你的系统和硬件支持所需要的硬件解码方式。
- 编译或获取一个支持所需硬件解码接口的FFmpeg版本。
- 在使用
ffmpeg
命令时,通过-hwaccel
选项指定硬件加速方法,例如-hwaccel dxva2
。 - 指定硬件特有的解码器,例如
-c:v h264_cuvid
。 - 如果需要的话,进行必要的额外设置,有些硬件解码器需要特别的过滤器或设置。
示例命令:
bash
ffmpeg -hwaccel dxva2 -c:v h264_dxva2 -i input.mp4 output.mkv
这个命令使用DXVA2进行H.264视频流的硬件解码,并将输出格式设置为mkv。
H265 解码
FFmpeg支持H.265/HEVC解码,这是一种高效的视频压缩标准,后继于H.264/AVC。H.265旨在在相同的视频质量下,提供比H.264更高的压缩率,这意味着更小的文件大小和更少的带宽消耗。
使用FFmpeg进行H.265解码时,你可以使用命令行工具ffmpeg
,并指定输入文件和输出文件的参数来实现。这里是一个基本的例子:
sh
ffmpeg -i input.mp4 -c:v libx265 output.mp4
这个命令会将input.mp4
这个使用H.265编码的视频文件解码并转码为另一个MP4文件。在这个例子中,-c:v libx265
部分指定了视频编码器为libx265
,这是一种用于H.265的开源编码库。
对于硬件加速,如果你的系统支持,FFmpeg也可以利用GPU加速解码。如使用NVIDIA GPU,你可以使用h264_nvenc
(对于H.264)或者hevc_nvenc
(对于H.265),这里有一个使用硬件加速的例子:
sh
ffmpeg -hwaccel cuvid -c:v hevc_cuvid -i input.mkv -c:v h264_nvenc output.mp4
在这个命令中,-hwaccel cuvid
启用了NVIDIA的硬件加速,-c:v hevc_cuvid
指定了NVIDIA的HEVC硬件解码器。输出编码器h264_nvenc
则为NVIDIA的H.264硬件编码器,这个例子中是将H.265解码并转码为使用H.264编码的MP4文件。
参考资料:
音视频流媒体开发课程(从基础到高级,从理论到实践)学习计划、一对一答疑
音视频开发(FFmpeg/WebRTC/RTMP)
整理了一些音视频开发学习资料、面试题 如有需要自行添加群:739729163 领取