/** * @file SoundOutput.cpp * @brief Checks for audio errors and emits appropriate error messages. * @return true if no error, false otherwise */ #include "SoundOutput.h" #include #include #include #include #include #include "moc_SoundOutput.cpp" Q_DECLARE_LOGGING_CATEGORY(soundout_js8) bool SoundOutput::checkStream() const { bool result{false}; Q_ASSERT_X(m_stream, "SoundOutput", "programming error"); if (m_stream) { switch (m_stream->error()) { case QAudio::OpenError: Q_EMIT error( tr("An error opening the audio output device has occurred.")); break; case QAudio::IOError: Q_EMIT error(tr( "An error occurred during write to the audio output device.")); break; case QAudio::UnderrunError: Q_EMIT error(tr("Audio data not being fed to the audio output " "device fast enough.")); break; case QAudio::FatalError: Q_EMIT error(tr("Non-recoverable error, audio output device not " "usable at this time.")); break; case QAudio::NoError: result = true; break; } } return result; } /** * @brief Sets the audio format based on the device and channel count. * @param device The QAudioDevice to use. * @param channels The number of audio channels (1 for mono, 2 for stereo). * @param msBuffered The buffer size in milliseconds. */ void SoundOutput::setFormat(QAudioDevice const &device, unsigned channels, unsigned msBuffered) { Q_ASSERT(0 < channels && channels < 3); QAudioFormat format(device.preferredFormat()); format.setChannelCount(channels); format.setSampleRate(48000); format.setSampleFormat(QAudioFormat::Int16); setDeviceFormat(device, format, msBuffered); } /** * @brief Sets the audio device and format. * @param device The QAudioDevice to use. * @param format The QAudioFormat to set. * @param msBuffered The buffer size in milliseconds. */ void SoundOutput::setDeviceFormat(QAudioDevice const &device, QAudioFormat const &format, unsigned msBuffered) { if (!format.isValid()) { Q_EMIT error(tr("Requested output audio format is not valid.")); return; } if (!device.isFormatSupported(format)) { Q_EMIT error( tr("Requested output audio format is not supported on device.")); return; } m_device = device; m_format = format; m_msBuffered = msBuffered; } /** * @brief Restarts audio output with the specified source. * @param source The QIODevice to read audio data from. */ void SoundOutput::restart(QIODevice *source) { if (!m_device.isNull()) { m_stream.reset(new QAudioSink(m_device, m_format)); qCDebug(soundout_js8) << "SoundOutput::restart Selected audio output format:" << m_stream->format(); checkStream(); m_stream->setVolume(m_volume); m_error = false; connect(m_stream.data(), &QAudioSink::stateChanged, this, &SoundOutput::handleStateChanged); } if (!m_stream) { if (!m_error) { m_error = true; // only signal error once Q_EMIT error(tr("No audio output device configured.")); } return; } else { m_error = false; } // we have to set this before every start on the stream because the // Windows implementation seems to forget the buffer size after a // stop. if (m_msBuffered > 0) { m_stream->setBufferSize( m_stream->format().bytesForDuration(m_msBuffered)); } m_stream->start(source); } /** * @brief Suspends audio output. */ void SoundOutput::suspend() { if (m_stream && QAudio::ActiveState == m_stream->state()) { m_stream->suspend(); checkStream(); } } /** * @brief Resumes audio output. */ void SoundOutput::resume() { if (m_stream && QAudio::SuspendedState == m_stream->state()) { m_stream->resume(); checkStream(); } } /** * @brief Resets the audio output. */ void SoundOutput::reset() { if (m_stream) { m_stream->reset(); checkStream(); } } /** * @brief Stops audio output. */ void SoundOutput::stop() { if (m_stream) { m_stream->reset(); m_stream->stop(); } // m_stream.reset (); // XXX in WSJTX, seems like a bug, will assert after // stop checks } /** * @brief Gets the current attenuation in decibels. * @return The attenuation value. */ qreal SoundOutput::attenuation() const { return -(20. * qLn(m_volume) / qLn(10.)); } /** * @brief Gets the current audio format. * @return The QAudioFormat object. */ QAudioFormat SoundOutput::format() const { return m_format; } /** * @brief Sets the attenuation in decibels. * @param a The attenuation value. */ void SoundOutput::setAttenuation(qreal a) { Q_ASSERT(0. <= a && a <= 999.); m_volume = qPow(10.0, -a / 20.0); // qCDebug (soundout_js8) << "SoundOut: attn = " << a << ", vol = " << // m_volume; if (m_stream) { m_stream->setVolume(m_volume); } } /** * @brief Resets the attenuation to zero. */ void SoundOutput::resetAttenuation() { m_volume = 1.; if (m_stream) { m_stream->setVolume(m_volume); } } /** * @brief Handles state changes of the audio output. * @param newState The new state of the audio output. */ void SoundOutput::handleStateChanged(QAudio::State newState) const { switch (newState) { case QAudio::IdleState: Q_EMIT status(tr("Idle")); break; case QAudio::ActiveState: Q_EMIT status(tr("Sending")); break; case QAudio::SuspendedState: Q_EMIT status(tr("Suspended")); break; case QAudio::StoppedState: if (!checkStream()) { Q_EMIT status(tr("Error")); } else { Q_EMIT status(tr("Stopped")); } break; } } Q_LOGGING_CATEGORY(soundout_js8, "soundout.js8", QtWarningMsg)