mirror of
https://github.com/barry-ran/QtScrcpy.git
synced 2025-07-23 01:11:30 +00:00
feat: record asynchronously
The record file was written from the stream thread. As a consequence, any blocking I/O to write the file delayed the decoder. For maximum performance even when recording is enabled, send (refcounted) packets to a separate recording thread.
This commit is contained in:
parent
65a7cb7467
commit
c79a93c429
7 changed files with 189 additions and 16 deletions
|
@ -6,11 +6,12 @@
|
||||||
|
|
||||||
static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us
|
static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us
|
||||||
|
|
||||||
Recorder::Recorder(const QString& fileName)
|
Recorder::Recorder(const QString& fileName, QObject* parent)
|
||||||
: m_fileName(fileName)
|
: QThread(parent)
|
||||||
|
, m_fileName(fileName)
|
||||||
, m_format(guessRecordFormat(fileName))
|
, m_format(guessRecordFormat(fileName))
|
||||||
{
|
{
|
||||||
|
queueInit(&m_queue);
|
||||||
}
|
}
|
||||||
|
|
||||||
Recorder::~Recorder()
|
Recorder::~Recorder()
|
||||||
|
@ -18,6 +19,75 @@ Recorder::~Recorder()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Recorder::RecordPacket* Recorder::packetNew(const AVPacket *packet) {
|
||||||
|
RecordPacket* rec = new RecordPacket;
|
||||||
|
if (!rec) {
|
||||||
|
return Q_NULLPTR;
|
||||||
|
}
|
||||||
|
if (av_packet_ref(&rec->packet, packet)) {
|
||||||
|
delete rec;
|
||||||
|
return Q_NULLPTR;
|
||||||
|
}
|
||||||
|
rec->next = Q_NULLPTR;
|
||||||
|
return rec;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Recorder::packetDelete(Recorder::RecordPacket *rec) {
|
||||||
|
av_packet_unref(&rec->packet);
|
||||||
|
delete rec;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Recorder::queueInit(Recorder::RecorderQueue *queue) {
|
||||||
|
queue->first = Q_NULLPTR;
|
||||||
|
// queue->last is undefined if queue->first == NULL
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Recorder::queueIsEmpty(Recorder::RecorderQueue *queue) {
|
||||||
|
return !queue->first;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Recorder::queuePush(Recorder::RecorderQueue *queue, const AVPacket *packet) {
|
||||||
|
RecordPacket *rec = packetNew(packet);
|
||||||
|
if (!rec) {
|
||||||
|
qCritical("Could not allocate record packet");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
rec->next = Q_NULLPTR;
|
||||||
|
|
||||||
|
if (queueIsEmpty(queue)) {
|
||||||
|
queue->first = queue->last = rec;
|
||||||
|
} else {
|
||||||
|
// chain rec after the (current) last packet
|
||||||
|
queue->last->next = rec;
|
||||||
|
// the last packet is now rec
|
||||||
|
queue->last = rec;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Recorder::RecordPacket* Recorder::queueTake(Recorder::RecorderQueue *queue) {
|
||||||
|
assert(!queueIsEmpty(queue));
|
||||||
|
|
||||||
|
RecordPacket *rec = queue->first;
|
||||||
|
assert(rec);
|
||||||
|
|
||||||
|
queue->first = rec->next;
|
||||||
|
// no need to update queue->last if the queue is left empty:
|
||||||
|
// queue->last is undefined if queue->first == NULL
|
||||||
|
|
||||||
|
return rec;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Recorder::queueClear(Recorder::RecorderQueue *queue) {
|
||||||
|
RecordPacket *rec = queue->first;
|
||||||
|
while (rec) {
|
||||||
|
RecordPacket *current = rec;
|
||||||
|
rec = rec->next;
|
||||||
|
packetDelete(current);
|
||||||
|
}
|
||||||
|
queue->first = Q_NULLPTR;
|
||||||
|
}
|
||||||
|
|
||||||
void Recorder::setFrameSize(const QSize &declaredFrameSize)
|
void Recorder::setFrameSize(const QSize &declaredFrameSize)
|
||||||
{
|
{
|
||||||
m_declaredFrameSize = declaredFrameSize;
|
m_declaredFrameSize = declaredFrameSize;
|
||||||
|
@ -201,3 +271,62 @@ Recorder::RecorderFormat Recorder::guessRecordFormat(const QString &fileName)
|
||||||
|
|
||||||
return Recorder::RECORDER_FORMAT_NULL;
|
return Recorder::RECORDER_FORMAT_NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Recorder::run() {
|
||||||
|
for (;;) {
|
||||||
|
QMutexLocker locker(&m_mutex);
|
||||||
|
while (!m_stopped && queueIsEmpty(&m_queue)) {
|
||||||
|
m_recvDataCond.wait(&m_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if stopped is set, continue to process the remaining events (to
|
||||||
|
// finish the recording) before actually stopping
|
||||||
|
if (m_stopped && queueIsEmpty(&m_queue)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
RecordPacket *rec = queueTake(&m_queue);
|
||||||
|
|
||||||
|
//mutex_unlock(recorder->mutex);
|
||||||
|
|
||||||
|
bool ok = write(&rec->packet);
|
||||||
|
packetDelete(rec);
|
||||||
|
if (!ok) {
|
||||||
|
qCritical("Could not record packet");
|
||||||
|
|
||||||
|
QMutexLocker locker(&m_mutex);
|
||||||
|
m_failed = true;
|
||||||
|
// discard pending packets
|
||||||
|
queueClear(&m_queue);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug("Recorder thread ended");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Recorder::startRecorder() {
|
||||||
|
start();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Recorder::stopRecorder() {
|
||||||
|
QMutexLocker locker(&m_mutex);
|
||||||
|
m_stopped = true;
|
||||||
|
m_recvDataCond.wakeOne();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Recorder::push(const AVPacket *packet) {
|
||||||
|
QMutexLocker locker(&m_mutex);
|
||||||
|
assert(!m_stopped);
|
||||||
|
|
||||||
|
if (m_failed) {
|
||||||
|
// reject any new packet (this will stop the stream)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ok = queuePush(&m_queue, packet);
|
||||||
|
m_recvDataCond.wakeOne();
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
|
@ -2,14 +2,18 @@
|
||||||
#define RECORDER_H
|
#define RECORDER_H
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QSize>
|
#include <QSize>
|
||||||
|
#include <QThread>
|
||||||
|
#include <QMutex>
|
||||||
|
#include <QWaitCondition>
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
{
|
{
|
||||||
#include "libavformat/avformat.h"
|
#include "libavformat/avformat.h"
|
||||||
}
|
}
|
||||||
|
|
||||||
class Recorder
|
class Recorder : public QThread
|
||||||
{
|
{
|
||||||
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
enum RecorderFormat {
|
enum RecorderFormat {
|
||||||
RECORDER_FORMAT_NULL = 0,
|
RECORDER_FORMAT_NULL = 0,
|
||||||
|
@ -17,7 +21,7 @@ public:
|
||||||
RECORDER_FORMAT_MKV,
|
RECORDER_FORMAT_MKV,
|
||||||
};
|
};
|
||||||
|
|
||||||
Recorder(const QString& fileName);
|
Recorder(const QString& fileName, QObject *parent = Q_NULLPTR);
|
||||||
virtual ~Recorder();
|
virtual ~Recorder();
|
||||||
|
|
||||||
void setFrameSize(const QSize& declaredFrameSize);
|
void setFrameSize(const QSize& declaredFrameSize);
|
||||||
|
@ -25,6 +29,9 @@ public:
|
||||||
bool open(const AVCodec* inputCodec);
|
bool open(const AVCodec* inputCodec);
|
||||||
void close();
|
void close();
|
||||||
bool write(AVPacket* packet);
|
bool write(AVPacket* packet);
|
||||||
|
bool startRecorder();
|
||||||
|
void stopRecorder();
|
||||||
|
bool push(const AVPacket *packet);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const AVOutputFormat* findMuxer(const char* name);
|
const AVOutputFormat* findMuxer(const char* name);
|
||||||
|
@ -33,12 +40,39 @@ private:
|
||||||
QString recorderGetFormatName(Recorder::RecorderFormat format);
|
QString recorderGetFormatName(Recorder::RecorderFormat format);
|
||||||
RecorderFormat guessRecordFormat(const QString& fileName);
|
RecorderFormat guessRecordFormat(const QString& fileName);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct RecordPacket {
|
||||||
|
AVPacket packet;
|
||||||
|
RecordPacket *next;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RecorderQueue {
|
||||||
|
RecordPacket *first = Q_NULLPTR;
|
||||||
|
RecordPacket *last = Q_NULLPTR; // undefined if first is NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
Recorder::RecordPacket* packetNew(const AVPacket *packet);
|
||||||
|
void packetDelete(Recorder::RecordPacket *rec);
|
||||||
|
void queueInit(Recorder::RecorderQueue *queue);
|
||||||
|
bool queueIsEmpty(Recorder::RecorderQueue *queue);
|
||||||
|
bool queuePush(Recorder::RecorderQueue *queue, const AVPacket *packet);
|
||||||
|
Recorder::RecordPacket* queueTake(Recorder::RecorderQueue *queue);
|
||||||
|
void queueClear(Recorder::RecorderQueue *queue);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void run();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString m_fileName = "";
|
QString m_fileName = "";
|
||||||
AVFormatContext* m_formatCtx = Q_NULLPTR;
|
AVFormatContext* m_formatCtx = Q_NULLPTR;
|
||||||
QSize m_declaredFrameSize;
|
QSize m_declaredFrameSize;
|
||||||
bool m_headerWritten = false;
|
bool m_headerWritten = false;
|
||||||
RecorderFormat m_format = RECORDER_FORMAT_NULL;
|
RecorderFormat m_format = RECORDER_FORMAT_NULL;
|
||||||
|
QMutex m_mutex;
|
||||||
|
QWaitCondition m_recvDataCond;
|
||||||
|
bool m_stopped = false; // set on recorder_stop() by the stream reader
|
||||||
|
bool m_failed = false; // set on packet write failure
|
||||||
|
RecorderQueue m_queue;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // RECORDER_H
|
#endif // RECORDER_H
|
||||||
|
|
|
@ -147,10 +147,17 @@ void Stream::run()
|
||||||
goto runQuit;
|
goto runQuit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_recorder && !m_recorder->open(codec)) {
|
if (m_recorder) {
|
||||||
qCritical("Could not open recorder");
|
if (!m_recorder->open(codec)) {
|
||||||
goto runQuit;
|
qCritical("Could not open recorder");
|
||||||
}
|
goto runQuit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_recorder->startRecorder()) {
|
||||||
|
qCritical("Could not start recorder");
|
||||||
|
goto runQuit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
m_parser = av_parser_init(AV_CODEC_ID_H264);
|
m_parser = av_parser_init(AV_CODEC_ID_H264);
|
||||||
if (!m_parser) {
|
if (!m_parser) {
|
||||||
|
@ -188,6 +195,10 @@ void Stream::run()
|
||||||
|
|
||||||
runQuit:
|
runQuit:
|
||||||
if (m_recorder) {
|
if (m_recorder) {
|
||||||
|
if (m_recorder->isRunning()) {
|
||||||
|
m_recorder->stopRecorder();
|
||||||
|
m_recorder->wait();
|
||||||
|
}
|
||||||
m_recorder->close();
|
m_recorder->close();
|
||||||
}
|
}
|
||||||
if (m_decoder) {
|
if (m_decoder) {
|
||||||
|
@ -300,7 +311,7 @@ bool Stream::pushPacket(AVPacket *packet)
|
||||||
|
|
||||||
bool Stream::processConfigPacket(AVPacket *packet)
|
bool Stream::processConfigPacket(AVPacket *packet)
|
||||||
{
|
{
|
||||||
if (m_recorder && !m_recorder->write(packet)) {
|
if (m_recorder && !m_recorder->push(packet)) {
|
||||||
qCritical("Could not send config packet to recorder");
|
qCritical("Could not send config packet to recorder");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -344,7 +355,7 @@ bool Stream::processFrame(AVPacket *packet)
|
||||||
if (m_recorder) {
|
if (m_recorder) {
|
||||||
packet->dts = packet->pts;
|
packet->dts = packet->pts;
|
||||||
|
|
||||||
if (!m_recorder->write(packet)) {
|
if (!m_recorder->push(packet)) {
|
||||||
qCritical("Could not send packet to recorder");
|
qCritical("Could not send packet to recorder");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
#include <QMutex>
|
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
{
|
{
|
||||||
|
|
|
@ -53,7 +53,7 @@ bool DeviceManage::disconnectDevice(const QString &serial)
|
||||||
if (!serial.isEmpty() && m_devices.contains(serial)) {
|
if (!serial.isEmpty() && m_devices.contains(serial)) {
|
||||||
auto it = m_devices.find(serial);
|
auto it = m_devices.find(serial);
|
||||||
if (it->data()) {
|
if (it->data()) {
|
||||||
it->data()->deleteLater();
|
delete it->data();
|
||||||
ret = true;
|
ret = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ void DeviceManage::disconnectAllDevice()
|
||||||
while (i.hasNext()) {
|
while (i.hasNext()) {
|
||||||
i.next();
|
i.next();
|
||||||
if (i.value()) {
|
if (i.value()) {
|
||||||
i.value()->deleteLater();
|
delete i.value();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,7 +64,7 @@ Dialog::Dialog(QWidget *parent) :
|
||||||
|
|
||||||
Dialog::~Dialog()
|
Dialog::~Dialog()
|
||||||
{
|
{
|
||||||
on_stopServerBtn_clicked();
|
m_deviceManage.disconnectAllDevice();
|
||||||
delete ui;
|
delete ui;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -94,7 +94,7 @@ void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QS
|
||||||
}
|
}
|
||||||
|
|
||||||
if (QtDebugMsg < type) {
|
if (QtDebugMsg < type) {
|
||||||
if (g_mainDlg && !msg.contains("app_proces")) {
|
if (g_mainDlg && g_mainDlg->isVisible() && !msg.contains("app_proces")) {
|
||||||
g_mainDlg->outLog(msg);
|
g_mainDlg->outLog(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue