前言
測試環境:
- 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;
注意:本文為個人記錄,新手照搬可能會出現各種問題,請謹慎使用
碼字不易,如果這篇博客對你有幫助,麻煩點贊收藏,非常感謝!有不對的地方