mirror of
https://github.com/barry-ran/QtScrcpy.git
synced 2025-04-20 03:25:02 +00:00
feat: support audio on win
This commit is contained in:
parent
055315f411
commit
e70070c0d6
12 changed files with 384 additions and 3 deletions
|
@ -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
|
||||
|
@ -309,5 +322,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
|
||||
)
|
||||
|
|
178
QtScrcpy/audio/audiooutput.cpp
Normal file
178
QtScrcpy/audio/audiooutput.cpp
Normal file
|
@ -0,0 +1,178 @@
|
|||
#include <QTcpSocket>
|
||||
#include <QHostAddress>
|
||||
#include <QAudioOutput>
|
||||
#include <QTime>
|
||||
|
||||
#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);
|
||||
}
|
||||
|
||||
bool AudioOutput::runSndcpyProcess(const QString &serial, int port)
|
||||
{
|
||||
if (QProcess::NotRunning != m_sndcpy.state()) {
|
||||
m_sndcpy.kill();
|
||||
}
|
||||
|
||||
QStringList params;
|
||||
params << serial;
|
||||
params << QString("%1").arg(port);
|
||||
m_sndcpy.start("sndcpy.bat", params);
|
||||
/*
|
||||
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::UnSignedInt);
|
||||
|
||||
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;
|
||||
|
||||
});
|
||||
connect(audioSocket, &QTcpSocket::errorOccurred, audioSocket, [](QAbstractSocket::SocketError error) {
|
||||
qInfo() << "AudioOutput::audio socket error occurred:" << error;
|
||||
});
|
||||
|
||||
m_workerThread.start();
|
||||
emit connectTo(port);
|
||||
}
|
||||
|
||||
void AudioOutput::stopRecvData()
|
||||
{
|
||||
if (!m_workerThread.isRunning()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_workerThread.quit();
|
||||
m_workerThread.wait();
|
||||
}
|
41
QtScrcpy/audio/audiooutput.h
Normal file
41
QtScrcpy/audio/audiooutput.h
Normal 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);
|
||||
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
|
Binary file not shown.
|
@ -273,5 +273,17 @@
|
|||
<source>refresh devices</source>
|
||||
<translation>refresh devices</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>install sndcpy</source>
|
||||
<translation>install sndcpy</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>start audio</source>
|
||||
<translation>start audio</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>stop audio</source>
|
||||
<translation>stop audio</translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
|
|
Binary file not shown.
|
@ -273,5 +273,17 @@
|
|||
<source>refresh devices</source>
|
||||
<translation>刷新设备列表</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>install sndcpy</source>
|
||||
<translation>安装sndcpy</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>start audio</source>
|
||||
<translation>开始音频</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>stop audio</source>
|
||||
<translation>停止音频</translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
|
|
BIN
QtScrcpy/sndcpy/sndcpy.apk
Normal file
BIN
QtScrcpy/sndcpy/sndcpy.apk
Normal file
Binary file not shown.
53
QtScrcpy/sndcpy/sndcpy.bat
Normal file
53
QtScrcpy/sndcpy/sndcpy.bat
Normal 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%
|
|
@ -687,3 +687,27 @@ 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);
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include "adbprocess.h"
|
||||
#include "../QtScrcpyCore/include/QtScrcpyCore.h"
|
||||
#include "audio/audiooutput.h"
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
|
@ -57,6 +58,12 @@ 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();
|
||||
|
||||
private:
|
||||
bool checkAdbRun();
|
||||
void initUI();
|
||||
|
@ -79,6 +86,7 @@ private:
|
|||
QMenu *m_menu;
|
||||
QAction *m_showWindow;
|
||||
QAction *m_quit;
|
||||
AudioOutput m_audioOutput;
|
||||
};
|
||||
|
||||
#endif // DIALOG_H
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1293</width>
|
||||
<height>419</height>
|
||||
<height>454</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
|
@ -941,6 +941,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>
|
||||
|
|
Loading…
Add table
Reference in a new issue