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 ~portaudio_engine() override
163 {
164 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 }
177 Pa_Terminate();
178 }
179
180private:
181 static int clearBuffers(float** float_output, unsigned long nframes, int outs)
182 {
183 for(int i = 0; i < outs; i++)
184 {
185 auto chan = float_output[i];
186 for(std::size_t j = 0; j < nframes; j++)
187 chan[j] = 0.f;
188 }
189
190 return paContinue;
191 }
192
193 static int PortAudioCallback(
194 const void* input, void* output, unsigned long nframes,
195 const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags,
196 void* userData)
197 {
198 [[maybe_unused]]
199 static const thread_local auto _
200 = [] {
201 ossia::set_thread_name("ossia audio 0");
202 ossia::set_thread_pinned(thread_type::Audio, 0);
203 return 0;
204 }();
205
206 // auto t0 = std::chrono::steady_clock::now();
207 auto& self = *static_cast<portaudio_engine*>(userData);
208 self.tick_start();
209 auto clt = self.m_stream.load();
210
211 if(self.stop_processing || !clt)
212 {
213 self.tick_clear();
214 return clearBuffers(((float**)output), nframes, self.effective_outputs);
215 }
216
217 auto float_input = ((float* const*)input);
218 auto float_output = ((float**)output);
219
220 ossia::audio_tick_state ts{
221 float_input, float_output, self.effective_inputs, self.effective_outputs,
222 nframes, timeInfo->currentTime};
223 self.audio_tick(ts);
224
225 self.tick_end();
226
227 // auto t1 = std::chrono::steady_clock::now();
228 //
229 // std::cerr << nframes << " => " <<
230 // std::chrono::duration_cast<std::chrono::microseconds>(t1 - t0).count()
231 // << std::endl;
232 return paContinue;
233 }
234
235 std::atomic<PaStream*> m_stream{};
236};
237}
238
239#endif
240#endif
Definition git_info.h:7