diff --git a/QtScrcpy/QtScrcpy.pro b/QtScrcpy/QtScrcpy.pro index 41e46bc..8ac2b36 100644 --- a/QtScrcpy/QtScrcpy.pro +++ b/QtScrcpy/QtScrcpy.pro @@ -140,7 +140,7 @@ macos { -L$$PWD/../third_party/ffmpeg/lib -lswscale.5 # mac bundle file - APP_SCRCPY_SERVER.files = $$files($$PWD/../third_party/scrcpy-server.jar) + APP_SCRCPY_SERVER.files = $$files($$PWD/../third_party/scrcpy-server) APP_SCRCPY_SERVER.path = Contents/MacOS QMAKE_BUNDLE_DATA += APP_SCRCPY_SERVER diff --git a/QtScrcpy/device/recorder/recorder.cpp b/QtScrcpy/device/recorder/recorder.cpp index f37d19f..b418810 100644 --- a/QtScrcpy/device/recorder/recorder.cpp +++ b/QtScrcpy/device/recorder/recorder.cpp @@ -1,5 +1,6 @@ #include #include +#include #include "compat.h" #include "recorder.h" @@ -24,6 +25,10 @@ Recorder::RecordPacket* Recorder::packetNew(const AVPacket *packet) { if (!rec) { return Q_NULLPTR; } + + // av_packet_ref() does not initialize all fields in old FFmpeg versions + av_init_packet(&rec->packet); + if (av_packet_ref(&rec->packet, packet)) { delete rec; return Q_NULLPTR; @@ -121,6 +126,10 @@ bool Recorder::open(const AVCodec* inputCodec) m_formatCtx->oformat = (AVOutputFormat*)format; + QString comment = "Recorded by QtScrcpy " + QCoreApplication::applicationVersion(); + av_dict_set(&m_formatCtx->metadata, "comment", + comment.toUtf8(), 0); + AVStream* outStream = avformat_new_stream(m_formatCtx, inputCodec); if (!outStream) { avformat_free_context(m_formatCtx); @@ -152,7 +161,7 @@ bool Recorder::open(const AVCodec* inputCodec) avformat_free_context(m_formatCtx); m_formatCtx = Q_NULLPTR; return false; - } + } return true; } @@ -160,11 +169,17 @@ bool Recorder::open(const AVCodec* inputCodec) void Recorder::close() { if (Q_NULLPTR != m_formatCtx) { - int ret = av_write_trailer(m_formatCtx); - if (ret < 0) { - qCritical(QString("Failed to write trailer to %1").arg(m_fileName).toUtf8().toStdString().c_str()); + if (m_headerWritten) { + int ret = av_write_trailer(m_formatCtx); + if (ret < 0) { + qCritical(QString("Failed to write trailer to %1").arg(m_fileName).toUtf8().toStdString().c_str()); + m_failed = true; + } else { + qInfo(QString("success record %1").arg(m_fileName).toStdString().c_str()); + } } else { - qInfo(QString("success record %1").arg(m_fileName).toStdString().c_str()); + // the recorded file is empty + m_failed = true; } avio_close(m_formatCtx->pb); avformat_free_context(m_formatCtx); @@ -274,23 +289,53 @@ Recorder::RecorderFormat Recorder::guessRecordFormat(const QString &fileName) void Recorder::run() { for (;;) { - QMutexLocker locker(&m_mutex); - while (!m_stopped && queueIsEmpty(&m_queue)) { - m_recvDataCond.wait(&m_mutex); + RecordPacket *rec = Q_NULLPTR; + { + 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)) { + RecordPacket* last = m_previous; + if (last) { + // assign an arbitrary duration to the last packet + last->packet.duration = 100000; + bool ok = write(&last->packet); + if (!ok) { + // failing to write the last frame is not very serious, no + // future frame may depend on it, so the resulting file + // will still be valid + qWarning("Could not record last packet"); + } + packetDelete(last); + } + break; + } + + rec = queueTake(&m_queue); } - // if stopped is set, continue to process the remaining events (to - // finish the recording) before actually stopping - if (m_stopped && queueIsEmpty(&m_queue)) { - break; + // recorder->previous is only written from this thread, no need to lock + RecordPacket* previous = m_previous; + m_previous = rec; + + if (!previous) { + // we just received the first packet + continue; } - RecordPacket *rec = queueTake(&m_queue); + // config packets have no PTS, we must ignore them + if (rec->packet.pts != AV_NOPTS_VALUE + && previous->packet.pts != AV_NOPTS_VALUE) { + // we now know the duration of the previous packet + previous->packet.duration = rec->packet.pts - previous->packet.pts; + } - //mutex_unlock(recorder->mutex); - - bool ok = write(&rec->packet); - packetDelete(rec); + bool ok = write(&previous->packet); + packetDelete(previous); if (!ok) { qCritical("Could not record packet"); @@ -300,7 +345,6 @@ void Recorder::run() { queueClear(&m_queue); break; } - } qDebug("Recorder thread ended"); diff --git a/QtScrcpy/device/recorder/recorder.h b/QtScrcpy/device/recorder/recorder.h index 35927cd..da9695c 100644 --- a/QtScrcpy/device/recorder/recorder.h +++ b/QtScrcpy/device/recorder/recorder.h @@ -73,6 +73,11 @@ private: bool m_stopped = false; // set on recorder_stop() by the stream reader bool m_failed = false; // set on packet write failure RecorderQueue m_queue; + // we can write a packet only once we received the next one so that we can + // set its duration (next_pts - current_pts) + // "previous" is only accessed from the recorder thread, so it does not + // need to be protected by the mutex + RecordPacket* m_previous = Q_NULLPTR; }; #endif // RECORDER_H diff --git a/QtScrcpy/device/server/server.cpp b/QtScrcpy/device/server/server.cpp index a4f3d96..f60da81 100644 --- a/QtScrcpy/device/server/server.cpp +++ b/QtScrcpy/device/server/server.cpp @@ -56,7 +56,7 @@ const QString& Server::getServerPath() m_serverPath = QString::fromLocal8Bit(qgetenv("QTSCRCPY_SERVER_PATH")); QFileInfo fileInfo(m_serverPath); if (m_serverPath.isEmpty() || !fileInfo.isFile()) { - m_serverPath = QCoreApplication::applicationDirPath() + "/scrcpy-server.jar"; + m_serverPath = QCoreApplication::applicationDirPath() + "/scrcpy-server"; } } return m_serverPath; @@ -129,10 +129,7 @@ bool Server::execute() args << "app_process"; args << "/"; // unused; args << "com.genymobile.scrcpy.Server"; - // version - QStringList versionList = QCoreApplication::applicationVersion().split("."); - QString version = versionList[0] + "." + versionList[1] + "." + versionList[2]; - args << version; + args << QCoreApplication::applicationVersion(); args << QString::number(m_params.maxSize); args << QString::number(m_params.bitRate); args << QString::number(m_params.maxFps); @@ -145,7 +142,7 @@ bool Server::execute() args << "true"; // always send frame meta (packet boundaries + timestamp) args << (m_params.control ? "true" : "false"); - // adb -s P7C0218510000537 shell CLASSPATH=/data/local/tmp/scrcpy-server.jar app_process / com.genymobile.scrcpy.Server 0 8000000 false + // adb -s P7C0218510000537 shell CLASSPATH=/data/local/tmp/scrcpy-server app_process / com.genymobile.scrcpy.Server 0 8000000 false // mark: crop input format: "width:height:x:y" or - for no crop, for example: "100:200:0:0" // 这条adb命令是阻塞运行的,m_serverProcess进程不会退出了 m_serverProcess.execute(m_params.serial, args); diff --git a/QtScrcpy/main.cpp b/QtScrcpy/main.cpp index d123e4e..e0182a9 100644 --- a/QtScrcpy/main.cpp +++ b/QtScrcpy/main.cpp @@ -30,6 +30,11 @@ int main(int argc, char *argv[]) qDebug() << a.applicationVersion(); qDebug() << a.applicationName(); + //update version + QStringList versionList = QCoreApplication::applicationVersion().split("."); + QString version = versionList[0] + "." + versionList[1] + "." + versionList[2]; + a.setApplicationVersion(version); + installTranslator(); #if defined(Q_OS_WIN32) || defined(Q_OS_OSX) MouseTap::getInstance()->initMouseEventTap(); @@ -37,13 +42,13 @@ int main(int argc, char *argv[]) #ifdef Q_OS_WIN32 qputenv("QTSCRCPY_ADB_PATH", "../../../../third_party/adb/win/adb.exe"); - qputenv("QTSCRCPY_SERVER_PATH", "../../../../third_party/scrcpy-server.jar"); + qputenv("QTSCRCPY_SERVER_PATH", "../../../../third_party/scrcpy-server"); qputenv("QTSCRCPY_KEYMAP_PATH", "../../../../keymap"); #endif #ifdef Q_OS_LINUX qputenv("QTSCRCPY_ADB_PATH", "../../../third_party/adb/linux/adb"); - qputenv("QTSCRCPY_SERVER_PATH", "../../../third_party/scrcpy-server.jar"); + qputenv("QTSCRCPY_SERVER_PATH", "../../../third_party/scrcpy-server"); #endif //加载样式表 @@ -59,6 +64,8 @@ int main(int argc, char *argv[]) g_mainDlg = new Dialog; g_mainDlg->show(); + qInfo(QString("QtScrcpy %1 ").arg(QCoreApplication::applicationVersion()).toUtf8()); + int ret = a.exec(); #if defined(Q_OS_WIN32) || defined(Q_OS_OSX) diff --git a/README.md b/README.md index c45019e..b3e7647 100644 --- a/README.md +++ b/README.md @@ -174,11 +174,11 @@ Try to provide all the dependencies and make it easy to compile. 3. Open the project root directory all.pro with QtCreator 4. Compile and run -### Android (If you do not need to modify the requirements, you can use the built-in scrcpy-server.jar directly) +### Android (If you do not need to modify the requirements, you can use the built-in scrcpy-server directly) 1. Set up an Android development environment on the target platform 2. Open the server project in the project root directory using Android Studio 3. Build it -4. After compiling apk, rename it to scrcpy-server.jar and replace third_party/scrcpy-server.jar. +4. After compiling apk, rename it to scrcpy-server and replace third_party/scrcpy-server ## Licence Since it is based on scrcpy, respect its Licence diff --git a/README_zh.md b/README_zh.md index ff156dc..dcaaae2 100644 --- a/README_zh.md +++ b/README_zh.md @@ -182,12 +182,12 @@ Mac OS平台,你可以直接使用我编译好的可执行程序: 3. 使用QtCreator打开项目根目录all.pro 4. 编译,运行即可 -### Android端 (没有修改需求的话直接使用自带的scrcpy-server.jar即可) +### Android端 (没有修改需求的话直接使用自带的scrcpy-server即可) 1. 目标平台上搭建Android开发环境 2. 使用Android Studio打开项目根目录中的server项目 3. 第一次打开如果你没有对应版本的gradle会提示找不到gradle,是否升级gradle并创建,选择取消,取消后会弹出选择已有gradle的位置,同样取消即可(会自动下载) 4. 按需编辑代码即可,当然也可以不编辑 -4. 编译出apk以后改名为scrcpy-server.jar并替换third_party/scrcpy-server.jar即可 +4. 编译出apk以后改名为scrcpy-server并替换third_party/scrcpy-server即可 ## Licence 由于是复刻的scrcpy,尊重它的Licence diff --git a/docs/TODO.md b/docs/TODO.md index 282a69b..1dbda13 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -1,20 +1,15 @@ -最后同步scrcpy b91ecf52256da73f5c8dca04fb82c13ec826cbd7 +最后同步scrcpy 31bd95022bc525be42ca273d59a3211d964d278b # TODO ## 低优先级 - 中文输入(server需要改为apk,作为一个输入法,暂不实现)(或者有其他方式案件注入方式,例如搜狗手机输入法可以监听当前注入?) -- 鼠标事件相关系列 b35733edb6df2a00b6af9b1c98627d344c377963 - [跳过帧改为动态配置,而不是静态编译](https://github.com/Genymobile/scrcpy/commit/ebccb9f6cc111e8acfbe10d656cac5c1f1b744a0) - [单独线程统计帧率](https://github.com/Genymobile/scrcpy/commit/e2a272bf99ecf48fcb050177113f903b3fb323c4) - ui提供show touch设置 -- 隐藏手机皮肤开关 ## 中优先级 - [截屏保存为jpg](https://blog.csdn.net/m0_37684310/article/details/77950390) -- 版本号升级优化 -- linux打包以及版本号 - 自动打包脚本 -- 按键映射可配置 - 脚本 - 群控 - 配置文件 @@ -24,10 +19,9 @@ - 分辨率码率可自定义 ## 高优先级 -- 同步延迟优化 +- linux打包以及版本号 # BUG -1. 魅族手机提示cant open video stream,解决方法 https://dim.red/2019/03/03/scrcpy_usage/ # mark [ffmpeg编译参数详解](https://www.cnblogs.com/wainiwann/p/4204230.html) diff --git a/third_party/scrcpy-server.jar b/third_party/scrcpy-server similarity index 100% rename from third_party/scrcpy-server.jar rename to third_party/scrcpy-server