OSSIA
Open Scenario System for Interactive Application
Loading...
Searching...
No Matches
portaudio_protocol.hpp
1#pragma once
2#include <ossia/detail/config.hpp>
3
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>
8
9#include <portaudio.h>
10#if defined(__linux__) && __has_include(<pa_linux_alsa.h>)
11#include <pa_linux_alsa.h>
12#endif
13
14#include <iostream>
15
16#define OSSIA_AUDIO_PORTAUDIO 1
17
18namespace ossia
19{
20class portaudio_engine final : public audio_engine
21{
22public:
23 portaudio_engine(
24 std::string name, std::string card_in, std::string card_out, int inputs,
25 int outputs, int rate, int bs, PaHostApiTypeId hostApi)
26 {
27 if(Pa_Initialize() != paNoError)
28 throw std::runtime_error("Audio error");
29
30 int card_in_idx = paNoDevice;
31 int card_out_idx = paNoDevice;
32
33 auto hostApiIndex = Pa_HostApiTypeIdToHostApiIndex(hostApi);
34
35 for(int i = 0; i < Pa_GetDeviceCount(); i++)
36 {
37 auto info = Pa_GetDeviceInfo(i);
38 if(info->hostApi != hostApiIndex && hostApiIndex != paInDevelopment)
39 continue;
40
41 auto raw_name = info->name;
42 // std::cerr << " - device " << i << " has name: " << raw_name << "\n";
43 if(raw_name == card_in && info->maxInputChannels > 0)
44 {
45 // std::cerr << " its the input" << inputs << " " <<
46 // info->maxInputChannels << "\n";
47 card_in_idx = i;
48 }
49 if(raw_name == card_out && info->maxOutputChannels > 0)
50 {
51 // std::cerr << " its the output" << outputs << " " <<
52 // info->maxOutputChannels << "\n";
53 card_out_idx = i;
54 }
55 if(card_in_idx != paNoDevice && card_out_idx != paNoDevice)
56 break;
57 }
58
59 auto devInInfo = Pa_GetDeviceInfo(card_in_idx);
60 if(!devInInfo)
61 {
62 std::cerr << "Audio error: no input device" << std::endl;
63 inputs = 0;
64 }
65 else
66 {
67 inputs = std::min(inputs, devInInfo->maxInputChannels);
68 }
69
70 auto devOutInfo = Pa_GetDeviceInfo(card_out_idx);
71 if(!devOutInfo)
72 {
73 std::cerr << "Audio error: no output device" << std::endl;
74 outputs = 0;
75 }
76 else
77 {
78 outputs = std::min(outputs, devOutInfo->maxOutputChannels);
79 }
80
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;
87
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;
94
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;
101 /*
102 std::cerr << "input: \n"
103 << bool(actualInput) << " "
104 << card_in_idx<< " "
105 << inputs << "\n";
106 std::cerr << "output: \n"
107 << bool(actualOutput) << " "
108 << card_out_idx<< " "
109 << outputs << "\n";
110 */
111 PaStream* stream;
112 auto ec = Pa_OpenStream(
113 &stream, actualInput, actualOutput, rate,
114 bs, // paFramesPerBufferUnspecified,
115 paNoFlag, &PortAudioCallback, this);
116 m_stream.store(stream);
117 if(ec == PaErrorCode::paNoError)
118 {
119#if defined(__linux__) && __has_include(<pa_linux_alsa.h>)
120 if(hostApi == paALSA)
121 PaAlsa_EnableRealtimeScheduling(stream, 1);
122#endif
123
124 ec = Pa_StartStream(stream);
125
126 if(ec != PaErrorCode::paNoError)
127 {
128 std::cerr << "Error while starting audio stream: " << Pa_GetErrorText(ec)
129 << std::endl;
130 Pa_CloseStream(stream);
131 m_stream.store(nullptr);
132 }
133 else
134 {
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;
140 }
141 }
142 else
143 {
144 std::cerr << "Error while opening audio stream: " << Pa_GetErrorText(ec)
145 << std::endl;
146 m_stream.store(nullptr);
147 }
148
149 if(!m_stream)
150 {
151 Pa_Terminate();
152 throw std::runtime_error("Could not start PortAudio stream");
153 }
154 }
155
156 bool running() const override
157 {
158 auto s = m_stream.load();
159 return s && Pa_IsStreamActive(m_stream);
160 }
161
162 void stop() override
163 {
164 audio_engine::stop();
165
166 if(auto stream = m_stream.load())
167 {
168 auto ec = Pa_StopStream(stream);
169 std::cerr << "=== stream stop ===\n";
170
171 if(ec != PaErrorCode::paNoError)
172 {
173 std::cerr << "Error while stopping audio stream: " << Pa_GetErrorText(ec)
174 << std::endl;
175 }
176 m_stream = {};
177 }
178 }
179
180 ~portaudio_engine() override
181 {
182 stop();
183 Pa_Terminate();
184 }
185
186private:
187 static int clearBuffers(float** float_output, unsigned long nframes, int outs)
188 {
189 for(int i = 0; i < outs; i++)
190 {
191 auto chan = float_output[i];
192 for(std::size_t j = 0; j < nframes; j++)
193 chan[j] = 0.f;
194 }
195
196 return paContinue;
197 }
198
199 static int PortAudioCallback(
200 const void* input, void* output, unsigned long nframes,
201 const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags,
202 void* userData)
203 {
204 [[maybe_unused]]
205 static const thread_local auto _
206 = [] {
207 ossia::set_thread_name("ossia audio 0");
208 ossia::set_thread_pinned(thread_type::Audio, 0);
209 return 0;
210 }();
211
212 // auto t0 = std::chrono::steady_clock::now();
213 auto& self = *static_cast<portaudio_engine*>(userData);
214 self.tick_start();
215 auto clt = self.m_stream.load();
216
217 if(self.stop_processing || !clt)
218 {
219 self.tick_clear();
220 return clearBuffers(((float**)output), nframes, self.effective_outputs);
221 }
222
223 auto float_input = ((float* const*)input);
224 auto float_output = ((float**)output);
225
226 ossia::audio_tick_state ts{
227 float_input, float_output, self.effective_inputs, self.effective_outputs,
228 nframes, timeInfo->currentTime};
229 self.audio_tick(ts);
230
231 self.tick_end();
232
233 // auto t1 = std::chrono::steady_clock::now();
234 //
235 // std::cerr << nframes << " => " <<
236 // std::chrono::duration_cast<std::chrono::microseconds>(t1 - t0).count()
237 // << std::endl;
238 return paContinue;
239 }
240
241 std::atomic<PaStream*> m_stream{};
242};
243}
244
245#endif
246#endif
Definition git_info.h:7