iOS 视频格式转换与压缩怎么实现?从 AVFoundation、H.264 到码率与分辨率
在前面的文章里,我们已经把文件浏览器逐步扩展成一个真正的文件处理平台:
01 iOS 文件浏览器架构
02 GB 级大文件处理
03 Wi-Fi 文件传输
04 SMB / NAS
05 WebDAV
06 FTP
07 ZIP / RAR / 7z
08 HEIC / JPEG / PNG / WebP
这一篇继续进入另一个非常高频的文件处理能力:
视频转换与压缩
用户最常见的问题通常不是:
AVFoundation 的底层时间轴怎么工作的?
而是:
MOV 能不能转成 MP4?
或者:
这个视频 2GB,能不能压到几百 MB?
又或者:
为什么视频格式看起来一样,有的设备能播,有的不能?
从产品角度看,这些需求很简单。
但从技术角度看,一个视频文件背后至少涉及:
- Container
- Video Codec
- Audio Codec
- Resolution
- Frame Rate
- Bitrate
- Duration
- Color Space
- HDR
- Rotation
- Metadata
- Keyframe
- Audio Track
- Subtitle Track
所以:
“视频格式转换”从来不只是改扩展名。
这篇就从一个文件管理 App 的角度,系统拆解 iOS 视频转换和压缩的完整架构。
1. MP4 和 H.264 不是一回事
这是视频领域最容易混淆的问题之一。
很多用户会说:
把视频转成 H.264。
也有人说:
把 H.264 转成 MP4。
实际上:
MP4
通常指:
Container
而:
H.264
通常指:
Video Codec
可以简单理解成:
MP4 Container
│
├── Video Track
│ └── H.264
│
├── Audio Track
│ └── AAC
│
└── Metadata
所以:
.mp4
只是一个“盒子”。
盒子里面的视频可以使用不同 Codec。
2. MOV 也是 Container
Apple 生态里经常遇到:
.mov
MOV 本质上也是一种容器格式。
里面可能是:
MOV
├── H.264
└── AAC
也可能:
MOV
├── HEVC
└── AAC
所以:
MOV → MP4
有时候可以:
不重新编码视频。
只需要:
Remux
也就是重新封装。
3. Remux 和 Transcode 完全不是一回事
假设:
movie.mov
内部已经是:
H.264 + AAC
目标:
movie.mp4
如果 MP4 容器能够承载现有 Track,
就可能:
MOV
↓
Read Tracks
↓
MP4
视频数据本身不重新压缩。
这叫:
Remux / Repackage
优点:
Fast
No Quality Loss
Low CPU
而 Transcode:
Decode
↓
Raw Frames
↓
Encode Again
成本要高很多。
4. Transcode 是什么?
例如:
HEVC
↓
H.264
就需要:
HEVC Decode
↓
Raw Video Frames
↓
H.264 Encode
或者:
4K
↓
1080p
也必须:
Decode
↓
Resize
↓
Encode
这就是:
Transcoding。
所以 VideoService 开始处理任务之前应该先问:
能不能直接 Remux?
如果可以:
不要重新编码。
5. 为什么 Remux 应该优先?
假设一个 10GB 视频。
重新编码可能需要:
Decode
+
Scale
+
Encode
带来:
- 大量 CPU / GPU 开销
- 电池消耗
- 发热
- 时间
- 质量损失
而 Remux:
Read Packet
↓
Write New Container
通常成本低很多。
所以可以设计:
Video Operation
↓
Can Remux?
/ \
Yes No
↓ ↓
Remux Transcode
这是视频工具非常重要的优化。
6. VideoInfo 应该先把文件分析清楚
就像图片处理有:
ImageInfo
视频也应该有:
struct VideoInfo {
let duration: TimeInterval
let width: Int
let height: Int
let frameRate: Double?
let bitrate: Int64?
let videoCodec: VideoCodec?
let audioCodec: AudioCodec?
let container: VideoContainer
let fileSize: Int64
}
UI 可以展示:
1920 × 1080
H.264
30 fps
8 Mbps
2.4 GB
这比只显示:
MP4
有用得多。
7. 文件后缀仍然不代表内部 Codec
例如:
movie.mp4
内部可能:
H.264
也可能:
HEVC
因此:
.mp4
并不能回答:
这个视频是不是 H.264?
需要读取:
Track Format Description
所以:
Extension
永远只能作为一层提示。
8. AVAsset 可以看作视频资产入口
在 iOS 视频处理中,一个非常核心的概念就是:
AVAsset
可以把它理解为:
一个音视频资源的抽象。
例如:
File URL
↓
AVAsset
然后可以获取:
Duration
Tracks
Metadata
Format
架构上:
FileItem
↓
VideoService
↓
AVAsset
↓
VideoInfo
这样 UI 不需要直接操作 AVFoundation。
9. 不要让 ViewController 到处直接操作 AVAsset
错误方向:
VideoViewController
↓
AVAsset
↓
AVAssetExportSession
然后另一个页面:
CompressViewController
↓
AVAsset
很快就会重复。
更合理:
UI
↓
VideoService
↓
VideoPipeline
↓
AVFoundation
这样所有:
Convert
Compress
Resize
Thumbnail
Inspect
都可以共用底层逻辑。
10. 可以定义统一 VideoService
例如:
protocol VideoService {
func inspect(
_ item: FileItem
) async throws -> VideoInfo
func convert(
_ item: FileItem,
options: VideoConvertOptions
) async throws -> FileItem
func compress(
_ item: FileItem,
options: VideoCompressOptions
) async throws -> FileItem
}
其中:
VideoConvertOptions
可能包含:
Container
Video Codec
Audio Codec
Resolution
Frame Rate
Bitrate
11. 视频压缩到底是在压什么?
用户说:
把视频压缩一下。
技术上主要可能发生:
Reduce Bitrate
Reduce Resolution
Reduce Frame Rate
Change Codec
例如:
4K
60fps
50 Mbps
压成:
1080p
30fps
6 Mbps
文件大小自然大幅下降。
12. Bitrate 是视频大小最关键的参数之一
非常粗略地说:
File Size
≈
Bitrate × Duration
例如:
10 Mbps
×
60 seconds
大约:
600 Mb
再除以:
8
约:
75 MB
这里只是粗略估算。
因为还存在:
Audio Bitrate
Container Overhead
Variable Bitrate
Metadata
但这个公式对产品设计非常有用。
13. 为什么 1 分钟视频也可能很大?
例如:
4K 60fps
Bitrate:
60 Mbps
一分钟:
60 × 60
=
3600 Mb
约:
450 MB
如果再有音频和其他数据,
会更大。
所以很多:
iPhone 视频太大
真正原因不是:
MP4 格式不好
而是:
Resolution
+
Frame Rate
+
Bitrate
都比较高。
14. 分辨率降低可以显著减少编码压力
例如:
3840 × 2160
大约:
8.3 million pixels
而:
1920 × 1080
约:
2.1 million pixels
像素数量约下降到四分之一。
因此:
4K → 1080p
通常会显著降低:
- 编码量
- 文件大小
- 播放压力
这也是视频压缩最有效的方法之一。
15. 720p / 1080p / 4K 应该提供给普通用户吗?
相比让用户输入:
1920 × 1080
更友好的产品设计通常是:
Original
1080p
720p
480p
高级设置再提供:
Custom Resolution
普通用户不需要理解每一个像素参数。
16. 帧率也会影响文件大小
例如:
60 fps
意味着每秒处理更多帧。
如果转换为:
30 fps
编码的数据量通常可以进一步降低。
但:
Frame Rate / 2
并不意味着:
File Size / 2
因为编码器会利用:
Inter-frame Compression
所以关系不完全线性。
17. H.264 为什么仍然非常重要?
H.264 最大的价值之一是:
兼容性。
如果用户的目标是:
Send
Share
Upload
Play Everywhere
那么:
H.264 + AAC + MP4
通常是非常实用的组合。
所以可以提供:
Compatibility Mode
内部可能选择:
MP4
H.264
AAC
用户无需理解 Codec。
18. HEVC 的价值是什么?
HEVC / H.265 的优势之一是:
在相似视觉质量下,可以获得更高压缩效率。
所以:
H.264
↓
HEVC
有机会明显降低文件大小。
但是:
Compatibility
需要考虑。
某些老设备、软件或网站可能并不支持。
因此产品上可以:
Smaller File
使用 HEVC。
而:
Best Compatibility
使用 H.264。
19. 编码设置不要全部扔给用户
技术层可能有:
Codec
Bitrate
Profile
Level
GOP
Keyframe Interval
Entropy Mode
普通用户真正关心的是:
Smaller
Balanced
High Quality
所以产品层可以做:
Small File
Balanced
High Quality
然后底层映射到具体编码设置。
高级用户再打开:
Advanced Settings
20. “压缩到 100MB 以下”也是高价值需求
和图片一样,
用户经常遇到:
Upload limit:
100 MB
这时用户不关心:
Bitrate = 2.34 Mbps
只关心:
能不能低于 100MB?
于是可以反向计算目标 Bitrate。
粗略:
Target Bitrate
≈
Target File Size × 8
÷
Duration
例如:
Target:
100 MB
Duration:
300 seconds
粗略总 Bitrate:
100 × 8 / 300
≈
2.67 Mbps
还需要给音频、封装等预留空间。
21. Target Size 模式非常适合产品化
用户设置:
Target Size:
≤ 100 MB
VideoService 可以:
Read Duration
↓
Reserve Audio
↓
Calculate Video Bitrate
↓
Encode
↓
Validate Output Size
必要时:
Adjust
↓
Encode Again
当然视频重新编码成本很高,
不能像 JPEG 那样频繁尝试几十次。
所以:
视频目标大小需要更合理的预估,而不是暴力多次重编码。
22. 为什么视频压缩比图片目标大小更难?
JPEG 重新编码一次:
可能只需要几秒。
而一个:
2 hour 4K video
重新转码可能很久。
所以如果:
第一次 520MB
目标 500MB
不能轻易再完整跑一遍。
因此应尽量:
Pre-calculate
合理 Bitrate。
目标文件大小功能本质上是:
Rate Control
问题。
23. AVAssetExportSession 适合什么场景?
对于很多相对标准的视频导出任务,
可以使用:
AVAssetExportSession
概念上:
AVAsset
↓
Export Preset
↓
Export Session
↓
Output File
它的优势:
Simple
System Managed
适合:
- 标准格式导出
- 常见分辨率
- 相对简单的视频转换
24. Export Preset 的限制是什么?
方便意味着:
控制粒度有限。
如果需要精准控制:
Bitrate
Codec
Frame Rate
Pixel Format
Audio Settings
就可能需要更底层方案。
例如:
AVAssetReader
+
AVAssetWriter
25. AVAssetReader / Writer 更像自定义 Pipeline
整体:
AVAsset
↓
AVAssetReader
↓
CMSampleBuffer
↓
Processing
↓
AVAssetWriter
↓
Output
这样可以更精细地控制:
Video Settings
Audio Settings
Transform
Timing
代价是:
Complexity ↑
所以产品架构里最好有:
VideoExportStrategy
动态选择实现。
26. 可以设计 VideoExportStrategy
例如:
VideoTask
↓
Analyze
↓
Strategy Resolver
可能得到:
Remux Strategy
ExportSession Strategy
ReaderWriter Strategy
整体:
Can Remux?
↓ Yes
Remux
No
↓
Can System Export Preset Handle?
↓ Yes
ExportSession
No
↓
Reader / Writer
这样:
不同任务用最合适的方案。
27. 视频方向是另一个高频坑
iPhone 视频经常不是直接:
width > height
就代表横屏。
视频 Track 可能带:
Transform
例如:
1920 × 1080
通过旋转矩阵显示成:
1080 × 1920
如果重新编码时忽略 Transform,
就可能:
Original:
Portrait
变成:
Output:
Rotated
这是视频转换工具非常常见的 Bug。
28. 所以转换时必须考虑 Preferred Transform
视频处理不能只看:
Natural Size
还需要看:
Transform
才能判断最终显示方向。
因此 VideoInfo 最好区分:
Encoded Size
Displayed Size
否则:
Resize
时非常容易算错。
29. HDR 视频又会增加复杂度
现代 iPhone 可能拍摄:
HDR Video
如果转换成普通 H.264,
可能涉及:
HDR
↓
SDR
如果处理不当,
可能出现:
过曝
颜色发灰
对比异常
因此不能简单认为:
Decode
↓
Encode H.264
就一定视觉正确。
如果产品要处理 HDR,
就需要明确:
Preserve HDR?
Convert to SDR?
30. Color Space 在视频里同样重要
视频可能涉及:
BT.709
BT.2020
以及:
Transfer Function
Color Primaries
YCbCr Matrix
普通用户不需要知道这些,
但底层 VideoService 必须尽量正确处理。
否则可能出现:
颜色偏差
HDR 错误
31. 视频通常不只有一条 Track
一个视频文件可能包含:
Video Track
Audio Track
Subtitle Track
Metadata Track
甚至:
Multiple Audio Tracks
所以转 MP4 时要明确:
保留哪些 Track?
比如:
Movie.mkv
│
├── Video
├── English Audio
├── Japanese Audio
└── Subtitle
如果输出格式不支持所有 Track,
就必须决定:
Keep
Drop
Convert
32. “MKV 转 MP4”特别容易被误解
用户认为:
MKV → MP4
只是改格式。
实际上要先看:
MKV 内部 Codec
如果:
H.264
+
AAC
可能可以相对低成本地重新封装。
但如果:
Video Codec unsupported by MP4 target
就可能需要重新编码。
所以:
Container Conversion
和:
Codec Conversion
必须分开。
33. 视频压缩最好尽量保留音频
如果用户只想:
视频小一点。
通常没必要:
AAC 256 kbps
↓
32 kbps
把声音压得非常糟。
视频占文件体积通常远高于音频。
例如:
Video:
8 Mbps
Audio:
128 kbps
所以先优化:
Video Bitrate
通常更有效。
34. 但音频也应该有统一处理策略
例如:
AAC
128 kbps
用于普通视频已经比较常见。
用户如果选择:
Smallest
可以适当降低。
如果是:
Music Performance
可能希望保留更高音频质量。
所以:
Audio Strategy
也应该进入 VideoConvertOptions。
35. 视频转换非常适合进入 FileOperationManager
因为它是典型的长任务:
Prepare
↓
Analyze
↓
Encode
↓
Progress
↓
Finalize
可能持续很长时间。
所以:
OperationType.videoConvert
OperationType.videoCompress
应统一进入:
FileOperationManager
状态:
Waiting
Preparing
Running
Cancelling
Completed
Failed
36. 视频进度怎么计算?
比普通文件复制复杂一些。
常见逻辑是:
Processed Time
÷
Total Duration
例如:
Processed:
00:01:30
Total:
00:05:00
进度:
30%
所以视频任务非常适合用:
Media Time
而不是:
Output Bytes
做进度。
37. 为什么输出字节不适合直接当进度?
例如:
Target:
500MB
当前输出:
250MB
不一定意味着:
50%
因为:
Variable Bitrate
可能让不同视频区间的数据量差别很大。
所以:
Timeline Progress
一般更自然。
38. 用户取消时一定要清理半成品
例如:
movie-compressed.mp4
编码到:
47%
用户取消。
如果直接保留最终路径:
可能出现一个:
损坏 MP4
更合理:
.movie-compressed.mp4.tmp
↓
Export
↓
Complete
↓
Validate
↓
Rename
取消:
Cancel
↓
Stop Export
↓
Delete Temp
39. 磁盘空间检查比视频任务更重要
假设原视频:
20GB
输出预计:
8GB
但设备剩余:
3GB
如果不提前检查,
可能编码到:
38%
才失败。
所以在任务开始前应该:
Estimate Output
↓
Available Storage
↓
Enough?
当然输出只是估算,
还要预留一定安全空间。
40. 为什么不能边覆盖原视频边转码?
因为:
Source
和:
Destination
不能安全地指向同一个正在读写的资源。
正确方式:
Original
↓
Read
↓
Temp Output
↓
Complete
↓
Validate
↓
Replace Original
如果失败,
原视频仍然存在。
41. 批量视频转换要比图片更保守
假设用户选择:
20 Videos
如果:
20 Transcodes
同时运行,
可能迅速导致:
- CPU 满载
- GPU / Video Encoder 压力
- 发热
- 内存压力
- 电池消耗
所以视频任务:
Concurrency
通常应该非常保守。
例如:
1
或者根据任务和设备决定非常有限的并发。
42. 为什么单任务有时候反而更快?
因为多个视频同时编码会竞争:
Hardware Encoder
CPU
Memory Bandwidth
Disk IO
所以:
2 videos parallel
不一定比:
1 + 1 sequential
更快。
还有可能整体更慢、设备更热。
所以:
视频处理的目标不是最大并发,而是最大有效吞吐量。
43. Thermal State 值得关注
长视频转码很容易让设备发热。
系统可能:
Thermal Throttle
导致编码速度下降。
所以专业一点的任务系统可以考虑:
Thermal State
在设备压力过高时:
- 降低并发
- 暂停后台预处理
- 降低低优先级任务
而不是继续硬跑。
44. 视频 Thumbnail 不应该走完整转码 Pipeline
文件列表可能只需要:
Video Thumbnail
例如第 1 秒:
Frame
这应该由:
ThumbnailService
处理。
例如:
AVAsset
↓
Image Generator
↓
Thumbnail
而不是调用:
VideoService.convert()
职责继续分离。
45. 缩略图生成也要注意异步和取消
用户快速滚动:
Video A
Video B
Video C
...
如果离开屏幕以后还继续给:
Video A
生成缩略图,
就是浪费资源。
所以 Thumbnail Task 应该:
Visible
↓
Generate
Off-screen
↓
Cancel
这和 NAS 缩略图逻辑完全一致。
46. 远程视频怎么办?
例如:
SMB
↓
movie.mp4
如果用户只是播放,
最好:
Stream
如果用户选择:
Compress
最简单路线:
Remote
↓
Download to Temp
↓
VideoService
↓
Output
但对于超大文件:
50GB
这会产生大量本地空间开销。
47. 更高级的架构可以让视频数据源抽象化
和前面的:
ArchiveDataSource
ImageDataSource
一样,
视频也可以有:
VideoDataSource
底层来自:
Local
SMB
WebDAV
但现实里很多 AVFoundation 工作流更偏向:
URL / Asset
所以远程媒体能力需要根据 Provider 和系统框架实际支持能力设计。
不要为了“统一抽象”而强行忽略底层约束。
48. 远程转码要考虑巨大的网络成本
例如:
NAS 50GB Video
如果:
NAS → iPhone → Transcode
就至少要读取大量数据。
如果转完又:
iPhone → NAS
还需要重新上传。
路径:
NAS
↓ 50GB
iPhone
↓ process
Output
↓
NAS
整个网络成本非常高。
所以产品最好提前告诉用户:
Remote file will be downloaded for processing.
而不是让用户以为:
NAS 自己会完成视频压缩。
49. 视频工具需要明确“转换”和“压缩”的区别
例如:
Convert
目标:
MOV → MP4
重点:
Compatibility
Compress
目标:
2GB → 500MB
重点:
File Size
虽然底层可能都涉及编码,
但用户意图不同。
所以 UI 不应该只放一个:
Convert Video
然后塞十几个参数。
50. 可以给普通用户三个入口
例如:
Convert Format
MOV → MP4
Compress Video
Reduce File Size
Advanced Convert
选择:
Codec
Resolution
Frame Rate
Bitrate
这样产品会比:
一上来给用户十几个编码参数
更容易使用。
51. 视频任务的错误应该分类
不要只:
Export Failed
可以建立:
enum VideoProcessingError: Error {
case unsupportedFormat
case unsupportedCodec
case invalidAsset
case insufficientStorage
case exportFailed
case cancelled
case permissionDenied
case destinationConflict
case unknown(Error)
}
UI 根据错误给行动建议。
例如:
Unsupported Codec
↓
尝试重新编码为 H.264
比:
Error -11800
有价值很多。
52. 一个完整 VideoService 架构
可以整理:
┌─────────────────────────────┐
│ UI │
│ │
│ Convert │
│ Compress │
│ Advanced Settings │
└──────────────┬──────────────┘
│
▼
┌─────────────────────────────┐
│ FileOperationManager │
│ │
│ Queue │
│ Progress │
│ Cancel │
│ Retry │
└──────────────┬──────────────┘
│
▼
┌─────────────────────────────┐
│ VideoService │
│ │
│ Inspect │
│ Strategy Resolver │
│ Remux │
│ Transcode │
│ Compress │
└──────────────┬──────────────┘
│
┌────────┼─────────────┐
▼ ▼ ▼
Remux ExportSession Reader/Writer
│
▼
AVFoundation
旁边继续存在:
ThumbnailService
StorageChecker
TemporaryFileManager
ConflictResolver
Thermal Monitor
53. 文件平台架构越来越清晰
现在可以得到:
FileProvider
│
├── Local
├── SMB
├── WebDAV
└── FTP
Services
│
├── ArchiveService
├── ImageService
├── VideoService
└── ThumbnailService
Operations
│
├── Copy
├── Upload
├── Download
├── Extract
├── Compress
├── ImageConvert
└── VideoConvert
这时候:
TS File Explorer
已经不只是:
浏览目录。
而是:
文件进入系统以后,可以继续完成处理。
54. TS File Explorer 的视频功能真正解决什么?
从开发者视角:
AVAsset
H.264
HEVC
Bitrate
Frame Rate
AVAssetWriter
都是技术实现。
但用户真正的问题可能只是:
这个视频太大,发不出去。
或者:
MOV 上传网站失败。
或者:
这个视频格式在电脑上打不开。
所以真正的用户流程:
2GB Video
↓
TS File Explorer
↓
Compress
↓
450MB
↓
Share
或者:
MOV
↓
Convert
↓
MP4
↓
Upload
这才是产品价值。
55. 开发视频处理功能前建议先回答这 20 个问题
1. Container 和 Codec 是否分开建模?
2. 什么情况可以 Remux?
3. 什么情况必须 Transcode?
4. 支持哪些输入格式?
5. 支持哪些输出格式?
6. H.264 / HEVC 如何选择?
7. Bitrate 如何配置?
8. 是否支持目标文件大小?
9. 分辨率如何缩放?
10. Frame Rate 是否允许修改?
11. Orientation 如何保留?
12. HDR 如何处理?
13. Color Space 如何处理?
14. Audio Track 如何处理?
15. Multiple Tracks 如何处理?
16. 大视频如何检查磁盘空间?
17. 用户取消以后如何清理?
18. 批量视频任务并发数多少?
19. Remote Video 如何处理?
20. 视频任务如何接入 FileOperationManager?
这些问题如果没有提前想清楚,
最终很容易变成:
一个视频按钮
+
大量编码特殊情况
+
大量设备兼容问题
写在最后
视频格式转换看起来只是:
MOV → MP4
视频压缩看起来只是:
2GB → 500MB
但真正实现以后,会发现它其实是一套完整的:
Container
+
Codec
+
Bitrate
+
Resolution
+
Frame Rate
+
Audio
+
Color
+
Long-running Operation
系统。
最重要的设计原则之一是:
能 Remux 就不要 Transcode;必须 Transcode 时,再根据用户真正的目标选择编码参数。
到目前为止,整个系列已经变成:
01 File Browser
↓
02 Large File IO
↓
03 Wi-Fi Transfer
↓
04 SMB
↓
05 WebDAV
↓
06 FTP
↓
07 Archive
↓
08 Image
↓
09 Video
下一篇最适合进入:
《iOS 音频格式转换怎么实现?从 AVAudioFile、AAC、MP3 到 FLAC 与批量转码架构》
继续覆盖:
MP3
AAC
WAV
FLAC
OGG
Opus
Sample Rate
Bit Depth
Bitrate
Channel
Batch Convert
这样 TS File Explorer 的媒体处理技术链就基本完整了。