FFmpeg抽取音频视频以及重新封装

5 阅读5分钟

一、概述

本文记录 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_AUDIOAVMEDIA_TYPE_VIDEO
时间戳PTS == DTSPTS 可能 ≠ 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