ffmpeg库进行编码
“encode” 是指将原始或解码后的媒体数据(如视频帧或音频样本)转换为一种特定的压缩格式或编码格式。
编码过程将未压缩的媒体数据(如原始的 RGB 图像数据或 PCM 音频数据)转换为压缩格式(如 H.264 视频或 AAC 音频),
以便于存储或传输。
这段代码展示了如何使用 FFmpeg 库进行视频编码。
整个过程包括查找编码器、分配和配置编码器上下文、生成未压缩数据帧、编码数据帧、处理编码后的数据包,并将其写入文件。
通过这种方式,可以将未压缩的视频帧转换为压缩的视频格式(如 H.264 或 H.265),以便于存储和传输。
编码生成一段10秒的h.264视频
【效果展示】
代码
1 |
|
(一)
1 | AVCodec *avcodec_find_encoder(enum AVCodecID id); |
avcodec_find_encoder
是 FFmpeg 库中用于查找指定编码器的函数。编码器是将未压缩的媒体数据(如视频帧或音频样本)转换为压缩格式的组件。该函数返回一个指向 AVCodec
结构的指针,代表找到的编码器。
参数说明
-
id
-
这是
AVCodecID
枚举类型,表示要查找的编码器的标识符。FFmpeg 定义了许多编码器的标识符,例如:AV_CODEC_ID_H264
:表示 H.264 视频编码器。
-
-
AV_CODEC_ID_HEVC
(或AV_CODEC_ID_H265
):表示 H.265/HEVC 视频编码器。-
AV_CODEC_ID_AAC
:表示 AAC 音频编码器。 -
AV_CODEC_ID_MP3
:表示 MP3 音频编码器。 -
你需要根据你要编码的媒体格式选择合适的编码器 ID。
-
返回值
-
返回值
- 成功时返回一个指向
AVCodec
结构的指针,该结构包含了编码器的相关信息。
- 成功时返回一个指向
-
如果函数找不到与
id
对应的编码器,则返回NULL
。
使用场景
- 当你想要将未压缩的视频帧编码为 H.264、H.265 或其他格式时,首先需要通过
avcodec_find_encoder
查找对应的编码器,然后通过编码器生成编码上下文,进行编码操作。
(二)
1 | AVCodecContext *avcodec_alloc_context3(const AVCodec *codec); |
avcodec_alloc_context3
是 FFmpeg 库中的一个函数,用于分配并初始化一个编码器或解码器上下文 (AVCodecContext
)。这个上下文是编码或解码过程中的核心结构,包含了所有相关的配置信息,如视频或音频的格式、分辨率、码率等。
参数说明
codec
- 这是一个指向
AVCodec
结构的指针,表示你想要使用的编码器或解码器。通常这个指针是通过avcodec_find_encoder
或avcodec_find_decoder
函数获取的。 - 如果你传递
NULL
,则会分配一个未初始化的AVCodecContext
,稍后你可以手动初始化它。
- 这是一个指向
返回值
- 返回值
- 成功时,返回一个指向
AVCodecContext
结构的指针,这个结构已经分配了内存,并且部分字段已经初始化。 - 如果分配失败,则返回
NULL
。
- 成功时,返回一个指向
使用场景
-
avcodec_alloc_context3
分配并初始化一个AVCodecContext
结构。这个上下文结构在编码或解码过程中存储所有的配置信息和状态。 -
如果传入了有效的
AVCodec
指针,函数将根据编码器或解码器的默认设置初始化一些字段(如时间基、像素格式、采样率等)。
(三)
c->time_base = { 1, 25 };
设置AVCodecContext
中的时间单位的长度
-
time_base
是一个分数形式的结构体,{ 1, 25 }
表示的就是分数1/25
。其中1
是分子,25
是分母。 -
这个设置的意思是:每个时间单位代表 1/25 秒,也就是说每秒钟有 25 个时间单位。
对于每一帧视频,都会有一个显示时间戳 pts
(Presentation Time Stamp),用来标识该帧在视频播放过程中的显示时间。pts * time_base
就代表该帧应该在视频播放的第几秒显示。
【例子】:
通过c->time_base = { 1, 25 };
定义时间单位的长度
-
如果
pts
为 0,则这帧应该在视频播放的第0 * (1/25) = 0
秒显示。 -
如果
pts
为 24,则这帧应该在视频播放的第24 * (1/25) = 0.96
秒显示。 -
如果
pts
为 25,则这帧应该在视频播放的第25 * (1/25) = 1
秒显示。
(四)
int re = avcodec_open2(c, codec, NULL);
打开指定的编码器,使其与编码上下文关联,并准备好编码操作。
函数作用:
avcodec_open2()
是 FFmpeg 库中的一个函数,用于初始化编码器或解码器并将其与给定的编码上下文(AVCodecContext
)关联。- 它会配置编码器,使其准备好处理数据(即编码或解码操作)。
参数说明:
c
: 这是一个指向AVCodecContext
的指针,也就是编码上下文。它包含了编码器的配置信息,比如视频的宽度、高度、像素格式等。c
是通过之前的avcodec_alloc_context3()
函数分配和初始化的。codec
: 这是一个指向AVCodec
结构的指针,代表你要使用的编码器。在你的代码中,codec
是通过avcodec_find_encoder(codec_id)
查找到的,表示具体的编码器(如 H.264 或 H.265 编码器)。NULL
: 这是一个AVDictionary
类型的指针,可以用来传递额外的选项给编码器。在这里传入NULL
表示没有额外的选项。
返回值:
re
: 这是avcodec_open2()
函数的返回值,类型为int
。- 如果返回值
re
为0
,表示编码器成功打开,编码上下文已准备好进行编码操作。 - 如果返回值为负数,表示出现错误,具体的错误代码可以通过 FFmpeg 的相关函数或宏定义来解析。
流程总结:
- 通过
avcodec_open2()
,编码器codec
被正式初始化,并与编码上下文c
关联。这一步骤之后,就可以使用该上下文c
进行帧的编码了。
(五)
分配内存,存储未压缩的数据,并将为每一帧画面填充数据
1 | int re = av_frame_get_buffer(frame, 0); |
-
frame->data[0]
并不是自动拥有数据的,AVFrame
创建时它的初始值为NULL
。 -
需要通过
av_frame_get_buffer()
函数或手动分配内存来初始化data[0]
,使其指向合适的缓冲区,以存储帧的像素数据。 -
初始化后,你可以通过
data[0]
来访问或操作具体的像素数据,这是图像处理的基础步骤之一。
【如何给每一帧填充画面?】
Y平面
-
外层循环遍历每一行 (
y
),内层循环遍历每一列 (x
)。 -
frame->data[0][y * frame->linesize[0] + x]
用于访问第y
行第x
列的像素。
U、V平面
-
U 和 V 平面的分辨率是 Y 平面的二分之一,因此这两个平面的循环分别只遍历一半的高度和宽度。
-
frame->data[1][y * frame->linesize[1] + x]
访问 U 平面的第y
行第x
列的像素 -
V平面同理
(六)
re = avcodec_send_frame(c, frame);
函数作用:
-
avcodec_send_frame()
是 FFmpeg 库中的一个函数,用于将一帧视频数据发送给编码器。 -
编码器会在接收到帧后开始处理,编码为压缩数据。
-
这个函数是非阻塞的,它将帧推送到编码器的内部队列中,并立即返回。
由于for(int i = 0; i < 250; i ++ )
循环250次,250帧,也就是10秒,每一帧都会调用avcodec_send_frame()
从而进行编码
参数说明:
c
:这是一个指向AVCodecContext
的指针,表示编码上下文。编码上下文包含了编码器的配置信息和状态,是编码操作的核心对象。frame
:这是一个指向AVFrame
的指针,表示要发送的一帧未压缩的原始视频数据。这个帧可能包含亮度(Y)和色度(U、V)的数据,具体格式取决于编码上下文的设置。frame
可以是实际的图像数据,也可以是NULL
。如果传入NULL
,则表示告诉编码器已经没有更多的帧需要编码,这个操作通常用于结束编码并刷新编码器的缓冲区。
返回值:
re
:这是avcodec_send_frame()
函数的返回值,类型为int
。- 如果返回值为
0
,表示帧已经成功发送给编码器。 - 如果返回负数,表示发送帧失败。可能的错误包括:
AVERROR(EAGAIN)
:编码器的内部队列已满,需要调用avcodec_receive_packet()
函数来提取已经编码好的数据包,以腾出空间。AVERROR_EOF
:编码器已经被标记为结束(例如在发送NULL
帧之后),不再接受新的帧。AVERROR(EINVAL)
:编码器的状态无效,可能是由于上下文配置错误。
编码流程:
-
在实际的编码过程中,
avcodec_send_frame()
通常与avcodec_receive_packet()
配合使用:- 首先通过
avcodec_send_frame()
将帧发送给编码器。
- 首先通过
-
然后通过
avcodec_receive_packet()
提取编码后的数据包(AVPacket
),该数据包包含了压缩后的视频数据。 -
这个过程会反复进行,直到所有帧都被发送并编码完成。
所谓的编码就是通过avcodec_send_frame将帧数据传给编码器,在通过avcodec_receive_packet提取编码后的数据,然后将其存储下来
补充
设置AVCodecContext
一些参数
1 | auto c = avcodec_alloc_context3(codec); |
c->max_b_frames = 0;
max_b_frames
:控制 B 帧的数量。B 帧是双向预测帧,通常在提高编码效率时使用,但会增加延迟。将 max_b_frames
设置为 0
可以禁用 B 帧,从而减少延迟,虽然这样可能会增加文件大小或降低压缩效率。
int opt_re = av_opt_set(c->priv_data, "preset", "ultrafast", 0);
preset
:这是编码器的一种预设配置,用于控制编码速度和质量的平衡。ultrafast
是一个选项,表示最快的编码速度,但质量和压缩率可能会较低。其他常见的预设包括 fast
、medium
、slow
等。
具体设置可以看XCodec中的SetOpt()
,里面有介绍。