3 <alsa/asoundlib.h>) && __has_include(<zita-alsa-pcmi-ardour.h>) && !defined(__EMSCRIPTEN__)
5#include <ossia/audio/audio_engine.hpp>
6#include <ossia/audio/libasound.hpp>
8#include <ossia/detail/pod_vector.hpp>
9#include <ossia/detail/thread.hpp>
11#include <alsa/asoundlib.h>
22#include <zita-alsa-pcmi-ardour.h>
24#define OSSIA_AUDIO_ALSA 1
28class alsa_engine final :
public audio_engine
33 std::string card_in, std::string card_out,
int inputs,
int outputs,
int rate,
34 int bs,
int periods = 0)
40 const bool capture = inputs > 0 && !card_in.empty();
41 const bool playback = (outputs > 0 && !card_out.empty()) || !capture;
42 if(playback && card_out.empty())
43 throw std::runtime_error(
"ALSA: no device selected");
53 bool use_capture = capture;
55 auto attempt = [&](
unsigned nfrag) {
57 m_client = std::make_unique<Alsa_pcmi>(
58 playback ? card_out.c_str() :
nullptr,
59 use_capture ? card_in.c_str() :
nullptr,
nullptr, rate, bs, nfrag, nfrag,
60 Alsa_pcmi::DEBUG_INIT, playback ? std::max(outputs, 0) : 0,
61 use_capture ? inputs : 0);
62 auto& client = *m_client;
65 if(client.state() == 0 && (!playback || client.play_handle()))
67 error = describe_error(client, card_in, card_out, playback, use_capture, rate, bs);
71 auto configure = [&](
unsigned nfrag) {
74 if(!(use_capture && playback))
78 return attempt(nfrag);
83 start(inputs, outputs, playback ? card_out : card_in);
86 catch(std::exception& e)
93 unsigned nfrag = periods > 0 ? periods : 2;
95 throw std::runtime_error(error);
96 if(periods <= 0 && m_client->play_handle() && m_client->capt_handle()
97 && !m_client->synced())
101 else if(!attempt(nfrag))
102 throw std::runtime_error(error);
105 throw std::runtime_error(error);
107 while(periods <= 0 && nfrag < 4 && keeps_underrunning())
110 "ALSA: {} under-runs with {} periods, trying {}",
111 playback ? card_out : card_in, nfrag, nfrag + 1);
113 if(attempt(nfrag + 1) && run())
119 if(!attempt(nfrag) || !run())
120 throw std::runtime_error(error);
124 if(capture && !m_client->capt_handle())
126 "ALSA: cannot open {} for capture ({}), continuing without inputs", card_in,
127 ossia::libasound::instance().strerror(m_client->capt_open_error()));
130 "ALSA: {} uses a period of {} frames instead of {}",
131 playback ? card_out : card_in, m_fsize, bs);
138 audio_engine::stop();
143 ~alsa_engine()
override { stop(); }
145 bool running()
const override
149 return m_activated && !m_failed.load(std::memory_order_relaxed);
152 bool failed() const noexcept
override
154 return m_failed.load(std::memory_order_relaxed);
158 void start(
int inputs,
int outputs,
const std::string& name)
160 auto& client = *m_client;
161 m_fsize = client.fsize();
162 effective_sample_rate = client.fsamp();
163 effective_buffer_size = m_fsize;
164 effective_inputs = std::min(inputs, (
int)client.ncapt());
165 effective_outputs = std::min(outputs, (
int)client.nplay());
166 effective_input_latency = client.capt_handle() ? m_fsize : 0;
167 effective_output_latency
168 = client.play_handle() ? m_fsize * (int)client.real_play_nfrag() : 0;
170 m_capture.assign(m_fsize * std::max(effective_inputs, 1), 0.f);
171 m_playback.assign(m_fsize * std::max(effective_outputs, 1), 0.f);
172 m_capture_ptrs.clear();
173 m_playback_ptrs.clear();
174 for(
int i = 0; i < effective_inputs; i++)
175 m_capture_ptrs.push_back(m_capture.data() + i * m_fsize);
176 for(
int i = 0; i < effective_outputs; i++)
177 m_playback_ptrs.push_back(m_playback.data() + i * m_fsize);
185 std::promise<int> started;
186 auto start_result = started.get_future();
187 m_thread = ossia::thread{[
this, started = std::move(started)]()
mutable {
189 const int err = m_client->pcm_start();
190 started.set_value(err);
196 if(start_result.get() != 0)
200 throw std::runtime_error(
"ALSA: " + name +
" could not be started");
209 bool keeps_underrunning()
211 const int period_us = int(int64_t(1'000'000) * m_fsize / std::max(effective_sample_rate, 1));
214 using namespace std::chrono;
215 std::this_thread::sleep_for(std::max<microseconds>(30ms, microseconds(4 * period_us)));
216 const auto before = xruns.load();
217 std::this_thread::sleep_for(std::max<microseconds>(60ms, microseconds(20 * period_us)));
218 return xruns.load() - before >= 2 || m_failed.load();
225 m_stop_token.store(
false, std::memory_order_relaxed);
226 if(m_thread.joinable())
234 auto& client = *this->m_client;
235 const int n = m_fsize;
241 int delay_period = 0;
242 int64_t delay_sum = 0;
245 while(m_stop_token.load(std::memory_order_relaxed))
247 long k = client.pcm_wait();
251 if(client.state() < 0)
255 ossia::logger().error(
"ALSA: the device stopped responding");
262 if(client.state() > 0)
264 xruns.fetch_add(1, std::memory_order_relaxed);
265 if(++idle_xruns >= 200)
268 "ALSA: the device under-runs continuously; try a larger buffer size");
280 if(client.capt_init(n) != n && client.capt_handle())
282 for(
int c = 0; c < effective_inputs; c++)
283 client.capt_chan(c, m_capture_ptrs[c], n, 1);
286 std::fill(m_playback.begin(), m_playback.end(), 0.f);
287 process(m_capture_ptrs.data(), m_playback_ptrs.data(), n);
289 if(client.play_init(n) != n && client.play_handle())
291 for(
int c = 0; c < effective_outputs; c++)
292 client.play_chan(c, m_playback_ptrs[c], n, 1);
293 for(
int c = effective_outputs; c < (int)client.nplay(); c++)
294 client.clear_chan(c, n);
296 if(delay_period < 16 && ++delay_period >= 8 && client.play_handle())
298 snd_pcm_sframes_t delay{};
299 if(ossia::libasound::instance().pcm_delay(client.play_handle(), &delay) == 0
302 delay_sum += delay - n;
305 if(delay_period == 16 && delay_count > 0)
306 effective_output_latency.store(
307 int(delay_sum / delay_count), std::memory_order_relaxed);
317 static std::string describe_error(
318 const Alsa_pcmi& client,
const std::string& card_in,
const std::string& card_out,
319 bool playback,
bool capture,
int rate,
int bs)
321 if(playback && !client.play_handle() && client.play_open_error() < 0)
322 return "ALSA: cannot open " + card_out +
" for playback: "
323 + ossia::libasound::instance().strerror(client.play_open_error());
325 if(!client.play_handle() && capture && !client.capt_handle()
326 && client.capt_open_error() < 0)
327 return "ALSA: cannot open " + card_in +
" for capture: "
328 + ossia::libasound::instance().strerror(client.capt_open_error());
330 const char* stream = client.error_stream() ? client.error_stream() :
"the device";
331 const std::string& name = (client.error_stream()
332 && std::strcmp(client.error_stream(),
"capture") == 0)
335 switch(client.state())
338 return "ALSA: " + name +
" does not support " + std::to_string(rate)
339 +
" Hz for " + stream;
341 return "ALSA: " + name +
" does not support a buffer of " + std::to_string(bs)
342 +
" frames for " + stream;
344 return "ALSA: " + name +
" does not support this number of periods for "
347 return "ALSA: " + card_out +
" cannot use the period " + card_in
348 +
" gave for capture (" + std::to_string(bs)
349 +
" frames requested): choose another buffer size";
351 return "ALSA: " + name +
" uses a sample format score cannot handle for "
354 return "ALSA: " + name +
" rejected the configuration for " + stream +
" ("
355 + std::to_string(rate) +
" Hz, " + std::to_string(bs) +
" frames)";
361 ossia::set_thread_name(
"ossia audio 0");
362 ossia::set_thread_pinned(thread_type::Audio, 0);
365 void process(
float** float_input,
float** float_output, uint64_t nframes)
375 using namespace std::chrono;
376 const auto now = clk::now();
377 if(m_start_time == clk::time_point{})
379 ossia::audio_tick_state ts{
385 duration_cast<nanoseconds>(now - m_start_time).count() / 1e9};
391 std::unique_ptr<Alsa_pcmi> m_client;
392 ossia::thread m_thread;
393 using clk = std::chrono::steady_clock;
394 clk::time_point m_start_time{};
395 ossia::float_vector m_capture;
396 ossia::float_vector m_playback;
397 ossia::pod_vector<float*> m_capture_ptrs;
398 ossia::pod_vector<float*> m_playback_ptrs;
400 std::atomic_bool m_stop_token{
true};
401 std::atomic_bool m_activated{};
402 std::atomic_bool m_failed{};
spdlog::logger & logger() noexcept
Where the errors will be logged. Default is stderr.
Definition context.cpp:120
bool set_thread_realtime(std::thread &t, int prio, bool algo_fifo)
Requests a real-time policy for the thread.
Definition thread.cpp:188