131 lines
4.5 KiB
C++
131 lines
4.5 KiB
C++
#include "DefaultAudioInterface.hpp"
|
|
|
|
#include <cstring>
|
|
#include <iostream>
|
|
|
|
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<char>& audio) {
|
|
{
|
|
std::lock_guard<std::mutex> lg(queueMutex_);
|
|
outputQueue_.emplace(audio);
|
|
}
|
|
queueCv_.notify_one();
|
|
}
|
|
|
|
void DefaultAudioInterface::interrupt() {
|
|
std::lock_guard<std::mutex> lg(queueMutex_);
|
|
std::queue<std::vector<char>> 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<DefaultAudioInterface*>(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<char> buffer(bytes);
|
|
std::memcpy(buffer.data(), input, bytes);
|
|
inputCallback_(buffer);
|
|
return paContinue;
|
|
}
|
|
|
|
void DefaultAudioInterface::outputThreadFunc() {
|
|
while (!shouldStop_.load()) {
|
|
std::vector<char> audio;
|
|
{
|
|
std::unique_lock<std::mutex> 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);
|
|
}
|
|
}
|
|
}
|