这篇文章主要介绍了Qt C++如何实现录屏录音功能的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Qt C++如何实现录屏录...
这篇文章主要介绍了Qt C++如何实现录屏录音功能的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Qt C++如何实现录屏录音功能文章都会有所收获,下面我们一起来看看吧。
录屏部分
录屏的主要思路为抓取屏幕截图,然后将其合成视频。抓取屏幕若使用qt自带的抓屏会出现抓不到鼠标的问题,所以应重写抓屏:
static QPixmap grabWindow(HWND winId, int x, int y, int w, int h)
{
RECT r;
GetClientRect(winId, &r);
if (w < 0) w = r.right - r.left;
if (h < 0) h = r.bottom - r.top;
HDC display_dc = GetDC(winId);
HDC bitmap_dc = CreateCompatibleDC(display_dc);
HBITMAP bitmap = CreateCompatibleBitmap(display_dc, w, h);
HGDIOBJ null_bitmap = SelectObject(bitmap_dc, bitmap);
BitBlt(bitmap_dc, 0, 0, w, h, display_dc, x, y, SRCCOPY | CAPTUREBLT);
CURSORINFO ci;
ci.cbSize = sizeof(CURSORINFO);
GetCursorInfo(&ci);
if ((ci.ptScreenPos.x > x) && (ci.ptScreenPos.y > y) && (ci.ptScreenPos.x < (x + w)) && (ci.ptScreenPos.y < (y + h)))
DrawIcon(bitmap_dc, ci.ptScreenPos.x - x, ci.ptScreenPos.y - y, ci.hCursor);
// clean up all but bitmap
ReleaseDC(winId, display_dc);
SelectObject(bitmap_dc, null_bitmap);
DeleteDC(bitmap_dc);
QPixmap pixmap = QtWin::fromHBITMAP(bitmap);
DeleteObject(bitmap);
return pixmap;
}
这样抓取的图片会包括鼠标。
但是,如果直接while循环进行抓屏的话,一秒顶多抓10帧。所以应该启动一个计时器,按照想要的帧率进行抓屏。可惜,Qt的计时器会有各种各样的限制,所以我自己实现了计时器进行处理:
#pragma once
#include <iostream>
#include <string>
#include <thread>
#include <chrono>
#include <atomic>
#include <memory>
#include <condition_variable>
using namespace std;
class STTimer
{
public:
~STTimer(void);
template<class F>
STTimer(F func):m_func(func){};
void Start(unsigned int secd,bool isBimmediately_run = false);
void Stop();
void SetExit(bool b_exit);
private: // 私有数据部分
std::atomic_bool m_bexit;
std::atomic_bool m_bimmediately_run; // 是否立即执行
unsigned int m_imsec; // 间隔时间
std::function<void()> m_func; // 执行函数
std::thread m_thread;
std::mutex m_mutex;
std::condition_variable m_cond;
void Run();
};
#include "STTimer.h"
#include "ScreenController.h"
#include <QDebug>
STTimer::~STTimer(void)
{
}
void STTimer::Start(unsigned int sec, bool bim_run)
{
m_bexit.store(false);
m_imsec = sec;
m_bimmediately_run.store(bim_run);
m_thread = std::thread(std::bind(&STTimer::Run,this));
}
void STTimer::Stop()
{
m_bexit.store(true);
m_cond.notify_all(); // 唤醒线程
if (m_thread.joinable())
{
m_thread.join();
}
}
void STTimer::SetExit(bool b_exit)
{
m_bexit.store(b_exit);
}
void STTimer::Run()
{
if(m_bimmediately_run.load())
{
if(m_func)
{
m_func();
}
}
while(!m_bexit.load())
{
qDebug()<<"runmning";
std::unique_lock<std::mutex> locker(m_mutex);
m_cond.wait_for(locker,std::chrono::milliseconds(m_imsec),[this](){return m_bexit.load(); });
if(m_func)
{
m_func();
}
}
if(m_bexit.load())
{
return;
}
}
这样,就可以多线程进行抓屏了,合成视频我使用的是avilib,理论上它可以同时合成音频,但合成后除了potplayer都无法解码,所以仅用它做合成视频。
void ScreenController::getOneFrame()
{
int ids = curController->getId();
controlIds(false, ids);
std::thread t1(startThread,ids);
t1.detach();
}
void ScreenController::startThread(int ids)
{
QPixmap mp = grabWindow((HWND)QApplication::desktop()->winId(), curController->curRect.x(), curController->curRect.y(), curController->curRect.width(), curController->curRect.height());
QByteArray ba;
QBuffer bf(&ba);
mp.save(&bf, "jpg", 100);
char* framBf = ba.data();
int byteLen = ba.length();
qDebug()<<byteLen;
QMutexLocker lockeer(&curController->m_smutex2);
AVI_write_frame(curController->avfd, framBf, byteLen, 1);
lockeer.unlock();
controlIds(true, ids);
}
在停止录屏时,需要判断抓屏线程是否结束,很多人会想到线程池,其实不必那么复杂,只需为每个线程绑定一个独立的id,然后操作含有这个id的列表即可。
void ScreenController::controlIds(bool isDelete, int index)
{
QMutexLocker locker(&curController->m_smutex);
if (isDelete)
{
int ind = curController->ids.indexOf(index);
curController->ids.removeAt(ind);
}
else
{
curController->ids.push_back(index);
}
}
录音部分
录音部分其实非常简单,仅需使用qt的模板即可实现:
QAudioDeviceInfo info = QAudioDeviceInfo::availableDevices(QAudio::AudioInput).at(macIndex);
recorder = new QAudioRecorder(this);
QAudioEncoderSettings settings = recorder->audioSettings();
settings.setCodec("audio/PCM"); // 这些是QAudioRecorder是设置,见名思意
settings.setBitRate(96000);
//settings.setSampleRate(44100);
settings.setChannelCount(2);
settings.setQuality(QMultimedia::EncodingQuality::HighQuality);
settings.setEncodingMode(QMultimedia::ConstantQualityEncoding);
recorder->setAudioSettings(settings);
recorder->setAudioInput(info.deviceName());
recorder->setOutputLocation(QUrl::fromLocalFile(fileName));
recorder->setContainerFormat("audio/wav");
recorder->record();
合成部分
合成我使用的是ffmpeg进行视频合成。仅需传入参数即可。
void CaptureController::MakeVideo()
{
if(curController->isMakingVideo)
{
return;
}
qDebug()<<"making video";
curController->isMakingVideo = true;
QString program = QCoreApplication::applicationDirPath();
program += "/ffmpeg.exe";
qDebug()<<"program";
qDebug() << program;
QProcess process;
QStringList arguments;
arguments << "-i" << curController->voicefileName
<< "-i" << curController->screenfileName
<< "-s" << QString::number(curController->screenRect.width()) + "x" + QString::number(curController->screenRect.height())
<<"-b:v" << "40000k"
<< curController->finalfileName;//传递到exe的参数
qDebug() << arguments;
process.start(program, arguments);
process.waitForFinished();
QFile f1(curController->voicefileName);
QFile f2(curController->screenfileName);
f1.remove();
f2.remove();
curController->isMakingVideo = false;
}
转成动态库
有时我们很想将这个功能提供给其他人使用,但是其他人未必使用qt,甚至未必使用C++,那么就需要将其封装成动态库。但是,qt的消息机制是十分独立的,在没有QApplication::exec()的时候,或者说没有发起qt独立的消息循环机制的时候,他的信号槽机制将不会起作用。比如这个录音模块,在直接提供给他人使用的时候将不会录制到任何声音。所以需要对录音部分进行封装。
class MCCtClass:public QThread{
public:
MCCtClass();
void startTestingMac(int index);
int getCurrentVoice();
void startCapVoice(int index);
void stopThread();
void setFileName(QString name);
protected:
virtual void run();
private:
volatile bool isStop;
int macIndex;
int currentRun;
QEventLoop *lp;
MacController *ct;
QString fileName;
};
MCCtClass::MCCtClass()
{
currentRun = -1;
ct = nullptr;
}
void MCCtClass::startCapVoice(int index)
{
currentRun = 1;
macIndex = index;
this->start();
}
void MCCtClass::startTestingMac(int index)
{
currentRun =2;
macIndex = index;
this->start();
}
void MCCtClass::setFileName(QString name)
{
fileName = name;
}
void MCCtClass::run()
{
ct = new MacController();
if(currentRun == 1)
{
ct->SetFileName(fileName);
ct->StartRecordingVoice(macIndex);
lp = new QEventLoop();
lp->exec();
}
else if(currentRun == 2)
{
qDebug()<<"run2";
ct->StartTestingMac(macIndex);
lp = new QEventLoop();
lp->exec();
}
}
int MCCtClass::getCurrentVoice()
{
if(ct == nullptr)
{
return 0;
}
return ct->getTestVolume();
}
void MCCtClass::stopThread()
{
lp->exit();
lp->deleteLater();
if(currentRun == 1)
{
ct->StopRecordingVoice();
}
else if(currentRun == 2)
{
ct->StopTestingMac();
}
ct->deleteLater();
ct = nullptr;
}
使用qthread派生出一个独立的麦克风操作类,在run函数中启动一个独立的消息循环,这样麦克风的录制功能就可以进行了。
关于动态库的封装,用到了传说中的QMFCAPP,这个大家百度一下随便下载一个就可以,但这样封装还有问题,因为别人未必有qt环境,所以我对它进行了二次封装。相信各位也有其他的解决办法。
关于“Qt C++如何实现录屏录音功能”这篇文章的内容就介绍到这里,感谢各位的阅读!相信大家对“Qt C++如何实现录屏录音功能”知识都有一定的了解,大家如果还想学习更多知识,欢迎关注捷杰建站行业资讯频道。