😎 作者介绍:欢迎来到我的主页👈,我是程序员行者孙,一个热爱分享技术的制能工人。计算机本硕,人工制能研究生。公众号:AI Sun(领取大厂面经等资料),欢迎加我的微信交流:sssun902
🎈 本文专栏:本文收录于《音视频》系列专栏,相信一份耕耘一份收获,我会分享音视频相关学习内容,不说废话,祝大家都offer拿到手软
🤓 欢迎大家关注其他专栏,我将分享Web前后端开发、人工智能、机器学习、深度学习从0到1系列文章。
🖥随时欢迎您跟我沟通,一起交流,一起成长、进步!
1. 解封装概述
解封装是音视频处理中的一个关键步骤,它涉及到将封装好的音视频文件按照特定的规则拆分成音频流和视频流,以便于进一步的处理和播放。
1.1 解封装定义
解封装,又称为“Demuxing”,是指将一个封装好的音视频文件(如MP4、FLV等)中的音频和视频数据流分离出来的过程。这个过程是封装的逆操作,它允许我们访问和操作原始的音视频数据,而不受到封装格式的限制。
1.2 音视频封装格式简介
音视频封装格式是指将音频和视频数据按照一定的规则组织存储到文件中的格式。以下是一些常见的音视频封装格式:
- MP4 (MPEG-4 Part 14): 一种广泛使用的封装格式,支持H.264和AAC编解码器,常用于互联网视频流和移动设备。
- FLV (Flash Video): 由Adobe Systems开发,常用于在线视频平台,支持H.264视频和AAC音频。
- AVI (Audio Video Interleave): 微软开发的早期视频格式,支持多种编解码器,但文件较大,不支持现代特性如字幕。
- MKV (Matroska Video): 开源的自由容器格式,支持多种编解码器和元数据,适合高清视频存储。
每种封装格式都有其特定的结构和规则,解封装过程需要根据这些规则来正确地分离音视频流。
2. 解封装技术流程
2.1 打开媒体文件
解封装的第一步是打开媒体文件,这通常涉及到使用特定的库或工具来读取文件头信息。例如,使用FFmpeg库中的avformat_open_input
函数,可以打开一个媒体文件并获取其封装格式的上下文。这个过程是至关重要的,因为它为后续的读取和解析设置了基础。
AVFormatContext* format_context = NULL;
int ret = avformat_open_input(&format_context, file_path, NULL, NULL);
if (ret < 0) {
// 错误处理:无法打开文件
}
2.2 读取媒体信息
一旦媒体文件被成功打开,下一步是读取媒体信息。这包括了解媒体文件中的流数量、每个流的类型(视频、音频或字幕)以及它们的编码格式。FFmpeg库提供了avformat_find_stream_info
函数来探测媒体文件中的流信息。
ret = avformat_find_stream_info(format_context, NULL);
if (ret < 0) {
// 错误处理:无法找到流信息
}
此外,可以使用av_dump_format
函数来打印媒体文件的详细信息,这对于调试和分析媒体文件结构非常有用。
2.3 解析媒体流
解析媒体流是解封装过程中的核心步骤,它涉及到从封装格式中提取原始的音视频数据。这通常意味着需要对每个流进行解码,将压缩的数据转换为可播放的格式。
对于视频流,可以使用av_read_frame
函数来读取压缩的视频帧,然后使用相应的解码器(如avcodec_decode_video2
)进行解码。对于音频流,过程类似,但使用不同的解码函数(如avcodec_decode_audio4
)。
AVPacket packet;
AVFrame *frame = av_frame_alloc();
while (av_read_frame(format_context, &packet) >= 0) {
// 根据packet的stream_index确定是视频还是音频流
if (packet.stream_index == video_stream_idx) {
// 解码视频帧
ret = avcodec_decode_video2(video_codec_context, frame, &got_picture, &packet);
if (ret < 0) {
// 错误处理:解码失败
}
if (got_picture) {
// 处理解码后的视频帧
}
} else if (packet.stream_index == audio_stream_idx) {
// 解码音频帧
ret = avcodec_decode_audio4(audio_codec_context, frame, &got_frame, &packet);
if (ret < 0) {
// 错误处理:解码失败
}
if (got_frame) {
// 处理解码后的音频帧
}
}
av_packet_unref(&packet);
}
av_frame_free(&frame);
在解析媒体流的过程中,重要的是要注意音视频同步问题,确保解码后的数据能够按照正确的时间顺序进行播放。这通常涉及到使用时间戳(PTS)和解码时间戳(DTS)来同步解码后的数据流。
3. 核心组件与数据结构
3.1 AVFormatContext
AVFormatContext
是 FFmpeg 中用于存储媒体文件格式信息的核心结构体。它包含了媒体文件的封装格式、流信息、时长、码率等关键信息。
- 封装格式信息:
iformat
指向AVInputFormat
结构体,表示输入文件的封装格式。 - 媒体流数量:
nb_streams
表示媒体文件中流的数量。 - 媒体流数组:
streams
是一个指向AVStream
结构体的数组,每个元素代表一个媒体流。 - 文件时长与码率:
duration
和bit_rate
分别表示媒体文件的时长和总码率。 - 关键函数:
avformat_open_input()
用于打开媒体文件并初始化AVFormatContext
;avformat_close_input()
用于释放AVFormatContext
及相关资源。
3.2 AVStream
AVStream
结构体用于表示单个媒体流(如视频流或音频流)的信息。
- 流索引:
index
唯一标识一个流。 - 编解码器上下文:
codecpar
指向AVCodecParameters
结构体,包含编码器的参数。 - 时间基准:
time_base
定义了时间戳的基准,用于将时间戳转换为实际时间。 - 流时长:
duration
表示该流的时长。 - 帧率:
avg_frame_rate
表示视频流的平均帧率。
3.3 AVPacket 和 AVFrame
AVPacket
和 AVFrame
是 FFmpeg 中处理音视频数据的两个关键结构体。
-
AVPacket:表示压缩后的音视频数据包。
- 数据指针:
data
指向压缩数据。 - 数据大小:
size
表示data
的大小。 - 显示时间戳:
pts
表示数据包的显示时间戳。 - 解码时间戳:
dts
表示数据包的解码时间戳。
- 数据指针:
-
AVFrame:表示解码后的原始音视频数据帧。
- 数据指针数组:
data
数组指向解码后的视频或音频数据。 - 行大小数组:
linesize
数组表示每行数据的大小。 - 帧宽度和高度:
width
和height
表示视频帧的尺寸。 - 帧类型:
pict_type
表示帧的类型(如 I 帧、P 帧等)。 - 关键帧标识:
key_frame
表示是否为关键帧。 - 时间戳:
pts
表示帧的显示时间戳。
- 数据指针数组:
这些结构体和组件共同协作,完成音视频数据的解封装、解码、处理和播放等流程。通过 API 如 av_read_frame()
读取 AVPacket
,然后使用相应的解码器填充 AVFrame
数据,最终实现音视频的流畅播放。
4. 解封装关键操作
4.1 读取帧数据
解封装过程中,读取帧数据是核心步骤之一。通过av_read_frame
函数,我们可以从封装格式的文件中提取出音频或视频的压缩数据包(Packet)。此函数返回一个AVPacket
结构体,它包含了帧的编码数据和相关的元信息,如PTS(Presentation Time Stamp)、DTS(Decoding Time Stamp)等。
- 帧数据读取:
av_read_frame
函数从解封装上下文AVFormatContext
中读取一个数据包。 - 数据包结构:每个
AVPacket
包含了stream_index
,用于标识该包属于哪个音视频流。
示例代码:
AVFormatContext *fmt_ctx;
AVPacket packet;
avformat_open_input(&fmt_ctx, file_path, NULL, NULL);
avformat_find_stream_info(fmt_ctx, NULL);
while (av_read_frame(fmt_ctx, &packet) >= 0) {
// 处理packet
av_packet_unref(&packet);
}
avformat_close_input(&fmt_ctx);
4.2 音视频同步
音视频同步是确保在播放过程中音频和视频能够协调一致地展现给用户。在解封装阶段,我们通过分析PTS和DTS来实现音视频同步。
- PTS(Presentation Time Stamp):表示帧应该被显示的时间。
- DTS(Decoding Time Stamp):表示帧应该被解码的时间。
在有B帧的视频流中,由于B帧可能依赖于未来的帧,因此解码顺序和显示顺序可能不同。这时,我们需要根据PTS来安排帧的显示顺序。
示例分析:
假设我们有一系列帧的PTS和DTS,我们需要按照PTS的顺序来显示帧,即使某些帧的解码(DTS)是在其显示时间之前完成的。
4.3 处理 PTS 和 DTS
在解封装过程中,正确处理PTS和DTS对于实现音视频同步至关重要。我们需要将PTS和DTS从流中的时间基转换为解码器或播放器可以识别的时间基。
- 时间基转换:使用
av_packet_rescale_ts
函数,我们可以将PTS和DTS从其原始时间基转换为另一个时间基。 - 应用场景:在转封装或转码过程中,源和目标容器可能有不同的时间基,这时就需要转换PTS和DTS以保证时间的一致性。
示例代码:
AVPacket packet;
av_read_frame(fmt_ctx, &packet);
av_packet_rescale_ts(&packet, fmt_ctx->streams[packet.stream_index]->time_base,
decoder_ctx->time_base);
在上述代码中,我们读取了一个数据包,并将其PTS和DTS从文件的时间基转换为解码器的时间基,以便进行后续的解码操作。正确处理PTS和DTS对于确保解码后的视频帧能够以正确的顺序和时间显示至关重要。
5. 应用实例分析
5.1 MP4 文件解封装
MP4文件作为广泛使用的视频容器格式,其解封装流程对于视频处理至关重要。MP4文件通常由以下几个关键部分组成:
- Ftyp Box:文件类型信息,标识文件及其兼容的编解码格式。
- Moov Box:包含媒体文件的元数据,如时间戳、音视频参数等。
- Mdat Box:存储实际的音视频数据。
解封装MP4文件的步骤通常包括:
- 使用工具或库(如FFmpeg)打开MP4文件,并读取文件头,获取Ftyp信息。
- 解析Moov Box,提取关键的元数据信息,包括但不限于视频的宽度、高度、帧率,音频的采样率、声道数等。
- 访问Mdat Box,读取音视频数据。这些数据通常以帧为单位,每帧数据前可能包含时间戳信息。
例如,使用FFmpeg进行MP4文件解封装的命令可能如下:
ffmpeg -i input.mp4 -vn -acodec copy audio.aac
此命令提取MP4文件中的音频流并保存为AAC格式。
5.2 TS 流解封装
TS(Transport Stream)流是一种常见的广播和流媒体格式,广泛应用于数字电视和网络流。TS流由固定长度的包组成,每个包包含音视频数据或其他流信息。
TS流的解封装流程涉及以下关键步骤:
- 同步字节识别:每个TS包以0x47作为同步字节开始。
- 解析包头:获取PID、payload_unit_start_indicator等关键信息。
- 提取PES包:根据payload_unit_start_indicator识别PES包的开始,并根据PES包头信息提取ES流数据。
- 解码ES流:将提取的ES流数据解码为原始的音视频数据。
在实际应用中,TS流的解封装可以通过软件如Elecard或使用开源库(如FFmpeg)来实现。例如,使用FFmpeg解封装TS流的命令如下:
ffmpeg -i input.ts -c copy -map 0:v -map 0:a output.mkv
此命令将TS流中的音视频流复制到MKV容器中,不进行重新编码。
祝大家学习顺利~
如有任何错误,恳请批评指正~~
以上是我通过各种方式得出的经验和方法,欢迎大家评论区留言讨论呀,如果文章对你们产生了帮助,也欢迎点赞收藏,我会继续努力分享更多干货~
🎈关注我的公众号AI Sun可以获取Chatgpt最新发展报告以及腾讯字节等众多大厂面经。
😎也欢迎大家和我交流,相互学习,提升技术,风里雨里,我在等你~