mirror of
https://github.com/JS8Call-improved/JS8Call-improved
synced 2026-08-13 17:47:36 -04:00
Fix 8bit playback Enable 24bit playback Enable 32bit playback Upmix mono to stereo Set the sample format appropriately based upon the bits per channel in the format chunk instead of defaulting to 16 bits
244 lines
6.1 KiB
C++
244 lines
6.1 KiB
C++
/**
|
|
* @file SoundOutput.cpp
|
|
* @brief Checks for audio errors and emits appropriate error messages.
|
|
* @return true if no error, false otherwise
|
|
*/
|
|
|
|
#include "SoundOutput.h"
|
|
|
|
#include <QAudioOutput>
|
|
#include <QDateTime>
|
|
#include <QLoggingCategory>
|
|
#include <QSysInfo>
|
|
#include <qmath.h>
|
|
|
|
#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)
|