本文的主要内容: 使用H.264编码对YUV视频进行压缩。
如果是命令行的操作,非常简单。
ffmpeg -s 640x480 -pix_fmt yuv420p -i in.yuv -c:v libx264 out.h264 # -c:v libx264是指定使用libx264作为编码器
接下来主要讲解如何通过代码的方式使用H.264编码,用到avcodec、avutil两个库。
类的声明
#define __STDC_CONSTANT_MACROS extern "C" { #include <libavutil/imgutils.h> } typedef struct { const char *filename; int width; int height; AVPixelFormat pixFmt; int fps; } VideoEncodeSpec; class FFmpegs { public: FFmpegs(); static void h264Encode(VideoEncodeSpec &in, const char *outFilename); };
类的使用
VideoEncodeSpec in; in.filename = "/Users/muzi/Desktop/out.yuv"; in.width = 320; in.height = 240; in.fps = 30; in.pixFmt = AV_PIX_FMT_YUV420P; FFmpegs::h264Encode(in, "/Users/muzi/Desktop/out.h264");
宏定义
extern "C" { #include <libavutil/avutil.h> #include <libavcodec/avcodec.h> } #define ERROR_BUF(ret) \ char errbuf[1024]; \ av_strerror(ret, errbuf, sizeof(errbuf));
变量定义
// 文件 QFile inFile(in.filename); QFile outFile(outFilename); // 一帧图片的大小 int imgSize = av_image_get_buffer_size(in.pixFmt, in.width, in.height, 1); // 返回结果 int ret = 0; // 编码器 AVCodec *codec = nullptr; // 编码上下文 AVCodecContext *ctx = nullptr; // 存放编码前的数据(yuv) AVFrame *frame = nullptr; // 存放编码后的数据(h264) AVPacket *pkt = nullptr;
初始化
// 获取编码器 codec = avcodec_find_encoder_by_name("libx264"); if (!codec) { qDebug() << "encoder not found"; return; } // 检查输入数据的采样格式 if (!check_pix_fmt(codec, in.pixFmt)) { qDebug() << "unsupported pixel format" << av_get_pix_fmt_name(in.pixFmt); return; } // 创建编码上下文 ctx = avcodec_alloc_context3(codec); if (!ctx) { qDebug() << "avcodec_alloc_context3 error"; return; } // 设置yuv参数 ctx->width = in.width; ctx->height = in.height; ctx->pix_fmt = in.pixFmt; // 设置帧率(1秒钟显示的帧数in.fps) ctx->time_base = {1, in.fps}; // 打开编码器 ret = avcodec_open2(ctx, codec, nullptr); if (ret < 0) { ERROR_BUF(ret); qDebug() << "avcodec_open2 error" << errbuf; goto end; } // 创建AVFrame frame = av_frame_alloc(); if (!frame) { qDebug() << "av_frame_alloc error"; goto end; } frame->width = ctx->width; frame->height = ctx->height; frame->format = ctx->pix_fmt; frame->pts = 0; // 利用width、height、format创建缓冲区 ret = av_image_alloc(frame->data, frame->linesize, in.width, in.height, in.pixFmt, 1); if (ret < 0) { ERROR_BUF(ret); qDebug() << "av_frame_get_buffer error" << errbuf; goto end; } // 创建AVPacket pkt = av_packet_alloc(); if (!pkt) { qDebug() << "ac_packet_alloc error"; goto end; }
编码
// 打开文件 if (!inFile.open(QFile::ReadOnly)) { qDebug() << "file open error" << in.filename; goto end; } if (!outFile.open(QFile::WriteOnly)) { qDebug() << "file open error" << outFilename; goto end; } // 读取数据到frame中 while ((ret = inFile.read((char *)frame->data[0], imgSize)) > 0) { // 进行编码 if (encode(ctx, frame, pkt, outFile) < 0) { goto end; } // 设置帧的序号 frame->pts++; } // 刷新缓冲区 encode(ctx, nullptr, pkt, outFile);
回收资源
// 关闭文件 inFile.close(); outFile.close(); // 释放资源 if (frame) { av_freep(&frame->data[0]); av_frame_free(&frame); } av_packet_free(&pkt); avcodec_free_context(&ctx);