一般编码流程:
1、创建编码器上下文并设置编码器参数(编码格式、时间基、编码器类型、最大最小质量、宽高等等)
2、寻找编码器
3、打开编码器
3、获取原始YUV或RGB数据
4、编码:avcodec_send_frame()、avcodec_receive_packet()
5、输出:保存为文件或封装为其他格式(mp4、flv、avi等)
本程序(将YUV文件编码为H264)流程:
1、创建输出码流的上下文AVFormatContext,并初始化
2、打开输出文件:avio_open2()
3、创建新流:avformat_new_stream() //用于保存视频流信息,一个完整的视频文件包含多个流信息:视频流、音频流、字幕流等
4、创建编码器上下文并设置编码器参数
5、查找编码器并打开编码器
6、写入文件头信息:avformat_write_header()
7、打开输入文件
8、循环读取输入文件的yuv值,并进行编码;编码成功写入文件:av_write_frame()
9、对编码器中剩余数据编码
10、写入文件尾信息:av_write_trailer()
11、释放资源
demo
/*
* 编码
* 将yuv视频文件按h264重新编码
*/
#include
using namespace std;
extern "C"
{
#include
#include
#include
#include
#include "libavutil/opt.h"
#include "libavutil/imgutils.h"
};
const char* outfile = "1.h264";
const char* infile = "output.yuv";
int main(int argc, char** argv)
{
AVFormatContext* afc = avformat_alloc_context();
AVOutputFormat* outformat;
outformat = av_guess_format(NULL, outfile, NULL);
afc->oformat = outformat;
if (avio_open2(&afc->pb, outfile, AVIO_FLAG_READ_WRITE, NULL, NULL) < 0)
{
cout << "open outfile error" << endl;
return 0;
}
AVStream* ast = avformat_new_stream(afc, NULL); //创建新流
cout << "ast.codecid = " << ast->codec->codec_id<< endl;
if (ast == NULL)
{
cout << "create stream error" << endl;
return 0;
}
AVCodecContext* acc = ast->codec;
acc->codec_id = afc->oformat->video_codec;
acc->codec_type = AVMEDIA_TYPE_VIDEO;
acc->pix_fmt = AV_PIX_FMT_YUV420P;
acc->width = 720;
acc->height = 480;
acc->bit_rate = 480000;
acc->gop_size = 25;
acc->time_base.num = 1;
acc->time_base.den = 25;
acc->qmin = 10;
acc->qmax = 30;
acc->max_b_frames = 3;
AVDictionary* para = 0;
if (acc->codec_id == AV_CODEC_ID_H264)
{
av_dict_set(¶, "preset", "slow", 0);
av_dict_set(¶, "tune", "zerolatency", 0);
}
//av_dump_format(afc, 0, outfile, 1);//输出文件信息
avformat_write_header(afc, NULL);
AVCodec* codec = avcodec_find_encoder(acc->codec_id);
if (!codec)
{
cout << "find codec error ,codec_id = " << acc->codec_id << endl;
return 0;
}
if (avcodec_open2(acc, codec, ¶) < 0)
{
cout << "open codec error" << endl;
return 0;
}
AVFrame* frame = av_frame_alloc();
int picsize = av_image_get_buffer_size(acc->pix_fmt, acc->width, acc->height, 1);
uint8_t* picbuf;
picbuf = (uint8_t*)av_malloc(picsize);
av_image_fill_arrays(frame->data, frame->linesize, picbuf, AV_PIX_FMT_YUV420P, acc->width, acc->height, 1); //填充像素数据缓冲区,没有这步会导致sws_scale()失败
AVPacket pkt;
av_new_packet(&pkt, picsize); //初始化pkt,并给data字段分配空间
int ysize = acc->width * acc->height;
FILE* inf = fopen(infile, "rb");
//FILE* f = fopen(outfile, "wb");
int count = 0;
int curr = 1;
while (1)
{
if (fread(picbuf, 1, ysize * 3 / 2, inf) <= 0)
{
cout << "read finsh" << endl;
break;
}
else if (feof(inf))
break;
frame->data[0] = picbuf;
frame->data[1] = picbuf + ysize;
frame->data[2] = picbuf + ysize * 5 / 4;
frame->pts = count* (ast->time_base.den) / ((ast->time_base.num) * 25); //pts:显示时间戳
count++;
int ret = avcodec_send_frame(acc, frame);
while (ret >= 0)
{
ret = avcodec_receive_packet(acc, &pkt);
if (ret == 0) //编码成功
{
pkt.stream_index = ast->index;
int er = av_write_frame(afc, &pkt);//将编码后的数据包写入文件
//int er = fwrite(pkt.data, 1, pkt.size, f);//也可以使用fwrite()实现 将编码后的数据包写入文件
cout << "write frame is " << curr << endl;
curr++;
if (er < 0)
{
cout << "write curr frame error" << endl;
return -1;
}
}
else if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) //接收到的数据无效 需要重新读入
{
cout << "receive video frame error, need again" << endl;
break;
}
else
return -1;
}
}
int codecnum = 0;
//对编码器中的剩余数据编码,注意这里和前面不一样的地方avcodec_send_frame()传入的fram为NULL
ret = avcodec_send_frame(acc, NULL);
while (ret >= 0)
{
ret = avcodec_receive_packet(acc, &pkt);
if (ret == 0) //编码成功
{
pkt.stream_index = ast->index;
int er = av_write_frame(afc, &pkt);
cout << "write codecContext frame is " << codecnum << endl;
codecnum++;
if (er < 0)
{
cout << "write codecnum frame error" << endl;
return -1;
}
}
else if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)//接收到的数据无效 需要重新读入
{
cout << "receive video frame error, need again" << endl;
break;
}
else
return -1;
}
cout << "encodec frame is " << curr - 1 << ", encodec codecnum is " << codecnum - 1 <codec);
av_free(frame);
av_free(picbuf);
}
avio_close(afc->pb);
av_packet_unref(&pkt);
av_frame_free(&frame);
avcodec_free_context(&acc);
avformat_free_context(afc);
fclose(inf);
return 0;
}
重要步骤解析:
outformat = av_guess_format(NULL, outfile, NULL);
afc->oformat = outformat;
av_guess_format()获取和参数最匹配的封装格式,这里的outfile为输出文件1.h264,所以最后的封装格式匹配的编码格式为H264
编码器的参数设置:
AVCodecContext* acc = ast->codec;
//编码器ID
acc->codec_id = afc->oformat->video_codec;
//编码器类型,这是是视频编码器
acc->codec_type = AVMEDIA_TYPE_VIDEO;
acc->pix_fmt = AV_PIX_FMT_YUV420P;
acc->width = 720;
acc->height = 480;
//平均码率,越大则文件越大
acc->bit_rate = 480000;
//一组图片中的图片数量,相当于两个I帧之间的间隔
acc->gop_size = 15;
//编码帧率,每秒多少帧。这里设置表示1秒25帧
acc->time_base.num = 1;
acc->time_base.den = 25;
//最小最大量化器
acc->qmin = 10;
acc->qmax = 30;
//最大B帧数
acc->max_b_frames = 0;
AVDictionary* para = 0;
if (acc->codec_id == AV_CODEC_ID_H264)
{
av_dict_set(¶, "preset", "slow", 0);
av_dict_set(¶, "tune", "zerolatency", 0);
}
av_dict_set()的详细使用可查看 AVDictionary各接口
preset 的参数主要调节编码速度和质量的平衡,有ultrafast、superfast、veryfast、faster、fast、medium、slow、slower、veryslow、placebo这10个选项,从快到慢。
tune 的参数主要配合视频类型和视觉优化的参数。tune的值有:
film: 电影、真人类型
animation: 动画
grain: 需要保留大量的grain时用
stillimage: 静态图像编码时使用
psnr: 为提高psnr做了优化的参数
ssim: 为提高ssim做了优化的参数
fastdecode: 可以快速解码的参数
zerolatency:零延迟,用在需要非常低的延迟的情况下
avformat_write_header(afc, NULL);
av_write_trailer(afc);
写入文件头尾信息,对于某些没有文件头的封装格式,不需要这一操作。如MPEG2TS