2#include <ossia/detail/config.hpp>
4#if defined(OSSIA_ENABLE_PORTAUDIO)
5#if __has_include(<portaudio.h>)
6#include <ossia/audio/audio_engine.hpp>
7#include <ossia/detail/thread.hpp>
10#if defined(__linux__) && __has_include(<pa_linux_alsa.h>)
11#include <pa_linux_alsa.h>
16#define OSSIA_AUDIO_PORTAUDIO 1
20class portaudio_engine final :
public audio_engine
24 std::string name, std::string card_in, std::string card_out,
int inputs,
25 int outputs,
int rate,
int bs, PaHostApiTypeId hostApi)
27 if(Pa_Initialize() != paNoError)
28 throw std::runtime_error(
"Audio error");
30 int card_in_idx = paNoDevice;
31 int card_out_idx = paNoDevice;
33 auto hostApiIndex = Pa_HostApiTypeIdToHostApiIndex(hostApi);
35 for(
int i = 0; i < Pa_GetDeviceCount(); i++)
37 auto info = Pa_GetDeviceInfo(i);
38 if(info->hostApi != hostApiIndex && hostApiIndex != paInDevelopment)
41 auto raw_name = info->name;
43 if(raw_name == card_in && info->maxInputChannels > 0)
49 if(raw_name == card_out && info->maxOutputChannels > 0)
55 if(card_in_idx != paNoDevice && card_out_idx != paNoDevice)
59 auto devInInfo = Pa_GetDeviceInfo(card_in_idx);
62 std::cerr <<
"Audio error: no input device" << std::endl;
67 inputs = std::min(inputs, devInInfo->maxInputChannels);
70 auto devOutInfo = Pa_GetDeviceInfo(card_out_idx);
73 std::cerr <<
"Audio error: no output device" << std::endl;
78 outputs = std::min(outputs, devOutInfo->maxOutputChannels);
81 PaStreamParameters inputParameters;
82 inputParameters.device = card_in_idx;
83 inputParameters.channelCount = inputs;
84 inputParameters.sampleFormat = paFloat32 | paNonInterleaved;
85 inputParameters.suggestedLatency = 0.01;
86 inputParameters.hostApiSpecificStreamInfo =
nullptr;
88 PaStreamParameters outputParameters;
89 outputParameters.device = card_out_idx;
90 outputParameters.channelCount = outputs;
91 outputParameters.sampleFormat = paFloat32 | paNonInterleaved;
92 outputParameters.suggestedLatency = 0.01;
93 outputParameters.hostApiSpecificStreamInfo =
nullptr;
95 PaStreamParameters* actualInput{};
96 if(card_in_idx != paNoDevice && inputs > 0)
97 actualInput = &inputParameters;
98 PaStreamParameters* actualOutput{};
99 if(card_out_idx != paNoDevice && outputs > 0)
100 actualOutput = &outputParameters;
112 auto ec = Pa_OpenStream(
113 &stream, actualInput, actualOutput, rate,
115 paNoFlag, &PortAudioCallback,
this);
116 m_stream.store(stream);
117 if(ec == PaErrorCode::paNoError)
119#if defined(__linux__) && __has_include(<pa_linux_alsa.h>)
120 if(hostApi == paALSA)
121 PaAlsa_EnableRealtimeScheduling(stream, 1);
124 ec = Pa_StartStream(stream);
126 if(ec != PaErrorCode::paNoError)
128 std::cerr <<
"Error while starting audio stream: " << Pa_GetErrorText(ec)
130 Pa_CloseStream(stream);
131 m_stream.store(
nullptr);
135 auto info = Pa_GetStreamInfo(stream);
136 this->effective_sample_rate = info->sampleRate;
137 this->effective_buffer_size = bs;
138 this->effective_inputs = actualInput ? actualInput->channelCount : 0;
139 this->effective_outputs = actualOutput ? actualOutput->channelCount : 0;
144 std::cerr <<
"Error while opening audio stream: " << Pa_GetErrorText(ec)
146 m_stream.store(
nullptr);
152 throw std::runtime_error(
"Could not start PortAudio stream");
156 bool running()
const override
158 auto s = m_stream.load();
159 return s && Pa_IsStreamActive(m_stream);
162 ~portaudio_engine()
override
166 if(
auto stream = m_stream.load())
168 auto ec = Pa_StopStream(stream);
169 std::cerr <<
"=== stream stop ===\n";
171 if(ec != PaErrorCode::paNoError)
173 std::cerr <<
"Error while stopping audio stream: " << Pa_GetErrorText(ec)
181 static int clearBuffers(
float** float_output,
unsigned long nframes,
int outs)
183 for(
int i = 0; i < outs; i++)
185 auto chan = float_output[i];
186 for(std::size_t j = 0; j < nframes; j++)
193 static int PortAudioCallback(
194 const void* input,
void* output,
unsigned long nframes,
195 const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags,
199 static const thread_local auto _
201 ossia::set_thread_name(
"ossia audio 0");
202 ossia::set_thread_pinned(thread_type::Audio, 0);
207 auto& self = *
static_cast<portaudio_engine*
>(userData);
209 auto clt = self.m_stream.load();
211 if(self.stop_processing || !clt)
214 return clearBuffers(((
float**)output), nframes, self.effective_outputs);
217 auto float_input = ((
float*
const*)input);
218 auto float_output = ((
float**)output);
220 ossia::audio_tick_state ts{
221 float_input, float_output, self.effective_inputs, self.effective_outputs,
222 nframes, timeInfo->currentTime};
235 std::atomic<PaStream*> m_stream{};