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"
12 #include "base/bind.h"
13 #include "base/json/json_string_value_serializer.h"
14 #include "base/logging.h"
15 #include "base/message_loop/message_loop.h"
16 #include "base/posix/eintr_wrapper.h"
17 #include "base/safe_strerror_posix.h"
18 #include "base/strings/string_number_conversions.h"
19 #include "base/strings/stringprintf.h"
20 #include "base/time/time.h"
21 #include "crypto/sha2.h"
22 #include "media/midi/midi_port_info.h"
29 // Per-output buffer. This can be smaller, but then large sysex messages
30 // will be (harmlessly) split across multiple seq events. This should
31 // not have any real practical effect, except perhaps to slightly reorder
32 // realtime messages with respect to sysex.
33 const size_t kSendBufferSize
= 256;
35 // Minimum client id for which we will have ALSA card devices for. When we
36 // are searching for card devices (used to get the path, id, and manufacturer),
37 // we don't want to get confused by kernel clients that do not have a card.
38 // See seq_clientmgr.c in the ALSA code for this.
39 // TODO(agoode): Add proper client -> card export from the kernel to avoid
41 const int kMinimumClientIdForCards
= 16;
44 const char kAlsaHw
[] = "hw";
47 const char kUdev
[] = "udev";
48 const char kUdevSubsystemSound
[] = "sound";
49 const char kUdevPropertySoundInitialized
[] = "SOUND_INITIALIZED";
50 const char kUdevActionChange
[] = "change";
51 const char kUdevActionRemove
[] = "remove";
53 const char kUdevIdVendor
[] = "ID_VENDOR";
54 const char kUdevIdVendorEnc
[] = "ID_VENDOR_ENC";
55 const char kUdevIdVendorFromDatabase
[] = "ID_VENDOR_FROM_DATABASE";
56 const char kUdevIdVendorId
[] = "ID_VENDOR_ID";
57 const char kUdevIdModelId
[] = "ID_MODEL_ID";
58 const char kUdevIdBus
[] = "ID_BUS";
59 const char kUdevIdPath
[] = "ID_PATH";
60 const char kUdevIdUsbInterfaceNum
[] = "ID_USB_INTERFACE_NUM";
61 const char kUdevIdSerialShort
[] = "ID_SERIAL_SHORT";
63 const char kSysattrVendorName
[] = "vendor_name";
64 const char kSysattrVendor
[] = "vendor";
65 const char kSysattrModel
[] = "model";
66 const char kSysattrGuid
[] = "guid";
68 const char kCardSyspath
[] = "/card";
70 // Constants for the capabilities we search for in inputs and outputs.
71 // See http://www.alsa-project.org/alsa-doc/alsa-lib/seq.html.
72 const unsigned int kRequiredInputPortCaps
=
73 SND_SEQ_PORT_CAP_READ
| SND_SEQ_PORT_CAP_SUBS_READ
;
74 const unsigned int kRequiredOutputPortCaps
=
75 SND_SEQ_PORT_CAP_WRITE
| SND_SEQ_PORT_CAP_SUBS_WRITE
;
77 const unsigned int kCreateOutputPortCaps
=
78 SND_SEQ_PORT_CAP_READ
| SND_SEQ_PORT_CAP_NO_EXPORT
;
79 const unsigned int kCreateInputPortCaps
=
80 SND_SEQ_PORT_CAP_WRITE
| SND_SEQ_PORT_CAP_NO_EXPORT
;
81 const unsigned int kCreatePortType
=
82 SND_SEQ_PORT_TYPE_MIDI_GENERIC
| SND_SEQ_PORT_TYPE_APPLICATION
;
84 int AddrToInt(int client
, int port
) {
85 return (client
<< 8) | port
;
88 // Returns true if this client has an ALSA card associated with it.
89 bool IsCardClient(snd_seq_client_type_t type
, int client_id
) {
90 return (type
== SND_SEQ_KERNEL_CLIENT
) &&
91 (client_id
>= kMinimumClientIdForCards
);
94 // TODO(agoode): Move this to device/udev_linux.
95 const std::string
UdevDeviceGetPropertyOrSysattr(
96 struct udev_device
* udev_device
,
97 const char* property_key
,
98 const char* sysattr_key
) {
99 // First try the property.
101 device::UdevDeviceGetPropertyValue(udev_device
, property_key
);
103 // If no property, look for sysattrs and walk up the parent devices too.
104 while (value
.empty() && udev_device
) {
105 value
= device::UdevDeviceGetSysattrValue(udev_device
, sysattr_key
);
106 udev_device
= device::udev_device_get_parent(udev_device
);
111 int GetCardNumber(udev_device
* dev
) {
112 const char* syspath
= device::udev_device_get_syspath(dev
);
116 std::string
syspath_str(syspath
);
117 size_t i
= syspath_str
.rfind(kCardSyspath
);
118 if (i
== std::string::npos
)
122 if (!base::StringToInt(syspath_str
.substr(i
+ strlen(kCardSyspath
)), &number
))
127 std::string
GetVendor(udev_device
* dev
) {
128 // Try to get the vendor string. Sometimes it is encoded.
129 std::string vendor
= device::UdevDecodeString(
130 device::UdevDeviceGetPropertyValue(dev
, kUdevIdVendorEnc
));
131 // Sometimes it is not encoded.
134 UdevDeviceGetPropertyOrSysattr(dev
, kUdevIdVendor
, kSysattrVendorName
);
138 void SetStringIfNonEmpty(base::DictionaryValue
* value
,
139 const std::string
& path
,
140 const std::string
& in_value
) {
141 if (!in_value
.empty())
142 value
->SetString(path
, in_value
);
147 MidiManagerAlsa::MidiManagerAlsa()
152 alsa_cards_deleter_(&alsa_cards_
),
153 alsa_card_midi_count_(0),
155 udev_(device::udev_new()),
156 send_thread_("MidiSendThread"),
157 event_thread_("MidiEventThread"),
158 event_thread_shutdown_(false) {
159 // Initialize decoder.
160 snd_midi_event_new(0, &decoder_
);
161 snd_midi_event_no_status(decoder_
, 1);
164 MidiManagerAlsa::~MidiManagerAlsa() {
165 // Tell the event thread it will soon be time to shut down. This gives
166 // us assurance the thread will stop in case the SND_SEQ_EVENT_CLIENT_EXIT
169 base::AutoLock
lock(shutdown_lock_
);
170 event_thread_shutdown_
= true;
173 // Stop the send thread.
176 // Close the out client. This will trigger the event thread to stop,
177 // because of SND_SEQ_EVENT_CLIENT_EXIT.
179 snd_seq_close(out_client_
);
181 // Wait for the event thread to stop.
182 event_thread_
.Stop();
184 // Close the in client.
186 snd_seq_close(in_client_
);
189 snd_midi_event_free(decoder_
);
192 void MidiManagerAlsa::StartInitialization() {
193 // TODO(agoode): Move off I/O thread. See http://crbug.com/374341.
195 // Create client handles.
197 snd_seq_open(&in_client_
, kAlsaHw
, SND_SEQ_OPEN_INPUT
, SND_SEQ_NONBLOCK
);
199 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err
);
200 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
202 in_client_id_
= snd_seq_client_id(in_client_
);
203 err
= snd_seq_open(&out_client_
, kAlsaHw
, SND_SEQ_OPEN_OUTPUT
, 0);
205 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err
);
206 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
208 out_client_id_
= snd_seq_client_id(out_client_
);
211 err
= snd_seq_set_client_name(in_client_
, "Chrome (input)");
213 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err
);
214 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
216 err
= snd_seq_set_client_name(out_client_
, "Chrome (output)");
218 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err
);
219 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
222 // Create input port.
223 in_port_id_
= snd_seq_create_simple_port(
224 in_client_
, NULL
, kCreateInputPortCaps
, kCreatePortType
);
225 if (in_port_id_
< 0) {
226 VLOG(1) << "snd_seq_create_simple_port fails: "
227 << snd_strerror(in_port_id_
);
228 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
231 // Subscribe to the announce port.
232 snd_seq_port_subscribe_t
* subs
;
233 snd_seq_port_subscribe_alloca(&subs
);
234 snd_seq_addr_t announce_sender
;
235 snd_seq_addr_t announce_dest
;
236 announce_sender
.client
= SND_SEQ_CLIENT_SYSTEM
;
237 announce_sender
.port
= SND_SEQ_PORT_SYSTEM_ANNOUNCE
;
238 announce_dest
.client
= in_client_id_
;
239 announce_dest
.port
= in_port_id_
;
240 snd_seq_port_subscribe_set_sender(subs
, &announce_sender
);
241 snd_seq_port_subscribe_set_dest(subs
, &announce_dest
);
242 err
= snd_seq_subscribe_port(in_client_
, subs
);
244 VLOG(1) << "snd_seq_subscribe_port on the announce port fails: "
245 << snd_strerror(err
);
246 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
249 // Generate hotplug events for existing ports.
250 // TODO(agoode): Check the return value for failure.
251 EnumerateAlsaPorts();
253 // Initialize udev monitor.
255 device::udev_monitor_new_from_netlink(udev_
.get(), kUdev
));
256 if (!udev_monitor_
.get()) {
257 VLOG(1) << "udev_monitor_new_from_netlink fails";
258 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
260 err
= device::udev_monitor_filter_add_match_subsystem_devtype(
261 udev_monitor_
.get(), kUdevSubsystemSound
, nullptr);
263 VLOG(1) << "udev_monitor_add_match_subsystem fails: "
264 << safe_strerror(-err
);
265 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
267 err
= device::udev_monitor_enable_receiving(udev_monitor_
.get());
269 VLOG(1) << "udev_monitor_enable_receiving fails: " << safe_strerror(-err
);
270 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
273 // Generate hotplug events for existing udev devices.
274 EnumerateUdevCards();
276 // Start processing events.
277 event_thread_
.Start();
278 event_thread_
.message_loop()->PostTask(
280 base::Bind(&MidiManagerAlsa::ScheduleEventLoop
, base::Unretained(this)));
282 CompleteInitialization(MIDI_OK
);
285 void MidiManagerAlsa::DispatchSendMidiData(MidiManagerClient
* client
,
287 const std::vector
<uint8
>& data
,
289 // Not correct right now. http://crbug.com/374341.
290 if (!send_thread_
.IsRunning())
291 send_thread_
.Start();
293 base::TimeDelta delay
;
294 if (timestamp
!= 0.0) {
295 base::TimeTicks time_to_send
=
296 base::TimeTicks() + base::TimeDelta::FromMicroseconds(
297 timestamp
* base::Time::kMicrosecondsPerSecond
);
298 delay
= std::max(time_to_send
- base::TimeTicks::Now(), base::TimeDelta());
301 send_thread_
.message_loop()->PostDelayedTask(
302 FROM_HERE
, base::Bind(&MidiManagerAlsa::SendMidiData
,
303 base::Unretained(this), port_index
, data
),
307 send_thread_
.message_loop()->PostTask(
308 FROM_HERE
, base::Bind(&MidiManagerClient::AccumulateMidiBytesSent
,
309 base::Unretained(client
), data
.size()));
312 MidiManagerAlsa::MidiPort::Id::Id() = default;
314 MidiManagerAlsa::MidiPort::Id::Id(const std::string
& bus
,
315 const std::string
& vendor_id
,
316 const std::string
& model_id
,
317 const std::string
& usb_interface_num
,
318 const std::string
& serial
)
320 vendor_id_(vendor_id
),
322 usb_interface_num_(usb_interface_num
),
326 MidiManagerAlsa::MidiPort::Id::Id(const Id
&) = default;
328 MidiManagerAlsa::MidiPort::Id::~Id() = default;
330 bool MidiManagerAlsa::MidiPort::Id::operator==(const Id
& rhs
) const {
331 return (bus_
== rhs
.bus_
) && (vendor_id_
== rhs
.vendor_id_
) &&
332 (model_id_
== rhs
.model_id_
) &&
333 (usb_interface_num_
== rhs
.usb_interface_num_
) &&
334 (serial_
== rhs
.serial_
);
337 bool MidiManagerAlsa::MidiPort::Id::empty() const {
338 return bus_
.empty() && vendor_id_
.empty() && model_id_
.empty() &&
339 usb_interface_num_
.empty() && serial_
.empty();
342 MidiManagerAlsa::MidiPort::MidiPort(const std::string
& path
,
347 const std::string
& client_name
,
348 const std::string
& port_name
,
349 const std::string
& manufacturer
,
350 const std::string
& version
,
353 midi_device_(midi_device
),
356 client_id_(client_id
),
358 client_name_(client_name
),
359 port_name_(port_name
),
360 manufacturer_(manufacturer
),
366 MidiManagerAlsa::MidiPort::~MidiPort() = default;
368 // Note: keep synchronized with the MidiPort::Match* methods.
369 scoped_ptr
<base::Value
> MidiManagerAlsa::MidiPort::Value() const {
370 scoped_ptr
<base::DictionaryValue
> value(new base::DictionaryValue
);
381 value
->SetString("type", type
);
382 SetStringIfNonEmpty(value
.get(), "path", path_
);
383 SetStringIfNonEmpty(value
.get(), "clientName", client_name_
);
384 SetStringIfNonEmpty(value
.get(), "portName", port_name_
);
385 value
->SetInteger("clientId", client_id_
);
386 value
->SetInteger("portId", port_id_
);
387 value
->SetInteger("midiDevice", midi_device_
);
389 // Flatten id fields.
390 SetStringIfNonEmpty(value
.get(), "bus", id_
.bus());
391 SetStringIfNonEmpty(value
.get(), "vendorId", id_
.vendor_id());
392 SetStringIfNonEmpty(value
.get(), "modelId", id_
.model_id());
393 SetStringIfNonEmpty(value
.get(), "usbInterfaceNum", id_
.usb_interface_num());
394 SetStringIfNonEmpty(value
.get(), "serial", id_
.serial());
399 std::string
MidiManagerAlsa::MidiPort::JSONValue() const {
401 JSONStringValueSerializer
serializer(&json
);
402 serializer
.Serialize(*Value().get());
406 // TODO(agoode): Do not use SHA256 here. Instead store a persistent
407 // mapping and just use a UUID or other random string.
408 // http://crbug.com/465320
409 std::string
MidiManagerAlsa::MidiPort::OpaqueKey() const {
410 uint8 hash
[crypto::kSHA256Length
];
411 crypto::SHA256HashString(JSONValue(), &hash
, sizeof(hash
));
412 return base::HexEncode(&hash
, sizeof(hash
));
415 bool MidiManagerAlsa::MidiPort::MatchConnected(const MidiPort
& query
) const {
426 return connected() && (type() == query
.type()) && (path() == query
.path()) &&
427 (id() == query
.id()) && (client_id() == query
.client_id()) &&
428 (port_id() == query
.port_id()) &&
429 (midi_device() == query
.midi_device()) &&
430 (client_name() == query
.client_name()) &&
431 (port_name() == query
.port_name());
434 bool MidiManagerAlsa::MidiPort::MatchCardPass1(const MidiPort
& query
) const {
436 // connected == false
442 return MatchCardPass2(query
) && (path() == query
.path());
445 bool MidiManagerAlsa::MidiPort::MatchCardPass2(const MidiPort
& query
) const {
447 // connected == false
452 return !connected() && (type() == query
.type()) && (id() == query
.id()) &&
453 (port_id() == query
.port_id()) &&
454 (midi_device() == query
.midi_device());
457 bool MidiManagerAlsa::MidiPort::MatchNoCardPass1(const MidiPort
& query
) const {
459 // connected == false
461 // path.empty(), for both this and query
462 // id.empty(), for both this and query
467 // midi_device == -1, for both this and query
468 return MatchNoCardPass2(query
) && (client_id() == query
.client_id());
471 bool MidiManagerAlsa::MidiPort::MatchNoCardPass2(const MidiPort
& query
) const {
473 // connected == false
475 // path.empty(), for both this and query
476 // id.empty(), for both this and query
480 // midi_device == -1, for both this and query
481 return !connected() && (type() == query
.type()) && path().empty() &&
482 query
.path().empty() && id().empty() && query
.id().empty() &&
483 (port_id() == query
.port_id()) &&
484 (client_name() == query
.client_name()) &&
485 (port_name() == query
.port_name()) && (midi_device() == -1) &&
486 (query
.midi_device() == -1);
489 MidiManagerAlsa::MidiPortStateBase::~MidiPortStateBase() = default;
491 MidiManagerAlsa::MidiPortStateBase::iterator
492 MidiManagerAlsa::MidiPortStateBase::Find(
493 const MidiManagerAlsa::MidiPort
& port
) {
494 auto result
= FindConnected(port
);
496 result
= FindDisconnected(port
);
500 MidiManagerAlsa::MidiPortStateBase::iterator
501 MidiManagerAlsa::MidiPortStateBase::FindConnected(
502 const MidiManagerAlsa::MidiPort
& port
) {
503 // Exact match required for connected ports.
504 auto it
= std::find_if(ports_
.begin(), ports_
.end(), [&port
](MidiPort
* p
) {
505 return p
->MatchConnected(port
);
510 MidiManagerAlsa::MidiPortStateBase::iterator
511 MidiManagerAlsa::MidiPortStateBase::FindDisconnected(
512 const MidiManagerAlsa::MidiPort
& port
) {
515 // Possible things to match on:
524 if (!port
.path().empty()) {
525 // If path is present, then we have a card-based client.
527 // Pass 1. Match on path, id, midi_device, port_id.
528 // This is the best possible match for hardware card-based clients.
529 // This will also match the empty id correctly for devices without an id.
530 auto it
= std::find_if(ports_
.begin(), ports_
.end(), [&port
](MidiPort
* p
) {
531 return p
->MatchCardPass1(port
);
533 if (it
!= ports_
.end())
536 if (!port
.id().empty()) {
537 // Pass 2. Match on id, midi_device, port_id.
538 // This will give us a high-confidence match when a user moves a device to
539 // another USB/Firewire/Thunderbolt/etc port, but only works if the device
540 // has a hardware id.
541 it
= std::find_if(ports_
.begin(), ports_
.end(), [&port
](MidiPort
* p
) {
542 return p
->MatchCardPass2(port
);
544 if (it
!= ports_
.end())
548 // Else, we have a non-card-based client.
549 // Pass 1. Match on client_id, port_id, client_name, port_name.
550 // This will give us a reasonably good match.
551 auto it
= std::find_if(ports_
.begin(), ports_
.end(), [&port
](MidiPort
* p
) {
552 return p
->MatchNoCardPass1(port
);
554 if (it
!= ports_
.end())
557 // Pass 2. Match on port_id, client_name, port_name.
558 // This is weaker but similar to pass 2 in the hardware card-based clients
560 it
= std::find_if(ports_
.begin(), ports_
.end(), [&port
](MidiPort
* p
) {
561 return p
->MatchNoCardPass2(port
);
563 if (it
!= ports_
.end())
571 MidiManagerAlsa::MidiPortStateBase::MidiPortStateBase() = default;
573 void MidiManagerAlsa::TemporaryMidiPortState::Insert(
574 scoped_ptr
<MidiPort
> port
) {
575 ports()->push_back(port
.Pass());
578 MidiManagerAlsa::MidiPortState::MidiPortState()
579 : num_input_ports_(0), num_output_ports_(0) {
582 uint32
MidiManagerAlsa::MidiPortState::Insert(scoped_ptr
<MidiPort
> port
) {
583 // Add the web midi index.
584 uint32 web_port_index
= 0;
585 switch (port
->type()) {
586 case MidiPort::Type::kInput
:
587 web_port_index
= num_input_ports_
++;
589 case MidiPort::Type::kOutput
:
590 web_port_index
= num_output_ports_
++;
593 port
->set_web_port_index(web_port_index
);
594 ports()->push_back(port
.Pass());
595 return web_port_index
;
598 MidiManagerAlsa::AlsaSeqState::AlsaSeqState()
599 : clients_deleter_(&clients_
), card_client_count_(0) {
602 MidiManagerAlsa::AlsaSeqState::~AlsaSeqState() = default;
604 void MidiManagerAlsa::AlsaSeqState::ClientStart(int client_id
,
605 const std::string
& client_name
,
606 snd_seq_client_type_t type
) {
607 ClientExit(client_id
);
608 clients_
[client_id
] = new Client(client_name
, type
);
609 if (IsCardClient(type
, client_id
))
610 ++card_client_count_
;
613 bool MidiManagerAlsa::AlsaSeqState::ClientStarted(int client_id
) {
614 return clients_
.find(client_id
) != clients_
.end();
617 void MidiManagerAlsa::AlsaSeqState::ClientExit(int client_id
) {
618 auto it
= clients_
.find(client_id
);
619 if (it
!= clients_
.end()) {
620 if (IsCardClient(it
->second
->type(), client_id
))
621 --card_client_count_
;
627 void MidiManagerAlsa::AlsaSeqState::PortStart(
630 const std::string
& port_name
,
631 MidiManagerAlsa::AlsaSeqState::PortDirection direction
,
633 auto it
= clients_
.find(client_id
);
634 if (it
!= clients_
.end())
635 it
->second
->AddPort(port_id
,
636 scoped_ptr
<Port
>(new Port(port_name
, direction
, midi
)));
639 void MidiManagerAlsa::AlsaSeqState::PortExit(int client_id
, int port_id
) {
640 auto it
= clients_
.find(client_id
);
641 if (it
!= clients_
.end())
642 it
->second
->RemovePort(port_id
);
645 snd_seq_client_type_t
MidiManagerAlsa::AlsaSeqState::ClientType(
646 int client_id
) const {
647 auto it
= clients_
.find(client_id
);
648 if (it
== clients_
.end())
649 return SND_SEQ_USER_CLIENT
;
650 return it
->second
->type();
653 scoped_ptr
<MidiManagerAlsa::TemporaryMidiPortState
>
654 MidiManagerAlsa::AlsaSeqState::ToMidiPortState(const AlsaCardMap
& alsa_cards
) {
655 scoped_ptr
<MidiManagerAlsa::TemporaryMidiPortState
> midi_ports(
656 new TemporaryMidiPortState
);
657 // TODO(agoode): Use more information from udev, to allow hardware matching.
658 // See http://crbug.com/486471.
659 auto card_it
= alsa_cards
.begin();
661 int card_midi_device
= -1;
662 for (const auto& client_pair
: clients_
) {
663 int client_id
= client_pair
.first
;
664 const auto& client
= client_pair
.second
;
666 // Get client metadata.
667 const std::string client_name
= client
->name();
668 std::string manufacturer
;
672 std::string card_name
;
673 std::string card_longname
;
674 int midi_device
= -1;
676 if (IsCardClient(client
->type(), client_id
)) {
677 auto& card
= card_it
->second
;
678 if (card_midi_device
== -1)
679 card_midi_device
= 0;
681 manufacturer
= card
->manufacturer();
683 id
= MidiPort::Id(card
->bus(), card
->vendor_id(), card
->model_id(),
684 card
->usb_interface_num(), card
->serial());
685 card_name
= card
->name();
686 card_longname
= card
->longname();
687 midi_device
= card_midi_device
;
690 if (card_midi_device
>= card
->midi_device_count()) {
691 card_midi_device
= -1;
696 for (const auto& port_pair
: *client
) {
697 int port_id
= port_pair
.first
;
698 const auto& port
= port_pair
.second
;
702 if (!driver
.empty()) {
703 version
= driver
+ " / ";
706 base::StringPrintf("ALSA library version %d.%d.%d", SND_LIB_MAJOR
,
707 SND_LIB_MINOR
, SND_LIB_SUBMINOR
);
708 PortDirection direction
= port
->direction();
709 if (direction
== PortDirection::kInput
||
710 direction
== PortDirection::kDuplex
) {
711 midi_ports
->Insert(scoped_ptr
<MidiPort
>(new MidiPort(
712 path
, id
, client_id
, port_id
, midi_device
, client
->name(),
713 port
->name(), manufacturer
, version
, MidiPort::Type::kInput
)));
715 if (direction
== PortDirection::kOutput
||
716 direction
== PortDirection::kDuplex
) {
717 midi_ports
->Insert(scoped_ptr
<MidiPort
>(new MidiPort(
718 path
, id
, client_id
, port_id
, midi_device
, client
->name(),
719 port
->name(), manufacturer
, version
, MidiPort::Type::kOutput
)));
725 return midi_ports
.Pass();
728 MidiManagerAlsa::AlsaSeqState::Port::Port(
729 const std::string
& name
,
730 MidiManagerAlsa::AlsaSeqState::PortDirection direction
,
732 : name_(name
), direction_(direction
), midi_(midi
) {
735 MidiManagerAlsa::AlsaSeqState::Port::~Port() = default;
737 MidiManagerAlsa::AlsaSeqState::Client::Client(const std::string
& name
,
738 snd_seq_client_type_t type
)
739 : name_(name
), type_(type
), ports_deleter_(&ports_
) {
742 MidiManagerAlsa::AlsaSeqState::Client::~Client() = default;
744 void MidiManagerAlsa::AlsaSeqState::Client::AddPort(int addr
,
745 scoped_ptr
<Port
> port
) {
747 ports_
[addr
] = port
.release();
750 void MidiManagerAlsa::AlsaSeqState::Client::RemovePort(int addr
) {
751 auto it
= ports_
.find(addr
);
752 if (it
!= ports_
.end()) {
758 MidiManagerAlsa::AlsaSeqState::Client::PortMap::const_iterator
759 MidiManagerAlsa::AlsaSeqState::Client::begin() const {
760 return ports_
.begin();
763 MidiManagerAlsa::AlsaSeqState::Client::PortMap::const_iterator
764 MidiManagerAlsa::AlsaSeqState::Client::end() const {
768 MidiManagerAlsa::AlsaCard::AlsaCard(udev_device
* dev
,
769 const std::string
& name
,
770 const std::string
& longname
,
771 const std::string
& driver
,
772 int midi_device_count
)
776 path_(device::UdevDeviceGetPropertyValue(dev
, kUdevIdPath
)),
777 bus_(device::UdevDeviceGetPropertyValue(dev
, kUdevIdBus
)),
779 UdevDeviceGetPropertyOrSysattr(dev
, kUdevIdVendorId
, kSysattrVendor
)),
781 UdevDeviceGetPropertyOrSysattr(dev
, kUdevIdModelId
, kSysattrModel
)),
783 device::UdevDeviceGetPropertyValue(dev
, kUdevIdUsbInterfaceNum
)),
784 serial_(UdevDeviceGetPropertyOrSysattr(dev
,
787 midi_device_count_(midi_device_count
),
788 manufacturer_(ExtractManufacturerString(
791 device::UdevDeviceGetPropertyValue(dev
, kUdevIdVendorFromDatabase
),
796 MidiManagerAlsa::AlsaCard::~AlsaCard() = default;
799 std::string
MidiManagerAlsa::AlsaCard::ExtractManufacturerString(
800 const std::string
& udev_id_vendor
,
801 const std::string
& udev_id_vendor_id
,
802 const std::string
& udev_id_vendor_from_database
,
803 const std::string
& alsa_name
,
804 const std::string
& alsa_longname
) {
805 // Let's try to determine the manufacturer. Here is the ordered preference
807 // 1. Vendor name from the hardware device string, from udev properties
809 // 2. Vendor name from the udev database (property ID_VENDOR_FROM_DATABASE).
810 // 3. Heuristic from ALSA.
812 // Is the vendor string present and not just the vendor hex id?
813 if (!udev_id_vendor
.empty() && (udev_id_vendor
!= udev_id_vendor_id
)) {
814 return udev_id_vendor
;
817 // Is there a vendor string in the hardware database?
818 if (!udev_id_vendor_from_database
.empty()) {
819 return udev_id_vendor_from_database
;
822 // Ok, udev gave us nothing useful, or was unavailable. So try a heuristic.
823 // We assume that card longname is in the format of
824 // "<manufacturer> <name> at <bus>". Otherwise, we give up to detect
825 // a manufacturer name here.
826 size_t at_index
= alsa_longname
.rfind(" at ");
827 if (at_index
&& at_index
!= std::string::npos
) {
828 size_t name_index
= alsa_longname
.rfind(alsa_name
, at_index
- 1);
829 if (name_index
&& name_index
!= std::string::npos
)
830 return alsa_longname
.substr(0, name_index
- 1);
837 void MidiManagerAlsa::SendMidiData(uint32 port_index
,
838 const std::vector
<uint8
>& data
) {
839 DCHECK(send_thread_
.message_loop_proxy()->BelongsToCurrentThread());
841 snd_midi_event_t
* encoder
;
842 snd_midi_event_new(kSendBufferSize
, &encoder
);
843 for (unsigned int i
= 0; i
< data
.size(); i
++) {
844 snd_seq_event_t event
;
845 int result
= snd_midi_event_encode_byte(encoder
, data
[i
], &event
);
847 // Full event, send it.
848 base::AutoLock
lock(out_ports_lock_
);
849 auto it
= out_ports_
.find(port_index
);
850 if (it
!= out_ports_
.end()) {
851 snd_seq_ev_set_source(&event
, it
->second
);
852 snd_seq_ev_set_subs(&event
);
853 snd_seq_ev_set_direct(&event
);
854 snd_seq_event_output_direct(out_client_
, &event
);
858 snd_midi_event_free(encoder
);
861 void MidiManagerAlsa::ScheduleEventLoop() {
862 event_thread_
.message_loop()->PostTask(
864 base::Bind(&MidiManagerAlsa::EventLoop
, base::Unretained(this)));
867 void MidiManagerAlsa::EventLoop() {
868 bool loop_again
= true;
870 struct pollfd pfd
[2];
871 snd_seq_poll_descriptors(in_client_
, &pfd
[0], 1, POLLIN
);
872 pfd
[1].fd
= device::udev_monitor_get_fd(udev_monitor_
.get());
873 pfd
[1].events
= POLLIN
;
875 int err
= HANDLE_EINTR(poll(pfd
, arraysize(pfd
), -1));
877 VLOG(1) << "poll fails: " << safe_strerror(errno
);
880 if (pfd
[0].revents
& POLLIN
) {
881 // Read available incoming MIDI data.
884 (base::TimeTicks::Now() - base::TimeTicks()).InSecondsF();
886 snd_seq_event_t
* event
;
887 err
= snd_seq_event_input(in_client_
, &event
);
888 remaining
= snd_seq_event_input_pending(in_client_
, 0);
890 if (err
== -ENOSPC
) {
891 // Handle out of space error.
892 VLOG(1) << "snd_seq_event_input detected buffer overrun";
893 // We've lost events: check another way to see if we need to shut
895 base::AutoLock
lock(shutdown_lock_
);
896 if (event_thread_shutdown_
)
898 } else if (err
== -EAGAIN
) {
899 // We've read all the data.
900 } else if (err
< 0) {
901 // Handle other errors.
902 VLOG(1) << "snd_seq_event_input fails: " << snd_strerror(err
);
903 // TODO(agoode): Use RecordAction() or similar to log this.
905 } else if (event
->source
.client
== SND_SEQ_CLIENT_SYSTEM
&&
906 event
->source
.port
== SND_SEQ_PORT_SYSTEM_ANNOUNCE
) {
907 // Handle announce events.
908 switch (event
->type
) {
909 case SND_SEQ_EVENT_PORT_START
:
910 // Don't use SND_SEQ_EVENT_CLIENT_START because the
911 // client name may not be set by the time we query
912 // it. It should be set by the time ports are made.
913 ProcessClientStartEvent(event
->data
.addr
.client
);
914 ProcessPortStartEvent(event
->data
.addr
);
916 case SND_SEQ_EVENT_CLIENT_EXIT
:
917 // Check for disconnection of our "out" client. This means "shut
919 if (event
->data
.addr
.client
== out_client_id_
) {
923 ProcessClientExitEvent(event
->data
.addr
);
925 case SND_SEQ_EVENT_PORT_EXIT
:
926 ProcessPortExitEvent(event
->data
.addr
);
931 ProcessSingleEvent(event
, timestamp
);
933 } while (remaining
> 0);
935 if (pfd
[1].revents
& POLLIN
) {
936 device::ScopedUdevDevicePtr
dev(
937 device::udev_monitor_receive_device(udev_monitor_
.get()));
939 ProcessUdevEvent(dev
.get());
941 VLOG(1) << "udev_monitor_receive_device fails";
950 void MidiManagerAlsa::ProcessSingleEvent(snd_seq_event_t
* event
,
953 source_map_
.find(AddrToInt(event
->source
.client
, event
->source
.port
));
954 if (source_it
!= source_map_
.end()) {
955 uint32 source
= source_it
->second
;
956 if (event
->type
== SND_SEQ_EVENT_SYSEX
) {
957 // Special! Variable-length sysex.
958 ReceiveMidiData(source
, static_cast<const uint8
*>(event
->data
.ext
.ptr
),
959 event
->data
.ext
.len
, timestamp
);
961 // Otherwise, decode this and send that on.
962 unsigned char buf
[12];
963 long count
= snd_midi_event_decode(decoder_
, buf
, sizeof(buf
), event
);
965 if (count
!= -ENOENT
) {
966 // ENOENT means that it's not a MIDI message, which is not an
967 // error, but other negative values are errors for us.
968 VLOG(1) << "snd_midi_event_decoder fails " << snd_strerror(count
);
969 // TODO(agoode): Record this failure.
972 ReceiveMidiData(source
, buf
, count
, timestamp
);
978 void MidiManagerAlsa::ProcessClientStartEvent(int client_id
) {
979 // Ignore if client is already started.
980 if (alsa_seq_state_
.ClientStarted(client_id
))
983 snd_seq_client_info_t
* client_info
;
984 snd_seq_client_info_alloca(&client_info
);
985 int err
= snd_seq_get_any_client_info(in_client_
, client_id
, client_info
);
989 // Skip our own clients.
990 if ((client_id
== in_client_id_
) || (client_id
== out_client_id_
))
993 // Update our view of ALSA seq state.
994 alsa_seq_state_
.ClientStart(client_id
,
995 snd_seq_client_info_get_name(client_info
),
996 snd_seq_client_info_get_type(client_info
));
998 // Generate Web MIDI events.
999 UpdatePortStateAndGenerateEvents();
1002 void MidiManagerAlsa::ProcessPortStartEvent(const snd_seq_addr_t
& addr
) {
1003 snd_seq_port_info_t
* port_info
;
1004 snd_seq_port_info_alloca(&port_info
);
1006 snd_seq_get_any_port_info(in_client_
, addr
.client
, addr
.port
, port_info
);
1010 unsigned int caps
= snd_seq_port_info_get_capability(port_info
);
1011 bool input
= (caps
& kRequiredInputPortCaps
) == kRequiredInputPortCaps
;
1012 bool output
= (caps
& kRequiredOutputPortCaps
) == kRequiredOutputPortCaps
;
1013 AlsaSeqState::PortDirection direction
;
1014 if (input
&& output
)
1015 direction
= AlsaSeqState::PortDirection::kDuplex
;
1017 direction
= AlsaSeqState::PortDirection::kInput
;
1019 direction
= AlsaSeqState::PortDirection::kOutput
;
1023 // Update our view of ALSA seq state.
1024 alsa_seq_state_
.PortStart(
1025 addr
.client
, addr
.port
, snd_seq_port_info_get_name(port_info
), direction
,
1026 snd_seq_port_info_get_type(port_info
) & SND_SEQ_PORT_TYPE_MIDI_GENERIC
);
1027 // Generate Web MIDI events.
1028 UpdatePortStateAndGenerateEvents();
1031 void MidiManagerAlsa::ProcessClientExitEvent(const snd_seq_addr_t
& addr
) {
1032 // Update our view of ALSA seq state.
1033 alsa_seq_state_
.ClientExit(addr
.client
);
1034 // Generate Web MIDI events.
1035 UpdatePortStateAndGenerateEvents();
1038 void MidiManagerAlsa::ProcessPortExitEvent(const snd_seq_addr_t
& addr
) {
1039 // Update our view of ALSA seq state.
1040 alsa_seq_state_
.PortExit(addr
.client
, addr
.port
);
1041 // Generate Web MIDI events.
1042 UpdatePortStateAndGenerateEvents();
1045 void MidiManagerAlsa::ProcessUdevEvent(udev_device
* dev
) {
1046 // Only card devices have this property set, and only when they are
1047 // fully initialized.
1048 if (!device::udev_device_get_property_value(dev
,
1049 kUdevPropertySoundInitialized
))
1052 // Get the action. If no action, then we are doing first time enumeration
1053 // and the device is treated as new.
1054 const char* action
= device::udev_device_get_action(dev
);
1056 action
= kUdevActionChange
;
1058 if (strcmp(action
, kUdevActionChange
) == 0) {
1060 // Generate Web MIDI events.
1061 UpdatePortStateAndGenerateEvents();
1062 } else if (strcmp(action
, kUdevActionRemove
) == 0) {
1063 RemoveCard(GetCardNumber(dev
));
1064 // Generate Web MIDI events.
1065 UpdatePortStateAndGenerateEvents();
1069 void MidiManagerAlsa::AddCard(udev_device
* dev
) {
1070 int number
= GetCardNumber(dev
);
1076 snd_ctl_card_info_t
* card
;
1077 snd_hwdep_info_t
* hwdep
;
1078 snd_ctl_card_info_alloca(&card
);
1079 snd_hwdep_info_alloca(&hwdep
);
1080 const std::string id
= base::StringPrintf("hw:CARD=%i", number
);
1082 int err
= snd_ctl_open(&handle
, id
.c_str(), 0);
1084 VLOG(1) << "snd_ctl_open fails: " << snd_strerror(err
);
1087 err
= snd_ctl_card_info(handle
, card
);
1089 VLOG(1) << "snd_ctl_card_info fails: " << snd_strerror(err
);
1090 snd_ctl_close(handle
);
1093 std::string name
= snd_ctl_card_info_get_name(card
);
1094 std::string longname
= snd_ctl_card_info_get_longname(card
);
1095 std::string driver
= snd_ctl_card_info_get_driver(card
);
1097 // Count rawmidi devices (not subdevices).
1099 for (int device
= -1;
1100 !snd_ctl_rawmidi_next_device(handle
, &device
) && device
>= 0;)
1103 // Count any hwdep synths that become MIDI devices outside of rawmidi.
1106 // Any kernel driver can create an ALSA client (visible to us).
1107 // With modern hardware, only rawmidi devices do this. Kernel
1108 // drivers create rawmidi devices and the rawmidi subsystem makes
1109 // the seq clients. But the OPL3 driver is special, it does not
1110 // make a rawmidi device but a seq client directly. (This is the
1111 // only one to worry about in the kernel code, as of 2015-03-23.)
1113 // OPL3 is very old (but still possible to get in new
1114 // hardware). It is unlikely that new drivers would not use
1115 // rawmidi and defeat our heuristic.
1117 // Longer term, support should be added in the kernel to expose a
1118 // direct link from card->client (or client->card) so that all
1119 // these heuristics will be obsolete. Once that is there, we can
1120 // assume our old heuristics will work on old kernels and the new
1121 // robust code will be used on new. Then we will not need to worry
1122 // about changes to kernel internals breaking our code.
1123 // See the TODO above at kMinimumClientIdForCards.
1124 for (int device
= -1;
1125 !snd_ctl_hwdep_next_device(handle
, &device
) && device
>= 0;) {
1126 err
= snd_ctl_hwdep_info(handle
, hwdep
);
1128 VLOG(1) << "snd_ctl_hwdep_info fails: " << snd_strerror(err
);
1131 snd_hwdep_iface_t iface
= snd_hwdep_info_get_iface(hwdep
);
1132 if (iface
== SND_HWDEP_IFACE_OPL2
|| iface
== SND_HWDEP_IFACE_OPL3
||
1133 iface
== SND_HWDEP_IFACE_OPL4
)
1136 snd_ctl_close(handle
);
1139 alsa_cards_
[number
] = new AlsaCard(dev
, name
, longname
, driver
, midi_count
);
1140 alsa_card_midi_count_
+= midi_count
;
1143 void MidiManagerAlsa::RemoveCard(int number
) {
1144 auto it
= alsa_cards_
.find(number
);
1145 if (it
== alsa_cards_
.end())
1148 alsa_card_midi_count_
-= it
->second
->midi_device_count();
1150 alsa_cards_
.erase(it
);
1153 void MidiManagerAlsa::UpdatePortStateAndGenerateEvents() {
1154 // Verify that our information from ALSA and udev are in sync. If
1155 // not, we cannot generate events right now.
1156 if (alsa_card_midi_count_
!= alsa_seq_state_
.card_client_count())
1159 // Generate new port state.
1160 auto new_port_state
= alsa_seq_state_
.ToMidiPortState(alsa_cards_
);
1162 // Disconnect any connected old ports that are now missing.
1163 for (auto* old_port
: port_state_
) {
1164 if (old_port
->connected() &&
1165 (new_port_state
->FindConnected(*old_port
) == new_port_state
->end())) {
1166 old_port
->set_connected(false);
1167 uint32 web_port_index
= old_port
->web_port_index();
1168 switch (old_port
->type()) {
1169 case MidiPort::Type::kInput
:
1171 AddrToInt(old_port
->client_id(), old_port
->port_id()));
1172 SetInputPortState(web_port_index
, MIDI_PORT_DISCONNECTED
);
1174 case MidiPort::Type::kOutput
:
1175 DeleteAlsaOutputPort(web_port_index
);
1176 SetOutputPortState(web_port_index
, MIDI_PORT_DISCONNECTED
);
1182 // Reconnect or add new ports.
1183 auto it
= new_port_state
->begin();
1184 while (it
!= new_port_state
->end()) {
1185 auto* new_port
= *it
;
1186 auto old_port
= port_state_
.Find(*new_port
);
1187 if (old_port
== port_state_
.end()) {
1189 uint32 web_port_index
=
1190 port_state_
.Insert(scoped_ptr
<MidiPort
>(new_port
));
1191 MidiPortInfo
info(new_port
->OpaqueKey(), new_port
->manufacturer(),
1192 new_port
->port_name(), new_port
->version(),
1194 switch (new_port
->type()) {
1195 case MidiPort::Type::kInput
:
1196 if (Subscribe(web_port_index
, new_port
->client_id(),
1197 new_port
->port_id()))
1200 case MidiPort::Type::kOutput
:
1201 if (CreateAlsaOutputPort(web_port_index
, new_port
->client_id(),
1202 new_port
->port_id()))
1203 AddOutputPort(info
);
1206 it
= new_port_state
->weak_erase(it
);
1207 } else if (!(*old_port
)->connected()) {
1209 uint32 web_port_index
= (*old_port
)->web_port_index();
1210 (*old_port
)->Update(new_port
->path(), new_port
->client_id(),
1211 new_port
->port_id(), new_port
->client_name(),
1212 new_port
->port_name(), new_port
->manufacturer(),
1213 new_port
->version());
1214 switch ((*old_port
)->type()) {
1215 case MidiPort::Type::kInput
:
1216 if (Subscribe(web_port_index
, (*old_port
)->client_id(),
1217 (*old_port
)->port_id()))
1218 SetInputPortState(web_port_index
, MIDI_PORT_OPENED
);
1220 case MidiPort::Type::kOutput
:
1221 if (CreateAlsaOutputPort(web_port_index
, (*old_port
)->client_id(),
1222 (*old_port
)->port_id()))
1223 SetOutputPortState(web_port_index
, MIDI_PORT_OPENED
);
1226 (*old_port
)->set_connected(true);
1234 // TODO(agoode): return false on failure.
1235 void MidiManagerAlsa::EnumerateAlsaPorts() {
1236 snd_seq_client_info_t
* client_info
;
1237 snd_seq_client_info_alloca(&client_info
);
1238 snd_seq_port_info_t
* port_info
;
1239 snd_seq_port_info_alloca(&port_info
);
1241 // Enumerate clients.
1242 snd_seq_client_info_set_client(client_info
, -1);
1243 while (!snd_seq_query_next_client(in_client_
, client_info
)) {
1244 int client_id
= snd_seq_client_info_get_client(client_info
);
1245 ProcessClientStartEvent(client_id
);
1248 snd_seq_port_info_set_client(port_info
, client_id
);
1249 snd_seq_port_info_set_port(port_info
, -1);
1250 while (!snd_seq_query_next_port(in_client_
, port_info
)) {
1251 const snd_seq_addr_t
* addr
= snd_seq_port_info_get_addr(port_info
);
1252 ProcessPortStartEvent(*addr
);
1257 bool MidiManagerAlsa::EnumerateUdevCards() {
1260 device::ScopedUdevEnumeratePtr
enumerate(
1261 device::udev_enumerate_new(udev_
.get()));
1262 if (!enumerate
.get()) {
1263 VLOG(1) << "udev_enumerate_new fails";
1267 err
= device::udev_enumerate_add_match_subsystem(enumerate
.get(),
1268 kUdevSubsystemSound
);
1270 VLOG(1) << "udev_enumerate_add_match_subsystem fails: "
1271 << safe_strerror(-err
);
1275 err
= device::udev_enumerate_scan_devices(enumerate
.get());
1277 VLOG(1) << "udev_enumerate_scan_devices fails: " << safe_strerror(-err
);
1281 udev_list_entry
* list_entry
;
1282 auto* devices
= device::udev_enumerate_get_list_entry(enumerate
.get());
1283 udev_list_entry_foreach(list_entry
, devices
) {
1284 const char* path
= device::udev_list_entry_get_name(list_entry
);
1285 device::ScopedUdevDevicePtr
dev(
1286 device::udev_device_new_from_syspath(udev_
.get(), path
));
1288 ProcessUdevEvent(dev
.get());
1294 bool MidiManagerAlsa::CreateAlsaOutputPort(uint32 port_index
,
1298 int out_port
= snd_seq_create_simple_port(
1299 out_client_
, NULL
, kCreateOutputPortCaps
, kCreatePortType
);
1301 VLOG(1) << "snd_seq_create_simple_port fails: " << snd_strerror(out_port
);
1304 // Activate port subscription.
1305 snd_seq_port_subscribe_t
* subs
;
1306 snd_seq_port_subscribe_alloca(&subs
);
1307 snd_seq_addr_t sender
;
1308 sender
.client
= out_client_id_
;
1309 sender
.port
= out_port
;
1310 snd_seq_port_subscribe_set_sender(subs
, &sender
);
1311 snd_seq_addr_t dest
;
1312 dest
.client
= client_id
;
1313 dest
.port
= port_id
;
1314 snd_seq_port_subscribe_set_dest(subs
, &dest
);
1315 int err
= snd_seq_subscribe_port(out_client_
, subs
);
1317 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err
);
1318 snd_seq_delete_simple_port(out_client_
, out_port
);
1323 base::AutoLock
lock(out_ports_lock_
);
1324 out_ports_
[port_index
] = out_port
;
1328 void MidiManagerAlsa::DeleteAlsaOutputPort(uint32 port_index
) {
1329 base::AutoLock
lock(out_ports_lock_
);
1330 auto it
= out_ports_
.find(port_index
);
1331 if (it
== out_ports_
.end())
1334 int alsa_port
= it
->second
;
1335 snd_seq_delete_simple_port(out_client_
, alsa_port
);
1336 out_ports_
.erase(it
);
1339 bool MidiManagerAlsa::Subscribe(uint32 port_index
, int client_id
, int port_id
) {
1340 // Activate port subscription.
1341 snd_seq_port_subscribe_t
* subs
;
1342 snd_seq_port_subscribe_alloca(&subs
);
1343 snd_seq_addr_t sender
;
1344 sender
.client
= client_id
;
1345 sender
.port
= port_id
;
1346 snd_seq_port_subscribe_set_sender(subs
, &sender
);
1347 snd_seq_addr_t dest
;
1348 dest
.client
= in_client_id_
;
1349 dest
.port
= in_port_id_
;
1350 snd_seq_port_subscribe_set_dest(subs
, &dest
);
1351 int err
= snd_seq_subscribe_port(in_client_
, subs
);
1353 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err
);
1358 source_map_
[AddrToInt(client_id
, port_id
)] = port_index
;
1362 MidiManager
* MidiManager::Create() {
1363 return new MidiManagerAlsa();
1367 } // namespace media