js8call/JS8_Audio/SoundInput.cpp
2026-01-18 19:19:15 -06:00

184 lines
4.6 KiB
C++

/**
* @file soundin.cpp
* @brief Implementation of SoundInput class
*/
#include "SoundInput.h"
#include "JS8_Main/DriftingDateTime.h"
#include <QAudioFormat>
#include <QLoggingCategory>
#include <QSysInfo>
#include "moc_SoundInput.cpp"
Q_DECLARE_LOGGING_CATEGORY(soundin_js8)
/**
* @brief Checks for audio errors and emits appropriate error messages.
*
* @return true
* @return false
*/
bool SoundInput::audioError() const {
bool result(true);
Q_ASSERT_X(m_stream, "SoundInput", "programming error");
if (m_stream) {
switch (m_stream->error()) {
case QAudio::OpenError:
Q_EMIT error(
tr("An error opening the audio input device has occurred."));
break;
case QAudio::IOError:
Q_EMIT error(tr(
"An error occurred during read from the audio input device."));
break;
case QAudio::UnderrunError:
Q_EMIT error(tr("Audio data not being fed to the audio input "
"device fast enough."));
break;
case QAudio::FatalError:
Q_EMIT error(tr("Non-recoverable error, audio input device not "
"usable at this time."));
break;
case QAudio::NoError:
result = false;
break;
}
}
return result;
}
/**
* @brief Starts audio input from the specified device.
*
* @param device The QAudioDevice to use for input.
* @param framesPerBuffer The number of frames per buffer.
* @param sink The AudioDevice sink to write audio data to.
* @param channel The audio channel configuration (Mono or Stereo).
*/
void SoundInput::start(QAudioDevice const &device, int framesPerBuffer,
AudioDevice *sink, AudioDevice::Channel channel) {
Q_ASSERT(sink);
stop();
m_sink = sink;
QAudioFormat format(device.preferredFormat());
// qCDebug (soundin_js8) << "Preferred audio input format:" << format;
format.setSampleFormat(QAudioFormat::Int16);
format.setChannelCount(AudioDevice::Mono == channel ? 1 : 2);
format.setSampleRate(48000);
if (!format.isValid()) {
Q_EMIT error(tr("Requested input audio format is not valid."));
return;
}
if (!device.isFormatSupported(format)) {
// qCDebug (soundin_js8) << "Nearest supported audio format:" <<
// device.nearestFormat (format);
Q_EMIT error(
tr("Requested input audio format is not supported on device."));
return;
}
// qCDebug (soundin_js8) << "Selected audio input format:" << format;
m_stream.reset(new QAudioSource{device, format});
if (audioError()) {
return;
}
connect(m_stream.data(), &QAudioSource::stateChanged, this,
&SoundInput::handleStateChanged);
m_stream->setBufferSize(m_stream->format().bytesForFrames(framesPerBuffer));
if (sink->initialize(QIODevice::WriteOnly, channel)) {
m_stream->start(sink);
audioError();
} else {
Q_EMIT error(tr("Failed to initialize audio sink device"));
}
}
/**
* @brief Suspends audio input.
*/
void SoundInput::suspend() {
if (m_stream) {
m_stream->suspend();
audioError();
}
}
/**
* @brief Resumes audio input.
*/
void SoundInput::resume() {
// qCDebug(soundin_js8) << "Resume" <<
// fmod(0.001*DriftingDateTime::currentMSecsSinceEpoch(),6.0);
if (m_sink) {
m_sink->reset();
}
if (m_stream) {
m_stream->resume();
audioError();
}
}
/**
* @brief Handles state changes of the audio input.
* @param newState The new state of the audio input.
*/
void SoundInput::handleStateChanged(QAudio::State newState) const {
// qCDebug (soundin_js8) << "SoundInput::handleStateChanged: newState:" <<
// newState;
switch (newState) {
case QAudio::IdleState:
Q_EMIT status(tr("Idle"));
break;
case QAudio::ActiveState:
Q_EMIT status(tr("Receiving"));
break;
case QAudio::SuspendedState:
Q_EMIT status(tr("Suspended"));
break;
case QAudio::StoppedState:
if (audioError()) {
Q_EMIT status(tr("Error"));
} else {
Q_EMIT status(tr("Stopped"));
}
break;
}
}
/**
* @brief Stops audio input.
*/
void SoundInput::stop() {
if (m_stream) {
m_stream->stop();
}
m_stream.reset();
if (m_sink) {
m_sink->close();
}
}
/**
* @brief Destructs the SoundInput object.
*/
SoundInput::~SoundInput() { stop(); }
Q_LOGGING_CATEGORY(soundin_js8, "soundin.js8", QtWarningMsg)