diff --git a/AudioDecoder.cpp b/AudioDecoder.cpp new file mode 100644 index 0000000..32bbc40 --- /dev/null +++ b/AudioDecoder.cpp @@ -0,0 +1,119 @@ +#include "AudioDecoder.h" + +AudioDecoder::AudioDecoder(QObject *parent) : + QIODevice(parent), + m_state { AudioDecoder::Stopped }, + m_input { &m_data, this }, + m_output { &m_data, this }, + m_init { false }, + m_isDecodingFinished { false } +{ + setOpenMode(QIODevice::ReadOnly); + + m_decoder = new QAudioDecoder(this); + connect(m_decoder, &QAudioDecoder::bufferReady, this, &AudioDecoder::bufferReady); + connect(m_decoder, static_cast(&QAudioDecoder::error), this, &AudioDecoder::errored); + connect(m_decoder, &QAudioDecoder::finished, this, &AudioDecoder::finished); +} + +AudioDecoder::~AudioDecoder(){ + stop(); +} + +// initialize an audio device +void AudioDecoder::init(const QAudioFormat &format) { + m_decoder->setAudioFormat(format); + + if (!m_output.open(QIODevice::ReadOnly) || !m_input.open(QIODevice::WriteOnly)){ + m_init = false; + return; + } + + m_init = true; + emit initialized(); +} + +// play an audio file +void AudioDecoder::start(const QString &filePath){ + if(!m_init){ + return; + } + + if(m_state == AudioDecoder::Decoding){ + return; + } + + m_state = AudioDecoder::Decoding; + m_decoder->setSourceFilename(filePath); + m_decoder->start(); +} + +// Stop playing audio +void AudioDecoder::stop() { + m_state = AudioDecoder::Stopped; + m_decoder->stop(); + m_data.clear(); + m_isDecodingFinished = false; +} + + +// io device, read into buffer. +qint64 AudioDecoder::readData(char* data, qint64 maxlen) { + memset(data, 0, maxlen); + + if (m_state == AudioDecoder::Decoding){ + m_output.read(data, maxlen); + + // Emulate QAudioProbe behaviour for audio data that is sent to output device + if (maxlen > 0){ + QByteArray buff(data, maxlen); + emit newData(buff); + } + + // Is finish of file + if (atEnd()){ + stop(); + } + } + + return maxlen; +} + +// io device, unused. +qint64 AudioDecoder::writeData(const char* data, qint64 len) { + Q_UNUSED(data); + Q_UNUSED(len); + + return 0; +} + +// io device, at end of device +bool AudioDecoder::atEnd() const { + bool value = m_output.size() + && m_output.atEnd() + && m_isDecodingFinished; + return value; +} + +// handle buffered data ready +void AudioDecoder::bufferReady() { + const QAudioBuffer &buffer = m_decoder->read(); + if(!buffer.isValid()){ + return; + } + + const int length = buffer.byteCount(); + const char *data = buffer.constData(); + + m_input.write(data, length); +} + +// handle buffered data decoding is finished +void AudioDecoder::finished() { + m_isDecodingFinished = true; +} + +// handle buffered data decoding error +void AudioDecoder::errored(QAudioDecoder::Error /*error*/) { + stop(); +} diff --git a/AudioDecoder.h b/AudioDecoder.h new file mode 100644 index 0000000..7455291 --- /dev/null +++ b/AudioDecoder.h @@ -0,0 +1,50 @@ +#ifndef AUDIODECODER_H +#define AUDIODECODER_H + +#include +#include +#include +#include +#include + + +class AudioDecoder : + public QIODevice +{ + Q_OBJECT +public: + enum State { Decoding, Stopped }; + explicit AudioDecoder(QObject *parent = 0); + ~AudioDecoder(); + + bool atEnd() const override; + +public slots: + void init(const QAudioFormat &format); + void start(const QString &filePath); + void stop(); + +protected: + qint64 readData(char* data, qint64 maxlen) override; + qint64 writeData(const char* data, qint64 len) override; + +private: + State m_state; + QPointer m_decoder; + QBuffer m_input; + QBuffer m_output; + QByteArray m_data; + bool m_init; + bool m_isDecodingFinished; + +private slots: + void bufferReady(); + void finished(); + void errored(QAudioDecoder::Error); + +signals: + void initialized(); + void newData(const QByteArray& data); +}; + +#endif // AUDIODECODER_H diff --git a/CMakeLists.txt b/CMakeLists.txt index 1daecdd..acb187c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -253,6 +253,7 @@ set (wsjtx_CXXSRCS wsprnet.cpp WSPRBandHopping.cpp TransmitTextEdit.cpp + AudioDecoder.cpp NotificationAudio.cpp ) diff --git a/NotificationAudio.cpp b/NotificationAudio.cpp index de2cfb6..23f67ea 100644 --- a/NotificationAudio.cpp +++ b/NotificationAudio.cpp @@ -1,163 +1,28 @@ #include "NotificationAudio.h" -NotificationAudio::NotificationAudio(QObject *parent) : - QIODevice(parent), - m_state(State::Stopped), - m_input(&m_data, this), - m_output(&m_data, this), - m_decoder(nullptr), - m_audio(nullptr) +NotificationAudio::NotificationAudio(QObject *parent): + QObject(parent) { - setOpenMode(QIODevice::ReadOnly); - - m_init = false; - m_isDecodingFinished = false; + m_stream = new SoundOutput(); + m_decoder = new AudioDecoder(this); } NotificationAudio::~NotificationAudio(){ stop(); - - if(m_decoder){ - delete m_decoder; - m_decoder = nullptr; - } - - if(m_audio){ - delete m_audio; - m_audio = nullptr; - } } -// initialize an audio device -void NotificationAudio::init(const QAudioDeviceInfo &device, const QAudioFormat& format) { - m_device = device; - m_format = format; - - if(!m_decoder){ - m_decoder = new QAudioDecoder(this); - connect(m_decoder, &QAudioDecoder::bufferReady, this, &NotificationAudio::bufferReady); - connect(m_decoder, static_cast(&QAudioDecoder::error), this, &NotificationAudio::errored); - connect(m_decoder, &QAudioDecoder::finished, this, &NotificationAudio::finished); - } - m_decoder->setAudioFormat(m_format); - - if(!m_audio){ - m_audio = new QAudioOutput(m_device, m_format, this); - } - - if (!m_output.open(QIODevice::ReadOnly) || !m_input.open(QIODevice::WriteOnly)){ - m_init = false; - return; - } - - m_init = true; - emit initialized(); +void NotificationAudio::setDevice(const QAudioDeviceInfo &device, unsigned channels, unsigned msBuffer){ + m_stream->setFormat(device, channels, msBuffer); + m_decoder->init(m_stream->format()); } -// play an audio file void NotificationAudio::play(const QString &filePath){ - if(m_state == NotificationAudio::Playing){ - return; - } - - if(!m_init || !m_decoder || !m_audio){ - return; - } - - resetBuffers(); - - m_state = State::Playing; - emit stateChanged(m_state); - - m_decoder->setSourceFilename(filePath); - m_decoder->start(); - m_audio->start(this); + m_decoder->start(filePath); + m_stream->restart(m_decoder); } -// Stop playing audio -void NotificationAudio::stop() { - resetBuffers(); - m_state = State::Stopped; - emit stateChanged(m_state); -} - -// Reset the internal buffers and ensure the decoder and audio device is stopped -void NotificationAudio::resetBuffers() { - if(m_audio){ - if(m_audio->state() != QAudio::SuspendedState){ - m_audio->suspend(); - } - } - - if(m_decoder){ - if(m_decoder->state() != QAudioDecoder::StoppedState){ - m_decoder->stop(); - } - } - - m_data.clear(); - m_isDecodingFinished = false; -} - -// io device, read into buffer. -qint64 NotificationAudio::readData(char* data, qint64 maxlen) { - memset(data, 0, maxlen); - - if (m_state == State::Playing){ - m_output.read(data, maxlen); - - // There is we send readed audio data via signal, for ability get audio signal for the who listen this signal. - // Other word this emulate QAudioProbe behaviour for retrieve audio data which of sent to output device (speaker). - // if (maxlen > 0){ - // QByteArray buff(data, maxlen); - // emit newData(buff); - // } - - // Is finish of file - if (atEnd()){ - stop(); - } - } - - return maxlen; -} - -// io device, unused. -qint64 NotificationAudio::writeData(const char* data, qint64 len) { - Q_UNUSED(data); - Q_UNUSED(len); - - return 0; -} - -// io device, at end of device -bool NotificationAudio::atEnd() const { - bool value = m_output.size() - && m_output.atEnd() - && m_isDecodingFinished; - return value; -} - -// handle buffered data ready -void NotificationAudio::bufferReady() { - const QAudioBuffer &buffer = m_decoder->read(); - if(!buffer.isValid()){ - return; - } - - const int length = buffer.byteCount(); - const char *data = buffer.constData(); - - m_input.write(data, length); -} - -// handle buffered data decoding is finished -void NotificationAudio::finished() { - m_isDecodingFinished = true; -} - -// handle buffered data decoding error -void NotificationAudio::errored(QAudioDecoder::Error /*error*/) { - stop(); +void NotificationAudio::stop(){ + m_decoder->stop(); + m_stream->stop(); } diff --git a/NotificationAudio.h b/NotificationAudio.h index 86207cc..dc37023 100644 --- a/NotificationAudio.h +++ b/NotificationAudio.h @@ -3,16 +3,18 @@ #include #include -#include #include #include #include #include +#include -// Class for decode audio files like MP3 and push decoded audio data to QOutputDevice (like speaker) and also signal newData(). -// For decoding it uses QAudioDecoder which uses QAudioFormat for decode audio file for desire format, then put decoded data to buffer. -// based on: https://github.com/Znurre/QtMixer -class NotificationAudio : public QIODevice +#include "AudioDevice.hpp" +#include "AudioDecoder.h" +#include "soundout.h" + +class NotificationAudio : + public QObject { Q_OBJECT @@ -20,46 +22,14 @@ public: NotificationAudio(QObject * parent=nullptr); ~NotificationAudio(); - bool isInitialized() const { return m_init; } - - enum State { Playing, Stopped }; - - bool atEnd() const override; - public slots: - void init(const QAudioDeviceInfo &device, const QAudioFormat& format); + void setDevice(const QAudioDeviceInfo &device, unsigned channels, unsigned msBuffer=0); void play(const QString &filePath); void stop(); -protected: - - qint64 readData(char* data, qint64 maxlen) override; - qint64 writeData(const char* data, qint64 len) override; - private: - State m_state; - QBuffer m_input; - QBuffer m_output; - QByteArray m_data; - QAudioFormat m_format; - QAudioDeviceInfo m_device; - QAudioDecoder * m_decoder; - QAudioOutput * m_audio; - - bool m_init; - bool m_isDecodingFinished; - - void resetBuffers(); - -private slots: - void bufferReady(); - void finished(); - void errored(QAudioDecoder::Error); - -signals: - void initialized(); - void stateChanged(NotificationAudio::State state); - void newData(const QByteArray& data); + QPointer m_stream; + QPointer m_decoder; }; #endif // NOTIFICATIONAUDIO_H diff --git a/js8call.pro b/js8call.pro index 1dcc7be..26f2320 100644 --- a/js8call.pro +++ b/js8call.pro @@ -86,7 +86,8 @@ SOURCES += \ TCPClient.cpp \ TransmitTextEdit.cpp \ NotificationAudio.cpp \ - CallsignValidator.cpp + CallsignValidator.cpp \ + AudioDecoder.cpp HEADERS += qt_helpers.hpp \ pimpl_h.hpp pimpl_impl.hpp \ @@ -123,7 +124,8 @@ HEADERS += qt_helpers.hpp \ TCPClient.h \ logbook/n3fjp.h \ TransmitTextEdit.h \ - NotificationAudio.h + NotificationAudio.h \ + AudioDecoder.h INCLUDEPATH += qmake_only diff --git a/mainwindow.cpp b/mainwindow.cpp index 13ccc62..531fb89 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -514,7 +514,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, connect (this, &MainWindow::outAttenuationChanged, m_soundOutput, &SoundOutput::setAttenuation); connect (&m_audioThread, &QThread::finished, m_soundOutput, &QObject::deleteLater); - connect (this, &MainWindow::initializeNotificationAudioOutputStream, m_notification, &NotificationAudio::init); + connect (this, &MainWindow::initializeNotificationAudioOutputStream, m_notification, &NotificationAudio::setDevice); connect (&m_config, &Configuration::test_notify, this, &MainWindow::tryNotify); connect (this, &MainWindow::playNotification, m_notification, &NotificationAudio::play); connect (&m_notificationAudioThread, &QThread::finished, m_notification, &QObject::deleteLater); @@ -945,7 +945,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, // Q_EMIT startAudioInputStream (m_config.audio_input_device (), m_framesAudioInputBuffered, &m_detector, m_downSampleFactor, m_config.audio_input_channel ()); Q_EMIT startAudioInputStream (m_config.audio_input_device (), m_framesAudioInputBuffered, m_detector, m_downSampleFactor, m_config.audio_input_channel ()); Q_EMIT initializeAudioOutputStream (m_config.audio_output_device (), AudioDevice::Mono == m_config.audio_output_channel () ? 1 : 2, m_msAudioOutputBuffered); - Q_EMIT initializeNotificationAudioOutputStream(m_config.notification_audio_output_device(), m_config.notification_audio_output_device().preferredFormat()); + Q_EMIT initializeNotificationAudioOutputStream(m_config.notification_audio_output_device(), AudioDevice::Mono == m_config.notification_audio_output_channel () ? 1 : 2, m_msAudioOutputBuffered); Q_EMIT transmitFrequency (ui->TxFreqSpinBox->value () - m_XIT); enable_DXCC_entity (m_config.DXCC ()); // sets text window proportions and (re)inits the logbook @@ -3023,9 +3023,9 @@ void MainWindow::openSettings(int tab){ } if(m_config.restart_notification_audio_output ()) { - Q_EMIT initializeNotificationAudioOutputStream( - m_config.notification_audio_output_device(), - m_config.notification_audio_output_device().preferredFormat()); + Q_EMIT initializeNotificationAudioOutputStream(m_config.notification_audio_output_device(), + AudioDevice::Mono == m_config.notification_audio_output_channel () ? 1 : 2, + m_msAudioOutputBuffered); } ui->bandComboBox->view ()->setMinimumWidth (ui->bandComboBox->view ()->sizeHintForColumn (FrequencyList_v2::frequency_mhz_column)); diff --git a/mainwindow.h b/mainwindow.h index a11548d..aea593c 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -438,7 +438,7 @@ private slots: private: Q_SIGNAL void playNotification(const QString &name); - Q_SIGNAL void initializeNotificationAudioOutputStream(QAudioDeviceInfo, QAudioFormat); + Q_SIGNAL void initializeNotificationAudioOutputStream(const QAudioDeviceInfo &, unsigned, unsigned) const; Q_SIGNAL void initializeAudioOutputStream (QAudioDeviceInfo, unsigned channels, unsigned msBuffered) const; Q_SIGNAL void stopAudioOutputStream () const; diff --git a/soundout.cpp b/soundout.cpp index 944f04e..a91026a 100644 --- a/soundout.cpp +++ b/soundout.cpp @@ -71,6 +71,7 @@ void SoundOutput::setFormat (QAudioDeviceInfo const& device, unsigned channels, } // qDebug () << "Selected audio output format:" << format; + m_format = format; m_stream.reset (new QAudioOutput (device, format)); audioError (); m_stream->setVolume (m_volume); @@ -146,6 +147,10 @@ qreal SoundOutput::attenuation () const return -(20. * qLn (m_volume) / qLn (10.)); } +QAudioFormat SoundOutput::format() const{ + return m_format; +} + void SoundOutput::setAttenuation (qreal a) { Q_ASSERT (0. <= a && a <= 999.); diff --git a/soundout.h b/soundout.h index 1e4b3a9..70a0759 100644 --- a/soundout.h +++ b/soundout.h @@ -24,6 +24,7 @@ public: } qreal attenuation () const; + QAudioFormat format() const; public Q_SLOTS: void setFormat (QAudioDeviceInfo const& device, unsigned channels, unsigned msBuffered = 0u); @@ -47,6 +48,7 @@ private Q_SLOTS: private: QScopedPointer m_stream; + QAudioFormat m_format; unsigned m_msBuffered; qreal m_volume; };