1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "media/midi/midi_manager_alsa.h"
7 #include <alsa/asoundlib.h>
12 #include "base/bind.h"
13 #include "base/json/json_string_value_serializer.h"
14 #include "base/logging.h"
15 #include "base/memory/scoped_ptr.h"
16 #include "base/memory/scoped_vector.h"
17 #include "base/message_loop/message_loop.h"
18 #include "base/posix/eintr_wrapper.h"
19 #include "base/strings/string_number_conversions.h"
20 #include "base/strings/stringprintf.h"
21 #include "base/threading/thread.h"
22 #include "base/time/time.h"
23 #include "base/values.h"
24 #include "crypto/sha2.h"
25 #include "media/midi/midi_port_info.h"
31 // Per-output buffer. This can be smaller, but then large sysex messages
32 // will be (harmlessly) split across multiple seq events. This should
33 // not have any real practical effect, except perhaps to slightly reorder
34 // realtime messages with respect to sysex.
35 const size_t kSendBufferSize
= 256;
38 const char kAlsaHw
[] = "hw";
40 // Constants for the capabilities we search for in inputs and outputs.
41 // See http://www.alsa-project.org/alsa-doc/alsa-lib/seq.html.
42 const unsigned int kRequiredInputPortCaps
=
43 SND_SEQ_PORT_CAP_READ
| SND_SEQ_PORT_CAP_SUBS_READ
;
44 const unsigned int kRequiredOutputPortCaps
=
45 SND_SEQ_PORT_CAP_WRITE
| SND_SEQ_PORT_CAP_SUBS_WRITE
;
47 const unsigned int kCreateOutputPortCaps
=
48 SND_SEQ_PORT_CAP_READ
| SND_SEQ_PORT_CAP_NO_EXPORT
;
49 const unsigned int kCreateInputPortCaps
=
50 SND_SEQ_PORT_CAP_WRITE
| SND_SEQ_PORT_CAP_NO_EXPORT
;
51 const unsigned int kCreatePortType
=
52 SND_SEQ_PORT_TYPE_MIDI_GENERIC
| SND_SEQ_PORT_TYPE_APPLICATION
;
54 int AddrToInt(int client
, int port
) {
55 return (client
<< 8) | port
;
58 void SetStringIfNonEmpty(base::DictionaryValue
* value
,
59 const std::string
& path
,
60 const std::string
& in_value
) {
61 if (!in_value
.empty())
62 value
->SetString(path
, in_value
);
67 MidiManagerAlsa::MidiManagerAlsa()
73 udev_(device::udev_new()),
74 send_thread_("MidiSendThread"),
75 event_thread_("MidiEventThread"),
76 event_thread_shutdown_(false) {
77 // Initialize decoder.
78 snd_midi_event_new(0, &decoder_
);
79 snd_midi_event_no_status(decoder_
, 1);
82 MidiManagerAlsa::~MidiManagerAlsa() {
83 // Tell the event thread it will soon be time to shut down. This gives
84 // us assurance the thread will stop in case the SND_SEQ_EVENT_CLIENT_EXIT
87 base::AutoLock
lock(shutdown_lock_
);
88 event_thread_shutdown_
= true;
91 // Stop the send thread.
94 // Close the out client. This will trigger the event thread to stop,
95 // because of SND_SEQ_EVENT_CLIENT_EXIT.
97 snd_seq_close(out_client_
);
99 // Wait for the event thread to stop.
100 event_thread_
.Stop();
102 // Close the in client.
104 snd_seq_close(in_client_
);
107 snd_midi_event_free(decoder_
);
110 void MidiManagerAlsa::StartInitialization() {
111 // TODO(agoode): Move off I/O thread. See http://crbug.com/374341.
113 // Create client handles.
114 int err
= snd_seq_open(&in_client_
, kAlsaHw
, SND_SEQ_OPEN_INPUT
, 0);
116 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err
);
117 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
119 in_client_id_
= snd_seq_client_id(in_client_
);
120 err
= snd_seq_open(&out_client_
, kAlsaHw
, SND_SEQ_OPEN_OUTPUT
, 0);
122 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err
);
123 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
125 out_client_id_
= snd_seq_client_id(out_client_
);
128 err
= snd_seq_set_client_name(in_client_
, "Chrome (input)");
130 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err
);
131 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
133 err
= snd_seq_set_client_name(out_client_
, "Chrome (output)");
135 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err
);
136 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
139 // Create input port.
140 in_port_id_
= snd_seq_create_simple_port(
141 in_client_
, NULL
, kCreateInputPortCaps
, kCreatePortType
);
142 if (in_port_id_
< 0) {
143 VLOG(1) << "snd_seq_create_simple_port fails: "
144 << snd_strerror(in_port_id_
);
145 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
148 // Subscribe to the announce port.
149 snd_seq_port_subscribe_t
* subs
;
150 snd_seq_port_subscribe_alloca(&subs
);
151 snd_seq_addr_t announce_sender
;
152 snd_seq_addr_t announce_dest
;
153 announce_sender
.client
= SND_SEQ_CLIENT_SYSTEM
;
154 announce_sender
.port
= SND_SEQ_PORT_SYSTEM_ANNOUNCE
;
155 announce_dest
.client
= in_client_id_
;
156 announce_dest
.port
= in_port_id_
;
157 snd_seq_port_subscribe_set_sender(subs
, &announce_sender
);
158 snd_seq_port_subscribe_set_dest(subs
, &announce_dest
);
159 err
= snd_seq_subscribe_port(in_client_
, subs
);
161 VLOG(1) << "snd_seq_subscribe_port on the announce port fails: "
162 << snd_strerror(err
);
163 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
166 // Generate hotplug events for existing ports.
167 EnumerateAlsaPorts();
169 // Start processing events.
170 event_thread_
.Start();
171 event_thread_
.message_loop()->PostTask(
173 base::Bind(&MidiManagerAlsa::ScheduleEventLoop
, base::Unretained(this)));
175 CompleteInitialization(MIDI_OK
);
178 void MidiManagerAlsa::DispatchSendMidiData(MidiManagerClient
* client
,
180 const std::vector
<uint8
>& data
,
182 // Not correct right now. http://crbug.com/374341.
183 if (!send_thread_
.IsRunning())
184 send_thread_
.Start();
186 base::TimeDelta delay
;
187 if (timestamp
!= 0.0) {
188 base::TimeTicks time_to_send
=
189 base::TimeTicks() + base::TimeDelta::FromMicroseconds(
190 timestamp
* base::Time::kMicrosecondsPerSecond
);
191 delay
= std::max(time_to_send
- base::TimeTicks::Now(), base::TimeDelta());
194 send_thread_
.message_loop()->PostDelayedTask(
195 FROM_HERE
, base::Bind(&MidiManagerAlsa::SendMidiData
,
196 base::Unretained(this), port_index
, data
),
200 send_thread_
.message_loop()->PostTask(
201 FROM_HERE
, base::Bind(&MidiManagerClient::AccumulateMidiBytesSent
,
202 base::Unretained(client
), data
.size()));
205 MidiManagerAlsa::MidiPort::MidiPort(const std::string
& path
,
206 const std::string
& id
,
210 const std::string
& client_name
,
211 const std::string
& port_name
,
212 const std::string
& manufacturer
,
213 const std::string
& version
,
216 midi_device_(midi_device
),
219 client_id_(client_id
),
221 client_name_(client_name
),
222 port_name_(port_name
),
223 manufacturer_(manufacturer
),
229 MidiManagerAlsa::MidiPort::~MidiPort() {
232 // Note: keep synchronized with the MidiPort::Match* methods.
233 scoped_ptr
<base::Value
> MidiManagerAlsa::MidiPort::Value() const {
234 scoped_ptr
<base::DictionaryValue
> value(new base::DictionaryValue
);
246 value
->SetString("type", type
);
247 SetStringIfNonEmpty(value
.get(), "path", path_
);
248 SetStringIfNonEmpty(value
.get(), "id", id_
);
249 SetStringIfNonEmpty(value
.get(), "clientName", client_name_
);
250 SetStringIfNonEmpty(value
.get(), "portName", port_name_
);
251 value
->SetInteger("clientId", client_id_
);
252 value
->SetInteger("portId", port_id_
);
253 value
->SetInteger("midiDevice", midi_device_
);
258 std::string
MidiManagerAlsa::MidiPort::JSONValue() const {
260 JSONStringValueSerializer
serializer(&json
);
261 serializer
.Serialize(*Value().get());
265 // TODO(agoode): Do not use SHA256 here. Instead store a persistent
266 // mapping and just use a UUID or other random string.
267 // http://crbug.com/465320
268 std::string
MidiManagerAlsa::MidiPort::OpaqueKey() const {
269 uint8 hash
[crypto::kSHA256Length
];
270 crypto::SHA256HashString(JSONValue(), &hash
, sizeof(hash
));
271 return base::HexEncode(&hash
, sizeof(hash
));
274 bool MidiManagerAlsa::MidiPort::MatchConnected(const MidiPort
& query
) const {
285 return connected() && (type() == query
.type()) && (path() == query
.path()) &&
286 (id() == query
.id()) && (client_id() == query
.client_id()) &&
287 (port_id() == query
.port_id()) &&
288 (midi_device() == query
.midi_device()) &&
289 (client_name() == query
.client_name()) &&
290 (port_name() == query
.port_name());
293 bool MidiManagerAlsa::MidiPort::MatchCardPass1(const MidiPort
& query
) const {
295 // connected == false
301 return MatchCardPass2(query
) && (path() == query
.path());
304 bool MidiManagerAlsa::MidiPort::MatchCardPass2(const MidiPort
& query
) const {
306 // connected == false
311 return !connected() && (type() == query
.type()) && (id() == query
.id()) &&
312 (port_id() == query
.port_id()) &&
313 (midi_device() == query
.midi_device());
316 bool MidiManagerAlsa::MidiPort::MatchNoCardPass1(const MidiPort
& query
) const {
318 // connected == false
320 // path.empty(), for both this and query
321 // id.empty(), for both this and query
326 // midi_device == -1, for both this and query
327 return MatchNoCardPass2(query
) && (client_id() == query
.client_id());
330 bool MidiManagerAlsa::MidiPort::MatchNoCardPass2(const MidiPort
& query
) const {
332 // connected == false
334 // path.empty(), for both this and query
335 // id.empty(), for both this and query
339 // midi_device == -1, for both this and query
340 return !connected() && (type() == query
.type()) && path().empty() &&
341 query
.path().empty() && id().empty() && query
.id().empty() &&
342 (port_id() == query
.port_id()) &&
343 (client_name() == query
.client_name()) &&
344 (port_name() == query
.port_name()) && (midi_device() == -1) &&
345 (query
.midi_device() == -1);
348 MidiManagerAlsa::MidiPortStateBase::~MidiPortStateBase() {
351 ScopedVector
<MidiManagerAlsa::MidiPort
>*
352 MidiManagerAlsa::MidiPortStateBase::ports() {
356 MidiManagerAlsa::MidiPortStateBase::iterator
357 MidiManagerAlsa::MidiPortStateBase::Find(
358 const MidiManagerAlsa::MidiPort
& port
) {
359 auto result
= FindConnected(port
);
361 result
= FindDisconnected(port
);
365 MidiManagerAlsa::MidiPortStateBase::iterator
366 MidiManagerAlsa::MidiPortStateBase::FindConnected(
367 const MidiManagerAlsa::MidiPort
& port
) {
368 // Exact match required for connected ports.
369 auto it
= std::find_if(ports_
.begin(), ports_
.end(), [&port
](MidiPort
* p
) {
370 return p
->MatchConnected(port
);
375 MidiManagerAlsa::MidiPortStateBase::iterator
376 MidiManagerAlsa::MidiPortStateBase::FindDisconnected(
377 const MidiManagerAlsa::MidiPort
& port
) {
380 // Possible things to match on:
389 if (!port
.path().empty()) {
390 // If path is present, then we have a card-based client.
392 // Pass 1. Match on path, id, midi_device, port_id.
393 // This is the best possible match for hardware card-based clients.
394 // This will also match the empty id correctly for devices without an id.
395 auto it
= std::find_if(ports_
.begin(), ports_
.end(), [&port
](MidiPort
* p
) {
396 return p
->MatchCardPass1(port
);
398 if (it
!= ports_
.end())
401 if (!port
.id().empty()) {
402 // Pass 2. Match on id, midi_device, port_id.
403 // This will give us a high-confidence match when a user moves a device to
404 // another USB/Firewire/Thunderbolt/etc port, but only works if the device
405 // has a hardware id.
406 it
= std::find_if(ports_
.begin(), ports_
.end(), [&port
](MidiPort
* p
) {
407 return p
->MatchCardPass2(port
);
409 if (it
!= ports_
.end())
413 // Else, we have a non-card-based client.
414 // Pass 1. Match on client_id, port_id, client_name, port_name.
415 // This will give us a reasonably good match.
416 auto it
= std::find_if(ports_
.begin(), ports_
.end(), [&port
](MidiPort
* p
) {
417 return p
->MatchNoCardPass1(port
);
419 if (it
!= ports_
.end())
422 // Pass 2. Match on port_id, client_name, port_name.
423 // This is weaker but similar to pass 2 in the hardware card-based clients
425 it
= std::find_if(ports_
.begin(), ports_
.end(), [&port
](MidiPort
* p
) {
426 return p
->MatchNoCardPass2(port
);
428 if (it
!= ports_
.end())
436 MidiManagerAlsa::MidiPortStateBase::MidiPortStateBase() {
439 void MidiManagerAlsa::TemporaryMidiPortState::Insert(
440 scoped_ptr
<MidiPort
> port
) {
441 ports()->push_back(port
.Pass());
444 MidiManagerAlsa::MidiPortState::MidiPortState()
445 : num_input_ports_(0), num_output_ports_(0) {
448 uint32
MidiManagerAlsa::MidiPortState::Insert(scoped_ptr
<MidiPort
> port
) {
449 // Add the web midi index.
450 uint32 web_port_index
= 0;
451 switch (port
->type()) {
452 case MidiPort::Type::kInput
:
453 web_port_index
= num_input_ports_
++;
455 case MidiPort::Type::kOutput
:
456 web_port_index
= num_output_ports_
++;
459 port
->set_web_port_index(web_port_index
);
460 ports()->push_back(port
.Pass());
461 return web_port_index
;
464 MidiManagerAlsa::AlsaSeqState::AlsaSeqState() : clients_deleter_(&clients_
) {
467 MidiManagerAlsa::AlsaSeqState::~AlsaSeqState() {
470 void MidiManagerAlsa::AlsaSeqState::ClientStart(int client_id
,
471 const std::string
& client_name
,
472 snd_seq_client_type_t type
) {
473 ClientExit(client_id
);
474 clients_
[client_id
] = new Client(client_name
, type
);
477 bool MidiManagerAlsa::AlsaSeqState::ClientStarted(int client_id
) {
478 return clients_
.find(client_id
) != clients_
.end();
481 void MidiManagerAlsa::AlsaSeqState::ClientExit(int client_id
) {
482 auto it
= clients_
.find(client_id
);
483 if (it
!= clients_
.end()) {
489 void MidiManagerAlsa::AlsaSeqState::PortStart(
492 const std::string
& port_name
,
493 MidiManagerAlsa::AlsaSeqState::PortDirection direction
,
495 auto it
= clients_
.find(client_id
);
496 if (it
!= clients_
.end())
497 it
->second
->AddPort(port_id
,
498 scoped_ptr
<Port
>(new Port(port_name
, direction
, midi
)));
501 void MidiManagerAlsa::AlsaSeqState::PortExit(int client_id
, int port_id
) {
502 auto it
= clients_
.find(client_id
);
503 if (it
!= clients_
.end())
504 it
->second
->RemovePort(port_id
);
507 snd_seq_client_type_t
MidiManagerAlsa::AlsaSeqState::ClientType(
508 int client_id
) const {
509 auto it
= clients_
.find(client_id
);
510 if (it
== clients_
.end())
511 return SND_SEQ_USER_CLIENT
;
512 return it
->second
->type();
515 scoped_ptr
<MidiManagerAlsa::TemporaryMidiPortState
>
516 MidiManagerAlsa::AlsaSeqState::ToMidiPortState() {
517 scoped_ptr
<MidiManagerAlsa::TemporaryMidiPortState
> midi_ports(
518 new TemporaryMidiPortState
);
519 // TODO(agoode): Use information from udev as well.
521 for (const auto& client_pair
: clients_
) {
522 int client_id
= client_pair
.first
;
523 const auto& client
= client_pair
.second
;
525 // Get client metadata.
526 const std::string client_name
= client
->name();
527 std::string manufacturer
;
532 std::string card_name
;
533 std::string card_longname
;
534 int midi_device
= -1;
536 for (const auto& port_pair
: *client
) {
537 int port_id
= port_pair
.first
;
538 const auto& port
= port_pair
.second
;
542 if (!driver
.empty()) {
543 version
= driver
+ " / ";
546 base::StringPrintf("ALSA library version %d.%d.%d", SND_LIB_MAJOR
,
547 SND_LIB_MINOR
, SND_LIB_SUBMINOR
);
548 PortDirection direction
= port
->direction();
549 if (direction
== PortDirection::kInput
||
550 direction
== PortDirection::kDuplex
) {
551 midi_ports
->Insert(scoped_ptr
<MidiPort
>(new MidiPort(
552 path
, id
, client_id
, port_id
, midi_device
, client
->name(),
553 port
->name(), manufacturer
, version
, MidiPort::Type::kInput
)));
555 if (direction
== PortDirection::kOutput
||
556 direction
== PortDirection::kDuplex
) {
557 midi_ports
->Insert(scoped_ptr
<MidiPort
>(new MidiPort(
558 path
, id
, client_id
, port_id
, midi_device
, client
->name(),
559 port
->name(), manufacturer
, version
, MidiPort::Type::kOutput
)));
565 return midi_ports
.Pass();
568 MidiManagerAlsa::AlsaSeqState::Port::Port(
569 const std::string
& name
,
570 MidiManagerAlsa::AlsaSeqState::PortDirection direction
,
572 : name_(name
), direction_(direction
), midi_(midi
) {
575 MidiManagerAlsa::AlsaSeqState::Port::~Port() {
578 std::string
MidiManagerAlsa::AlsaSeqState::Port::name() const {
582 MidiManagerAlsa::AlsaSeqState::PortDirection
583 MidiManagerAlsa::AlsaSeqState::Port::direction() const {
587 bool MidiManagerAlsa::AlsaSeqState::Port::midi() const {
591 MidiManagerAlsa::AlsaSeqState::Client::Client(const std::string
& name
,
592 snd_seq_client_type_t type
)
593 : name_(name
), type_(type
), ports_deleter_(&ports_
) {
596 MidiManagerAlsa::AlsaSeqState::Client::~Client() {
599 std::string
MidiManagerAlsa::AlsaSeqState::Client::name() const {
603 snd_seq_client_type_t
MidiManagerAlsa::AlsaSeqState::Client::type() const {
607 void MidiManagerAlsa::AlsaSeqState::Client::AddPort(int addr
,
608 scoped_ptr
<Port
> port
) {
610 ports_
[addr
] = port
.release();
613 void MidiManagerAlsa::AlsaSeqState::Client::RemovePort(int addr
) {
614 auto it
= ports_
.find(addr
);
615 if (it
!= ports_
.end()) {
621 MidiManagerAlsa::AlsaSeqState::Client::PortMap::const_iterator
622 MidiManagerAlsa::AlsaSeqState::Client::begin() const {
623 return ports_
.begin();
626 MidiManagerAlsa::AlsaSeqState::Client::PortMap::const_iterator
627 MidiManagerAlsa::AlsaSeqState::Client::end() const {
632 std::string
MidiManagerAlsa::ExtractManufacturerString(
633 const std::string
& udev_id_vendor
,
634 const std::string
& udev_id_vendor_id
,
635 const std::string
& udev_id_vendor_from_database
,
636 const std::string
& alsa_name
,
637 const std::string
& alsa_longname
) {
638 // Let's try to determine the manufacturer. Here is the ordered preference
640 // 1. Vendor name from the hardware device string, from udev properties
642 // 2. Vendor name from the udev database (property ID_VENDOR_FROM_DATABASE).
643 // 3. Heuristic from ALSA.
645 // Is the vendor string present and not just the vendor hex id?
646 if (!udev_id_vendor
.empty() && (udev_id_vendor
!= udev_id_vendor_id
)) {
647 return udev_id_vendor
;
650 // Is there a vendor string in the hardware database?
651 if (!udev_id_vendor_from_database
.empty()) {
652 return udev_id_vendor_from_database
;
655 // Ok, udev gave us nothing useful, or was unavailable. So try a heuristic.
656 // We assume that card longname is in the format of
657 // "<manufacturer> <name> at <bus>". Otherwise, we give up to detect
658 // a manufacturer name here.
659 size_t at_index
= alsa_longname
.rfind(" at ");
660 if (at_index
&& at_index
!= std::string::npos
) {
661 size_t name_index
= alsa_longname
.rfind(alsa_name
, at_index
- 1);
662 if (name_index
&& name_index
!= std::string::npos
)
663 return alsa_longname
.substr(0, name_index
- 1);
670 void MidiManagerAlsa::SendMidiData(uint32 port_index
,
671 const std::vector
<uint8
>& data
) {
672 DCHECK(send_thread_
.message_loop_proxy()->BelongsToCurrentThread());
674 snd_midi_event_t
* encoder
;
675 snd_midi_event_new(kSendBufferSize
, &encoder
);
676 for (unsigned int i
= 0; i
< data
.size(); i
++) {
677 snd_seq_event_t event
;
678 int result
= snd_midi_event_encode_byte(encoder
, data
[i
], &event
);
680 // Full event, send it.
681 base::AutoLock
lock(out_ports_lock_
);
682 auto it
= out_ports_
.find(port_index
);
683 if (it
!= out_ports_
.end()) {
684 snd_seq_ev_set_source(&event
, it
->second
);
685 snd_seq_ev_set_subs(&event
);
686 snd_seq_ev_set_direct(&event
);
687 snd_seq_event_output_direct(out_client_
, &event
);
691 snd_midi_event_free(encoder
);
694 void MidiManagerAlsa::ScheduleEventLoop() {
695 event_thread_
.message_loop()->PostTask(
697 base::Bind(&MidiManagerAlsa::EventLoop
, base::Unretained(this)));
700 void MidiManagerAlsa::EventLoop() {
701 // Read available incoming MIDI data.
702 snd_seq_event_t
* event
;
703 int err
= snd_seq_event_input(in_client_
, &event
);
704 double timestamp
= (base::TimeTicks::Now() - base::TimeTicks()).InSecondsF();
707 if (err
== -ENOSPC
) {
708 VLOG(1) << "snd_seq_event_input detected buffer overrun";
709 // We've lost events: check another way to see if we need to shut down.
710 base::AutoLock
lock(shutdown_lock_
);
711 if (!event_thread_shutdown_
)
714 } else if (err
< 0) {
715 VLOG(1) << "snd_seq_event_input fails: " << snd_strerror(err
);
716 // TODO(agoode): Use RecordAction() or similar to log this.
720 // Handle announce events.
721 if (event
->source
.client
== SND_SEQ_CLIENT_SYSTEM
&&
722 event
->source
.port
== SND_SEQ_PORT_SYSTEM_ANNOUNCE
) {
723 switch (event
->type
) {
724 case SND_SEQ_EVENT_PORT_START
:
725 // Don't use SND_SEQ_EVENT_CLIENT_START because the client name may not
726 // be set by the time we query it. It should be set by the time ports
728 ProcessClientStartEvent(event
->data
.addr
.client
);
729 ProcessPortStartEvent(event
->data
.addr
);
732 case SND_SEQ_EVENT_CLIENT_EXIT
:
733 // Check for disconnection of our "out" client. This means "shut down".
734 if (event
->data
.addr
.client
== out_client_id_
)
737 ProcessClientExitEvent(event
->data
.addr
);
740 case SND_SEQ_EVENT_PORT_EXIT
:
741 ProcessPortExitEvent(event
->data
.addr
);
745 ProcessSingleEvent(event
, timestamp
);
752 void MidiManagerAlsa::ProcessSingleEvent(snd_seq_event_t
* event
,
755 source_map_
.find(AddrToInt(event
->source
.client
, event
->source
.port
));
756 if (source_it
!= source_map_
.end()) {
757 uint32 source
= source_it
->second
;
758 if (event
->type
== SND_SEQ_EVENT_SYSEX
) {
759 // Special! Variable-length sysex.
760 ReceiveMidiData(source
, static_cast<const uint8
*>(event
->data
.ext
.ptr
),
761 event
->data
.ext
.len
, timestamp
);
763 // Otherwise, decode this and send that on.
764 unsigned char buf
[12];
765 long count
= snd_midi_event_decode(decoder_
, buf
, sizeof(buf
), event
);
767 if (count
!= -ENOENT
) {
768 // ENOENT means that it's not a MIDI message, which is not an
769 // error, but other negative values are errors for us.
770 VLOG(1) << "snd_midi_event_decoder fails " << snd_strerror(count
);
771 // TODO(agoode): Record this failure.
774 ReceiveMidiData(source
, buf
, count
, timestamp
);
780 void MidiManagerAlsa::ProcessClientStartEvent(int client_id
) {
781 // Ignore if client is already started.
782 if (alsa_seq_state_
.ClientStarted(client_id
))
785 snd_seq_client_info_t
* client_info
;
786 snd_seq_client_info_alloca(&client_info
);
787 int err
= snd_seq_get_any_client_info(in_client_
, client_id
, client_info
);
791 // Skip our own clients.
792 if ((client_id
== in_client_id_
) || (client_id
== out_client_id_
))
795 // Update our view of ALSA seq state.
796 alsa_seq_state_
.ClientStart(client_id
,
797 snd_seq_client_info_get_name(client_info
),
798 snd_seq_client_info_get_type(client_info
));
800 // Generate Web MIDI events.
801 UpdatePortStateAndGenerateEvents();
804 void MidiManagerAlsa::ProcessPortStartEvent(const snd_seq_addr_t
& addr
) {
805 snd_seq_port_info_t
* port_info
;
806 snd_seq_port_info_alloca(&port_info
);
808 snd_seq_get_any_port_info(in_client_
, addr
.client
, addr
.port
, port_info
);
812 unsigned int caps
= snd_seq_port_info_get_capability(port_info
);
813 bool input
= (caps
& kRequiredInputPortCaps
) == kRequiredInputPortCaps
;
814 bool output
= (caps
& kRequiredOutputPortCaps
) == kRequiredOutputPortCaps
;
815 AlsaSeqState::PortDirection direction
;
817 direction
= AlsaSeqState::PortDirection::kDuplex
;
819 direction
= AlsaSeqState::PortDirection::kInput
;
821 direction
= AlsaSeqState::PortDirection::kOutput
;
825 // Update our view of ALSA seq state.
826 alsa_seq_state_
.PortStart(
827 addr
.client
, addr
.port
, snd_seq_port_info_get_name(port_info
), direction
,
828 snd_seq_port_info_get_type(port_info
) & SND_SEQ_PORT_TYPE_MIDI_GENERIC
);
829 // Generate Web MIDI events.
830 UpdatePortStateAndGenerateEvents();
833 void MidiManagerAlsa::ProcessClientExitEvent(const snd_seq_addr_t
& addr
) {
834 // Update our view of ALSA seq state.
835 alsa_seq_state_
.ClientExit(addr
.client
);
836 // Generate Web MIDI events.
837 UpdatePortStateAndGenerateEvents();
840 void MidiManagerAlsa::ProcessPortExitEvent(const snd_seq_addr_t
& addr
) {
841 // Update our view of ALSA seq state.
842 alsa_seq_state_
.PortExit(addr
.client
, addr
.port
);
843 // Generate Web MIDI events.
844 UpdatePortStateAndGenerateEvents();
847 void MidiManagerAlsa::UpdatePortStateAndGenerateEvents() {
848 // Generate new port state.
849 auto new_port_state
= alsa_seq_state_
.ToMidiPortState();
851 // Disconnect any connected old ports that are now missing.
852 for (auto* old_port
: port_state_
) {
853 if (old_port
->connected() &&
854 (new_port_state
->FindConnected(*old_port
) == new_port_state
->end())) {
855 old_port
->set_connected(false);
856 uint32 web_port_index
= old_port
->web_port_index();
857 switch (old_port
->type()) {
858 case MidiPort::Type::kInput
:
860 AddrToInt(old_port
->client_id(), old_port
->port_id()));
861 SetInputPortState(web_port_index
, MIDI_PORT_DISCONNECTED
);
863 case MidiPort::Type::kOutput
:
864 DeleteAlsaOutputPort(web_port_index
);
865 SetOutputPortState(web_port_index
, MIDI_PORT_DISCONNECTED
);
871 // Reconnect or add new ports.
872 auto it
= new_port_state
->begin();
873 while (it
!= new_port_state
->end()) {
874 auto* new_port
= *it
;
875 auto old_port
= port_state_
.Find(*new_port
);
876 if (old_port
== port_state_
.end()) {
878 uint32 web_port_index
=
879 port_state_
.Insert(scoped_ptr
<MidiPort
>(new_port
));
880 MidiPortInfo
info(new_port
->OpaqueKey(), new_port
->manufacturer(),
881 new_port
->port_name(), new_port
->version(),
883 switch (new_port
->type()) {
884 case MidiPort::Type::kInput
:
885 if (Subscribe(web_port_index
, new_port
->client_id(),
886 new_port
->port_id()))
890 case MidiPort::Type::kOutput
:
891 if (CreateAlsaOutputPort(web_port_index
, new_port
->client_id(),
892 new_port
->port_id()))
896 it
= new_port_state
->weak_erase(it
);
897 } else if (!(*old_port
)->connected()) {
899 uint32 web_port_index
= (*old_port
)->web_port_index();
900 (*old_port
)->Update(new_port
->path(), new_port
->client_id(),
901 new_port
->port_id(), new_port
->client_name(),
902 new_port
->port_name(), new_port
->manufacturer(),
903 new_port
->version());
904 switch ((*old_port
)->type()) {
905 case MidiPort::Type::kInput
:
906 if (Subscribe(web_port_index
, (*old_port
)->client_id(),
907 (*old_port
)->port_id()))
908 SetInputPortState(web_port_index
, MIDI_PORT_OPENED
);
911 case MidiPort::Type::kOutput
:
912 if (CreateAlsaOutputPort(web_port_index
, (*old_port
)->client_id(),
913 (*old_port
)->port_id()))
914 SetOutputPortState(web_port_index
, MIDI_PORT_OPENED
);
917 (*old_port
)->set_connected(true);
925 void MidiManagerAlsa::EnumerateAlsaPorts() {
926 snd_seq_client_info_t
* client_info
;
927 snd_seq_client_info_alloca(&client_info
);
928 snd_seq_port_info_t
* port_info
;
929 snd_seq_port_info_alloca(&port_info
);
931 // Enumerate clients.
932 snd_seq_client_info_set_client(client_info
, -1);
933 while (!snd_seq_query_next_client(in_client_
, client_info
)) {
934 int client_id
= snd_seq_client_info_get_client(client_info
);
935 ProcessClientStartEvent(client_id
);
938 snd_seq_port_info_set_client(port_info
, client_id
);
939 snd_seq_port_info_set_port(port_info
, -1);
940 while (!snd_seq_query_next_port(in_client_
, port_info
)) {
941 const snd_seq_addr_t
* addr
= snd_seq_port_info_get_addr(port_info
);
942 ProcessPortStartEvent(*addr
);
947 bool MidiManagerAlsa::CreateAlsaOutputPort(uint32 port_index
,
951 int out_port
= snd_seq_create_simple_port(
952 out_client_
, NULL
, kCreateOutputPortCaps
, kCreatePortType
);
954 VLOG(1) << "snd_seq_create_simple_port fails: " << snd_strerror(out_port
);
957 // Activate port subscription.
958 snd_seq_port_subscribe_t
* subs
;
959 snd_seq_port_subscribe_alloca(&subs
);
960 snd_seq_addr_t sender
;
961 sender
.client
= out_client_id_
;
962 sender
.port
= out_port
;
963 snd_seq_port_subscribe_set_sender(subs
, &sender
);
965 dest
.client
= client_id
;
967 snd_seq_port_subscribe_set_dest(subs
, &dest
);
968 int err
= snd_seq_subscribe_port(out_client_
, subs
);
970 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err
);
971 snd_seq_delete_simple_port(out_client_
, out_port
);
976 base::AutoLock
lock(out_ports_lock_
);
977 out_ports_
[port_index
] = out_port
;
981 void MidiManagerAlsa::DeleteAlsaOutputPort(uint32 port_index
) {
982 base::AutoLock
lock(out_ports_lock_
);
983 auto it
= out_ports_
.find(port_index
);
984 if (it
== out_ports_
.end())
987 int alsa_port
= it
->second
;
988 snd_seq_delete_simple_port(out_client_
, alsa_port
);
989 out_ports_
.erase(it
);
992 bool MidiManagerAlsa::Subscribe(uint32 port_index
, int client_id
, int port_id
) {
993 // Activate port subscription.
994 snd_seq_port_subscribe_t
* subs
;
995 snd_seq_port_subscribe_alloca(&subs
);
996 snd_seq_addr_t sender
;
997 sender
.client
= client_id
;
998 sender
.port
= port_id
;
999 snd_seq_port_subscribe_set_sender(subs
, &sender
);
1000 snd_seq_addr_t dest
;
1001 dest
.client
= in_client_id_
;
1002 dest
.port
= in_port_id_
;
1003 snd_seq_port_subscribe_set_dest(subs
, &dest
);
1004 int err
= snd_seq_subscribe_port(in_client_
, subs
);
1006 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err
);
1011 source_map_
[AddrToInt(client_id
, port_id
)] = port_index
;
1015 MidiManager
* MidiManager::Create() {
1016 return new MidiManagerAlsa();
1019 } // namespace media