diff --git a/QtScrcpy/device/decoder/decoder.pri b/QtScrcpy/device/decoder/decoder.pri index d5366e7..8618ad0 100644 --- a/QtScrcpy/device/decoder/decoder.pri +++ b/QtScrcpy/device/decoder/decoder.pri @@ -9,6 +9,3 @@ SOURCES += \ $$PWD/fpscounter.cpp \ $$PWD/avframeconvert.cpp \ $$PWD/videobuffer.cpp - -#DEFINES += SKIP_FRAMES - diff --git a/QtScrcpy/device/decoder/videobuffer.cpp b/QtScrcpy/device/decoder/videobuffer.cpp index 23fb0bf..d9c0c05 100644 --- a/QtScrcpy/device/decoder/videobuffer.cpp +++ b/QtScrcpy/device/decoder/videobuffer.cpp @@ -15,8 +15,9 @@ VideoBuffer::~VideoBuffer() } -bool VideoBuffer::init() +bool VideoBuffer::init(bool renderExpiredFrames) { + m_renderExpiredFrames = renderExpiredFrames; m_decodingFrame = av_frame_alloc(); if (!m_decodingFrame) { goto error; @@ -71,17 +72,17 @@ void VideoBuffer::offerDecodedFrame(bool& previousFrameSkipped) { m_mutex.lock(); -#ifndef SKIP_FRAMES - // if SKIP_FRAMES is disabled, then the decoder must wait for the current - // frame to be consumed - while (!m_renderingFrameConsumed && !m_interrupted) { - m_renderingFrameConsumedCond.wait(&m_mutex); - } -#else - if (m_fpsCounter.isStarted() && !m_renderingFrameConsumed) { - m_fpsCounter.addSkippedFrame(); + if (m_renderExpiredFrames) { + // if m_renderExpiredFrames is enable, then the decoder must wait for the current + // frame to be consumed + while (!m_renderingFrameConsumed && !m_interrupted) { + m_renderingFrameConsumedCond.wait(&m_mutex); + } + } else { + if (m_fpsCounter.isStarted() && !m_renderingFrameConsumed) { + m_fpsCounter.addSkippedFrame(); + } } -#endif swap(); previousFrameSkipped = !m_renderingFrameConsumed; @@ -96,23 +97,23 @@ const AVFrame *VideoBuffer::consumeRenderedFrame() if (m_fpsCounter.isStarted()) { m_fpsCounter.addRenderedFrame(); } -#ifndef SKIP_FRAMES - // if SKIP_FRAMES is disabled, then notify the decoder the current frame is - // consumed, so that it may push a new one - m_renderingFrameConsumedCond.wakeOne(); -#endif + if (m_renderExpiredFrames) { + // if m_renderExpiredFrames is enable, then notify the decoder the current frame is + // consumed, so that it may push a new one + m_renderingFrameConsumedCond.wakeOne(); + } return m_renderingframe; } void VideoBuffer::interrupt() { -#ifndef SKIP_FRAMES - m_mutex.lock(); - m_interrupted = true; - m_mutex.unlock(); - // wake up blocking wait - m_renderingFrameConsumedCond.wakeOne(); -#endif + if (m_renderExpiredFrames) { + m_mutex.lock(); + m_interrupted = true; + m_mutex.unlock(); + // wake up blocking wait + m_renderingFrameConsumedCond.wakeOne(); + } } void VideoBuffer::swap() diff --git a/QtScrcpy/device/decoder/videobuffer.h b/QtScrcpy/device/decoder/videobuffer.h index 7138fd1..5cdef2b 100644 --- a/QtScrcpy/device/decoder/videobuffer.h +++ b/QtScrcpy/device/decoder/videobuffer.h @@ -15,7 +15,7 @@ public: VideoBuffer(); virtual ~VideoBuffer(); - bool init(); + bool init(bool renderExpiredFrames = false); void deInit(); void lock(); void unLock(); @@ -45,10 +45,12 @@ private: bool m_renderingFrameConsumed = true; FpsCounter m_fpsCounter; -#ifndef SKIP_FRAMES + bool m_renderExpiredFrames = false; QWaitCondition m_renderingFrameConsumedCond; - bool m_interrupted = true; -#endif + + // interrupted is not used if expired frames are not rendered + // since offering a frame will never block + bool m_interrupted = false; }; #endif // VIDEO_BUFFER_H diff --git a/QtScrcpy/device/device.cpp b/QtScrcpy/device/device.cpp index cebc2f6..1bf9bad 100644 --- a/QtScrcpy/device/device.cpp +++ b/QtScrcpy/device/device.cpp @@ -30,7 +30,7 @@ Device::Device(DeviceParams params, QObject *parent) if (params.display) { m_vb = new VideoBuffer(); - m_vb->init(); + m_vb->init(params.renderExpiredFrames); m_decoder = new Decoder(m_vb, this); m_fileHandler = new FileHandler(this); m_controller = new Controller(params.gameScript, this); diff --git a/QtScrcpy/device/device.h b/QtScrcpy/device/device.h index 424ddfd..d5c7f20 100644 --- a/QtScrcpy/device/device.h +++ b/QtScrcpy/device/device.h @@ -18,16 +18,17 @@ class Device : public QObject Q_OBJECT public: struct DeviceParams { - QString recordFileName = ""; // 视频录制文件名 - QString serial = ""; // 设备序列号 - quint16 localPort = 27183; // reverse时本地监听端口 - quint16 maxSize = 720; // 视频分辨率 - quint32 bitRate = 8000000; // 视频比特率 - quint32 maxFps = 60; // 视频最大帧率 - bool closeScreen = false; // 启动时自动息屏 - bool useReverse = true; // true:先使用adb reverse,失败后自动使用adb forward;false:直接使用adb forward - bool display = true; // 是否显示画面(或者仅仅后台录制) - QString gameScript = ""; // 游戏映射脚本 + QString recordFileName = ""; // 视频录制文件名 + QString serial = ""; // 设备序列号 + quint16 localPort = 27183; // reverse时本地监听端口 + quint16 maxSize = 720; // 视频分辨率 + quint32 bitRate = 8000000; // 视频比特率 + quint32 maxFps = 60; // 视频最大帧率 + bool closeScreen = false; // 启动时自动息屏 + bool useReverse = true; // true:先使用adb reverse,失败后自动使用adb forward;false:直接使用adb forward + bool display = true; // 是否显示画面(或者仅仅后台录制) + QString gameScript = ""; // 游戏映射脚本 + bool renderExpiredFrames = false; // 是否渲染延迟视频帧 }; explicit Device(DeviceParams params, QObject *parent = nullptr); virtual ~Device(); diff --git a/QtScrcpy/dialog.cpp b/QtScrcpy/dialog.cpp index 095aa96..d5cf11a 100644 --- a/QtScrcpy/dialog.cpp +++ b/QtScrcpy/dialog.cpp @@ -162,6 +162,7 @@ void Dialog::on_startServerBtn_clicked() params.closeScreen = ui->closeScreenCheck->isChecked(); params.useReverse = ui->useReverseCheck->isChecked(); params.display = !ui->notDisplayCheck->isChecked(); + params.renderExpiredFrames = Config::getInstance().getRenderExpiredFrames(); m_deviceManage.connectDevice(params); diff --git a/QtScrcpy/util/config.cpp b/QtScrcpy/util/config.cpp index 6009223..e5fb9de 100644 --- a/QtScrcpy/util/config.cpp +++ b/QtScrcpy/util/config.cpp @@ -30,6 +30,9 @@ #define COMMON_SKIN_KEY "UseSkin" #define COMMON_SKIN_DEF 1 +#define COMMON_RENDER_EXPIRED_FRAMES_KEY "RenderExpiredFrames" +#define COMMON_RENDER_EXPIRED_FRAMES_DEF 0 + QString Config::s_configPath = ""; Config::Config(QObject *parent) : QObject(parent) @@ -108,6 +111,15 @@ int Config::getSkin() return skin; } +int Config::getRenderExpiredFrames() +{ + int renderExpiredFrames = 1; + m_settings->beginGroup(GROUP_COMMON); + renderExpiredFrames = m_settings->value(COMMON_RENDER_EXPIRED_FRAMES_KEY, COMMON_RENDER_EXPIRED_FRAMES_DEF).toInt(); + m_settings->endGroup(); + return renderExpiredFrames; +} + QString Config::getPushFilePath() { QString pushFile; diff --git a/QtScrcpy/util/config.h b/QtScrcpy/util/config.h index f23d3b6..9d8514c 100644 --- a/QtScrcpy/util/config.h +++ b/QtScrcpy/util/config.h @@ -17,6 +17,7 @@ public: int getMaxFps(); int getDesktopOpenGL(); int getSkin(); + int getRenderExpiredFrames(); QString getPushFilePath(); QString getServerPath(); diff --git a/config/config.ini b/config/config.ini index ae2f632..d13d8cd 100644 --- a/config/config.ini +++ b/config/config.ini @@ -9,6 +9,8 @@ PushFilePath=/sdcard/ MaxFps=60 # 是否显示手机皮肤,0不显示 UseSkin=1 +# 是否渲染过期视频帧(跳过过期视频帧意味着更低的延迟) +RenderExpiredFrames=0 # 视频解码方式:-1 自动,0 软解,1 dx硬解,2 opengl硬解 UseDesktopOpenGL=-1 # scrcpy-server的版本号(不要修改) diff --git a/docs/TODO.md b/docs/TODO.md index 4fc5d00..d4de436 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -3,7 +3,6 @@ # TODO ## 低优先级 - 中文输入(server需要改为apk,作为一个输入法,暂不实现)(或者有其他方式案件注入方式,例如搜狗手机输入法可以监听当前注入?) -- [跳过帧改为动态配置,而不是静态编译](https://github.com/Genymobile/scrcpy/commit/ebccb9f6cc111e8acfbe10d656cac5c1f1b744a0) - [单独线程统计帧率](https://github.com/Genymobile/scrcpy/commit/e2a272bf99ecf48fcb050177113f903b3fb323c4) - text转换 https://github.com/Genymobile/scrcpy/commit/c916af0984f72a60301d13fa8ef9a85112f54202?tdsourcetag=s_pctim_aiomsg - ui提供show touch设置