视频编码
void Class_mp4io::write_3D_buffer_to_mp4(const char filename[], unsigned char * buffer)
{
AVFrame * pFrame = NULL;//RAVFrame结构体一般用于存储原始数据(即非压缩数据,例如对视频来说是YUV,RGB,对音频来说是PCM),此外还包含了一些相关的信息。比如说,解码的时候存储了宏块类型表,QP表,运动矢量表等数据。编码的时候也存储了相关的数据。因此在使用FFMPEG进行码流分析的时候
AVDictionary *params = NULL;//存关键字和值
AVFormatContext *pFmtCtx = NULL;//是API中直接接触到的结构体,该文件中包含了多路流,包括音频流、视频流、字幕流等
struct SwsContext *sws = NULL;//主要用于视频图像的转换,比如格式转换
try
{
using std::endl;
using std::cout;
//no warning no error notice
av_log_set_level(AV_LOG_QUIET);//这个函数是用来设置日志的标准, 其意义是指: 设置日志打印的标准, 高于这个标准的将不会打印
TRY(filename);
av_register_all();//初始化所有组件,只有调用了该函数,才能使用复用器和编解码器
size_t width = shape[0];
size_t height = shape[1];
size_t dirNum = shape[2];
//-nostats
//av_dict_set(¶ms, "nostats","", 0);
av_dict_set(¶ms, "preset", "slow", 0);
av_dict_set(¶ms, "crf", crfInClass, 0);
av_dict_set(¶ms, "tune", "film", 0);//tune的值有: film: 电影、真人类型;
av_dict_set(¶ms, "nostats", "yes", 0);
//alloc "pFmtCtx" and guess "pFmtCtx->oformat" by ext
AVTRY(avformat_alloc_output_context2(&pFmtCtx, NULL, NULL, filename), "Failed to detect output file format from the file name.");//初始化输出码流的AVFormatContext
AVCodec *pCodec = NULL;//存储编解码器信息的结构体
TRY(pCodec = avcodec_find_encoder(pFmtCtx->oformat->video_codec));//用于查找FFmpeg的编码器
/***********************************************/
//anthor method
/*pFmtCtx = avformat_alloc_context();
AVOutputFormat *oFmt = av_guess_format(NULL, outPath.c_str(), NULL);
pFmtCtx -> oformat = oFmt;*/
/***********************************************/
TRY(avformat_new_stream(pFmtCtx, pCodec));//创建输出码流的AVStream。
//´ò¿ªÊä³öÁ÷
AVTRY(avio_open(&pFmtCtx->pb, filename, AVIO_FLAG_WRITE), "Failed to open output file.");//该函数用于打开FFmpeg的输入输出文件
//avformat_write_header(pFmtCtx, NULL);
//is necessary?
//********************************************************
pFmtCtx->streams[0]->codec->codec = pCodec;
pFmtCtx->streams[0]->codec->pix_fmt = pCodec->pix_fmts[0];
pFmtCtx->streams[0]->codec->width = width;
pFmtCtx->streams[0]->codec->height = height;
pFmtCtx->streams[0]->codec->time_base.num = 1;
pFmtCtx->streams[0]->codec->time_base.den = 25;
//***********************************************************
//open stream[0]'s codecContext
AVTRY(avcodec_open2(pFmtCtx->streams[0]->codec, pCodec, ¶ms), "Failed to initialize encoder.");//构造AVCodecContext结构体,打开编码器。
pFrame = av_frame_alloc();//这个函数只是分配AVFrame结构体,但data指向的内存并没有分配,需要我们指定。
TRY(av_image_alloc(pFrame->data, pFrame->linesize, width, height, pFmtCtx->streams[0]->codec->pix_fmt, 1));//此函数的功能是按照指定的宽、高、像素格式来分析图像内存,返回值:所申请的内存空间的总大小。如果是负值,表示申请失败
AVTRY(avformat_write_header(pFmtCtx, ¶ms), "Failed to write header.");//写视频文件头
if (pFrame->pts == AV_NOPTS_VALUE)
{
pFrame->pts = 0;
}
AVPixelFormat sorcePixFormat = AV_PIX_FMT_NONE;
switch (color_bytes)
{
case 1:
sorcePixFormat = AV_PIX_FMT_GRAY8;
break;
case 2:
sorcePixFormat = AV_PIX_FMT_GRAY16LE;
break;
default:
break;
}
sws = sws_getContext(width, height, sorcePixFormat,
width, height, pFmtCtx->streams[0]->codec->pix_fmt,
SWS_BICUBIC, NULL, NULL, NULL);//初始化一个SwsContext, 函数可以看做是初始化函数
size_t planeStride = width * height * color_bytes;
for (int i = 0; i < dirNum; i++, pFrame->pts++)
{
// //Nathan
const uint8_t* plane = (uint8_t*)buffer + planeStride * i; //
const uint8_t* slice[4] = { plane + color_bytes * 0,
plane + color_bytes * 1,
plane + color_bytes * 2,
plane + color_bytes * 3 };
size_t linestride = color_bytes * width;
const int stride[4] = { linestride,linestride,linestride,linestride };
int got_packet = 0;
//av_init_packet(&p);
AVPacket p = { 0 };
sws_scale(sws, slice, stride, 0, height, pFrame->data, pFrame->linesize); //
AVTRY(avcodec_encode_video2(pFmtCtx->streams[0]->codec, &p, pFrame, &got_packet), pFrame ? "Failed to encode frame." : "Failed to encode terminating frame.");//编码一帧视频。即将AVFrame(存储YUV像素数据)编码为AVPacket(存储H.264等格式的码流数据)。
if (got_packet)
{
if ((&p)->pts != AV_NOPTS_VALUE)
(&p)->pts = av_rescale_q((&p)->pts, pFmtCtx->streams[0]->codec->time_base, pFmtCtx->streams[0]->time_base);//是用来把时间戳从一个时基调整到另外一个时基时候用的函数。它基本的动作是计算a*b/c,
if ((&p)->dts != AV_NOPTS_VALUE)
(&p)->dts = av_rescale_q((&p)->dts, pFmtCtx->streams[0]->codec->time_base, pFmtCtx->streams[0]->time_base);
AVTRY(av_write_frame(pFmtCtx, &p), "Failed to write packet.");
av_packet_unref(&p); //???
}
}
//flush
if (pFmtCtx->streams[0]->codec->codec->capabilities & CODEC_CAP_DELAY)// AVCodecContext codec 第二个AVCodec codec
{
AVPacket temp = { 0 };
int got_packet_temp = 1;
//av_init_packet(&temp);
while (got_packet_temp)
{
avcodec_encode_video2(pFmtCtx->streams[0]->codec, &temp, NULL, &got_packet_temp);
if (got_packet_temp)
{
if ((&temp)->pts != AV_NOPTS_VALUE)
(&temp)->pts = av_rescale_q((&temp)->pts, pFmtCtx->streams[0]->codec->time_base, pFmtCtx->streams[0]->time_base);
if ((&temp)->dts != AV_NOPTS_VALUE)
(&temp)->dts = av_rescale_q((&temp)->dts, pFmtCtx->streams[0]->codec->time_base, pFmtCtx->streams[0]->time_base);
av_write_frame(pFmtCtx, &temp);
av_packet_unref(&temp); //p±»Ä¨µôÁË
}
}
}
av_write_trailer(pFmtCtx);
}
//*********************³öÏÖÒì³£µÄÇé¿öÏÂÊÍ·Å×ÊÔ´
catch (Exception_mp4io e)
{
if (params)
{
av_dict_free(¶ms);
}
if (pFrame)
{
av_free(pFrame);
}
if (pFmtCtx)
{
if (pFmtCtx->streams[0]->codec)
{
avcodec_close(pFmtCtx->streams[0]->codec);
}
if (pFmtCtx->pb)
{
avio_close(pFmtCtx->pb);
}
avformat_free_context(pFmtCtx);
}
if (sws)
{
sws_freeContext(sws);
}
}//end of catch
//********************************
//normal release
//*****************************ûÓÐÒì³£µÄÇé¿öÏÂÊÍ·Å×ÊÔ´
if (params)
{
av_dict_free(¶ms);
}
if (pFrame)
{
av_free(pFrame);
}
if (pFmtCtx)
{
if (pFmtCtx->streams[0]->codec)
{
avcodec_close(pFmtCtx->streams[0]->codec);
}
if (pFmtCtx->pb)
{
avio_close(pFmtCtx->pb);
}
avformat_free_context(pFmtCtx);
}
if (sws)
{
sws_freeContext(sws);
}
//**********************************************
}