- By刘立博
- 2021-01-19 22:30:21
- 4859人已阅读
程序流程设计
初始化编码器
初始化编码器需要使用以下3个函数:
(1)设置编码器 avcodec_find_encoder
需要传入编码器ID,如:AV_CODEC_ID_H264,返回编码器指针
(2)初始化、设置编码器上下文 avcodec_alloc_context3
需要传入编码器指针,获取上下文后,我们需要设置上下文的基础属性,如:宽度(width)、高度(height)、帧数(time_base)、像素格式(pix_fmt)和使用多少个线程进行编码(thread_count)
编码器上下文常用设置:
设置视频宽度为1280
context->width = 1280;
设置视频高度为720
context->height = 720;
设置时间单位为30分之1秒
context->time_base = { 1,30 };
设置像素格式为 YUV420P
context->pix_fmt = AV_PIX_FMT_YUV420P;
设置线程数为4
context->thread_count = 4;
禁用B帧
context->max_b_frames = 0;
设置平均比特率为400KB
context->bit_rate = 400000;
设置CQP恒定质量为18
av_opt_set_int(context->priv_data, "qp", 18,0);
设置恒定速率因子为18
av_opt_set_int(context->priv_data, "crf", 18, 0);
设置约束编码VBV为2M
context->rc_max_rate = 20000000;
context->rc_buffer_size = 20000000 * 2;
(3)编码器初始化 avcodec_open2
需要传入编码器指针和编码器上下文
初始化AV FRAME、AV PACKET
AV FRAME用于装载构造出来的YUV帧数据,而AV PACKET用于装载编码器转换后的H264帧数据。
需要使用以下3个函数:
(1)初始化AV FRAME空间 av_frame_alloc
同时我们需要设置AV FRAME的宽度(width)、高度(height)和像素格式(pix_fmt)
(2)为AV FRAME分配缓冲区 av_frame_get_buffer
需要传入AV FRAME指针和对齐方式
(3)初始化AV PACKET空间
AV PACKET用于存放转码后的帧数据
H264转码
转码需要使用以下3个函数:
(1)将源视频帧发送至编码器 avcodec_send_frame
该函数需要传入编码器上下文和AV FRAME
*frame : 需要将构造出的YUV帧数据放入AV FRAME的data属性,设置AV FRAME的时序(pts)
*avctx : 编码器上下文
(2)获取转换为H264格式的PACKET数据 avcodec_receive_packet
该函数需要传入编码器上下文和用于接收H264帧数据的AV PACKET指针。需要注意的是,每次传入AV PACKET指针后,该函数都会开辟一块新空间用于存放H264数据,所以使用完AV PACKET后,需要将引用计数器减1,避免内存溢出。
(3)AV PACKET引用计数器减1 av_packet_unref
使用完该帧的数据后,需要调用该函数将引用计数器减1
一个栗子:YUV转H264并写入文件
使用4个线程逐帧将YUV编码的AV FRAME帧转换为H264编码的AV PACKET帧同时写入至文件:output.h264
#include <iostream>
#include <fstream>
using namespace std;
extern "C"{
#include <libavcodec/avcodec.h>
}
#pragma comment(lib,"avcodec.lib")
#pragma comment(lib,"avutil.lib")
int main(int argc, char* argv[])
{
//打开输出文件流
ofstream outputFile;
outputFile.open("output.h264", ios::binary);
//设置编码器
AVCodec* encoder = avcodec_find_encoder(AV_CODEC_ID_H264);
//初始化并设置编码器上下文
AVCodecContext* context = avcodec_alloc_context3(encoder);
context->width = 1280;
context->height = 720;
context->time_base = { 1,30 };
context->pix_fmt = AV_PIX_FMT_YUV420P;
context->thread_count = 4;
//编码器初始化
avcodec_open2(context, encoder, NULL);
//初始化并设置 AV FRAME
AVFrame* frame = av_frame_alloc();
frame->width = context->width;
frame->height = context->height;
frame->format = context->pix_fmt;
//为AV FRAME分配缓冲区
av_frame_get_buffer(frame, 0);
//初始化AV PACKET
AVPacket* packet = av_packet_alloc();
//构造3000帧YUV数据并将其转换为H264
for (int i = 0; i < 3000; i++)
{
//构造Y
for (int y = 0; y < frame->height; y++)
{
for (int x = 0; x < frame->width; x++)
{
frame->data[0][y * frame->linesize[0] + x] = x + y + i * 3;
}
}
//构造UV
for (int y = 0; y < frame->height / 2; y++)
{
for (int x = 0; x < frame->width / 2; x++)
{
frame->data[1][y * frame->linesize[1] + x] = 128 + y + i * 2;
frame->data[2][y * frame->linesize[2] + x] = 64 + x + i * 5;
}
}
frame->pts = i;
//将生成出来的YUV帧发送至编码器
int sendResult = avcodec_send_frame(context, frame);
while (sendResult >= 0)
{
//获取转为H264的packet数据
int receiveResult = avcodec_receive_packet(context, packet);
//如果暂无处理完成的packet数据,则继续发送其他YUV帧
if (receiveResult == AVERROR(EAGAIN) || receiveResult == AVERROR_EOF) break;
outputFile.write((char*)packet->data, packet->size);
//释放packet中的数据
av_packet_unref(packet);
}
}
//释放
outputFile.close();
av_packet_free(&packet);
av_frame_free(&frame);
avcodec_free_context(&context);
}