|
| 1 | +#include <QTcpSocket> |
| 2 | +#include <QHostAddress> |
| 3 | +#include <QAudioOutput> |
| 4 | +#include <QTime> |
| 5 | +#include <QElapsedTimer> |
| 6 | + |
| 7 | +#include "audiooutput.h" |
| 8 | + |
| 9 | +AudioOutput::AudioOutput(QObject *parent) |
| 10 | + : QObject(parent) |
| 11 | +{ |
| 12 | + connect(&m_sndcpy, &QProcess::readyReadStandardOutput, this, [this]() { |
| 13 | + qInfo() << QString("AudioOutput::") << QString(m_sndcpy.readAllStandardOutput()); |
| 14 | + }); |
| 15 | + connect(&m_sndcpy, &QProcess::readyReadStandardError, this, [this]() { |
| 16 | + qInfo() << QString("AudioOutput::") << QString(m_sndcpy.readAllStandardError()); |
| 17 | + }); |
| 18 | +} |
| 19 | + |
| 20 | +AudioOutput::~AudioOutput() |
| 21 | +{ |
| 22 | + if (QProcess::NotRunning != m_sndcpy.state()) { |
| 23 | + m_sndcpy.kill(); |
| 24 | + } |
| 25 | + stop(); |
| 26 | +} |
| 27 | + |
| 28 | +bool AudioOutput::start(const QString& serial, int port) |
| 29 | +{ |
| 30 | + if (m_running) { |
| 31 | + stop(); |
| 32 | + } |
| 33 | + |
| 34 | + QElapsedTimer timeConsumeCount; |
| 35 | + timeConsumeCount.start(); |
| 36 | + bool ret = runSndcpyProcess(serial, port); |
| 37 | + qInfo() << "AudioOutput::run sndcpy cost:" << timeConsumeCount.elapsed() << "milliseconds"; |
| 38 | + if (!ret) { |
| 39 | + return ret; |
| 40 | + } |
| 41 | + |
| 42 | + startAudioOutput(); |
| 43 | + startRecvData(port); |
| 44 | + |
| 45 | + m_running = true; |
| 46 | + return true; |
| 47 | +} |
| 48 | + |
| 49 | +void AudioOutput::stop() |
| 50 | +{ |
| 51 | + if (!m_running) { |
| 52 | + return; |
| 53 | + } |
| 54 | + m_running = false; |
| 55 | + |
| 56 | + stopRecvData(); |
| 57 | + stopAudioOutput(); |
| 58 | +} |
| 59 | + |
| 60 | +void AudioOutput::installonly(const QString &serial, int port) |
| 61 | +{ |
| 62 | + runSndcpyProcess(serial, port, false); |
| 63 | +} |
| 64 | + |
| 65 | +bool AudioOutput::runSndcpyProcess(const QString &serial, int port, bool wait) |
| 66 | +{ |
| 67 | + if (QProcess::NotRunning != m_sndcpy.state()) { |
| 68 | + m_sndcpy.kill(); |
| 69 | + } |
| 70 | + |
| 71 | +#ifdef Q_OS_WIN32 |
| 72 | + QStringList params; |
| 73 | + params << serial; |
| 74 | + params << QString("%1").arg(port); |
| 75 | + m_sndcpy.start("sndcpy.bat", params); |
| 76 | +#else |
| 77 | + QStringList params; |
| 78 | + params << "sndcpy.sh"; |
| 79 | + params << serial; |
| 80 | + params << QString("%1").arg(port); |
| 81 | + m_sndcpy.start("bash", params); |
| 82 | +#endif |
| 83 | + |
| 84 | + if (!wait) { |
| 85 | + return true; |
| 86 | + } |
| 87 | + |
| 88 | + if (!m_sndcpy.waitForStarted()) { |
| 89 | + qWarning() << "AudioOutput::start sndcpy.bat failed"; |
| 90 | + return false; |
| 91 | + } |
| 92 | + if (!m_sndcpy.waitForFinished()) { |
| 93 | + qWarning() << "AudioOutput::sndcpy.bat crashed"; |
| 94 | + return false; |
| 95 | + } |
| 96 | + |
| 97 | + return true; |
| 98 | +} |
| 99 | + |
| 100 | +void AudioOutput::startAudioOutput() |
| 101 | +{ |
| 102 | + if (m_audioOutput) { |
| 103 | + return; |
| 104 | + } |
| 105 | + |
| 106 | + QAudioFormat format; |
| 107 | + format.setSampleRate(48000); |
| 108 | + format.setChannelCount(2); |
| 109 | + format.setSampleSize(16); |
| 110 | + format.setCodec("audio/pcm"); |
| 111 | + format.setByteOrder(QAudioFormat::LittleEndian); |
| 112 | + format.setSampleType(QAudioFormat::SignedInt); |
| 113 | + |
| 114 | + QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice()); |
| 115 | + if (!info.isFormatSupported(format)) { |
| 116 | + qWarning() << "AudioOutput::audio format not supported, cannot play audio."; |
| 117 | + return; |
| 118 | + } |
| 119 | + |
| 120 | + m_audioOutput = new QAudioOutput(format, this); |
| 121 | + connect(m_audioOutput, &QAudioOutput::stateChanged, this, [](QAudio::State state) { |
| 122 | + qInfo() << "AudioOutput::audio state changed:" << state; |
| 123 | + }); |
| 124 | + m_audioOutput->setBufferSize(48000*2*15/1000 * 20); |
| 125 | + m_outputDevice = m_audioOutput->start(); |
| 126 | +} |
| 127 | + |
| 128 | +void AudioOutput::stopAudioOutput() |
| 129 | +{ |
| 130 | + if (!m_audioOutput) { |
| 131 | + return; |
| 132 | + } |
| 133 | + |
| 134 | + m_audioOutput->stop(); |
| 135 | + delete m_audioOutput; |
| 136 | + m_audioOutput = nullptr; |
| 137 | +} |
| 138 | + |
| 139 | +void AudioOutput::startRecvData(int port) |
| 140 | +{ |
| 141 | + if (m_workerThread.isRunning()) { |
| 142 | + stopRecvData(); |
| 143 | + } |
| 144 | + |
| 145 | + auto audioSocket = new QTcpSocket(); |
| 146 | + audioSocket->moveToThread(&m_workerThread); |
| 147 | + connect(&m_workerThread, &QThread::finished, audioSocket, &QObject::deleteLater); |
| 148 | + |
| 149 | + connect(this, &AudioOutput::connectTo, audioSocket, [audioSocket](int port) { |
| 150 | + audioSocket->connectToHost(QHostAddress::LocalHost, port); |
| 151 | + if (!audioSocket->waitForConnected(500)) { |
| 152 | + qWarning("AudioOutput::audio socket connect failed"); |
| 153 | + return; |
| 154 | + } |
| 155 | + qInfo("AudioOutput::audio socket connect success"); |
| 156 | + }); |
| 157 | + connect(audioSocket, &QIODevice::readyRead, audioSocket, [this, audioSocket]() { |
| 158 | + qint64 recv = audioSocket->bytesAvailable(); |
| 159 | + //qDebug() << "AudioOutput::recv data:" << recv; |
| 160 | + |
| 161 | + if (!m_outputDevice) { |
| 162 | + return; |
| 163 | + } |
| 164 | + if (m_buffer.capacity() < recv) { |
| 165 | + m_buffer.reserve(recv); |
| 166 | + } |
| 167 | + |
| 168 | + qint64 count = audioSocket->read(m_buffer.data(), audioSocket->bytesAvailable()); |
| 169 | + m_outputDevice->write(m_buffer.data(), count); |
| 170 | + }); |
| 171 | + connect(audioSocket, &QTcpSocket::stateChanged, audioSocket, [](QAbstractSocket::SocketState state) { |
| 172 | + qInfo() << "AudioOutput::audio socket state changed:" << state; |
| 173 | + |
| 174 | + }); |
| 175 | +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) |
| 176 | + connect(audioSocket, &QTcpSocket::errorOccurred, audioSocket, [](QAbstractSocket::SocketError error) { |
| 177 | + qInfo() << "AudioOutput::audio socket error occurred:" << error; |
| 178 | + }); |
| 179 | +#else |
| 180 | + connect(audioSocket, QOverload<QAbstractSocket::SocketError>::of(&QAbstractSocket::error), audioSocket, [](QAbstractSocket::SocketError error) { |
| 181 | + qInfo() << "AudioOutput::audio socket error occurred:" << error; |
| 182 | + }); |
| 183 | +#endif |
| 184 | + |
| 185 | + m_workerThread.start(); |
| 186 | + emit connectTo(port); |
| 187 | +} |
| 188 | + |
| 189 | +void AudioOutput::stopRecvData() |
| 190 | +{ |
| 191 | + if (!m_workerThread.isRunning()) { |
| 192 | + return; |
| 193 | + } |
| 194 | + |
| 195 | + m_workerThread.quit(); |
| 196 | + m_workerThread.wait(); |
| 197 | +} |
0 commit comments