mirror of
https://github.com/barry-ran/QtScrcpy.git
synced 2025-08-03 06:08:39 +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_AUTOMOC ON)
|
||||||
set(CMAKE_AUTORCC ON)
|
set(CMAKE_AUTORCC ON)
|
||||||
|
|
||||||
find_package(QT NAMES Qt6 Qt5 COMPONENTS Widgets Network REQUIRED)
|
find_package(QT NAMES Qt6 Qt5 COMPONENTS Widgets Network Multimedia REQUIRED)
|
||||||
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets Network REQUIRED)
|
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets Network Multimedia REQUIRED)
|
||||||
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||||
find_package(QT NAMES Qt6 Qt5 COMPONENTS X11Extras REQUIRED)
|
find_package(QT NAMES Qt6 Qt5 COMPONENTS X11Extras REQUIRED)
|
||||||
find_package(Qt${QT_VERSION_MAJOR} 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})
|
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
|
# ui
|
||||||
set(QC_UI_SOURCES
|
set(QC_UI_SOURCES
|
||||||
ui/toolform.h
|
ui/toolform.h
|
||||||
|
@ -199,6 +206,7 @@ set(QC_PROJECT_SOURCES
|
||||||
${QC_MAIN_SOURCES}
|
${QC_MAIN_SOURCES}
|
||||||
${QC_GROUP_CONTROLLER}
|
${QC_GROUP_CONTROLLER}
|
||||||
${QC_PLANTFORM_SOURCES}
|
${QC_PLANTFORM_SOURCES}
|
||||||
|
${QC_AUDIO_SOURCES}
|
||||||
)
|
)
|
||||||
|
|
||||||
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
||||||
|
@ -242,6 +250,11 @@ set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||||
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
||||||
get_target_property(QSC_BIN_OUTPUT_PATH ${PROJECT_NAME} RUNTIME_OUTPUT_DIRECTORY)
|
get_target_property(QSC_BIN_OUTPUT_PATH ${PROJECT_NAME} RUNTIME_OUTPUT_DIRECTORY)
|
||||||
set(QSC_DEPLOY_PATH ${QSC_BIN_OUTPUT_PATH})
|
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()
|
endif()
|
||||||
|
|
||||||
# MacOS
|
# MacOS
|
||||||
|
@ -309,5 +322,6 @@ add_subdirectory(QtScrcpyCore)
|
||||||
target_link_libraries(${PROJECT_NAME} PRIVATE
|
target_link_libraries(${PROJECT_NAME} PRIVATE
|
||||||
Qt${QT_VERSION_MAJOR}::Widgets
|
Qt${QT_VERSION_MAJOR}::Widgets
|
||||||
Qt${QT_VERSION_MAJOR}::Network
|
Qt${QT_VERSION_MAJOR}::Network
|
||||||
|
Qt${QT_VERSION_MAJOR}::Multimedia
|
||||||
QtScrcpyCore
|
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>
|
<source>refresh devices</source>
|
||||||
<translation>refresh devices</translation>
|
<translation>refresh devices</translation>
|
||||||
</message>
|
</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>
|
</context>
|
||||||
</TS>
|
</TS>
|
||||||
|
|
Binary file not shown.
|
@ -273,5 +273,17 @@
|
||||||
<source>refresh devices</source>
|
<source>refresh devices</source>
|
||||||
<translation>刷新设备列表</translation>
|
<translation>刷新设备列表</translation>
|
||||||
</message>
|
</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>
|
</context>
|
||||||
</TS>
|
</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;
|
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 "adbprocess.h"
|
||||||
#include "../QtScrcpyCore/include/QtScrcpyCore.h"
|
#include "../QtScrcpyCore/include/QtScrcpyCore.h"
|
||||||
|
#include "audio/audiooutput.h"
|
||||||
|
|
||||||
namespace Ui
|
namespace Ui
|
||||||
{
|
{
|
||||||
|
@ -57,6 +58,12 @@ private slots:
|
||||||
void on_useSingleModeCheck_clicked();
|
void on_useSingleModeCheck_clicked();
|
||||||
void on_serialBox_currentIndexChanged(const QString &arg1);
|
void on_serialBox_currentIndexChanged(const QString &arg1);
|
||||||
|
|
||||||
|
void on_startAudioBtn_clicked();
|
||||||
|
|
||||||
|
void on_stopAudioBtn_clicked();
|
||||||
|
|
||||||
|
void on_installSndcpyBtn_clicked();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool checkAdbRun();
|
bool checkAdbRun();
|
||||||
void initUI();
|
void initUI();
|
||||||
|
@ -79,6 +86,7 @@ private:
|
||||||
QMenu *m_menu;
|
QMenu *m_menu;
|
||||||
QAction *m_showWindow;
|
QAction *m_showWindow;
|
||||||
QAction *m_quit;
|
QAction *m_quit;
|
||||||
|
AudioOutput m_audioOutput;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // DIALOG_H
|
#endif // DIALOG_H
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>1293</width>
|
<width>1293</width>
|
||||||
<height>419</height>
|
<height>454</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
|
@ -941,6 +941,45 @@
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</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>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue