1 Star 2 Fork 4

shaoguangcn/TinyScreenRecorder

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
TinyScreenRecorder.cpp 12.43 KB
一键复制 编辑 原始数据 按行查看 历史
/*
* Copyright 2021 Shaoguang.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "TinyScreenRecorder.h"
#include <chrono>
#include <atomic>
#ifdef __cplusplus
extern "C" {
#endif
// source from luvcview (https://github.com/ksv1986/luvcview)
#include "3rdparty/avilib.h"
#ifdef __cplusplus
} // extern "C"
#endif
#include <QThread>
#include <QApplication> // qmake: QT += widgets
#include <QWidget>
#include <QDesktopWidget>
#include <QScreen>
#include <QBuffer>
#include <QDir>
#include <QFile>
#include <QElapsedTimer>
#include <QDebug>
class TinyScreenRecorderPrivate : public QThread
{
public:
explicit TinyScreenRecorderPrivate(QObject *parent = nullptr);
~TinyScreenRecorderPrivate();
void stop();
bool waitForFinished(int msec = 30000);
/**
* Fixed bug for MSVC compiler on windows (debug mode):
* "ASSERT failure in QCoreApplication::sendEvent: "Cannot send events to objects owned by a different thread. "
* "Current thread 0x0x15535b5e000. Receiver 'desktopWindow' (of type 'QWidgetWindow') was created in thread 0x0x15535b4e260", "
* "file kernel\qcoreapplication.cpp, line 558"
*/
static inline void tryGetWinId(QWidget* widget);
TinyScreenRecorder* recorder{ nullptr };
int pixmapQuality{ 75 };
bool autoFps{ true };
double fps{ 10 };
char aviCompressor[5]{ "MJPG" };
QString outputFileName;
QWidget* targetWidget{ nullptr };
QRect roiRect;
std::atomic_bool m_loopFlag{ false };
std::atomic_bool running{ false };
static bool aviErrorPrintable;
protected:
void run() override;
void tryPrintAviError();
double detectFPS();
void emitErrorString(const QString& error);
void emitRecordingStarted();
void emitFrameRecorded(qint64 frameCount);
void emitFrameMissed(qint64 frameCount);
void emitRecordingFinished();
void emitFpsDetected(double fps);
template<class UnaryPredicate>
bool startRecording(const QString& fileName, bool invokeEvent, UnaryPredicate pred);
};
bool TinyScreenRecorderPrivate::aviErrorPrintable = true;
/*****************************************************************************
TinyScreenRecorderPrivate member functions
*****************************************************************************/
/**
* Fixed bug for MSVC compiler on windows (debug mode):
* "ASSERT failure in QCoreApplication::sendEvent: "Cannot send events to objects owned by a different thread. "
* "Current thread 0x0x15535b5e000. Receiver 'desktopWindow' (of type 'QWidgetWindow') was created in thread 0x0x15535b4e260", "
* "file kernel\qcoreapplication.cpp, line 558"
*/
void TinyScreenRecorderPrivate::tryGetWinId(QWidget* widget)
{
#if defined(Q_CC_MSVC)
auto wid = widget->winId();
Q_UNUSED(wid);
#endif
}
TinyScreenRecorderPrivate::TinyScreenRecorderPrivate(QObject *parent) : QThread(parent)
{
targetWidget = QApplication::desktop();
tryGetWinId(targetWidget);
}
TinyScreenRecorderPrivate::~TinyScreenRecorderPrivate()
{
stop();
this->exit();
}
void TinyScreenRecorderPrivate::stop()
{
m_loopFlag = false;
}
bool TinyScreenRecorderPrivate::waitForFinished(int msec)
{
if (!this->running) {
return true;
}
QElapsedTimer elapsedTimer;
elapsedTimer.start();
while (elapsedTimer.elapsed() < static_cast<qint64>(msec)) {
if (!this->running) {
return true;
}
}
return false;
}
double TinyScreenRecorderPrivate::detectFPS()
{
double fps = -1.0;
QString tmpVideoFileName = QDir::tempPath() + "/.TinyScreenRecorderTemp.avi";
using chrono_clock_type = std::chrono::high_resolution_clock;
using chrono_seconds_t = std::chrono::duration<double, std::chrono::seconds::period>;
using chrono_time_point_t = typename std::chrono::high_resolution_clock::time_point;
chrono_time_point_t timePoint;
int frameIndex = 0;
const int frameCount = 5;
auto lmd_recordingRoutine = [&timePoint, &frameIndex, frameCount]() {
if (frameIndex == 0) {
timePoint = std::chrono::high_resolution_clock::now();
}
frameIndex++;
return frameIndex <= frameCount;
};
if (startRecording(tmpVideoFileName, false, lmd_recordingRoutine)) {
double seconds = std::chrono::duration_cast<chrono_seconds_t>(chrono_clock_type::now() - timePoint).count();
fps = 1.0 / (seconds / static_cast<double>(frameCount));
fps = static_cast<int>(fps);
}
QFile::remove(tmpVideoFileName);
return fps;
}
void TinyScreenRecorderPrivate::run()
{
if (this->autoFps) {
this->fps = detectFPS();
emitFpsDetected(this->fps);
qDebug() << fps;
}
if (this->fps < 1.0) {
this->fps = 1.0;
}
this->running = true;
emitRecordingStarted();
startRecording(this->outputFileName, true, [](){return true;});
this->running = false;
emitRecordingFinished();
}
template<class UnaryPredicate>
bool TinyScreenRecorderPrivate::startRecording(const QString& fileName, bool invokeEvent, UnaryPredicate pred)
{
avi_t* aviHandle = AVI_open_output_file(fileName.toLocal8Bit().data());
if (!aviHandle) {
emitErrorString(QString("open output file \"%1\" failed").arg(fileName));
tryPrintAviError();
return false;
}
if (!this->roiRect.isValid()) {
this->roiRect = this->targetWidget->geometry();
}
AVI_set_video(aviHandle, this->targetWidget->width(), this->targetWidget->height(), this->fps, this->aviCompressor);
qint64 frameCount = 0;
qint64 frameMissed = 0;
m_loopFlag = true;
while (pred() && m_loopFlag) {
QPixmap pixmap = QApplication::primaryScreen()->grabWindow(
this->targetWidget->winId(),
this->roiRect.x(), this->roiRect.y(),
this->roiRect.width(), this->roiRect.height());
QByteArray data;
QBuffer buff(&data);
if (!pixmap.save(&buff, "jpg", this->pixmapQuality)) {
emitErrorString(QString("save screenshot pixmap failed"));
continue;
}
if (0 != AVI_write_frame(aviHandle, buff.buffer().data(), buff.size(), true)) {
emitErrorString(QString("missing frame"));
tryPrintAviError();
if (invokeEvent) {
emitFrameMissed(++frameMissed);
}
}
else {
if (invokeEvent) {
emitFrameRecorded(++frameCount);
}
}
}
if (0 != AVI_close(aviHandle)) {
emitErrorString(QString("close output file \"%1\" failed").arg(fileName));
tryPrintAviError();
m_loopFlag = false;
return false;
}
m_loopFlag = false;
return true;
}
void TinyScreenRecorderPrivate::emitErrorString(const QString& error)
{
if (!QMetaObject::invokeMethod(recorder, "errorOccurred", Q_ARG(QString, error))) {
qWarning("invoke errorOccurred failed");
}
}
void TinyScreenRecorderPrivate::emitRecordingStarted()
{
if (!QMetaObject::invokeMethod(recorder, "recordingStarted")) {
qWarning("invoke recordingStarted failed");
}
}
void TinyScreenRecorderPrivate::emitFrameRecorded(qint64 frameCount)
{
if (!QMetaObject::invokeMethod(recorder, "frameRecorded", Q_ARG(qint64, frameCount))) {
qWarning("invoke frameRecorded failed");
}
}
void TinyScreenRecorderPrivate::emitFrameMissed(qint64 frameCount)
{
if (!QMetaObject::invokeMethod(recorder, "frameMissed", Q_ARG(qint64, frameCount))) {
qWarning("invoke frameMissed failed");
}
}
void TinyScreenRecorderPrivate::emitRecordingFinished()
{
if (!QMetaObject::invokeMethod(recorder, "recordingFinished")) {
qWarning("invoke recordingFinished failed");
}
}
void TinyScreenRecorderPrivate::emitFpsDetected(double fps)
{
if (!QMetaObject::invokeMethod(recorder, "fpsDetected", Q_ARG(double, fps))) {
qWarning("invoke fpsDetected failed");
}
}
void TinyScreenRecorderPrivate::tryPrintAviError()
{
if (TinyScreenRecorderPrivate::aviErrorPrintable) {
char preffix[] = "[AVILIB]";
AVI_print_error(preffix);
}
}
/*****************************************************************************
TinyScreenRecorder member functions
*****************************************************************************/
TinyScreenRecorder::TinyScreenRecorder(QObject* parent)
: QObject(parent)
, d_ptr(new TinyScreenRecorderPrivate)
{
Q_D(TinyScreenRecorder);
d->recorder = this;
}
TinyScreenRecorder::~TinyScreenRecorder()
{
if (d_ptr) {
delete d_ptr;
}
}
void TinyScreenRecorder::setRoiArguments(QWidget* targetWidget, QRect roiRect)
{
Q_D(TinyScreenRecorder);
d->targetWidget = targetWidget;
d->roiRect = roiRect;
d->tryGetWinId(targetWidget);
}
QWidget* TinyScreenRecorder::targetWidget() const
{
Q_D(const TinyScreenRecorder);
return d->targetWidget;
}
QRect TinyScreenRecorder::roiRect() const
{
Q_D(const TinyScreenRecorder);
return d->roiRect;
}
void TinyScreenRecorder::start()
{
Q_D(TinyScreenRecorder);
d->start();
}
void TinyScreenRecorder::stop()
{
Q_D(TinyScreenRecorder);
d->stop();
}
bool TinyScreenRecorder::waitForFinished(int msec)
{
Q_D(TinyScreenRecorder);
return d->waitForFinished(msec);
}
bool TinyScreenRecorder::isFinished() const
{
Q_D(const TinyScreenRecorder);
return !d->running;
}
bool TinyScreenRecorder::isRunning() const
{
Q_D(const TinyScreenRecorder);
return d->running;
}
const QString& TinyScreenRecorder::videoFileName() const
{
Q_D(const TinyScreenRecorder);
return d->outputFileName;
}
void TinyScreenRecorder::setVideoFileName(const QString& fileName)
{
Q_D(TinyScreenRecorder);
d->outputFileName = fileName;
}
void TinyScreenRecorder::setVideoQuality(int quality)
{
Q_D(TinyScreenRecorder);
d->pixmapQuality = quality;
}
int TinyScreenRecorder::videoQuality() const
{
Q_D(const TinyScreenRecorder);
return d->pixmapQuality;
}
void TinyScreenRecorder::setVideoFps(double fps)
{
Q_D(TinyScreenRecorder);
d->fps = fps;
}
double TinyScreenRecorder::videoFps() const
{
Q_D(const TinyScreenRecorder);
return d->fps;
}
void TinyScreenRecorder::setAutoFps(bool automatic)
{
Q_D(TinyScreenRecorder);
d->autoFps = automatic;
}
bool TinyScreenRecorder::isAutoFps() const
{
Q_D(const TinyScreenRecorder);
return d->autoFps;
}
void TinyScreenRecorder::enablePrintAviError()
{
TinyScreenRecorderPrivate::aviErrorPrintable = true;
}
void TinyScreenRecorder::disablePrintAviError()
{
TinyScreenRecorderPrivate::aviErrorPrintable = false;
}
void TinyScreenRecorder::setAviErrorPrintable(bool printable)
{
TinyScreenRecorderPrivate::aviErrorPrintable = printable;
}
bool TinyScreenRecorder::isAviErrorPrintable()
{
return TinyScreenRecorderPrivate::aviErrorPrintable;
}
#ifndef QT_NO_DEBUG_STREAM
QDebug operator<<(QDebug debug, const TinyScreenRecorder* recorder)
{
const QDebugStateSaver saver(debug);
debug.nospace();
if (recorder) {
debug << recorder->metaObject()->className() << "(" << (const void*)recorder << ",\n";
if (!recorder->objectName().isEmpty()) {
debug <<" name = " << recorder->objectName() << ",\n";
}
if (recorder->targetWidget()) {
debug << " targetWidget = " << recorder->targetWidget() << ",\n";
}
debug << " roiRect = " << recorder->roiRect() << ",\n";
debug << " videoFileName = " << recorder->videoFileName() << ",\n";
debug << " videoQuality = " << recorder->videoQuality() << ",\n";
debug << " videoFps = " << recorder->videoFps() << ",\n";
debug << " autoFps = " << recorder->isAutoFps() << ",\n";
debug << " isAviErrorPrintable = " << recorder->isAviErrorPrintable();
debug << "\n)";
} else {
debug << "TinyScreenRecorder(0x0)";
}
return debug;
}
QDebug operator<<(QDebug debug, const TinyScreenRecorder& recorder)
{
return (debug << &recorder);
}
#endif
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
C/C++
1
https://gitee.com/shaoguangcn/tiny-screen-recorder.git
git@gitee.com:shaoguangcn/tiny-screen-recorder.git
shaoguangcn
tiny-screen-recorder
TinyScreenRecorder
master

搜索帮助