iOS 视频格式转换与压缩怎么实现?从 AVFoundation、H.264 到码率与分辨率

0 阅读17分钟

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 的媒体处理技术链就基本完整了。