本文代码是基于雷霄骅的《最简单的基于FFMPEG的视频编码器(YUV编码为H.264)》一文修改得来的。
由于雷霄骅原文中采用的API在新版本ffmpeg中,关健的编码API发生了变化,我在此基础上,参考了ffmpeg3.x的sample和header修改后得到。
主要的变化是在编码API上,原来是通过avcodec_encode_video2()来完成编码的,现在编码API变为avcodec_send_frame()/avcodec_receive_packet()组合。
avcodec_send_frame()顾名词义就是发送一帧去编码。avcodec_receive_packet()是吐出来的编码包,写到文件中就是H.264数据了。
另外我也是初学者,对个别编码参数的含义还没有完全搞懂,还有就是在将所有帧编码写入文件后,最后为什么要flush一下,也还没有搞明白,相信以后我会知道答案的^_^
以下代码在vs 2013上跑通,ffmpeg来自:ffmpeg-20170525-b946bd8-win32-dev
extern "C"
{
#include "libavutil/opt.h"
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavutil/imgutils.h"
};
//encode one frame
static int encode(AVCodecContext *pCodecCtx, AVFrame *pFrame, AVPacket *pPkt, FILE *out_file) {
int got_packet = 0;
int ret = avcodec_send_frame(pCodecCtx, pFrame);
if (ret < 0) {
//failed to send frame for encoding
return -1;
}
while (!ret) {
ret = avcodec_receive_packet(pCodecCtx, pPkt);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
return 0;
}else if (ret < 0) {
//error during encoding
return -1;
}
printf("Write frame %d, size=%d\n", pPkt->pts, pPkt->size);
fwrite(pPkt->data, 1, pPkt->size, out_file);
av_packet_unref(pPkt);
}
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
AVFormatContext *pFormatCtx;
AVOutputFormat *pOutputFmt;
AVStream *pStream;
AVCodecContext *pCodecCtx;
AVCodec *pCodec;
AVPacket *pkt;
AVFrame *pFrame;
FILE *in_file = NULL, *out_file = NULL;
in_file = fopen("ds_480x272.yuv", "rb");
if (in_file == NULL){
printf("cannot open in file\n");
goto OUT;
}
int in_w = 480, in_h = 272;
int nFrameNum = 100;
out_file = fopen("out.h264", "wb");
if (out_file == NULL) {
printf("cannot create out file\n");
goto OUT;
}
uint8_t* pFrameBuf = NULL;
int frame_buf_size = 0;
int y_size = 0;
int nEncodedFrameCount = 0;
uint8_t endcode[] = { 0, 0, 1, 0xb7 };
av_register_all();
pFormatCtx = avformat_alloc_context();
pOutputFmt = av_guess_format(NULL, "test.h264", NULL);
pFormatCtx->oformat = pOutputFmt;
//除了以下方法,另外还可以使用avcodec_find_encoder_by_name()来获取AVCodec
pCodec = avcodec_find_encoder(pOutputFmt->video_codec);
if (!pCodec) {
//cannot find encoder
return -1;
}
pCodecCtx = avcodec_alloc_context3(pCodec);
if (!pCodecCtx) {
//failed get AVCodecContext
return -1;
}
pkt = av_packet_alloc();
if (!pkt){
return -1;
}
//pCodecCtx = pStream->codec;
pCodecCtx->codec_id = pOutputFmt->video_codec;
pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
pCodecCtx->width = in_w;
pCodecCtx->height = in_h;
pCodecCtx->time_base.num = 1;
pCodecCtx->time_base.den = 25;
//pCodecCtx->time_base = (AVRational){ 1, 25 };
pCodecCtx->bit_rate = 400000; //???
pCodecCtx->gop_size = 250; //???
//pCodecCtx->framerate = (AVRational){ 25, 1 };
//H264
//pCodecCtx->me_range = 16; //???
//pCodecCtx->max_qdiff = 4; //???
//pCodecCtx->qcompress = 0.6; //???
pCodecCtx->qmin = 10; //???
pCodecCtx->qmax = 51; //???
//Optional Param
pCodecCtx->max_b_frames = 3;
//Set Option
AVDictionary *param = NULL;
//H.264
if (pCodecCtx->codec_id == AV_CODEC_ID_H264) {
av_dict_set(¶m, "preset", "slow", 0);
av_dict_set(¶m, "tune", "zerolatency", 0);
//av_dict_set(¶m, "profile", "main", 0);
}
//H.265
if (pCodecCtx->codec_id == AV_CODEC_ID_H265) {
av_dict_set(¶m, "preset", "ultrafast", 0);
av_dict_set(¶m, "tune", "zero-latency", 0);
}
if (avcodec_open2(pCodecCtx, pCodec, ¶m) < 0) {
//failed to open codec
return -1;
}
pFrame = av_frame_alloc();
if (!pFrame) {
fprintf(stderr, "Could not allocate the video frame data\n");
goto OUT;
}
pFrame->format = pCodecCtx->pix_fmt;
pFrame->width = pCodecCtx->width;
pFrame->height = pCodecCtx->height;
int ret = av_frame_get_buffer(pFrame, 32);
if (ret < 0) {
fprintf(stderr, "Could not allocate the video frame data\n");
goto OUT;
}
frame_buf_size = av_image_get_buffer_size(pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, 1);
pFrameBuf = (uint8_t*)av_malloc(frame_buf_size);
av_image_fill_arrays(pFrame->data, pFrame->linesize,
pFrameBuf, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, 1);
y_size = pCodecCtx->width * pCodecCtx->height;
for (int i = 0; i < nFrameNum; i++) {
//Read raw YUV data
if (fread(pFrameBuf, 1, y_size * 3 / 2, in_file) <= 0) {
//failed to read raw YUV data
return -1;
}
else if (feof(in_file)) {
break;
}
ret = av_frame_make_writable(pFrame);
if (ret < 0) {
goto OUT;
}
pFrame->data[0] = pFrameBuf; //Y
pFrame->data[1] = pFrameBuf + y_size; //U
pFrame->data[2] = pFrameBuf + y_size*5/4; //V
//PTS
pFrame->pts = i;
//encode
encode(pCodecCtx, pFrame, pkt, out_file);
}
//flush the encoder
encode(pCodecCtx, NULL, pkt, out_file);
//add sequence end code to have a real MPEG file
fwrite(endcode, 1, sizeof(endcode), out_file);
avcodec_free_context(&pCodecCtx);
avformat_free_context(pFormatCtx);
av_frame_free(&pFrame);
av_packet_free(&pkt);
av_free(pFrameBuf);
OUT:
if (out_file)
fclose(out_file);
if (in_file)
fclose(in_file);
system("pause");
return 0;
}