mirror of
https://github.com/barry-ran/QtScrcpy.git
synced 2025-04-20 11:35:56 +00:00
feat: sync scrcpy
This commit is contained in:
parent
5710b08301
commit
d7e9b7809f
9 changed files with 86 additions and 39 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include <QDebug>
|
||||
#include <QFileInfo>
|
||||
#include <QCoreApplication>
|
||||
|
||||
#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");
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 <https://github.com/barry-ran/QtScrcpy>").arg(QCoreApplication::applicationVersion()).toUtf8());
|
||||
|
||||
int ret = a.exec();
|
||||
|
||||
#if defined(Q_OS_WIN32) || defined(Q_OS_OSX)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
10
docs/TODO.md
10
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)
|
||||
|
|
BIN
third_party/scrcpy-server
vendored
Normal file
BIN
third_party/scrcpy-server
vendored
Normal file
Binary file not shown.
Loading…
Add table
Reference in a new issue