Skip to content

Commit da1dc74

Browse files
committed
alsa: handle observation events with a slight delay to account for udev populating its fields
1 parent 39c8c96 commit da1dc74

3 files changed

Lines changed: 138 additions & 12 deletions

File tree

include/libremidi/backends/alsa_seq/helpers.hpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,24 @@ inline constexpr std::pair<int, int> seq_from_port_handle(port_handle p) noexcep
5656
return {client, port};
5757
}
5858

59+
inline void for_all_clients(
60+
const libasound& snd, snd_seq_t* seq, const std::function<void(snd_seq_client_info_t&)>& func)
61+
{
62+
snd_seq_client_info_t* cinfo{};
63+
snd_seq_client_info_alloca(&cinfo);
64+
snd_seq_port_info_t* pinfo{};
65+
snd_seq_port_info_alloca(&pinfo);
66+
67+
snd.seq.client_info_set_client(cinfo, -1);
68+
while (snd.seq.query_next_client(seq, cinfo) >= 0)
69+
{
70+
int client = snd.seq.client_info_get_client(cinfo);
71+
if (client == 0)
72+
continue;
73+
func(*cinfo);
74+
}
75+
}
76+
5977
inline void for_all_ports(
6078
const libasound& snd, snd_seq_t* seq,
6179
const std::function<void(snd_seq_client_info_t&, snd_seq_port_info_t&)>& func)
@@ -82,6 +100,21 @@ inline void for_all_ports(
82100
}
83101
}
84102

103+
inline void for_all_ports(
104+
const libasound& snd, snd_seq_t* seq, int client,
105+
const std::function<void(snd_seq_port_info_t&)>& func)
106+
{
107+
snd_seq_port_info_t* pinfo{};
108+
snd_seq_port_info_alloca(&pinfo);
109+
110+
snd.seq.port_info_set_client(pinfo, client);
111+
snd.seq.port_info_set_port(pinfo, -1);
112+
while (snd.seq.query_next_port(seq, pinfo) >= 0)
113+
{
114+
func(*pinfo);
115+
}
116+
}
117+
85118
// This function is used to count or get the pinfo structure for a given port
86119
// number.
87120
inline unsigned int iterate_port_info(

include/libremidi/backends/alsa_seq/observer.hpp

Lines changed: 104 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@
1616
namespace libremidi::alsa_seq
1717
{
1818

19+
struct client_info
20+
{
21+
std::string client_name;
22+
int client_id{};
23+
std::optional<int> card{};
24+
};
25+
1926
struct port_info
2027
{
2128
std::string client_name;
@@ -93,6 +100,21 @@ class observer_impl
93100
}
94101
}
95102

103+
std::optional<client_info>
104+
get_client_info(int client, snd_seq_client_info_t& cinfo) const noexcept
105+
{
106+
client_info p;
107+
p.client_id = client;
108+
109+
if (auto name = snd.seq.client_info_get_name(&cinfo))
110+
p.client_name = name;
111+
112+
if (int card = snd.seq.client_info_get_card(&cinfo); card >= 0)
113+
p.card = card;
114+
115+
return p;
116+
}
117+
96118
std::optional<port_info> get_info(
97119
int client, int port, snd_seq_client_info_t& cinfo,
98120
snd_seq_port_info_t& pinfo) const noexcept
@@ -281,19 +303,43 @@ class observer_impl
281303
}
282304
}
283305

284-
void handle_event(const snd_seq_event_t& ev)
306+
void handle_event_direct(const snd_seq_event_t& ev)
285307
{
286308
switch (ev.type)
287309
{
310+
case SND_SEQ_EVENT_CLIENT_START: {
311+
// TODO
312+
break;
313+
}
314+
case SND_SEQ_EVENT_CLIENT_EXIT: {
315+
// TODO
316+
break;
317+
}
318+
case SND_SEQ_EVENT_CLIENT_CHANGE: {
319+
// TODO
320+
break;
321+
}
322+
#if __has_include(<alsa/ump.h>)
323+
case SND_SEQ_EVENT_UMP_EP_CHANGE: {
324+
// TODO
325+
break;
326+
}
327+
case SND_SEQ_EVENT_UMP_BLOCK_CHANGE: {
328+
// TODO
329+
break;
330+
}
331+
#endif
288332
case SND_SEQ_EVENT_PORT_START: {
289-
register_port(ev.data.addr.client, ev.data.addr.port);
333+
this->register_port(ev.data.addr.client, ev.data.addr.port);
290334
break;
291335
}
292336
case SND_SEQ_EVENT_PORT_EXIT: {
293-
unregister_port(ev.data.addr.client, ev.data.addr.port);
337+
this->unregister_port(ev.data.addr.client, ev.data.addr.port);
294338
break;
295339
}
296340
case SND_SEQ_EVENT_PORT_CHANGE:
341+
// TODO
342+
break;
297343
default:
298344
break;
299345
}
@@ -328,38 +374,83 @@ class observer_threaded : public observer_impl<ConfigurationImpl>
328374
{
329375
// Create relevant descriptors
330376
auto& snd = alsa_data::snd;
377+
378+
// 1. Descriptor count
331379
const auto n = snd.seq.poll_descriptors_count(this->seq, POLLIN);
332-
descriptors_.resize(n + 1);
333-
snd.seq.poll_descriptors(this->seq, descriptors_.data(), n, POLLIN);
334-
descriptors_.back() = this->termination_event;
380+
int total_descriptors = n;
381+
total_descriptors++; // eventfd for terminating the thread
382+
383+
// 2. Create storage
384+
descriptors.resize(total_descriptors);
385+
386+
// 3. Store descriptors
387+
snd.seq.poll_descriptors(this->seq, descriptors.data(), n, POLLIN);
388+
descriptors[n] = this->termination_event;
335389

336390
// Start the listening thread
337-
thread = std::thread{[this] {
391+
thread = std::thread{[this, n] {
338392
auto& snd = alsa_data::snd;
339393
const auto period
340394
= std::chrono::duration_cast<std::chrono::milliseconds>(this->configuration.poll_period)
341395
.count();
342396
for (;;)
343397
{
344-
int err = poll(descriptors_.data(), descriptors_.size(), static_cast<int32_t>(period));
398+
int err = poll(descriptors.data(), descriptors.size(), static_cast<int32_t>(period));
345399
if (err >= 0)
346400
{
347401
// We got our stop-thread signal
348-
if (descriptors_.back().revents & POLLIN)
402+
if (descriptors[n].revents & POLLIN)
349403
break;
350404

405+
// Put ALSA event in our queue
351406
snd_seq_event_t* ev{};
352407
event_handle handle{snd};
353408
while (snd.seq.event_input(this->seq, &ev) >= 0)
354409
{
355410
handle.reset(ev);
356-
this->handle_event(*ev);
411+
this->handle_event_delayed(*ev);
412+
}
413+
414+
// Process the events in a deferred way.
415+
// This is because udev takes some milliseconds to populate its field after a
416+
// port was added
417+
auto tm = std::chrono::steady_clock::now();
418+
for (auto it = queued_events.begin(); it != queued_events.end();)
419+
{
420+
if ((tm - it->second) >= this->configuration.poll_period)
421+
{
422+
this->handle_event_direct(it->first);
423+
it = queued_events.erase(it);
424+
}
425+
else
426+
{
427+
break;
428+
}
357429
}
358430
}
359431
}
360432
}};
361433
}
362434

435+
void handle_event_delayed(const snd_seq_event_t& ev)
436+
{
437+
switch (ev.type)
438+
{
439+
case SND_SEQ_EVENT_CLIENT_START:
440+
case SND_SEQ_EVENT_CLIENT_EXIT:
441+
case SND_SEQ_EVENT_CLIENT_CHANGE:
442+
case SND_SEQ_EVENT_UMP_EP_CHANGE:
443+
case SND_SEQ_EVENT_UMP_BLOCK_CHANGE:
444+
case SND_SEQ_EVENT_PORT_START:
445+
case SND_SEQ_EVENT_PORT_EXIT:
446+
case SND_SEQ_EVENT_PORT_CHANGE:
447+
queued_events.emplace_back(ev, std::chrono::steady_clock::now());
448+
break;
449+
default:
450+
break;
451+
}
452+
}
453+
363454
~observer_threaded()
364455
{
365456
termination_event.notify();
@@ -370,7 +461,8 @@ class observer_threaded : public observer_impl<ConfigurationImpl>
370461

371462
eventfd_notifier termination_event{};
372463
std::thread thread;
373-
std::vector<pollfd> descriptors_;
464+
std::vector<pollfd> descriptors;
465+
std::vector<std::pair<snd_seq_event_t, std::chrono::steady_clock::time_point>> queued_events;
374466
};
375467

376468
template <typename ConfigurationImpl>
@@ -382,7 +474,7 @@ class observer_manual : public observer_impl<ConfigurationImpl>
382474
{
383475
this->configuration.manual_poll(
384476
poll_parameters{.addr = this->vaddr, .callback = [this](const auto& v) {
385-
this->handle_event(v);
477+
this->handle_event_direct(v);
386478
return 0;
387479
}});
388480
}

include/libremidi/backends/linux/alsa.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,7 @@ struct libasound
278278
LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_sizeof)
279279
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_free)
280280
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_malloc)
281+
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_sizeof)
281282
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_dest)
282283
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_sender)
283284
LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_time_real)

0 commit comments

Comments
 (0)