Trying out new notification code

This commit is contained in:
Jordan Sherer 2019-10-15 13:52:30 -04:00
parent 8bec6fdce0
commit 05a625dfe8
10 changed files with 209 additions and 195 deletions

119
AudioDecoder.cpp Normal file
View File

@ -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<void(QAudioDecoder::*)(QAudioDecoder::Error)>(&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<char>();
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();
}

50
AudioDecoder.h Normal file
View File

@ -0,0 +1,50 @@
#ifndef AUDIODECODER_H
#define AUDIODECODER_H
#include <QAudioDecoder>
#include <QAudioFormat>
#include <QBuffer>
#include <QIODevice>
#include <QPointer>
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<QAudioDecoder> 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

View File

@ -253,6 +253,7 @@ set (wsjtx_CXXSRCS
wsprnet.cpp
WSPRBandHopping.cpp
TransmitTextEdit.cpp
AudioDecoder.cpp
NotificationAudio.cpp
)

View File

@ -2,162 +2,27 @@
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)
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;
}
void NotificationAudio::setDevice(const QAudioDeviceInfo &device, unsigned channels, unsigned msBuffer){
m_stream->setFormat(device, channels, msBuffer);
m_decoder->init(m_stream->format());
}
// 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<void(QAudioDecoder::*)(QAudioDecoder::Error)>(&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();
}
// play an audio file
void NotificationAudio::play(const QString &filePath){
if(m_state == NotificationAudio::Playing){
return;
m_decoder->start(filePath);
m_stream->restart(m_decoder);
}
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);
}
// 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<char>();
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();
m_stream->stop();
}

View File

@ -3,16 +3,18 @@
#include <QIODevice>
#include <QBuffer>
#include <QAudioDecoder>
#include <QAudioDeviceInfo>
#include <QAudioFormat>
#include <QAudioOutput>
#include <QFile>
#include <QPointer>
// 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<SoundOutput> m_stream;
QPointer<AudioDecoder> m_decoder;
};
#endif // NOTIFICATIONAUDIO_H

View File

@ -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

View File

@ -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));

View File

@ -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;

View File

@ -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.);

View File

@ -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<QAudioOutput> m_stream;
QAudioFormat m_format;
unsigned m_msBuffered;
qreal m_volume;
};