23、音视频文件格式解析:MP4、FLV、TS、AAC、MP3容器与编码细节
说实话,做音视频开发这么多年,我见过不少新手一上来就撸FFmpeg命令行,结果遇到个MP4文件播放不了,或者FLV直播流卡成PPT,完全不知道问题出在哪。说白了,你不懂容器格式的底层结构,遇到坑就只能靠猜。
今天咱们就把MP4、FLV、TS、AAC、MP3这五个最常见的格式扒个底朝天。我会结合我实际踩过的坑来讲,保证你听完能真正理解它们的设计思路。
核心观点:容器格式决定了你怎么存、怎么传、怎么播。编码格式决定了压缩率和画质。两者是两码事,但必须配合好。
一、MP4:最通用的“万能容器”
MP4是我个人用得最多的格式。它基于ISO Base Media File Format(ISOBMFF),说白了就是一堆Box(也叫Atom)的嵌套结构。
每个Box都有固定的头部:4字节的Size + 4字节的Type。Type决定了这个Box里装的是什么数据。比如moov装元数据,mdat装实际的音视频帧。
我记得有一次,客户给了一个MP4文件,播放器打开直接黑屏。我拿二进制编辑器一看,发现moov Box被放在了文件末尾。很多播放器需要先读到moov才能知道视频的宽高、时长、关键帧位置。如果moov在末尾,播放器就得把整个文件下载完才能开始播——这在点播场景下就是灾难。
避坑指南:我曾经在服务端转码时忘记设置-movflags faststart,结果生成的MP4文件在浏览器里要等十几秒才能播放。后来强制把moov Box移到文件头部,秒开。
MP4支持的编码非常广泛:视频可以用H.264、H.265、VP9,音频可以用AAC、MP3、Opus。但要注意,MP4对AAC的支持最好,MP3次之。
二、FLV:直播和点播的“轻量级选手”
FLV是Adobe搞出来的格式,结构比MP4简单得多。它由文件头(FLV Header)和一系列Tag组成。每个Tag要么是视频帧,要么是音频帧,要么是脚本数据(比如元数据)。
为什么FLV在直播领域这么火?因为它结构简单,解析快,而且支持RTMP协议直接推流。你想想看,直播场景下每一毫秒都很关键,MP4那种复杂的Box嵌套结构反而成了负担。
FLV的音频Tag里,音频编码类型字段占4位。我记得有一次排查一个直播卡顿问题,发现推流端把AAC音频的编码类型写成了MP3(0x02),播放器解码时直接崩溃。后来我写了个校验逻辑,发现编码类型不对就自动修正。
注意:FLV不支持H.265(HEVC)原生封装。虽然有些魔改版本支持,但兼容性很差。如果你要用H.265做直播,建议用TS或MPEG-TS。
三、TS:广播电视的“抗丢包神器”
TS(Transport Stream)是MPEG-2标准的一部分,最初是为数字电视设计的。它的核心思想是:把音视频数据切成固定大小的小包(通常是188字节),每个包都有独立的同步头和时间戳。
为什么要这么做?因为广播电视信号不稳定,容易丢包。如果丢了一个包,TS流还能从下一个同步头继续解码。而MP4如果丢了一个Box,整个文件可能就废了。
我做过一个项目,需要把HLS直播流录制成文件。HLS底层就是TS切片。当时遇到一个问题:某些TS切片里的PTS(显示时间戳)和DTS(解码时间戳)顺序是乱的,导致播放器画面跳跃。后来我加了一个重排序缓冲区,按PTS顺序输出帧,问题解决。
关键点:TS的PES(Packetized Elementary Stream)层负责把编码后的帧打包成PES包,再切成TS包。每个PES包都有一个PTS和DTS,用于音视频同步。
四、AAC:音频编码的“工业标准”
AAC(Advanced Audio Coding)本身是一种编码格式,不是容器。但它在封装时通常有两种形式:ADIF(Audio Data Interchange Format)和ADTS(Audio Data Transport Stream)。
ADIF用于文件存储,只有一个文件头,后面全是原始音频数据。ADTS用于流传输,每个帧前面都有一个7字节的ADTS头,包含采样率、声道数、帧长度等信息。
我个人习惯在直播场景下用ADTS,因为每个帧独立,丢了一个帧不影响后续解码。在点播场景下用ADIF,因为文件头只写一次,节省空间。
我曾经遇到过一个AAC解码失败的问题:播放器报“invalid sample rate”。我查了半天,发现是ADTS头里的采样率索引写错了。AAC标准里采样率索引0x04对应44100Hz,但推流端写成了0x05(48000Hz)。解码器按48000Hz去解,出来的声音直接变调。
小技巧:用FFmpeg查看AAC文件的ADTS头信息:ffprobe -show_packets input.aac。如果每个包的大小不一致,说明可能是VBR(可变码率)编码。
五、MP3:音频格式的“老前辈”
MP3(MPEG-1 Audio Layer 3)虽然老了,但至今仍在大量使用。它的结构比AAC简单:每个MP3帧都有一个帧头(4字节),包含同步字、比特率索引、采样率、声道模式等信息。
MP3的帧头同步字是0xFFE0(11个连续的1)。解码器通过扫描这个同步字来定位帧边界。这也是为什么MP3文件即使损坏了一部分,也能从下一个同步帧继续播放。
我记得有一次做音频剪辑功能,用户上传了一个MP3文件,但播放时总是开头有几毫秒的静音。后来发现是ID3v2标签占用了文件开头,解码器把标签数据当成了音频帧来解。解决方案是跳过ID3v2标签头里的Size字段,直接定位到第一个音频帧。
注意:MP3的比特率索引有“free format”模式(比特率索引为0000),表示比特率不固定。很多播放器不支持这种模式,遇到会直接跳过。建议不要用。
六、核心知识体系:容器与编码的关系
下面这张图是我自己总结的,帮你理清容器和编码的对应关系:
七、实际项目中的选择建议
说了这么多理论,咱们来点实际的。你在项目中到底该选哪种格式?
| 场景 | 推荐容器 | 推荐编码 | 原因 |
|---|---|---|---|
| 点播(VOD) | MP4 | H.264 + AAC | 兼容性最好,支持seek |
| 直播(RTMP) | FLV | H.264 + AAC | 延迟低,结构简单 |
| HLS直播 | TS | H.264 + AAC | 抗丢包,支持多码率 |
| 纯音频 | AAC/MP3 | AAC LC / MP3 | 文件小,解码快 |
个人经验:如果你做的是移动端App,建议统一用MP4 + H.264 + AAC。Android和iOS都原生支持,省去编解码器适配的麻烦。如果你做的是直播,FLV + RTMP是目前延迟最低的方案。
八、避坑总结
最后,我把这些年踩过的坑总结一下:
- MP4的moov Box位置:一定要放在文件头部,否则播放器无法秒开。
- FLV的音频编码类型:写对AAC的编码类型值(0x0F),别写成MP3。
- TS的PTS/DTS乱序:加缓冲区重排序,否则画面跳跃。
- AAC的ADTS头:采样率索引别写错,否则声音变调。
- MP3的ID3v2标签:解析时跳过标签头,直接定位到音频帧。
嗯,这些格式的细节其实还有很多,但掌握了核心结构,遇到问题你就能快速定位。下次你拿到一个音视频文件,不妨用二进制编辑器打开看看,亲自验证一下我说的这些结构。实践出真知嘛。