学习资源
官方文档: Documentation
雷神博客
可移步 [总结]FFMPEG视音频编解码零基础学习方法
结构体学习:FFMPEG结构体分析
基础知识
视频编码
视频编码的主要作用是将视频像素数据(RGB,YUV等)压缩成为视频码流,从而降低视频的数据量。不压缩就视频就太大了。
主要视频编码
名称 | 推出机构 |
---|---|
HEVC(H.265) | MPEG/ITU-T |
H.264 | MPEG/ITU-T |
MPEG4 | MPEG |
MPEG2 | MPEG |
VP9 | |
VP8 | |
VC-1 | Microsoft Inc. |
优劣关系
HEVC(H.265) > VP9 > H.264 > VP8 > MPEG4 > H.263 > MPEG2
结构体学习
AVFrame 结构体一般用于存储原始数据(即非压缩数据,例如对视频来说是YUV,RGB,对音频来说是PCM),此外还包含了一些相关的信息
AVCodecContext 是FFmpeg编解码上下文的结构体,而AVCodec是编解码参数的结构体
AVPacket 是存储压缩编码数据相关信息的结构体
AVCodec是存储编解码器信息的结构体
结构体关系
每个AVStream存储一个视频/音频流的相关数据;每个AVStream对应一个AVCodecContext,存储该视频/音频流使用解码方式的相关数据;
每个AVCodecContext中对应一个AVCodec,包含该视频/音频对应的解码器。每种解码器都对应一个AVCodec结构。
官方demo之encode.c学习理解
学习官方demo:encode_video.c
我自己对官方demo进行了修改并加了注释,修改后的文件可直接运行,已上传至 资源下载地址
流程分析:
- 程序运行后给定输出的文件路径
- 调用
avcodec_find_encoder
查找编码器 AVCodec - 调用
avcodec_alloc_context3
分配编码器上下文 AVCodecContext - 调用
av_packet_alloc
分配 AVPacket,编码器会将每个AVFrame编码为AVPacket - 给 AVCodecContext 配置参数,如码率、视频宽高、帧格式等等
- 调用
avcodec_open2
打开编码器 - 调用
av_frame_alloc
分配 AVFrame 内存并设置帧的格式和宽高 - 调用
av_frame_get_buffer
分配帧数据使用的空间 - 开始for循环,首先调用
av_frame_make_writable
确保帧数据 AVFrame 可写,然后准备一帧虚拟图像数据写入frame->data
,最后给每帧的pts赋值,告诉播放器在什么时候显示这一帧数据 - 开始编码,调用
avcodec_send_frame
把帧数据 AVFrame 发送给编码器 - 调用
avcodec_receive_packet
把编码后的帧 AVPacket 从编码器取出,然后把编码后的每一帧写入输出文件 File ,写完后调用av_packet_unref
解除对 AVPacket 的引用 - 如果编码器ID是MPEG,需要向输出文件 File 的结尾添加一个固定序列从而得到一个真正的MPEG文件
- 最后关闭文件 File,并释放 AVCodecContext、AVFrame、AVPacket 占用的内存,调用
avcodec_free_context
、av_frame_free
、av_packet_free
API学习
avcodec_find_encoder
根据一个AVCodecID找到一个注册的编码器
const AVCodec *avcodec_find_encoder(enum AVCodecID id);
常见 AVCodecID 随便列举一些
enum AVCodecID {
AV_CODEC_ID_NONE,
/* video codecs */
AV_CODEC_ID_H263,
AV_CODEC_ID_MPEG4,
AV_CODEC_ID_H264,
AV_CODEC_ID_HEVC,
}
实例:
const AVCodec *codec;
enum AVCodecID codec_id;
codec_id = AV_CODEC_ID_HEVC;
codec = avcodec_find_encoder(codec_id);
if (!codec) {
printf("Codec '%d' not found\n", codec_id);
exit(1);
}
avcodec_alloc_context3
为 AVCodecContext
结构体分配内存,并将各字段设置为默认值,结果返回的结构体需要使用 avcodec_free_context()
进行释放内存
AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);
实例:
AVCodecContext *c= NULL;
const AVCodec *codec;
c = avcodec_alloc_context3(codec);
if (!c) {
fprintf(stderr, "Could not allocate video codec context\n");
exit(1);
}
...
...
...
avcodec_free_context(&c);
av_packet_alloc
为 AVPacket
结构体分配内存,并将各字段设置为默认值,结果返回的结构体需要使用 av_packet_free()
进行释放内存
AVPacket *av_packet_alloc(void);
实例:
AVPacket *pkt;
pkt = av_packet_alloc();
if (!pkt)
exit(1);
...
...
...
av_packet_free(&pkt);
avcodec_open2
初始化 AVCodecContext
以使用给定的 AVCodec
,使用此函数之前需要使用 avcodec_alloc_context3()
分配内存
int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);
av_frame_get_buffer
分配新的buffers给视频数据
在调用该函数之前需要设置 frame 的一些参数format、width、height
int av_frame_get_buffer(AVFrame *frame, int align);
参数 align
强烈推荐设置为0,系统会根据当前CPU自动设置alignment
实例:
AVFrame *frame;
frame = av_frame_alloc();
frame->format = c->pix_fmt;
frame->width = c->width;
frame->height = c->height;
av_frame_get_buffer(frame, 32);
avcodec_send_frame
发送数据帧到编码器。使用 avcodec_receive_packet()
函数来检索缓冲输出数据包
int avcodec_send_frame(AVCodecContext *avctx, const AVFrame *frame);
avcodec_receive_packet
从编码器读取编码好的数据
int avcodec_receive_packet(AVCodecContext *avctx, AVPacket *avpkt);
返回类型:
0 表示成功
AVERROR(EAGAIN) 输出当前不可用,用户必须给予输入
AVERROR_EOF 编码器被完全冲洗,不再会有输出的 packets
AVERROR(EINVAL) codec没有开启,或者它是一个解码器而非编码器
实例:
ret = avcodec_receive_packet(enc_ctx, pkt);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
return;
else if (ret < 0) {
fprintf(stderr, "Error during encoding\n");
exit(1);
}
结束语
刚开始学ffmpeg,摸着石头过河,一点一点看demo和源码还是蛮有意思的
英文阅读有待提高,要不然看不懂开发者给的注释