Qt C++實(shí)現(xiàn)錄屏錄音功能的示例詳解
錄屏部分
錄屏的主要思路為抓取屏幕截圖,然后將其合成視頻。抓取屏幕若使用qt自帶的抓屏?xí)霈F(xiàn)抓不到鼠標(biāo)的問題,所以應(yīng)重寫抓屏:
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; }
這樣抓取的圖片會(huì)包括鼠標(biāo)。
但是,如果直接while循環(huán)進(jìn)行抓屏的話,一秒頂多抓10幀。所以應(yīng)該啟動(dòng)一個(gè)計(jì)時(shí)器,按照想要的幀率進(jìn)行抓屏??上В琎t的計(jì)時(shí)器會(huì)有各種各樣的限制,所以我自己實(shí)現(xiàn)了計(jì)時(shí)器進(jìn)行處理:
#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: // 私有數(shù)據(jù)部分 std::atomic_bool m_bexit; std::atomic_bool m_bimmediately_run; // 是否立即執(zhí)行 unsigned int m_imsec; // 間隔時(shí)間 std::function<void()> m_func; // 執(zhí)行函數(shù) 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; } }
這樣,就可以多線程進(jìn)行抓屏了,合成視頻我使用的是avilib,理論上它可以同時(shí)合成音頻,但合成后除了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); }
在停止錄屏?xí)r,需要判斷抓屏線程是否結(jié)束,很多人會(huì)想到線程池,其實(shí)不必那么復(fù)雜,只需為每個(gè)線程綁定一個(gè)獨(dú)立的id,然后操作含有這個(gè)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); } }
錄音部分
錄音部分其實(shí)非常簡(jiǎn)單,僅需使用qt的模板即可實(shí)現(xiàn):
QAudioDeviceInfo info = QAudioDeviceInfo::availableDevices(QAudio::AudioInput).at(macIndex); recorder = new QAudioRecorder(this); QAudioEncoderSettings settings = recorder->audioSettings(); settings.setCodec("audio/PCM"); // 這些是QAudioRecorder是設(shè)置,見名思意 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進(jìn)行視頻合成。僅需傳入?yún)?shù)即可。
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的參數(shù) qDebug() << arguments; process.start(program, arguments); process.waitForFinished(); QFile f1(curController->voicefileName); QFile f2(curController->screenfileName); f1.remove(); f2.remove(); curController->isMakingVideo = false; }
轉(zhuǎn)成動(dòng)態(tài)庫
有時(shí)我們很想將這個(gè)功能提供給其他人使用,但是其他人未必使用qt,甚至未必使用C++,那么就需要將其封裝成動(dòng)態(tài)庫。但是,qt的消息機(jī)制是十分獨(dú)立的,在沒有QApplication::exec()的時(shí)候,或者說沒有發(fā)起qt獨(dú)立的消息循環(huán)機(jī)制的時(shí)候,他的信號(hào)槽機(jī)制將不會(huì)起作用。比如這個(gè)錄音模塊,在直接提供給他人使用的時(shí)候?qū)⒉粫?huì)錄制到任何聲音。所以需要對(duì)錄音部分進(jìn)行封裝。
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派生出一個(gè)獨(dú)立的麥克風(fēng)操作類,在run函數(shù)中啟動(dòng)一個(gè)獨(dú)立的消息循環(huán),這樣麥克風(fēng)的錄制功能就可以進(jìn)行了。
關(guān)于動(dòng)態(tài)庫的封裝,用到了傳說中的QMFCAPP,這個(gè)大家百度一下隨便下載一個(gè)就可以,但這樣封裝還有問題,因?yàn)閯e人未必有qt環(huán)境,所以我對(duì)它進(jìn)行了二次封裝。相信各位也有其他的解決辦法。
完整項(xiàng)目
我做的demo提供了額外的麥克風(fēng)檢測(cè)和屏幕檢測(cè)功能,同時(shí)也提供了視頻合成進(jìn)度檢查的接口,喜歡的朋友可以下載進(jìn)行參考。
完整的項(xiàng)目和msvc_2012版本的動(dòng)態(tài)庫可以復(fù)制下面的鏈接進(jìn)行下載:
鏈接:https://pan.baidu.com/s/1eBtLfE21rZ545T7rrmmXpg
提取碼:qg1h
到此這篇關(guān)于Qt C++實(shí)現(xiàn)錄屏錄音功能的示例詳解的文章就介紹到這了,更多相關(guān)Qt C++錄屏錄音內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++淺析數(shù)據(jù)在內(nèi)存中如何存儲(chǔ)
使用編程語言進(jìn)行編程時(shí),需要用到各種變量來存儲(chǔ)各種信息。變量保留的是它所存儲(chǔ)的值的內(nèi)存位置。這意味著,當(dāng)您創(chuàng)建一個(gè)變量時(shí),就會(huì)在內(nèi)存中保留一些空間。您可能需要存儲(chǔ)各種數(shù)據(jù)類型的信息,操作系統(tǒng)會(huì)根據(jù)變量的數(shù)據(jù)類型,來分配內(nèi)存和決定在保留內(nèi)存中存儲(chǔ)什么2022-08-08C++實(shí)現(xiàn)LeetCode(208.實(shí)現(xiàn)字典樹(前綴樹))
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(208.實(shí)現(xiàn)字典樹(前綴樹)),本篇文章通過簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08C語言中l(wèi)seek()函數(shù)和fseek()函數(shù)的使用詳解
這篇文章主要介紹了C語言中l(wèi)seek()函數(shù)和fseek()函數(shù)的使用詳解,是C語言入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-08-08C++中關(guān)鍵字Struct和Class的區(qū)別
這篇文章主要介紹了C++中關(guān)鍵字Struct和Class的區(qū)別,本文用大量實(shí)例講解了Struct和Class的區(qū)別,需要的朋友可以參考下2014-09-09C++ std::unique_lock 用法實(shí)例詳解
std::unique_lock 是 C++11 提供的一個(gè)用于管理互斥鎖的類,它提供了更靈活的鎖管理功能,適用于各種多線程場(chǎng)景,這篇文章給大家介紹了C++ std::unique_lock 用法,感興趣的朋友跟隨小編一起看看吧2023-09-09