ffmepg介绍(二)——解码
AVCodec
1. avcodec_register_all()
作用:
全局注册所有内置的音频/视频编解码器(如 H.264、AAC、MP3 等),使得后续通过编码 ID 或名称查找解码器时能直接匹配到已注册的编解码器。在最新版本的 FFmpeg(如 FFmpeg 4.0 及以上)中,avcodec_register_all() 已被标记为弃用(deprecated)
关键点:
• 必须先调用:在调用任何编解码器查找函数(如 avcodec_find_decoder()
)前,必须确保已调用此函数注册所有编解码器。
• 自动注册:FFmpeg 默认会在程序启动时自动注册编解码器,但在某些动态加载场景(如插件化支持)中可能需要手动调用。
示例:
#include <libavcodec/avcodec.h>
int main() {
avcodec_register_all(); // 注册所有编解码器
// 后续代码...
return 0;
}
2. avcodec_find_decoder()
作用:
根据编码 ID 查找对应的解码器结构体 AVCodec
。
参数:
• codec_id
:目标编解码器的唯一标识符(如 AV_CODEC_ID_H264
)。
• 返回值:找到的解码器指针 AVCodec*
,若未找到则返回 NULL
。
使用场景:
已知编码 ID 时(例如从媒体文件头中解析出的编码类型)。
示例:
AVCodec *decoder = avcodec_find_decoder(AV_CODEC_ID_H264);
if (!decoder) {
fprintf(stderr, "H.264 decoder not found\n");
exit(1);
}
3. avcodec_find_decoder_by_name()
作用:
根据编解码器名称(字符串)查找对应的解码器结构体 AVCodec
。
参数:
• name
:编解码器名称(如 "h264"
或 "aac"
,区分大小写)。
• 返回值:找到的解码器指针 AVCodec*
,若未找到则返回 NULL
。
使用场景:
需要通过用户输入或配置文件动态指定编解码器名称时。
示例:
AVCodec *decoder = avcodec_find_decoder_by_name("h264");
if (!decoder) {
fprintf(stderr, "Decoder 'h264' not found\n");
exit(1);
}
常见问题与注意事项
-
忘记调用
avcodec_register_all()
:
如果未注册编解码器,avcodec_find_decoder()
和avcodec_find_decoder_by_name()
将始终返回NULL
。
解决方案:确保在程序入口处调用avcodec_register_all()
。 -
编码 ID 与名称的映射:
• 编码 ID 是整数常量(如AV_CODEC_ID_H264
),定义在libavcodec/avcodec.h
中。
• 名称是字符串(如"h264"
),需与编解码器实现中的名称严格匹配。 -
动态加载编解码器:
某些场景(如插件系统)可能需要动态加载编解码器库,此时需结合dlopen()
和avcodec_register,strlen)
手动注册。 -
错误处理:
• 检查avcodec_findDecoder()
的返回值是否为NULL
。
• 检查avcodec_open2()
的返回值是否小于 0。
附录:编码 ID 与名称的对应关系
编码 ID | 名称 | 常见用途 |
---|---|---|
AV_CODEC_ID_H264 | “h264” | 视频编码 |
AV_CODEC_ID_AAC | “aac” | 音频编码 |
AV_CODEC_ID_MP3 | “mp3” | 音频编码 |
AV_CODEC_ID_MPEG4 | “mpeg4” | 视频/音频编码 |
AV_CODEC_ID_H265 | “hevc” | 视频编码(H.265) |
AVCodecContext
关键字段
字段名 | 类型 | 说明 |
---|---|---|
pix_fmt | enum AVPixelFormat | 像素格式(如 AV_PIX_FMT_YUV420P ) |
width / height | int | 视频分辨率 |
sample_rate | int | 音频采样率 |
bit_rate | int | 编码/解码比特率 |
flags | int | 编解码器标志(如低延迟模式) |
1. avcodec_alloc_context3()
作用:
分配并初始化 AVCodecContext
结构体,存储编解码器配置参数(如分辨率、帧率、编码格式等)。
参数:
• codec
:已找到的编解码器指针 AVCodec*
。
• parent
:父上下文(可选,通常为 NULL
)。
• flags
:标志位(如 AV_CODEC_FLAG_LOW_LATENCY
,主要用于音频)。
返回值:
成功返回 AVCodecContext*
指针,失败返回 NULL
。
示例:
AVCodec *decoder = avcodec_find_decoder(AV_CODEC_ID_H264);
AVCodecContext *ctx = avcodec_alloc_context3(decoder);
if (!ctx) {
fprintf(stderr, "Failed to allocate decoder context\n");
exit(1);
}
2. avcodec_free_context()
作用:
释放 AVCodecContext
内存,防止泄漏。
参数:
• pp_ctx
:指向 AVCodecContext*
的指针的指针(用于直接修改原指针)。
示例:
avcodec_free_context(&ctx);
ctx = NULL; // 防止悬空指针
3. avcodec_open2()
作用:
初始化编解码器上下文,绑定到具体编解码器并应用配置参数。
参数:
• ctx
:AVCodecContext*
指针。
• codec
:编解码器结构体 AVCodec*
。
• options
:编解码器参数表(如分辨率、帧率等),类型为 const AVOption* const[]
。
返回值:
成功返回 0
,失败返回负错误码。
示例:
ctx->pix_fmt = AV_PIX_FMT_YUV420P;
ctx->width = 1280;
ctx->height = 720;
int ret = avcodec_open2(ctx, decoder, NULL);
if (ret < 0) {
fprintf(stderr, "Failed to open decoder: %s\n", av_err2str(ret));
avcodec_free_context(&ctx);
exit(1);
}
AVCodecParameters
AVCodecParameters
是 FFmpeg 中用于存储编解码器参数的核心结构体,存储了流的编解码器参数。,包括视频、音频的分辨率、帧率、编码格式、比特率等关键参数。 它的主要目的是在解复用(Demuxing)时提取流的编解码信息,而不需要初始化完整的编解码器上下文(AVCodecContext
)。
1. 结构体字段
AVCodecParameters
包含以下核心字段(视频、音频、通用参数):
视频参数
字段名 | 类型 | 说明 |
---|---|---|
codec_id | enum AVCodecID | 视频编解码器 ID(如 AV_CODEC_ID_H264 ) |
width / height | int | 视频分辨率(像素) |
pix_fmt | enum AVPixelFormat | 像素格式(如 AV_PIX_FMT_YUV420P ) |
frame_rate | AVRational | 帧率(分数形式,如 30/1) |
bit_rate | int | 编码/解码比特率(kbps) |
bits_per_raw_sample | int | 每个原始样本的位数(如 8、10、12) |
profile | char* | 编码器配置文件(如 H.264 的 “main” 或 “high”) |
level | int | 编码器级别(如 H.264 的 4.2) |
音频参数
字段名 | 类型 | 说明 |
---|---|---|
codec_id | enum AVCodecID | 音频编解码器 ID(如 AV_CODEC_ID_AAC ) |
sample_rate | int | 音频采样率(Hz,如 44100) |
channels | int | 音频通道数(如 2、5.1) |
channel_layout | uint64_t | 音频布局(如 AV_CH_LAYOUT_STEREO ) |
bit_rate | int | 编码/解码比特率(kbps) |
sample_fmt | enum AVSampleFormat | 音频样本格式(如 AV_SAMPLE_FMT_FLTP ) |
通用参数
字段名 | 类型 | 说明 |
---|---|---|
codec_type | enum AVMediaType | 媒体类型(视频 AVMEDIA_TYPE_VIDEO ,音频 AVMEDIA_TYPE_AUDIO ) |
extradata | uint8_t* | 额外数据(如 H.264 的 SPS/PPS 数据) |
extradata_size | int | extradata 的长度 |
2. 与类似结构的对比
结构体 | 用途 | 关键区别 |
---|---|---|
AVCodecContext | 存储编解码器的运行时状态(如缓冲区、线程池) | 包含硬件加速信息、内部状态机 |
AVCodecParameters | 存储编解码器的配置参数(静态信息) | 不包含执行状态,仅用于配置同步 |
AVStream | 表示容器中的一个媒体流(如视频流、音频流) | 包含 AVCodecParameters 和其他流元数据 |
3.avcodec_parameters_to_context()
将 AVCodecParameters 结构体中的编解码器参数
配置同步到 AVCodecContext 上下文对象
中。
核心用途:
- 动态参数更新:在编解码器上下文已初始化后,从外部(如容器元数据、用户配置)动态更新参数(如分辨率、帧率、比特率)。
- 参数迁移:将解码器参数迁移到编码器(需手动映射关键字段)。 避免重复分配:无需重新分配上下文即可修改参数,提升性能。
AVFrame
一、AVFrame 的核心字段
1. 基础信息字段
字段 | 类型/说明 | 示例值 |
---|---|---|
codec_ctx | AVCodecContext* 编解码器上下文,包含编码/解码参数(如采样率、通道数等)。 | 解码时从 AVPacket 中获取 |
format | enum AVPixelFormat 或 enum AVSampleFormat ,表示像素格式(视频)或采样格式(音频)。 | AV_PIX_FMT_YUV420P 、AV_SAMPLE_FMT_PCM_S16LE |
width /height | 视频帧的宽度和高度(仅视频有效)。音频帧则用 sample_rate 和 nb_samples 表示。 | 1920、1080 |
sample_rate | 音频帧的采样率(Hz),如 44100 Hz。视频帧无此字段。 | 44100 |
nb_samples | 音频帧的采样点数量。视频帧无此字段。 | 1024 |
channel_layout | 声道布局(音频),如 AV_CH_LAYOUT_STEREO 。视频帧无此字段。 | 0x3(立体声) |
pts | 呈现时间戳(Presentation Timestamp),表示帧在时间轴上的位置。 | 视频帧:90000(假设时间基为 1/1000 ms) |
tbn | 时间基准(Time Base),单位为 1/pts ,用于计算时间差。 | 视频帧:AV_RB(1000000, 30)(30 fps) |
metadata | AVDictionary* ,存储帧的元数据(如 EXIF 信息)。 | 可包含相机型号、GPS 等信息 |
2. 数据缓冲区字段
字段 | 类型/说明 | 示例行为 |
---|---|---|
data | uint8_t* data[AV_NUM_DATA_POINTERS] ,指向实际数据缓冲区的指针数组。 | 视频帧:data[0] 是 Y 分量,data[1] 是 U 分量等。 |
linesize | int linesize[AV_NUM_DATA_POINTERS] ,每行的字节长度(仅视频有效)。 | YUV420P 中 linesize[0] = width * 2 |
nb_planes | 数据平面的数量(如 YUV420P 有 3 个平面)。 | 3 |
3. 内存管理字段
字段 | 类型/说明 | 作用 |
---|---|---|
data_ptr | AVBufferRef* data_ptr[AV_NUM_DATA_POINTERS] ,指向数据缓冲区的引用计数指针。 | 管理缓冲区的共享和释放 |
ref_count | 引用计数(内部使用),用于避免手动释放内存。 | 当 ref_count 为 0 时自动释放 |
二、AVFrame 的核心函数
1. 分配与初始化
函数 | 作用 | 示例代码 |
---|---|---|
av_frame_alloc() | 分配一个新的 AVFrame 结构体,初始化为零。 | AVFrame* frame = av_frame_alloc(); |
av_frame_get_buffer() | 为 AVFrame 分配数据缓冲区(根据 format 、width 、height 等参数)。 | if (av_frame_get_buffer(frame, 0) < 0) { ... } |
av_frame_unref() | 减少 AVFrame 的引用计数,当计数为零时释放内存。 | av_frame_unref(frame); |
2. 数据操作
函数 | 作用 | 示例代码 |
---|---|---|
av_frame_copy() | 复制 src 的数据到 dst ,包括格式、尺寸、缓冲区等。 | av_frame_copy(dst, src); |
av_frame_move() | 移动 src 的数据到 dst (浅拷贝,接管内存)。 | av_frame_move(dst, src); |
avcodec_decode_audio4() | 解码音频帧到 AVFrame (FFmpeg 内部函数,用户通常通过 AVCodecContext 调用)。 | avcodec_decode_audio4(..., (int16_t*)frame->data, ...); |
3. 时间戳与元数据
函数 | 作用 | 示例代码 |
---|---|---|
av_rescale_q() | 时间戳转换(将 a 从时间基 tb_a 转换为时间基 tb_b )。 | frame->pts = av_rescale_q(frame->pts, in_tb, out_tb); |
av_dict_set() | 向 metadata 添加键值对。 | av_dict_set(frame->metadata, "key", "value", 0); |
4. 自定义缓冲区管理
函数 | 作用 | 示例代码 |
---|---|---|
av_frame_set_data() | 手动设置 data 和 linesize (需自行管理内存)。 | av_frame_set_data(frame, gpu_data, gpu_linesize); |
av_frame_set_sample_fmt() | 设置采样格式(音频)或像素格式(视频)。 | av_frame_set_sample_fmt(frame, AV_SAMPLE_FMT_PCM_S16LE); |
三、注意事项
-
内存安全:
• 引用计数:AVFrame
的data
缓冲区通过引用计数管理,避免手动free
。
• 正确释放:使用av_frame_unref()
而非free(frame)
。 -
数据对齐:
• 视频帧的linesize
可能大于width
(如 YUV420P 中linesize = width * 2
),访问数据时需按linesize
对齐。 -
类型匹配:
• 音频帧的data
指向int16_t
或float
数组,视频帧的data
指向uint8_t
数组,需根据sample_fmt
或pix_fmt
正确解码。 -
多线程:
•AVFrame
不是线程安全的,跨线程操作时需加锁。
avcodec_send_packet() 和 avcodec_receive_frame()
1. 核心功能与设计目的
函数 | 核心功能 | 设计目的 |
---|---|---|
avcodec_send_packet() | 向解码器发送压缩数据包(如 H.264、AAC)。 | 触发解码器处理数据,生成解码后的帧(AVFrame )。 |
avcodec_receive_frame() | 从解码器接收解码后的原始帧(如 RGB、PCM)。 | 获取解码结果,进行后续处理(如渲染、存储或转码)。 |
2. 函数原型与参数
avcodec_send_packet()
int avcodec_send_packet(AVCodecContext *ctx, const AVPacket *pkt);
• 参数:
• ctx
:解码器上下文(AVCodecContext*
)。
• pkt
:指向压缩数据包的指针(AVPacket*
)。
• 返回值:
• ≥0
:成功处理的字节数(可能为 0,表示数据包未被完全消耗)。
• AVERROR(EAGAIN)
:需要更多数据包才能完成解码。
• 负值:其他错误(如内存不足、格式不支持)。
avcodec_receive_frame()
int avcodec_receive_frame(AVCodecContext *ctx, AVFrame *frame);
• 参数:
• ctx
:解码器上下文(AVCodecContext*
)。
• frame
:用于存储解码后帧的指针(AVFrame*
)。
• 返回值:
• ≥0
:成功接收的帧大小(通常无意义,仅表示成功)。
• AVERROR(EAGAIN)
:无可用帧,需继续发送数据包。
• 负值:解码失败(如数据损坏、参数错误)。
3. 总结
函数 | 用途场景 | 关键行为 | 常见错误处理 |
---|---|---|---|
avcodec_send_packet() | 解码(发送压缩数据)、编码(发送元数据) | 推动编解码器处理数据 | AVERROR(EAGAIN) (需要更多数据) |
avcodec_receive_frame() | 解码(接收帧)、编码(无) | 拉取解码后的原始帧 | AVERROR(EAGAIN) (无可用帧) |
最佳实践:
• 解码:始终循环调用 avcodec_send_packet()
和 avcodec_receive_frame()
。
• 内存安全:确保 AVPacket
和 AVFrame
的引用计数正确释放(av_packet_unref()
和 av_frame_unref()
)。
• 错误处理:重点处理 AVERROR(EAGAIN)
和负值错误,避免程序崩溃或数据丢失。
通过这两个函数的配合,FFmpeg 实现了高效的编解码流水线,是处理音视频数据的核心机制!