Qt C++ 录屏录音功能实现(avilib+ffmpeg)以及动态库生成

Qt C++ 录屏录音功能实现(avilib+ffmpeg)以及动态库生成,第1张

实现一个录屏+录音的功能且需要快速开发,Qt无疑是一个非常好的选择。他有丰富的类库和接口可以很好的满足开发需求。

完整demo代码在文章最下方的百度网盘链接中,有需要的各位可以随意下载。

录屏部分

录屏的主要思路为抓取屏幕截图,然后将其合成视频。抓取屏幕若使用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 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

class STTimer
{
public:
	~STTimer(void);
	template
	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 m_func;	// 执行函数
	std::thread m_thread;
	std::mutex m_mutex;
	std::condition_variable m_cond;

	void Run();


};
#include "STTimer.h"

#include "ScreenController.h"
#include 
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 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()<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环境,所以我对它进行了二次封装。相信各位也有其他的解决办法。

完整项目

我做的demo提供了额外的麦克风检测和屏幕检测功能,同时也提供了视频合成进度检查的接口,喜欢的朋友可以下载进行参考。

完整的项目和msvc_2012版本的动态库可以复制下面的链接进行下载:

链接:https://pan.baidu.com/s/1UNJ--Ief7QN-NkbOo5E3Qw 
提取码:qtsc 
--来自百度网盘超级会员V2的分享

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/langs/1498286.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-06-25
下一篇2022-06-25

发表评论

登录后才能评论

评论列表(0条)

    保存