Merge pull request #628 from barry-ran/dev

sync
This commit is contained in:
Barry 2022-06-20 13:27:06 +08:00 committed by GitHub
commit 2a4093a487
30 changed files with 757 additions and 257 deletions

View file

@ -18,7 +18,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-18.04]
os: [ubuntu-22.04]
qt-ver: [5.15.1]
qt-arch-install: [gcc_64]
gcc-arch: [x64]

View file

@ -79,8 +79,8 @@ set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
find_package(QT NAMES Qt6 Qt5 COMPONENTS Widgets Network REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets Network REQUIRED)
find_package(QT NAMES Qt6 Qt5 COMPONENTS Widgets Network Multimedia REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets Network Multimedia REQUIRED)
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
find_package(QT NAMES Qt6 Qt5 COMPONENTS X11Extras REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS X11Extras REQUIRED)
@ -108,6 +108,13 @@ set(QC_UIBASE_SOURCES
)
source_group(uibase FILES ${QC_UIBASE_SOURCES})
# audio
set(QC_AUDIO_SOURCES
audio/audiooutput.h
audio/audiooutput.cpp
)
source_group(audio FILES ${QC_AUDIO_SOURCES})
# ui
set(QC_UI_SOURCES
ui/toolform.h
@ -199,6 +206,7 @@ set(QC_PROJECT_SOURCES
${QC_MAIN_SOURCES}
${QC_GROUP_CONTROLLER}
${QC_PLANTFORM_SOURCES}
${QC_AUDIO_SOURCES}
)
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
@ -242,6 +250,11 @@ set_target_properties(${PROJECT_NAME} PROPERTIES
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
get_target_property(QSC_BIN_OUTPUT_PATH ${PROJECT_NAME} RUNTIME_OUTPUT_DIRECTORY)
set(QSC_DEPLOY_PATH ${QSC_BIN_OUTPUT_PATH})
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/sndcpy/sndcpy.bat" "${QSC_BIN_OUTPUT_PATH}"
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/sndcpy/sndcpy.apk" "${QSC_BIN_OUTPUT_PATH}"
)
endif()
# MacOS
@ -255,6 +268,8 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
# config file copy to Contents/MacOS/config
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/../config/config.ini" "${MACOS_BUNDLE_PATH}/MacOS/config/config.ini"
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/sndcpy/sndcpy.sh" "${MACOS_BUNDLE_PATH}/MacOS"
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/sndcpy/sndcpy.apk" "${MACOS_BUNDLE_PATH}/MacOS"
)
# Step 2. ues MACOSX_PACKAGE_LOCATION copy icns to Resources
@ -284,6 +299,11 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
get_target_property(QSC_BIN_OUTPUT_PATH ${PROJECT_NAME} RUNTIME_OUTPUT_DIRECTORY)
set(QSC_DEPLOY_PATH ${QSC_BIN_OUTPUT_PATH})
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/sndcpy/sndcpy.sh" "${QSC_BIN_OUTPUT_PATH}"
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/sndcpy/sndcpy.apk" "${QSC_BIN_OUTPUT_PATH}"
)
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
@ -309,5 +329,6 @@ add_subdirectory(QtScrcpyCore)
target_link_libraries(${PROJECT_NAME} PRIVATE
Qt${QT_VERSION_MAJOR}::Widgets
Qt${QT_VERSION_MAJOR}::Network
Qt${QT_VERSION_MAJOR}::Multimedia
QtScrcpyCore
)

@ -1 +1 @@
Subproject commit 7ac8571daadf5af9b548a4c02eb7c6d504af6a97
Subproject commit 3004e63935fe8a3e57b91e117a91c1a6aa68ae42

View file

@ -0,0 +1,197 @@
#include <QTcpSocket>
#include <QHostAddress>
#include <QAudioOutput>
#include <QTime>
#include <QElapsedTimer>
#include "audiooutput.h"
AudioOutput::AudioOutput(QObject *parent)
: QObject(parent)
{
connect(&m_sndcpy, &QProcess::readyReadStandardOutput, this, [this]() {
qInfo() << QString("AudioOutput::") << QString(m_sndcpy.readAllStandardOutput());
});
connect(&m_sndcpy, &QProcess::readyReadStandardError, this, [this]() {
qInfo() << QString("AudioOutput::") << QString(m_sndcpy.readAllStandardError());
});
}
AudioOutput::~AudioOutput()
{
if (QProcess::NotRunning != m_sndcpy.state()) {
m_sndcpy.kill();
}
stop();
}
bool AudioOutput::start(const QString& serial, int port)
{
if (m_running) {
stop();
}
QElapsedTimer timeConsumeCount;
timeConsumeCount.start();
bool ret = runSndcpyProcess(serial, port);
qInfo() << "AudioOutput::run sndcpy cost:" << timeConsumeCount.elapsed() << "milliseconds";
if (!ret) {
return ret;
}
startAudioOutput();
startRecvData(port);
m_running = true;
return true;
}
void AudioOutput::stop()
{
if (!m_running) {
return;
}
m_running = false;
stopRecvData();
stopAudioOutput();
}
void AudioOutput::installonly(const QString &serial, int port)
{
runSndcpyProcess(serial, port, false);
}
bool AudioOutput::runSndcpyProcess(const QString &serial, int port, bool wait)
{
if (QProcess::NotRunning != m_sndcpy.state()) {
m_sndcpy.kill();
}
#ifdef Q_OS_WIN32
QStringList params;
params << serial;
params << QString("%1").arg(port);
m_sndcpy.start("sndcpy.bat", params);
#else
QStringList params;
params << "sndcpy.sh";
params << serial;
params << QString("%1").arg(port);
m_sndcpy.start("bash", params);
#endif
if (!wait) {
return true;
}
if (!m_sndcpy.waitForStarted()) {
qWarning() << "AudioOutput::start sndcpy.bat failed";
return false;
}
if (!m_sndcpy.waitForFinished()) {
qWarning() << "AudioOutput::sndcpy.bat crashed";
return false;
}
return true;
}
void AudioOutput::startAudioOutput()
{
if (m_audioOutput) {
return;
}
QAudioFormat format;
format.setSampleRate(48000);
format.setChannelCount(2);
format.setSampleSize(16);
format.setCodec("audio/pcm");
format.setByteOrder(QAudioFormat::LittleEndian);
format.setSampleType(QAudioFormat::SignedInt);
QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice());
if (!info.isFormatSupported(format)) {
qWarning() << "AudioOutput::audio format not supported, cannot play audio.";
return;
}
m_audioOutput = new QAudioOutput(format, this);
connect(m_audioOutput, &QAudioOutput::stateChanged, this, [](QAudio::State state) {
qInfo() << "AudioOutput::audio state changed:" << state;
});
m_audioOutput->setBufferSize(48000*2*15/1000 * 20);
m_outputDevice = m_audioOutput->start();
}
void AudioOutput::stopAudioOutput()
{
if (!m_audioOutput) {
return;
}
m_audioOutput->stop();
delete m_audioOutput;
m_audioOutput = nullptr;
}
void AudioOutput::startRecvData(int port)
{
if (m_workerThread.isRunning()) {
stopRecvData();
}
auto audioSocket = new QTcpSocket();
audioSocket->moveToThread(&m_workerThread);
connect(&m_workerThread, &QThread::finished, audioSocket, &QObject::deleteLater);
connect(this, &AudioOutput::connectTo, audioSocket, [audioSocket](int port) {
audioSocket->connectToHost(QHostAddress::LocalHost, port);
if (!audioSocket->waitForConnected(500)) {
qWarning("AudioOutput::audio socket connect failed");
return;
}
qInfo("AudioOutput::audio socket connect success");
});
connect(audioSocket, &QIODevice::readyRead, audioSocket, [this, audioSocket]() {
qint64 recv = audioSocket->bytesAvailable();
//qDebug() << "AudioOutput::recv data:" << recv;
if (!m_outputDevice) {
return;
}
if (m_buffer.capacity() < recv) {
m_buffer.reserve(recv);
}
qint64 count = audioSocket->read(m_buffer.data(), audioSocket->bytesAvailable());
m_outputDevice->write(m_buffer.data(), count);
});
connect(audioSocket, &QTcpSocket::stateChanged, audioSocket, [](QAbstractSocket::SocketState state) {
qInfo() << "AudioOutput::audio socket state changed:" << state;
});
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
connect(audioSocket, &QTcpSocket::errorOccurred, audioSocket, [](QAbstractSocket::SocketError error) {
qInfo() << "AudioOutput::audio socket error occurred:" << error;
});
#else
connect(audioSocket, QOverload<QAbstractSocket::SocketError>::of(&QAbstractSocket::error), audioSocket, [](QAbstractSocket::SocketError error) {
qInfo() << "AudioOutput::audio socket error occurred:" << error;
});
#endif
m_workerThread.start();
emit connectTo(port);
}
void AudioOutput::stopRecvData()
{
if (!m_workerThread.isRunning()) {
return;
}
m_workerThread.quit();
m_workerThread.wait();
}

View file

@ -0,0 +1,41 @@
#ifndef AUDIOOUTPUT_H
#define AUDIOOUTPUT_H
#include <QThread>
#include <QProcess>
#include <QPointer>
#include <QVector>
class QAudioOutput;
class QIODevice;
class AudioOutput : public QObject
{
Q_OBJECT
public:
explicit AudioOutput(QObject *parent = nullptr);
~AudioOutput();
bool start(const QString& serial, int port);
void stop();
void installonly(const QString& serial, int port);
private:
bool runSndcpyProcess(const QString& serial, int port, bool wait = true);
void startAudioOutput();
void stopAudioOutput();
void startRecvData(int port);
void stopRecvData();
signals:
void connectTo(int port);
private:
QAudioOutput* m_audioOutput = nullptr;
QPointer<QIODevice> m_outputDevice;
QThread m_workerThread;
QProcess m_sndcpy;
QVector<char> m_buffer;
bool m_running = false;
};
#endif // AUDIOOUTPUT_H

View file

@ -22,21 +22,24 @@ int main(int argc, char *argv[])
{
// set env
#ifdef Q_OS_WIN32
qputenv("QTSCRCPY_ADB_PATH", "D:/android/sdk/platform-tools/adb.exe");
qputenv("QTSCRCPY_ADB_PATH", "../../../QtScrcpy/QtScrcpyCore/src/third_party/adb/win/adb.exe");
qputenv("QTSCRCPY_SERVER_PATH", "../../../QtScrcpy/QtScrcpyCore/src/third_party/scrcpy-server");
qputenv("QTSCRCPY_KEYMAP_PATH", "../../../keymap");
qputenv("QTSCRCPY_CONFIG_PATH", "../../../config");
#endif
#ifdef Q_OS_OSX
qputenv("QTSCRCPY_KEYMAP_PATH", "../../../../../keymap");
qputenv("QTSCRCPY_ADB_PATH", "../../../../../../QtScrcpy/QtScrcpyCore/src/third_party/adb/mac/adb");
qputenv("QTSCRCPY_SERVER_PATH", "../../../../../../QtScrcpy/QtScrcpyCore/src/third_party/scrcpy-server");
qputenv("QTSCRCPY_KEYMAP_PATH", "../../../../../../keymap");
qputenv("QTSCRCPY_CONFIG_PATH", "../../../../../../config");
#endif
#ifdef Q_OS_LINUX
qputenv("QTSCRCPY_ADB_PATH", "../../QtScrcpy/QtScrcpyCore/src/third_party/adb/linux/adb");
qputenv("QTSCRCPY_SERVER_PATH", "../../QtScrcpy/QtScrcpyCore/src/third_party/scrcpy-server");
qputenv("QTSCRCPY_CONFIG_PATH", "../../config");
qputenv("QTSCRCPY_KEYMAP_PATH", "../../keymap");
qputenv("QTSCRCPY_ADB_PATH", "../../../QtScrcpy/QtScrcpyCore/src/third_party/adb/linux/adb");
qputenv("QTSCRCPY_SERVER_PATH", "../../../QtScrcpy/QtScrcpyCore/src/third_party/scrcpy-server");
qputenv("QTSCRCPY_KEYMAP_PATH", "../../../keymap");
qputenv("QTSCRCPY_CONFIG_PATH", "../../../config");
#endif
g_msgType = covertLogLevel(Config::getInstance().getLogLevel());

Binary file not shown.

View file

@ -3,6 +3,114 @@
<TS version="2.1" language="en_US">
<context>
<name>Dialog</name>
<message>
<source>show</source>
<translation>show</translation>
</message>
<message>
<source>quit</source>
<translation>quit</translation>
</message>
<message>
<source>original</source>
<translation>original</translation>
</message>
<message>
<source>no lock</source>
<translation>no lock</translation>
</message>
<message>
<source>Notice</source>
<translation>Notice</translation>
</message>
<message>
<source>Hidden here!</source>
<translation>Hidden here!</translation>
</message>
<message>
<source>select path</source>
<translation>select path</translation>
</message>
</context>
<context>
<name>QObject</name>
<message>
<source>This software is completely open source and free. Use it at your own risk. You can download it at the following address:</source>
<translation>This software is completely open source and free. Use it at your own risk. You can download it at the following address:</translation>
</message>
</context>
<context>
<name>ToolForm</name>
<message>
<source>Tool</source>
<translation>Tool</translation>
</message>
<message>
<source>full screen</source>
<translation>full screen</translation>
</message>
<message>
<source>expand notify</source>
<translation>expand notify</translation>
</message>
<message>
<source>touch switch</source>
<translation>touch switch</translation>
</message>
<message>
<source>close screen</source>
<translation>close screen</translation>
</message>
<message>
<source>power</source>
<translation>power</translation>
</message>
<message>
<source>volume up</source>
<translation>volume up</translation>
</message>
<message>
<source>volume down</source>
<translation>volume down</translation>
</message>
<message>
<source>app switch</source>
<translation>app switch</translation>
</message>
<message>
<source>menu</source>
<translation>menu</translation>
</message>
<message>
<source>home</source>
<translation>home</translation>
</message>
<message>
<source>return</source>
<translation>return</translation>
</message>
<message>
<source>screen shot</source>
<translation>screen shot</translation>
</message>
<message>
<source>open screen</source>
<translation>open screen</translation>
</message>
<message>
<source>group control</source>
<translation>group control</translation>
</message>
</context>
<context>
<name>VideoForm</name>
<message>
<source>file does not exist</source>
<translation>file does not exist</translation>
</message>
</context>
<context>
<name>Widget</name>
<message>
<source>Wireless</source>
<translation>Wireless</translation>
@ -166,109 +274,20 @@
<translation>refresh devices</translation>
</message>
<message>
<source>show</source>
<translatorcomment>show</translatorcomment>
<translation>show</translation>
<source>install sndcpy</source>
<translation>install sndcpy</translation>
</message>
<message>
<source>quit</source>
<translatorcomment>quit</translatorcomment>
<translation>quit</translation>
<source>start audio</source>
<translation>start audio</translation>
</message>
<message>
<source>original</source>
<translation>original</translation>
<source>stop audio</source>
<translation>stop audio</translation>
</message>
<message>
<source>no lock</source>
<translation>no lock</translation>
</message>
<message>
<source>Notice</source>
<translatorcomment>Notice</translatorcomment>
<translation>Notice</translation>
</message>
<message>
<source>Hidden here!</source>
<translatorcomment>Hidden here!</translatorcomment>
<translation>Hidden here!</translation>
</message>
</context>
<context>
<name>QObject</name>
<message>
<source>This software is completely open source and free. Use it at your own risk. You can download it at the following address:</source>
<translation>This software is completely open source and free. Use it at your own risk. You can download it at the following address:</translation>
</message>
</context>
<context>
<name>ToolForm</name>
<message>
<source>Tool</source>
<translation>Tool</translation>
</message>
<message>
<source>full screen</source>
<translation>full screen</translation>
</message>
<message>
<source>expand notify</source>
<translation>expand notify</translation>
</message>
<message>
<source>touch switch</source>
<translation>touch switch</translation>
</message>
<message>
<source>close screen</source>
<translation>close screen</translation>
</message>
<message>
<source>power</source>
<translation>power</translation>
</message>
<message>
<source>volume up</source>
<translation>volume up</translation>
</message>
<message>
<source>volume down</source>
<translation>volume down</translation>
</message>
<message>
<source>app switch</source>
<translation>app switch</translation>
</message>
<message>
<source>menu</source>
<translation>menu</translation>
</message>
<message>
<source>home</source>
<translation>home</translation>
</message>
<message>
<source>return</source>
<translation>return</translation>
</message>
<message>
<source>screen shot</source>
<translation>screen shot</translation>
</message>
<message>
<source>open screen</source>
<translation>open screen</translation>
</message>
<message>
<source>group control</source>
<translation>group control</translation>
</message>
</context>
<context>
<name>VideoForm</name>
<message>
<source>file does not exist</source>
<translation>file does not exist</translation>
<source>auto update</source>
<translation>autp update</translation>
</message>
</context>
</TS>

Binary file not shown.

View file

@ -3,6 +3,114 @@
<TS version="2.1" language="zh_CN">
<context>
<name>Dialog</name>
<message>
<source>show</source>
<translation></translation>
</message>
<message>
<source>quit</source>
<translation>退</translation>
</message>
<message>
<source>original</source>
<translation></translation>
</message>
<message>
<source>no lock</source>
<translation></translation>
</message>
<message>
<source>Notice</source>
<translation></translation>
</message>
<message>
<source>Hidden here!</source>
<translation></translation>
</message>
<message>
<source>select path</source>
<translation></translation>
</message>
</context>
<context>
<name>QObject</name>
<message>
<source>This software is completely open source and free. Use it at your own risk. You can download it at the following address:</source>
<translation>使</translation>
</message>
</context>
<context>
<name>ToolForm</name>
<message>
<source>Tool</source>
<translation></translation>
</message>
<message>
<source>full screen</source>
<translation></translation>
</message>
<message>
<source>expand notify</source>
<translation></translation>
</message>
<message>
<source>touch switch</source>
<translation></translation>
</message>
<message>
<source>close screen</source>
<translation></translation>
</message>
<message>
<source>power</source>
<translation></translation>
</message>
<message>
<source>volume up</source>
<translation></translation>
</message>
<message>
<source>volume down</source>
<translation></translation>
</message>
<message>
<source>app switch</source>
<translation></translation>
</message>
<message>
<source>menu</source>
<translation></translation>
</message>
<message>
<source>home</source>
<translation></translation>
</message>
<message>
<source>return</source>
<translation></translation>
</message>
<message>
<source>screen shot</source>
<translation></translation>
</message>
<message>
<source>open screen</source>
<translation></translation>
</message>
<message>
<source>group control</source>
<translation></translation>
</message>
</context>
<context>
<name>VideoForm</name>
<message>
<source>file does not exist</source>
<translation></translation>
</message>
</context>
<context>
<name>Widget</name>
<message>
<source>Wireless</source>
<translation>线</translation>
@ -166,109 +274,20 @@
<translation></translation>
</message>
<message>
<source>show</source>
<translatorcomment></translatorcomment>
<translation></translation>
<source>install sndcpy</source>
<translation>sndcpy</translation>
</message>
<message>
<source>quit</source>
<translatorcomment>退</translatorcomment>
<translation>退</translation>
<source>start audio</source>
<translation></translation>
</message>
<message>
<source>original</source>
<translation></translation>
<source>stop audio</source>
<translation></translation>
</message>
<message>
<source>no lock</source>
<translation></translation>
</message>
<message>
<source>Notice</source>
<translatorcomment></translatorcomment>
<translation></translation>
</message>
<message>
<source>Hidden here!</source>
<translatorcomment></translatorcomment>
<translation></translation>
</message>
</context>
<context>
<name>QObject</name>
<message>
<source>This software is completely open source and free. Use it at your own risk. You can download it at the following address:</source>
<translation>使</translation>
</message>
</context>
<context>
<name>ToolForm</name>
<message>
<source>Tool</source>
<translation></translation>
</message>
<message>
<source>full screen</source>
<translation></translation>
</message>
<message>
<source>expand notify</source>
<translation></translation>
</message>
<message>
<source>touch switch</source>
<translation></translation>
</message>
<message>
<source>close screen</source>
<translation></translation>
</message>
<message>
<source>power</source>
<translation></translation>
</message>
<message>
<source>volume up</source>
<translation></translation>
</message>
<message>
<source>volume down</source>
<translation></translation>
</message>
<message>
<source>app switch</source>
<translation></translation>
</message>
<message>
<source>menu</source>
<translation></translation>
</message>
<message>
<source>home</source>
<translation></translation>
</message>
<message>
<source>return</source>
<translation></translation>
</message>
<message>
<source>screen shot</source>
<translation></translation>
</message>
<message>
<source>open screen</source>
<translation></translation>
</message>
<message>
<source>group control</source>
<translation></translation>
</message>
</context>
<context>
<name>VideoForm</name>
<message>
<source>file does not exist</source>
<translation></translation>
<source>auto update</source>
<translation></translation>
</message>
</context>
</TS>

BIN
QtScrcpy/sndcpy/sndcpy.apk Normal file

Binary file not shown.

View file

@ -0,0 +1,53 @@
@echo off
echo Begin Runing...
set SNDCPY_PORT=28200
set SNDCPY_APK=sndcpy.apk
set ADB=adb.exe
if not "%1"=="" (
set serial=-s %1
)
if not "%2"=="" (
set SNDCPY_PORT=%2
)
echo Waiting for device %1...
%ADB% %serial% wait-for-device || goto :error
echo Find device %1
for /f "delims=" %%i in ('%ADB% %serial% shell pm path com.rom1v.sndcpy') do set sndcpy_installed=%%i
if "%sndcpy_installed%"=="" (
echo Install %SNDCPY_APK%...
%ADB% %serial% uninstall com.rom1v.sndcpy || goto :error
%ADB% %serial% install -t -r -g %SNDCPY_APK% || goto :error
echo Install %SNDCPY_APK% success
)
echo Request PROJECT_MEDIA permission...
%ADB% %serial% shell appops set com.rom1v.sndcpy PROJECT_MEDIA allow
echo Forward port %SNDCPY_PORT%...
%ADB% %serial% forward tcp:%SNDCPY_PORT% localabstract:sndcpy || goto :error
echo Start %SNDCPY_APK%...
%ADB% %serial% shell am start com.rom1v.sndcpy/.MainActivity || goto :error
:check_start
echo Waiting %SNDCPY_APK% start...
::timeout /T 1 /NOBREAK > nul
%ADB% %serial% shell sleep 0.1
for /f "delims=" %%i in ("%ADB% shell 'ps | grep com.rom1v.sndcpy'") do set sndcpy_started=%%i
if "%sndcpy_started%"=="" (
goto :check_start
)
echo %SNDCPY_APK% started...
echo Ready playing...
::vlc.exe -Idummy --demux rawaud --network-caching=0 --play-and-exit tcp://localhost:%SNDCPY_PORT%
::ffplay.exe -nodisp -autoexit -probesize 32 -sync ext -f s16le -ar 48k -ac 2 tcp://localhost:%SNDCPY_PORT%
goto :EOF
:error
echo Failed with error #%errorlevel%.
exit /b %errorlevel%

46
QtScrcpy/sndcpy/sndcpy.sh Executable file
View file

@ -0,0 +1,46 @@
#!/bin/bash
echo Begin Runing...
SNDCPY_PORT=28200
SNDCPY_APK=sndcpy.apk
ADB=./adb
serial=
if [[ $# -ge 2 ]]
then
serial="-s $1"
SNDCPY_PORT=$2
fi
echo "Waiting for device $1..."
$ADB $serial wait-for-device
echo "Find device $1"
sndcpy_installed=$($ADB $serial shell pm path com.rom1v.sndcpy)
if [[ $sndcpy_installed == "" ]]; then
echo Install $SNDCPY_APK...
$ADB $serial uninstall com.rom1v.sndcpy
$ADB $serial install -t -r -g $SNDCPY_APK
echo Install $SNDCPY_APK success
fi
echo Request PROJECT_MEDIA permission...
$ADB $serial shell appops set com.rom1v.sndcpy PROJECT_MEDIA allow
echo Forward port $SNDCPY_PORT...
$ADB $serial forward tcp:$SNDCPY_PORT localabstract:sndcpy
echo Start $SNDCPY_APK...
$ADB $serial shell am start com.rom1v.sndcpy/.MainActivity
while ((1))
do
echo Waiting $SNDCPY_APK start...
sleep 0.1
sndcpy_started=$($ADB shell 'ps | grep com.rom1v.sndcpy')
if [[ $sndcpy_started != "" ]]; then
break
fi
done
echo Ready playing...

View file

@ -25,11 +25,21 @@ const QString &getKeyMapPath()
return s_keyMapPath;
}
Dialog::Dialog(QWidget *parent) : QDialog(parent), ui(new Ui::Dialog)
Dialog::Dialog(QWidget *parent) : QWidget(parent), ui(new Ui::Widget)
{
ui->setupUi(this);
initUI();
updateBootConfig(true);
on_useSingleModeCheck_clicked();
on_updateDevice_clicked();
connect(&m_autoUpdatetimer, &QTimer::timeout, this, &Dialog::on_updateDevice_clicked);
if (ui->autoUpdatecheckBox->isChecked()) {
m_autoUpdatetimer.start(5000);
}
connect(&m_adb, &qsc::AdbProcess::adbProcessResult, this, [this](qsc::AdbProcess::ADB_EXEC_RESULT processResult) {
QString log = "";
bool newLine = true;
@ -101,7 +111,7 @@ Dialog::Dialog(QWidget *parent) : QDialog(parent), ui(new Ui::Dialog)
m_menu->addAction(m_quit);
m_hideIcon->setContextMenu(m_menu);
m_hideIcon->show();
connect(m_showWindow, &QAction::triggered, this, &Dialog::slotShow);
connect(m_showWindow, &QAction::triggered, this, &Dialog::show);
connect(m_quit, &QAction::triggered, this, [this]() {
m_hideIcon->hide();
qApp->quit();
@ -123,7 +133,7 @@ Dialog::~Dialog()
void Dialog::initUI()
{
setAttribute(Qt::WA_DeleteOnClose);
setWindowFlags(windowFlags() | Qt::WindowMinimizeButtonHint | Qt::WindowCloseButtonHint | Qt::CustomizeWindowHint);
//setWindowFlags(windowFlags() | Qt::WindowMinimizeButtonHint | Qt::WindowCloseButtonHint | Qt::CustomizeWindowHint);
setWindowTitle(Config::getInstance().getTitle());
@ -145,12 +155,6 @@ void Dialog::initUI()
ui->lockOrientationBox->addItem("180");
ui->lockOrientationBox->addItem("270");
ui->lockOrientationBox->setCurrentIndex(0);
updateBootConfig(true);
on_useSingleModeCheck_clicked();
on_updateDevice_clicked();
}
void Dialog::updateBootConfig(bool toView)
@ -181,6 +185,7 @@ void Dialog::updateBootConfig(bool toView)
ui->closeScreenCheck->setChecked(config.autoOffScreen);
ui->stayAwakeCheck->setChecked(config.keepAlive);
ui->useSingleModeCheck->setChecked(config.simpleMode);
ui->autoUpdatecheckBox->setChecked(config.autoUpdateDevice);
} else {
UserBootConfig config;
@ -198,6 +203,7 @@ void Dialog::updateBootConfig(bool toView)
config.framelessWindow = ui->framelessCheck->isChecked();
config.keepAlive = ui->stayAwakeCheck->isChecked();
config.simpleMode = ui->useSingleModeCheck->isChecked();
config.autoUpdateDevice = ui->autoUpdatecheckBox->isChecked();
Config::getInstance().setUserBootConfig(config);
}
}
@ -238,17 +244,13 @@ QString Dialog::getGameScript(const QString &fileName)
return ret;
}
void Dialog::slotShow()
{
this->show();
m_hideIcon->hide();
}
void Dialog::slotActivated(QSystemTrayIcon::ActivationReason reason)
{
switch (reason) {
case QSystemTrayIcon::Trigger:
#ifdef Q_OS_WIN32
this->show();
#endif
break;
default:
break;
@ -464,8 +466,11 @@ void Dialog::onDeviceConnected(bool success, const QString &serial, const QStrin
void Dialog::onDeviceDisconnected(QString serial)
{
GroupController::instance().removeDevice(serial);
auto data = qsc::IDeviceManage::getInstance().getDevice(serial)->getUserData();
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
return;
}
auto data = device->getUserData();
if (data) {
VideoForm* vf = static_cast<VideoForm*>(data);
qsc::IDeviceManage::getInstance().getDevice(serial)->deRegisterDeviceObserver(vf);
@ -688,3 +693,36 @@ const QString &Dialog::getServerPath()
}
return serverPath;
}
void Dialog::on_startAudioBtn_clicked()
{
if (ui->serialBox->count() == 0) {
qWarning() << "No device is connected!";
return;
}
m_audioOutput.start(ui->serialBox->currentText(), 28200);
}
void Dialog::on_stopAudioBtn_clicked()
{
m_audioOutput.stop();
}
void Dialog::on_installSndcpyBtn_clicked()
{
if (ui->serialBox->count() == 0) {
qWarning() << "No device is connected!";
return;
}
m_audioOutput.installonly(ui->serialBox->currentText(), 28200);
}
void Dialog::on_autoUpdatecheckBox_toggled(bool checked)
{
if (checked) {
m_autoUpdatetimer.start(5000);
} else {
m_autoUpdatetimer.stop();
}
}

View file

@ -1,24 +1,26 @@
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
#include <QWidget>
#include <QPointer>
#include <QMessageBox>
#include <QMenu>
#include <QSystemTrayIcon>
#include <QListWidget>
#include <QTimer>
#include "adbprocess.h"
#include "../QtScrcpyCore/include/QtScrcpyCore.h"
#include "audio/audiooutput.h"
namespace Ui
{
class Dialog;
class Widget;
}
class QYUVOpenGLWidget;
class Dialog : public QDialog
class Dialog : public QWidget
{
Q_OBJECT
@ -57,6 +59,14 @@ private slots:
void on_useSingleModeCheck_clicked();
void on_serialBox_currentIndexChanged(const QString &arg1);
void on_startAudioBtn_clicked();
void on_stopAudioBtn_clicked();
void on_installSndcpyBtn_clicked();
void on_autoUpdatecheckBox_toggled(bool checked);
private:
bool checkAdbRun();
void initUI();
@ -64,7 +74,6 @@ private:
void execAdbCmd();
void delayMs(int ms);
QString getGameScript(const QString &fileName);
void slotShow();
void slotActivated(QSystemTrayIcon::ActivationReason reason);
int findDeviceFromeSerialBox(bool wifi);
quint32 getBitRate();
@ -74,12 +83,14 @@ protected:
void closeEvent(QCloseEvent *event);
private:
Ui::Dialog *ui;
Ui::Widget *ui;
qsc::AdbProcess m_adb;
QSystemTrayIcon *m_hideIcon;
QMenu *m_menu;
QAction *m_showWindow;
QAction *m_quit;
AudioOutput m_audioOutput;
QTimer m_autoUpdatetimer;
};
#endif // DIALOG_H

View file

@ -1,13 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<class>Widget</class>
<widget class="QWidget" name="Widget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1293</width>
<height>419</height>
<height>454</height>
</rect>
</property>
<property name="windowTitle">
@ -100,17 +100,34 @@
</layout>
</item>
<item>
<widget class="QLabel" name="label_10">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
<layout class="QHBoxLayout" name="horizontalLayout_13">
<property name="topMargin">
<number>0</number>
</property>
<property name="text">
<string>Double click to connect:</string>
</property>
</widget>
<item>
<widget class="QLabel" name="label_10">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Double click to connect:</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="autoUpdatecheckBox">
<property name="text">
<string>auto update</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QListWidget" name="connectedPhoneList">
@ -941,6 +958,45 @@
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="usbWidget3" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_12">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QPushButton" name="installSndcpyBtn">
<property name="text">
<string>install sndcpy</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="startAudioBtn">
<property name="text">
<string>start audio</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="stopAudioBtn">
<property name="text">
<string>stop audio</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>

View file

@ -87,6 +87,9 @@
#define COMMON_SIMPLE_MODE_KEY "SimpleMode"
#define COMMON_SIMPLE_MODE_DEF false
#define COMMON_AUTO_UPDATE_DEVICE_KEY "AutoUpdateDevice"
#define COMMON_AUTO_UPDATE_DEVICE_DEF true
// device config
#define SERIAL_WINDOW_RECT_KEY_X "WindowRectX"
#define SERIAL_WINDOW_RECT_KEY_Y "WindowRectY"
@ -145,6 +148,7 @@ void Config::setUserBootConfig(const UserBootConfig &config)
m_userData->setValue(COMMON_AUTO_OFF_SCREEN_KEY, config.autoOffScreen);
m_userData->setValue(COMMON_KEEP_ALIVE_KEY, config.keepAlive);
m_userData->setValue(COMMON_SIMPLE_MODE_KEY, config.simpleMode);
m_userData->setValue(COMMON_AUTO_UPDATE_DEVICE_KEY, config.autoUpdateDevice);
m_userData->endGroup();
m_userData->sync();
}
@ -167,6 +171,7 @@ UserBootConfig Config::getUserBootConfig()
config.autoOffScreen = m_userData->value(COMMON_AUTO_OFF_SCREEN_KEY, COMMON_AUTO_OFF_SCREEN_DEF).toBool();
config.keepAlive = m_userData->value(COMMON_KEEP_ALIVE_KEY, COMMON_KEEP_ALIVE_DEF).toBool();
config.simpleMode = m_userData->value(COMMON_SIMPLE_MODE_KEY, COMMON_SIMPLE_MODE_DEF).toBool();
config.autoUpdateDevice = m_userData->value(COMMON_AUTO_UPDATE_DEVICE_KEY, COMMON_AUTO_UPDATE_DEVICE_DEF).toBool();
m_userData->endGroup();
return config;
}

View file

@ -21,6 +21,7 @@ struct UserBootConfig
bool framelessWindow = false;
bool keepAlive = false;
bool simpleMode = false;
bool autoUpdateDevice = true;
};
class QSettings;

View file

@ -25,11 +25,11 @@ It focuses on:
[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646
![win](screenshot/win.png)
![win](screenshot/win-en.png)
![mac](screenshot/mac.jpg)
![mac](screenshot/mac-en.png)
![linux](screenshot/ubuntu.png)
![linux](screenshot/linux-en.png)
## Customized key mapping
You can write your own script to map keyboard and mouse actions to touches and clicks of the mobile phone according to your needs. [Here](docs/KeyMapDes.md) are the rules.
@ -122,12 +122,7 @@ you can [build it by yourself](##Build)(just ubuntu test)
## Run
### Simple Mode
Connect to your Android device on your computer, then run the program and click `USB connect` or `WiFi connect`
### Not Simple Mode
Connect to your Android device on your computer, then run the program and click the button below to connect to the Android device.
![run](screenshot/run.png)
### Wireless connection steps (ensure that the mobile phone and PC are in the same LAN):
1. Enable USB debugging in developer options on the Android device
@ -139,7 +134,6 @@ Connect to your Android device on your computer, then run the program and click
7. Click update device again, and another device with IP address will be found. Select this device.
8. Click start service
Note: it is not necessary to keep you Android device connected via USB after you start adbd.
@ -187,6 +181,7 @@ Note: it is not necessary to keep you Android device connected via USB after you
- `Ctrl`+`v` _pastes_ the computer clipboard as a sequence of text events (but
breaks non-ASCII characters).
- Group control
- Sync device speaker sound to the computer (based on [sndcpy](https://github.com/rom1v/sndcpy), Android 10+ only)
## Shortcuts

View file

@ -24,12 +24,11 @@ QtScrcpy可以通过USB(或通过TCP/IP)连接Android设备并进行显示和
[低延迟]: https://github.com/Genymobile/scrcpy/pull/646
![win](screenshot/win-zh.png)
![win](screenshot/win.png)
![mac](screenshot/mac-zh.png)
![mac](screenshot/mac.jpg)
![linux](screenshot/ubuntu.png)
![linux](screenshot/linux-zh.png)
## 自定义按键映射
可以根据需要自己编写脚本将PC键盘按键映射为手机的触摸点击编写规则在[这里](docs/KeyMapDes_zh.md)。
@ -126,12 +125,7 @@ Mac OS平台你可以直接使用我编译好的可执行程序:
目前只在ubuntu上测试过
## 运行
### 精简模式
在你的电脑上接入Android设备然后运行程序点击`一键USB连接`或者`一键WIFI连接`
### 非精简模式
在你的电脑上接入Android设备然后运行程序按顺序点击如下按钮即可连接到Android设备
![运行](screenshot/run.png)
### 无线连接步骤(保证手机和电脑在同一个局域网):
1. 安卓手机端在开发者选项中打开usb调试
@ -186,6 +180,7 @@ Mac OS平台你可以直接使用我编译好的可执行程序:
- `Ctrl` + `Shift` + `v`将计算机剪贴板复制到设备剪贴板;
- `Ctrl` +`v` 将计算机剪贴板作为一系列文本事件发送到设备不支持非ASCII字符
- 群控
- 同步设备扬声器声音到电脑(基于[sndcpy](https://github.com/rom1v/sndcpy)仅支持安卓10+
## 快捷键

BIN
screenshot/linux-en.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

BIN
screenshot/linux-zh.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

BIN
screenshot/mac-en.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 MiB

BIN
screenshot/mac-zh.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 769 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 911 KiB

BIN
screenshot/win-en.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 919 KiB

BIN
screenshot/win-zh.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB