一、概述
本文记录 FFmpeg 开发中的三个核心功能:音频抽取、视频抽取和格式转封装。这些都是解封装(Demuxing)的基础应用,在安防监控、视频处理等领域有广泛应用。
二、核心概念
2.1 什么是解封装?
解封装是从多媒体容器(如 MP4、FLV、MKV)中分离出音视频流的过程,不涉及编解码,只是改变数据组织方式。 例子:
┌─────────────┐ 解封装 ┌─────────────┐
│ MP4容器 │ ──────────→ │ H.264裸流 │
│ 音视频混合 │ │ 纯视频数据 │
└─────────────┘ └─────────────┘
2.2 关键数据结构
| 结构体 | 作用 |
|---|---|
AVFormatContext | 封装格式上下文,代表一个媒体文件 |
AVStream | 媒体流(视频/音频/字幕) |
AVPacket | 压缩数据包(编码后的数据) |
三、音/视频抽取
3.1 核心流程
1. avformat_open_input() // 打开输入文件
2. avformat_find_stream_info() // 获取流信息
3. av_find_best_stream() // 查找音频流
4. avformat_alloc_output_context2() // 创建输出上下文
5. avformat_new_stream() // 创建输出流
6. avcodec_parameters_copy() // 复制编码参数
7. avio_open2() // 打开输出文件
8. avformat_write_header() // 写文件头
9. 循环 av_read_frame() + av_interleaved_write_frame() // 读写数据
10. av_write_trailer() // 写文件尾
11. 释放资源
3.2 涉及的API注释
/**
* avformat_open_input:
* - 作用: 打开媒体文件,读取文件头
* - 参数1: AVFormatContext** 格式上下文的指针(传出参数)
* - 参数2: const char* 文件名或URL
* - 参数3: AVInputFormat* 输入格式(NULL=自动检测)
* - 参数4: AVDictionary** 选项(NULL=默认)
* - 返回值: 0成功,负数失败
*/
/**
* avformat_find_stream_info:
* - 作用: 读取一部分数据,获取流信息(编码类型、参数、时长等)
* - 参数1: AVFormatContext* 格式上下文
* - 参数2: AVDictionary** 选项(NULL=默认)
* - 返回值: 0成功,负数失败
*/
/**
* av_find_best_stream:
* - 作用: 找到指定类型的最佳流
* - 参数1: AVFormatContext* 格式上下文
* - 参数2: enum AVMediaType 媒体类型(AVMEDIA_TYPE_AUDIO/VIDEO)
* - 参数3: int 搜索起始索引(-1=从头开始)
* - 参数4: int 相关流索引(-1=忽略)
* - 参数5: AVCodec** 返回解码器(NULL=不需要)
* - 参数6: int 标志(0)
* - 返回值: 流索引(>=0),负数失败
*/
/**
* avformat_alloc_output_context2:
* - 作用: 分配输出格式上下文,根据文件名自动选择封装格式
* - 参数1: AVFormatContext** 输出上下文的指针
* - 参数2: AVOutputFormat* 输出格式(NULL=自动选择)
* - 参数3: const char* 格式名称(NULL=根据后缀)
* - 参数4: const char* 文件名
* - 返回值: 0成功,负数失败
*/
/**
* avformat_new_stream:
* - 作用: 在输出文件中创建一个新的媒体流(视频/音频/字幕)
* - 参数1: AVFormatContext* 输出格式上下文
* - 参数2: AVCodec* 编解码器(NULL=先不指定,稍后复制参数)
* - 返回值: AVStream* 新创建的流,失败返回NULL
*/
/**
* avcodec_parameters_copy:
* - 作用: 复制编解码器参数(编码类型、采样率、声道数等)
* - 参数1: AVCodecParameters* 目标参数
* - 参数2: AVCodecParameters* 源参数
* - 返回值: 0成功,负数失败
* - 注意: 必须复制,不能直接赋值(避免内存问题)
*/
/**
* avio_open2:
* - 作用: 打开输出文件,建立文件I/O上下文
* - 参数1: AVIOContext** 输出I/O上下文的指针(传出参数)
* - 参数2: const char* 输出文件名
* - 参数3: int 打开标志(AVIO_FLAG_WRITE=写模式)
* - 参数4: AVIOInterruptCB* 中断回调(NULL=不使用)
* - 参数5: AVDictionary** 额外选项(NULL=默认)
* - 返回值: 0成功,负数失败
* - 注意: 必须在写文件头之前调用,成功后oFmtCtx->pb会被填充
*/
/**
* avformat_write_header:
* - 作用: 写入封装格式的文件头
* - 参数1: AVFormatContext* 输出格式上下文
* - 参数2: AVDictionary** 选项(NULL=默认)
* - 返回值: 0成功,负数失败
* - 注意: 必须在写数据之前调用
*/
/**
* av_packet_alloc:
* - 作用: 分配AVPacket结构体并初始化
* - 返回值: AVPacket* 新分配的packet
* - 注意: 使用后必须调用av_packet_free释放
*/
/**
* av_read_frame:
* - 作用: 从输入文件读取一个数据包
* - 参数1: AVFormatContext* 输入格式上下文
* - 参数2: AVPacket* 输出的数据包
* - 返回值: 0成功,<0失败或文件结束
*/
/**
* av_rescale_q_rnd:
* - 作用: 时间戳转换(可指定舍入方式)
* - 参数1: int64_t 输入时间戳
* - 参数2: AVRational 输入时间基准
* - 参数3: AVRational 输出时间基准
* - 参数4: enum AVRounding 舍入方式
* - 返回值: 转换后的时间戳
*/
/**
* av_rescale_q:
* - 作用: 将时间戳从一个时间基准转换到另一个时间基准。
* - 参数1: 输入时间戳
* - 参数2: 输入时间基准
* - 参数3: 输出时间基准
* - 返回值: 转换后的时间戳
*/
/**
* av_interleaved_write_frame:
* - 作用: 交错写入数据包(按时间戳排序)
* - 参数1: AVFormatContext* 输出格式上下文
* - 参数2: AVPacket* 要写入的数据包
* - 返回值: 0成功,负数失败
* - 注意: 写入后packet会被内部释放,不需要手动unref
*/
/**
* av_write_trailer:
* - 作用: 写入文件尾(如MP4的moov box)
* - 参数1: AVFormatContext* 输出格式上下文
* - 注意: 必须在关闭文件之前调用
*/
| 对比 | 音频抽取 | 视频抽取 |
|---|---|---|
| 流类型 | AVMEDIA_TYPE_AUDIO | AVMEDIA_TYPE_VIDEO |
| 时间戳 | PTS == DTS | PTS 可能 ≠ DTS(有B帧时) |
四、什么是转封装?
4.1、核心概念
转封装是指改变多媒体文件的容器格式,但不改变音视频的编码格式。 如:
MP4文件 ──→ 转封装 ──→ FLV文件
(H.264/AAC) (H.264/AAC)
特点:
- 不重新编码,速度快
- 保持原始画质和音质
- 文件大小基本不变
4.2、完整流程图
┌─────────────────────────────────────────────────────────────┐
│ 转封装核心流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. avformat_open_input() 打开输入文件 │
│ ↓ │
│ 2. avformat_find_stream_info() 获取流信息 │
│ ↓ │
│ 3. avformat_alloc_output_context2() 创建输出上下文 │
│ ↓ │
│ 4. 遍历输入流,为每个音/视/字流创建输出流 │
│ └── avformat_new_stream() │
│ └── avcodec_parameters_copy() 复制编码参数 │
│ ↓ │
│ 5. avio_open2() 打开输出文件 │
│ ↓ │
│ 6. avformat_write_header() 写文件头 │
│ ↓ │
│ 7. 循环读取/写入数据包 │
│ └── av_read_frame() 读一包 │
│ └── av_packet_rescale_ts() 转换时间戳 │
│ └── av_interleaved_write_frame() 写一包 │
│ ↓ │
│ 8. av_write_trailer() 写文件尾 │
│ ↓ │
│ 9. 释放所有资源 │
│ │
└─────────────────────────────────────────────────────────────┘
4.3、小点
| 要点 | 说明 |
|---|---|
| 流映射 | 输入输出流索引可能不同,需要映射表 |
| av_interleaved_write_frame | 写入后自动释放 packet,不用重复 unref |