前言
测试环境:
- ffmpeg的4.3.2自行编译版本
- windows环境
- qt5.12
使用H.264编码对YUV视频进行压缩
ffmpeg -s 640x480 -pix_fmt yuv420p -i in.yuv -c:v libx264 out.h264
-c:v libx264是指定使用libx264作为编码器
完整代码:
H264EncodeThread.h
#ifndef H264ENCODETHREAD_H
#define H264ENCODETHREAD_H#include <QObject>
#include <QThread>extern "C" {
#include <libavutil/avutil.h>
}typedef struct {const char *filename;int width;int height;AVPixelFormat pixFmt;int fps;
} VideoEncodeSpec;class H264EncodeThread : public QThread
{Q_OBJECT
public:explicit H264EncodeThread(QObject *parent = nullptr);~H264EncodeThread();static void h264Encode(VideoEncodeSpec &in,const char *outFilename);signals:// QThread interface
protected:virtual void run() override;
};#endif // H264ENCODETHREAD_H
H264EncodeThread.cpp
#include "h264encodethread.h"extern "C" {
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
#include <libavutil/imgutils.h>
}#include <QDebug>
#include <QFile>#define ERROR_BUF(ret) \char errbuf[1024]; \av_strerror(ret, errbuf, sizeof (errbuf));H264EncodeThread::H264EncodeThread(QObject *parent) : QThread(parent)
{// 当监听到线程结束时(finished),就调用deleteLater回收内存connect(this,&H264EncodeThread::finished,this,[=](){this->deleteLater();qDebug()<<"RecordPcmThread线程结束,线程指针被dlete";});
}H264EncodeThread::~H264EncodeThread()
{// 断开所有的连接disconnect();//强制关闭窗口时,线程也能安全关闭requestInterruption();wait();qDebug()<<"RecordPcmThread析构函数";
}// 检查像素格式
static int check_pix_fmt(const AVCodec *codec,enum AVPixelFormat pixFmt) {const enum AVPixelFormat *p = codec->pix_fmts;while (*p != AV_PIX_FMT_NONE) {if (*p == pixFmt) return 1;qDebug()<<*p;p++;}return 0;
}// 返回负数:中途出现了错误
// 返回0:编码操作正常完成
static int encode(AVCodecContext *ctx,AVFrame *frame,AVPacket *pkt,QFile &outFile) {// 发送数据到编码器int ret = avcodec_send_frame(ctx, frame);if (ret < 0) {ERROR_BUF(ret);qDebug() << "avcodec_send_frame error" << errbuf;return ret;}// 不断从编码器中取出编码后的数据while (true) {ret = avcodec_receive_packet(ctx, pkt);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {// 继续读取数据到frame,然后送到编码器return 0;} else if (ret < 0) { // 其他错误return ret;}// 成功从编码器拿到编码后的数据// 将编码后的数据写入文件outFile.write((char *) pkt->data, pkt->size);// 释放pkt内部的资源av_packet_unref(pkt);}
}void H264EncodeThread::h264Encode(VideoEncodeSpec &in, const char *outFilename)
{// 文件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;// uint8_t *buf = nullptr;// 获取编码器//codec = avcodec_find_encoder_by_name("libx264");codec = avcodec_find_encoder(AV_CODEC_ID_H264);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;//手动设置gop的数量//ctx->gop_size=5;// 设置帧率(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;}// 创建AVFrameframe = 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;}// 创建输入缓冲区(方法2)
// buf = (uint8_t *) av_malloc(imgSize);
// ret = av_image_fill_arrays(frame->data, frame->linesize,
// buf,
// in.pixFmt, in.width, in.height, 1);
// if (ret < 0) {
// ERROR_BUF(ret);
// qDebug() << "av_image_fill_arrays error" << errbuf;
// goto end;
// }
// qDebug() << buf << frame->data[0];// 创建输入缓冲区(方法3)
// ret = av_frame_get_buffer(frame, 0);
// if (ret < 0) {
// ERROR_BUF(ret);
// qDebug() << "av_frame_get_buffer error" << errbuf;
// goto end;
// }// 创建AVPacketpkt = av_packet_alloc();if (!pkt) {qDebug() << "av_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);end:// 关闭文件inFile.close();outFile.close();// av_freep(&buf);// 释放资源if (frame) {av_freep(&frame->data[0]);
// av_free(frame->data[0]);
// frame->data[0] = nullptr;av_frame_free(&frame);}av_packet_free(&pkt);avcodec_free_context(&ctx);qDebug() << "线程正常结束";
}void H264EncodeThread::run()
{VideoEncodeSpec in;in.filename = "E:/media/out-yuv420p.yuv";in.width = 640;in.height = 480;in.fps = 30;in.pixFmt = AV_PIX_FMT_YUV420P;h264Encode(in, "E:/media/out-yuv420p.h264");
}
线程调用:
void MainWindow::on_pushButton_h264_encode_clicked()
{m_pH264EncodeThread=new H264EncodeThread(this);m_pH264EncodeThread->start();
}
注意:.h文件中提前声明了以下全局变量
H264EncodeThread *m_pH264EncodeThread=nullptr;
注意:本文为个人记录,新手照搬可能会出现各种问题,请谨慎使用
码字不易,如果这篇博客对你有帮助,麻烦点赞收藏,非常感谢!有不对的地方