1、yuv->H264经过编码后可以明显缩小视频文件的体积,例如我们经常看到的MP4文件其实就是由H264格式的视频文件和aac音频格式文件打包而成。
2、整个编码流程:
网上的一个关于AVFormatContext结构体的一张图,比较直观:
3、编码实现:
extern "C" {
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavfilter/avfilter.h"
#include "libavutil/avutil.h"
#include "libavutil/ffversion.h"
#include <libavutil/imgutils.h>
#include "libswresample/swresample.h"
#include "libswscale/swscale.h"
#include "libavdevice/avdevice.h"
#include "libpostproc/postprocess.h"
}
void MainWindow::yuvToh264()
{
AVFormatContext* pFmtCtx = nullptr;
AVCodec* pCodec = nullptr;
AVStream* pVStream = nullptr;
AVCodecContext* pCodecCtx = nullptr;
AVFrame* pSrcFrame = nullptr;
uint8_t* pSrcBuffer = nullptr;
AVPacket* pkt = av_packet_alloc();
do
{
QString strOutFile = "out.h264";
//创建输出码流的上下文并初始化
if(avformat_alloc_output_context2(&pFmtCtx, nullptr, nullptr, strOutFile.toStdString().c_str()))
{
qDebug() << "cannot alloc output file context." << endl;
break;
}
//打开输出文件
if(avio_open(&pFmtCtx->pb, strOutFile.toStdString().c_str(), AVIO_FLAG_READ_WRITE))
{
qDebug() << "cannot open output file." << endl;
break;
}
//为AVFormatContext上下文添加视频流
if(nullptr == (pVStream = avformat_new_stream(pFmtCtx, nullptr)))
{
qDebug() << "cannot create AVStream." << endl;
break;
}
pVStream->time_base.den = 25;
pVStream->time_base.num = 1;
//查找编码器
if(nullptr == (pCodec = const_cast<AVCodec*>(avcodec_find_encoder(pFmtCtx->oformat->video_codec))))
{
qDebug() << "connot find AVCodec." << endl;
break;
}
//设置视频流的编码器参数
AVCodecParameters* pCodecParam = pFmtCtx->streams[pVStream->index]->codecpar;
if(nullptr == pCodecParam)
{
qDebug() << "AVCodecParameters is nullptr." << endl;
break;
}
pCodecParam->codec_type = AVMEDIA_TYPE_VIDEO;
pCodecParam->width = 352;
pCodecParam->height = 288;
//根据编码器参数设置编码器内容
if(nullptr == (pCodecCtx = avcodec_alloc_context3(pCodec)))
{
qDebug() << "connot create CodecContext." << endl;
break;
}
if(avcodec_parameters_to_context(pCodecCtx, pCodecParam) < 0)
{
qDebug() << "set CodecContext failed." << endl;
break;
}
//编码器的ID
pCodecCtx->codec_id = pFmtCtx->oformat->video_codec;
//编码器的类型
pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
//像素格式
pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
//编码视频每一帧的宽、高
pCodecCtx->width = 352;
pCodecCtx->height = 288;
//根据下面两个参数可以将PTS转化为时间
pCodecCtx->time_base.num = 1;
pCodecCtx->time_base.den = 25;
//平均比特率
pCodecCtx->bit_rate = 400000;
//一组图片的数量
pCodecCtx->gop_size = 12;
if (pCodecCtx->codec_id == AV_CODEC_ID_H264) {
pCodecCtx->qmin = 10;
pCodecCtx->qmax = 51;
pCodecCtx->qcompress = (float)0.6;
}
if (pCodecCtx->codec_id == AV_CODEC_ID_MPEG2VIDEO)
pCodecCtx->max_b_frames = 2;
if (pCodecCtx->codec_id == AV_CODEC_ID_MPEG1VIDEO)
pCodecCtx->mb_decision = 2;
//打开编码器
if(avcodec_open2(pCodecCtx, pCodec, nullptr) < 0)
{
qDebug() << "open encoder failed." << endl;
break;
}
av_dump_format(pFmtCtx,0,strOutFile.toStdString().c_str(),1);
//创建需要编码的一帧(并没有申请内存),并绑定内存,
if(nullptr == (pSrcFrame = av_frame_alloc()))
{
qDebug() << "alloc frame failed." << endl;
break;
}
pSrcFrame->width = 352;
pSrcFrame->height = 288;
pSrcFrame->format = pCodecCtx->pix_fmt;
int nSize = av_image_get_buffer_size(pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, 1);
pSrcBuffer = static_cast<uint8_t*>(av_malloc(static_cast<size_t>(nSize)));
av_image_fill_arrays(pSrcFrame->data, pSrcFrame->linesize,
pSrcBuffer, pCodecCtx->pix_fmt,
pCodecCtx->width, pCodecCtx->height, 1);
//写头文件
avformat_write_header(pFmtCtx, nullptr);
//循环编码每一帧
int y_Size = pCodecCtx->width * pCodecCtx->height;
av_new_packet(pkt, (int)(nSize * 3));
FILE *in_file = fopen("D:\\QT\\Project\\ffmpeg01\\Debug\\debug\\akiyo_cif(352_288).yuv", "rb");
if (!in_file) {
qDebug() << "cannot open yuv file." << endl;
break;
}
for (int i = 0; i < 300; i++) {
if(fread(pSrcBuffer, 1, (unsigned long)(y_Size * 3 / 2), in_file) <= 0)
{
qDebug() << "cannot read yuv file." << endl;
}
else if(feof(in_file))
{
break;
}
pSrcFrame->data[0] = pSrcBuffer; // Y
pSrcFrame->data[1] = pSrcBuffer + y_Size; // U
pSrcFrame->data[2] = pSrcBuffer + y_Size * 5 / 4; // V
pSrcFrame->pts = i;
if(avcodec_send_frame(pCodecCtx, pSrcFrame) >= 0)
{
while(avcodec_receive_packet(pCodecCtx, pkt) >= 0)
{
pkt->stream_index = pVStream->index;
av_packet_rescale_ts(pkt, pCodecCtx->time_base, pVStream->time_base);
pkt->pos = -1;
int ret = -1;
if((ret = av_interleaved_write_frame(pFmtCtx, pkt)) < 0)
{
qDebug() << "encode error." << endl;
}
av_packet_unref(pkt);
}
}
}
//写文件尾
av_write_trailer(pFmtCtx);
fclose(in_file);
}while(false);
av_packet_free(&pkt);
av_free(pSrcFrame);
av_free(pSrcBuffer);
if(pCodecCtx)
{
avcodec_close(pCodecCtx);
avcodec_free_context(&pCodecCtx);
}
//销毁输出码流上下文
if(pFmtCtx)
{
if(pFmtCtx->pb)
{
avio_close(pFmtCtx->pb);
}
avformat_free_context(pFmtCtx);
}
}
4、效果:
(1)、使用专用播放器播放转码前的yuv格式文件:
(2)、使用ffplayer播放转码后的H264文件:
下面附上YUV格式文件下载地址:http://trace.eas.asu.edu/yuv/
网站下载速度太慢,急需的话可以去csdn搜索下载。