#include "DefaultAudioInterface.hpp" #include #include DefaultAudioInterface::DefaultAudioInterface() { PaError err = Pa_Initialize(); if (err != paNoError) { throw std::runtime_error("PortAudio initialization failed"); } } DefaultAudioInterface::~DefaultAudioInterface() { if (!shouldStop_.load()) { stop(); } Pa_Terminate(); } void DefaultAudioInterface::start(AudioCallback inputCallback) { inputCallback_ = std::move(inputCallback); PaStreamParameters inputParams; std::memset(&inputParams, 0, sizeof(inputParams)); inputParams.channelCount = 1; inputParams.device = Pa_GetDefaultInputDevice(); inputParams.sampleFormat = paInt16; inputParams.suggestedLatency = Pa_GetDeviceInfo(inputParams.device)->defaultLowInputLatency; inputParams.hostApiSpecificStreamInfo = nullptr; PaStreamParameters outputParams; std::memset(&outputParams, 0, sizeof(outputParams)); outputParams.channelCount = 1; outputParams.device = Pa_GetDefaultOutputDevice(); outputParams.sampleFormat = paInt16; outputParams.suggestedLatency = Pa_GetDeviceInfo(outputParams.device)->defaultLowOutputLatency; outputParams.hostApiSpecificStreamInfo = nullptr; PaError err = Pa_OpenStream(&inputStream_, &inputParams, nullptr, 16000, INPUT_FRAMES_PER_BUFFER, paClipOff, &DefaultAudioInterface::inputCallbackStatic, this); if (err != paNoError) { throw std::runtime_error("Failed to open input stream"); } err = Pa_OpenStream(&outputStream_, nullptr, &outputParams, 16000, OUTPUT_FRAMES_PER_BUFFER, paClipOff, nullptr, nullptr); if (err != paNoError) { throw std::runtime_error("Failed to open output stream"); } if ((err = Pa_StartStream(inputStream_)) != paNoError) { throw std::runtime_error("Failed to start input stream"); } if ((err = Pa_StartStream(outputStream_)) != paNoError) { throw std::runtime_error("Failed to start output stream"); } shouldStop_.store(false); outputThread_ = std::thread(&DefaultAudioInterface::outputThreadFunc, this); } void DefaultAudioInterface::stop() { shouldStop_.store(true); queueCv_.notify_all(); if (outputThread_.joinable()) { outputThread_.join(); } if (inputStream_) { Pa_StopStream(inputStream_); Pa_CloseStream(inputStream_); inputStream_ = nullptr; } if (outputStream_) { Pa_StopStream(outputStream_); Pa_CloseStream(outputStream_); outputStream_ = nullptr; } } void DefaultAudioInterface::output(const std::vector& audio) { { std::lock_guard lg(queueMutex_); outputQueue_.emplace(audio); } queueCv_.notify_one(); } void DefaultAudioInterface::interrupt() { std::lock_guard lg(queueMutex_); std::queue> empty; std::swap(outputQueue_, empty); } int DefaultAudioInterface::inputCallbackStatic(const void* input, void* /*output*/, unsigned long frameCount, const PaStreamCallbackTimeInfo* /*timeInfo*/, PaStreamCallbackFlags /*statusFlags*/, void* userData) { auto* self = static_cast(userData); return self->inputCallbackInternal(input, frameCount); } int DefaultAudioInterface::inputCallbackInternal(const void* input, unsigned long frameCount) { if (!input || !inputCallback_) { return paContinue; } if (outputPlaying_.load()) { // Suppress microphone input while playing output to avoid echo feedback. return paContinue; } const size_t bytes = frameCount * sizeof(int16_t); std::vector buffer(bytes); std::memcpy(buffer.data(), input, bytes); inputCallback_(buffer); return paContinue; } void DefaultAudioInterface::outputThreadFunc() { while (!shouldStop_.load()) { std::vector audio; { std::unique_lock lk(queueMutex_); queueCv_.wait(lk, [this] { return shouldStop_.load() || !outputQueue_.empty(); }); if (shouldStop_.load()) break; audio = std::move(outputQueue_.front()); outputQueue_.pop(); } if (!audio.empty() && outputStream_) { outputPlaying_.store(true); Pa_WriteStream(outputStream_, audio.data(), audio.size() / sizeof(int16_t)); outputPlaying_.store(false); } } }