PS_AI_Agent/CPP/elevenlabs-convai-cpp-main/src/DefaultAudioInterface.cpp
2026-02-21 20:48:10 +01:00

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);
}
}
}