FFmpeg实战:录制Windows屏幕并用FFmpeg压缩成H.264/H.265并保存
一、概述
前面讲过【Qt+Windows录制屏幕并将屏幕录制的BGRA数据保存到本地】。本节就在这一篇的基础上加上【利用ffmpeg的SwsContext将BGRA数据转YUV420P】,并将转换好的YUV420P数据
通过H264/H265编码压缩,然后再存储到本地。
工具截图:

功能介绍:
1.视频格式可以选择要编码视频的编码格式,目前只能选择H264和H265
2.输入框中代表录制文件的存储路径
3.下面两个按钮是开始录制和停止录制
4.录制的宽高为全屏,也就是电脑屏幕的宽高
二、代码示例
- 选择编码格式
void GenerateH26XWindow::onVideoFormatChanged(int pos) { switch (pos) { case 0: codecId = AV_CODEC_ID_H264; break; case 1: codecId = AV_CODEC_ID_HEVC; break; } }
- 选择输出文件路径
void GenerateH26XWindow::choiceOutputFilePathFunc() { QString filter = "所有文件(*.*)"; //文件过滤器 QString filePath = QFileDialog::getSaveFileName(this, "选择文件输出目录", "", filter); this->outFilePath = filePath; this->ui.outputPathEdit->setText(this->outFilePath); }
- 开始录制
- 开始录屏
//开始录屏,开启后可得到屏幕宽高 TonyCaptureScreen::Get()->outFilePath = filePath; TonyCaptureScreen::Get()->Start();
- 实例化编码器,创建AVCodecContext并设置参数
int screenWidth = TonyCaptureScreen::Get()->getWidth(); int screenHeight = TonyCaptureScreen::Get()->getHeight(); //编码器 encoder = new YEncoder(); auto mContext = encoder->Create(codecId); mContext->width = screenWidth; mContext->height = screenHeight; encoder->set_c(mContext); encoder->SetOpt("crf", 18); //恒定速率因子(CRF)
- 打开编码器并初始化像素格式转换上下文
-
encoder->Open(); encoder->InitSwsCtx();
-
- 初始化文件写入磁盘工具,并开启写入线程
//文件写入工具 writer = BinaryFileWriter::Create(); writer->Init(filePath.toStdString()); // 创建并启动后台线程 renderThread = std::make_unique<std::thread>(&GenerateH26XWindow::Save, this);
- 保存方法的代码如下
void GenerateH26XWindow::Save() { int count = 0; auto frame = encoder->CreateFrame(); while (isSaveing) { std::list<char*> mList = TonyCaptureScreen::Get()->getScreenDataList(); if (!mList.empty()) { char* data = mList.front(); frame->pts = count; encoder->scale((uint8_t*)data, frame); auto pkt = encoder->Encode(frame); if (pkt) { writer->Write((char*)pkt->data, pkt->size); av_packet_free(&pkt); } mList.pop_front(); count++; } } //取出编码缓冲区中的数据,也就是最后几帧 auto pkts = encoder->End(); for (auto pkt : pkts) { writer->Write((char*)pkt->data, pkt->size); av_packet_free(&pkt); } encoder->set_c(nullptr); if (frame) { av_frame_free(&frame); } qDebug() << "数据保存结束"; }
- 保存方法的代码如下
- 开始录屏
- 停止录制
void GenerateH26XWindow::closeEvent(QCloseEvent* event) { event->accept(); // 接受关闭事件(允许关闭) if (isClickedRecord) { TonyCaptureScreen::Get()->Stop(); isSaveing = false; if (renderThread && renderThread->joinable()) { renderThread->join(); } if (writer) { writer->Close(); writer = nullptr; } if (encoder) { encoder->Release(); delete encoder; encoder = nullptr; } } qDebug() << "窗口已关闭"; }
5.编解码封装工具类
#include "YEncoder.h" #include <QDebug> extern "C" { #include "libavcodec/avcodec.h" #include "libswscale/swscale.h" #include "libavutil/avutil.h" #include "libavutil/opt.h" } /// <summary> /// 输出错误日志 /// </summary> /// <param name="err"></param> static void PrintErr(int err) { char buf[1024] = { 0 }; av_strerror(err, buf, sizeof(buf) - 1); qDebug() << buf; } ////////////////////////////////////////// /// 创建编码上下文 /// @para codec_id 编码器ID号,对应ffmpeg /// @return 编码上下文 ,失败返回nullptr AVCodecContext* YEncoder::Create(int codec_id) { //1 找到编码器 auto codec = avcodec_find_encoder((AVCodecID)codec_id); if (!codec) { qDebug() << "avcodec_find_encoder failed!" << codec_id << endl; return nullptr; } //创建上下文 auto c = avcodec_alloc_context3(codec); if (!c) { qDebug() << "avcodec_alloc_context3 failed!" << codec_id << endl; return nullptr; } //设置参数默认值 c->time_base = { 1,25 }; c->pix_fmt = AV_PIX_FMT_YUV420P; //c->pix_fmt = AV_PIX_FMT_RGBA; c->thread_count = 16; return c; } ////////////////////////////////////////// /// 设置对象的编码器上下文 上下文传递到对象中,资源由YEncoder维护 /// 加锁 线程安全 /// @para c 编码器上下文 如果mContext不为nullptr,则先清理资源 void YEncoder::set_c(AVCodecContext* c) { std::unique_lock<std::mutex>lock(mMutex); if (mContext) { avcodec_free_context(&mContext); } this->mContext = c; } bool YEncoder::SetOpt(const char* key, const char* val) { std::unique_lock<std::mutex>lock(mMutex); if (!mContext)return false; auto re = av_opt_set(mContext->priv_data, key, val, 0); if (re != 0) { qDebug() << "av_opt_set failed!"; PrintErr(re); } return true; } bool YEncoder::SetOpt(const char* key, int val) { std::unique_lock<std::mutex>lock(mMutex); if (!mContext)return false; auto re = av_opt_set_int(mContext->priv_data, key, val, 0); if (re != 0) { qDebug() << "av_opt_set failed!"; PrintErr(re); } return true; } ////////////////////////////////////////////////////////////// /// 打开编码器 线程安全 bool YEncoder::Open() { std::unique_lock<std::mutex>lock(mMutex); if (!mContext)return false; auto re = avcodec_open2(mContext, NULL, NULL); if (re != 0) { PrintErr(re); return false; } return true; } ////////////////////////////////////////////////////////////// /// 编码数据 线程安全 每次新创建AVPacket /// @para frame 空间由用户维护 /// @return 失败范围nullptr 返回的AVPacket用户需要通过av_packet_free 清理 AVPacket* YEncoder::Encode(const AVFrame* frame) { if (!frame)return nullptr; std::unique_lock<std::mutex>lock(mMutex); if (!mContext)return nullptr; //发送到编码线程 auto re = avcodec_send_frame(mContext, frame); if (re != 0)return nullptr; auto pkt = av_packet_alloc(); //接收编码线程数据 re = avcodec_receive_packet(mContext, pkt); if (re == 0) { return pkt; } av_packet_free(&pkt); if (re == AVERROR(EAGAIN) || re == AVERROR_EOF) { return nullptr; } if (re < 0) { PrintErr(re); } return nullptr; } ////////////////////////////////////////////////////////////// //返回所有编码缓存中AVPacket std::vector<AVPacket*> YEncoder::End() { std::vector<AVPacket*> res; std::unique_lock<std::mutex>lock(mMutex); if (!mContext)return res; auto re = avcodec_send_frame(mContext, NULL); //发送NULL 获取缓冲 if (re != 0)return res; while (re >= 0) { auto pkt = av_packet_alloc(); re = avcodec_receive_packet(mContext, pkt); if (re != 0) { av_packet_free(&pkt); break; } res.push_back(pkt); } return res; } /////////////////////////////////////////////////////////////// //根据AVCodecContext 创建一个AVFrame,需要调用者释放av_frame_free AVFrame* YEncoder::CreateFrame() { std::unique_lock<std::mutex>lock(mMutex); if (!mContext)return nullptr; auto frame = av_frame_alloc(); frame->width = mContext->width; frame->height = mContext->height; frame->format = mContext->pix_fmt; auto re = av_frame_get_buffer(frame, 0); if (re != 0) { av_frame_free(&frame); PrintErr(re); return nullptr; } return frame; } //像素格式转换 void YEncoder::InitSwsCtx() { std::unique_lock<std::mutex>lock(mMutex); if (!mContext)return; swsCtx = sws_getCachedContext(swsCtx, mContext->width, mContext->height, AV_PIX_FMT_BGRA,//输入的宽高及像素格式 mContext->width, mContext->height,//输出的宽高 AV_PIX_FMT_YUV420P,//输出的像素格式 SWS_BILINEAR,//双线性插值算法 0, 0, 0 ); if (!swsCtx) { qDebug() << "sws_getCachedContext failed!"; } } void YEncoder::scale(uint8_t* inData, AVFrame* frame) { std::unique_lock<std::mutex>lock(mMutex); if (!mContext)return; const uint8_t* srcData[AV_NUM_DATA_POINTERS]; srcData[0] = inData; int srcLines[AV_NUM_DATA_POINTERS]; srcLines[0] = mContext->width * 4; int ret = sws_scale(swsCtx , srcData, srcLines, 0, mContext->height, frame->data, frame->linesize ); if (ret <= 0) { PrintErr(ret); } } void YEncoder::Release() { if (swsCtx) { sws_freeContext(swsCtx); swsCtx = nullptr; } } YEncoder::YEncoder() { } YEncoder::~YEncoder() { }
浙公网安备 33010602011771号