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 send_thread_("MidiSendThread"),
74 event_thread_("MidiEventThread"),
75 event_thread_shutdown_(false) {
76 // Initialize decoder.
77 snd_midi_event_new(0, &decoder_
);
78 snd_midi_event_no_status(decoder_
, 1);
81 MidiManagerAlsa::~MidiManagerAlsa() {
82 // Tell the event thread it will soon be time to shut down. This gives
83 // us assurance the thread will stop in case the SND_SEQ_EVENT_CLIENT_EXIT
86 base::AutoLock
lock(shutdown_lock_
);
87 event_thread_shutdown_
= true;
90 // Stop the send thread.
93 // Close the out client. This will trigger the event thread to stop,
94 // because of SND_SEQ_EVENT_CLIENT_EXIT.
96 snd_seq_close(out_client_
);
98 // Wait for the event thread to stop.
101 // Close the in client.
103 snd_seq_close(in_client_
);
106 snd_midi_event_free(decoder_
);
109 void MidiManagerAlsa::StartInitialization() {
110 // TODO(agoode): Move off I/O thread. See http://crbug.com/374341.
112 // Create client handles.
113 int err
= snd_seq_open(&in_client_
, kAlsaHw
, SND_SEQ_OPEN_INPUT
, 0);
115 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err
);
116 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
118 in_client_id_
= snd_seq_client_id(in_client_
);
119 err
= snd_seq_open(&out_client_
, kAlsaHw
, SND_SEQ_OPEN_OUTPUT
, 0);
121 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err
);
122 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
124 out_client_id_
= snd_seq_client_id(out_client_
);
127 err
= snd_seq_set_client_name(in_client_
, "Chrome (input)");
129 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err
);
130 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
132 err
= snd_seq_set_client_name(out_client_
, "Chrome (output)");
134 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err
);
135 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
138 // Create input port.
139 in_port_id_
= snd_seq_create_simple_port(
140 in_client_
, NULL
, kCreateInputPortCaps
, kCreatePortType
);
141 if (in_port_id_
< 0) {
142 VLOG(1) << "snd_seq_create_simple_port fails: "
143 << snd_strerror(in_port_id_
);
144 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
147 // Subscribe to the announce port.
148 snd_seq_port_subscribe_t
* subs
;
149 snd_seq_port_subscribe_alloca(&subs
);
150 snd_seq_addr_t announce_sender
;
151 snd_seq_addr_t announce_dest
;
152 announce_sender
.client
= SND_SEQ_CLIENT_SYSTEM
;
153 announce_sender
.port
= SND_SEQ_PORT_SYSTEM_ANNOUNCE
;
154 announce_dest
.client
= in_client_id_
;
155 announce_dest
.port
= in_port_id_
;
156 snd_seq_port_subscribe_set_sender(subs
, &announce_sender
);
157 snd_seq_port_subscribe_set_dest(subs
, &announce_dest
);
158 err
= snd_seq_subscribe_port(in_client_
, subs
);
160 VLOG(1) << "snd_seq_subscribe_port on the announce port fails: "
161 << snd_strerror(err
);
162 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
165 // Generate hotplug events for existing ports.
166 EnumerateAlsaPorts();
168 // Start processing events.
169 event_thread_
.Start();
170 event_thread_
.message_loop()->PostTask(
172 base::Bind(&MidiManagerAlsa::ScheduleEventLoop
, base::Unretained(this)));
174 CompleteInitialization(MIDI_OK
);
177 void MidiManagerAlsa::DispatchSendMidiData(MidiManagerClient
* client
,
179 const std::vector
<uint8
>& data
,
181 // Not correct right now. http://crbug.com/374341.
182 if (!send_thread_
.IsRunning())
183 send_thread_
.Start();
185 base::TimeDelta delay
;
186 if (timestamp
!= 0.0) {
187 base::TimeTicks time_to_send
=
188 base::TimeTicks() + base::TimeDelta::FromMicroseconds(
189 timestamp
* base::Time::kMicrosecondsPerSecond
);
190 delay
= std::max(time_to_send
- base::TimeTicks::Now(), base::TimeDelta());
193 send_thread_
.message_loop()->PostDelayedTask(
194 FROM_HERE
, base::Bind(&MidiManagerAlsa::SendMidiData
,
195 base::Unretained(this), port_index
, data
),
199 send_thread_
.message_loop()->PostTask(
200 FROM_HERE
, base::Bind(&MidiManagerClient::AccumulateMidiBytesSent
,
201 base::Unretained(client
), data
.size()));
204 MidiManagerAlsa::MidiPort::MidiPort(const std::string
& path
,
205 const std::string
& id
,
209 const std::string
& client_name
,
210 const std::string
& port_name
,
211 const std::string
& manufacturer
,
212 const std::string
& version
,
215 midi_device_(midi_device
),
218 client_id_(client_id
),
220 client_name_(client_name
),
221 port_name_(port_name
),
222 manufacturer_(manufacturer
),
228 MidiManagerAlsa::MidiPort::~MidiPort() {
231 // Note: keep synchronized with the MidiPort::Match* methods.
232 scoped_ptr
<base::Value
> MidiManagerAlsa::MidiPort::Value() const {
233 scoped_ptr
<base::DictionaryValue
> value(new base::DictionaryValue
);
245 value
->SetString("type", type
);
246 SetStringIfNonEmpty(value
.get(), "path", path_
);
247 SetStringIfNonEmpty(value
.get(), "id", id_
);
248 SetStringIfNonEmpty(value
.get(), "clientName", client_name_
);
249 SetStringIfNonEmpty(value
.get(), "portName", port_name_
);
250 value
->SetInteger("clientId", client_id_
);
251 value
->SetInteger("portId", port_id_
);
252 value
->SetInteger("midiDevice", midi_device_
);
257 std::string
MidiManagerAlsa::MidiPort::JSONValue() const {
259 JSONStringValueSerializer
serializer(&json
);
260 serializer
.Serialize(*Value().get());
264 // TODO(agoode): Do not use SHA256 here. Instead store a persistent
265 // mapping and just use a UUID or other random string.
266 // http://crbug.com/465320
267 std::string
MidiManagerAlsa::MidiPort::OpaqueKey() const {
268 uint8 hash
[crypto::kSHA256Length
];
269 crypto::SHA256HashString(JSONValue(), &hash
, sizeof(hash
));
270 return base::HexEncode(&hash
, sizeof(hash
));
273 bool MidiManagerAlsa::MidiPort::MatchConnected(const MidiPort
& query
) const {
284 return connected() && (type() == query
.type()) && (path() == query
.path()) &&
285 (id() == query
.id()) && (client_id() == query
.client_id()) &&
286 (port_id() == query
.port_id()) &&
287 (midi_device() == query
.midi_device()) &&
288 (client_name() == query
.client_name()) &&
289 (port_name() == query
.port_name());
292 bool MidiManagerAlsa::MidiPort::MatchCardPass1(const MidiPort
& query
) const {
294 // connected == false
300 return MatchCardPass2(query
) && (path() == query
.path());
303 bool MidiManagerAlsa::MidiPort::MatchCardPass2(const MidiPort
& query
) const {
305 // connected == false
310 return !connected() && (type() == query
.type()) && (id() == query
.id()) &&
311 (port_id() == query
.port_id()) &&
312 (midi_device() == query
.midi_device());
315 bool MidiManagerAlsa::MidiPort::MatchNoCardPass1(const MidiPort
& query
) const {
317 // connected == false
319 // path.empty(), for both this and query
320 // id.empty(), for both this and query
325 // midi_device == -1, for both this and query
326 return MatchNoCardPass2(query
) && (client_id() == query
.client_id());
329 bool MidiManagerAlsa::MidiPort::MatchNoCardPass2(const MidiPort
& query
) const {
331 // connected == false
333 // path.empty(), for both this and query
334 // id.empty(), for both this and query
338 // midi_device == -1, for both this and query
339 return !connected() && (type() == query
.type()) && path().empty() &&
340 query
.path().empty() && id().empty() && query
.id().empty() &&
341 (port_id() == query
.port_id()) &&
342 (client_name() == query
.client_name()) &&
343 (port_name() == query
.port_name()) && (midi_device() == -1) &&
344 (query
.midi_device() == -1);
347 MidiManagerAlsa::MidiPortStateBase::~MidiPortStateBase() {
350 ScopedVector
<MidiManagerAlsa::MidiPort
>*
351 MidiManagerAlsa::MidiPortStateBase::ports() {
355 MidiManagerAlsa::MidiPortStateBase::iterator
356 MidiManagerAlsa::MidiPortStateBase::Find(
357 const MidiManagerAlsa::MidiPort
& port
) {
358 auto result
= FindConnected(port
);
360 result
= FindDisconnected(port
);
364 MidiManagerAlsa::MidiPortStateBase::iterator
365 MidiManagerAlsa::MidiPortStateBase::FindConnected(
366 const MidiManagerAlsa::MidiPort
& port
) {
367 // Exact match required for connected ports.
368 auto it
= std::find_if(ports_
.begin(), ports_
.end(), [&port
](MidiPort
* p
) {
369 return p
->MatchConnected(port
);
374 MidiManagerAlsa::MidiPortStateBase::iterator
375 MidiManagerAlsa::MidiPortStateBase::FindDisconnected(
376 const MidiManagerAlsa::MidiPort
& port
) {
379 // Possible things to match on:
388 if (!port
.path().empty()) {
389 // If path is present, then we have a card-based client.
391 // Pass 1. Match on path, id, midi_device, port_id.
392 // This is the best possible match for hardware card-based clients.
393 // This will also match the empty id correctly for devices without an id.
394 auto it
= std::find_if(ports_
.begin(), ports_
.end(), [&port
](MidiPort
* p
) {
395 return p
->MatchCardPass1(port
);
397 if (it
!= ports_
.end())
400 if (!port
.id().empty()) {
401 // Pass 2. Match on id, midi_device, port_id.
402 // This will give us a high-confidence match when a user moves a device to
403 // another USB/Firewire/Thunderbolt/etc port, but only works if the device
404 // has a hardware id.
405 it
= std::find_if(ports_
.begin(), ports_
.end(), [&port
](MidiPort
* p
) {
406 return p
->MatchCardPass2(port
);
408 if (it
!= ports_
.end())
412 // Else, we have a non-card-based client.
413 // Pass 1. Match on client_id, port_id, client_name, port_name.
414 // This will give us a reasonably good match.
415 auto it
= std::find_if(ports_
.begin(), ports_
.end(), [&port
](MidiPort
* p
) {
416 return p
->MatchNoCardPass1(port
);
418 if (it
!= ports_
.end())
421 // Pass 2. Match on port_id, client_name, port_name.
422 // This is weaker but similar to pass 2 in the hardware card-based clients
424 it
= std::find_if(ports_
.begin(), ports_
.end(), [&port
](MidiPort
* p
) {
425 return p
->MatchNoCardPass2(port
);
427 if (it
!= ports_
.end())
435 MidiManagerAlsa::MidiPortStateBase::MidiPortStateBase() {
438 void MidiManagerAlsa::TemporaryMidiPortState::Insert(
439 scoped_ptr
<MidiPort
> port
) {
440 ports()->push_back(port
.Pass());
443 MidiManagerAlsa::MidiPortState::MidiPortState()
444 : num_input_ports_(0), num_output_ports_(0) {
447 uint32
MidiManagerAlsa::MidiPortState::Insert(scoped_ptr
<MidiPort
> port
) {
448 // Add the web midi index.
449 uint32 web_port_index
= 0;
450 switch (port
->type()) {
451 case MidiPort::Type::kInput
:
452 web_port_index
= num_input_ports_
++;
454 case MidiPort::Type::kOutput
:
455 web_port_index
= num_output_ports_
++;
458 port
->set_web_port_index(web_port_index
);
459 ports()->push_back(port
.Pass());
460 return web_port_index
;
463 MidiManagerAlsa::AlsaSeqState::AlsaSeqState() : clients_deleter_(&clients_
) {
466 MidiManagerAlsa::AlsaSeqState::~AlsaSeqState() {
469 void MidiManagerAlsa::AlsaSeqState::ClientStart(int client_id
,
470 const std::string
& client_name
,
471 snd_seq_client_type_t type
) {
472 ClientExit(client_id
);
473 clients_
[client_id
] = new Client(client_name
, type
);
476 bool MidiManagerAlsa::AlsaSeqState::ClientStarted(int client_id
) {
477 return clients_
.find(client_id
) != clients_
.end();
480 void MidiManagerAlsa::AlsaSeqState::ClientExit(int client_id
) {
481 auto it
= clients_
.find(client_id
);
482 if (it
!= clients_
.end()) {
488 void MidiManagerAlsa::AlsaSeqState::PortStart(
491 const std::string
& port_name
,
492 MidiManagerAlsa::AlsaSeqState::PortDirection direction
,
494 auto it
= clients_
.find(client_id
);
495 if (it
!= clients_
.end())
496 it
->second
->AddPort(port_id
,
497 scoped_ptr
<Port
>(new Port(port_name
, direction
, midi
)));
500 void MidiManagerAlsa::AlsaSeqState::PortExit(int client_id
, int port_id
) {
501 auto it
= clients_
.find(client_id
);
502 if (it
!= clients_
.end())
503 it
->second
->RemovePort(port_id
);
506 snd_seq_client_type_t
MidiManagerAlsa::AlsaSeqState::ClientType(
507 int client_id
) const {
508 auto it
= clients_
.find(client_id
);
509 if (it
== clients_
.end())
510 return SND_SEQ_USER_CLIENT
;
511 return it
->second
->type();
514 scoped_ptr
<MidiManagerAlsa::TemporaryMidiPortState
>
515 MidiManagerAlsa::AlsaSeqState::ToMidiPortState() {
516 scoped_ptr
<MidiManagerAlsa::TemporaryMidiPortState
> midi_ports(
517 new TemporaryMidiPortState
);
518 // TODO(agoode): Use information from udev as well.
520 for (const auto& client_pair
: clients_
) {
521 int client_id
= client_pair
.first
;
522 const auto& client
= client_pair
.second
;
524 // Get client metadata.
525 const std::string client_name
= client
->name();
526 std::string manufacturer
;
531 std::string card_name
;
532 std::string card_longname
;
533 int midi_device
= -1;
535 for (const auto& port_pair
: *client
) {
536 int port_id
= port_pair
.first
;
537 const auto& port
= port_pair
.second
;
541 if (!driver
.empty()) {
542 version
= driver
+ " / ";
545 base::StringPrintf("ALSA library version %d.%d.%d", SND_LIB_MAJOR
,
546 SND_LIB_MINOR
, SND_LIB_SUBMINOR
);
547 PortDirection direction
= port
->direction();
548 if (direction
== PortDirection::kInput
||
549 direction
== PortDirection::kDuplex
) {
550 midi_ports
->Insert(scoped_ptr
<MidiPort
>(new MidiPort(
551 path
, id
, client_id
, port_id
, midi_device
, client
->name(),
552 port
->name(), manufacturer
, version
, MidiPort::Type::kInput
)));
554 if (direction
== PortDirection::kOutput
||
555 direction
== PortDirection::kDuplex
) {
556 midi_ports
->Insert(scoped_ptr
<MidiPort
>(new MidiPort(
557 path
, id
, client_id
, port_id
, midi_device
, client
->name(),
558 port
->name(), manufacturer
, version
, MidiPort::Type::kOutput
)));
564 return midi_ports
.Pass();
567 MidiManagerAlsa::AlsaSeqState::Port::Port(
568 const std::string
& name
,
569 MidiManagerAlsa::AlsaSeqState::PortDirection direction
,
571 : name_(name
), direction_(direction
), midi_(midi
) {
574 MidiManagerAlsa::AlsaSeqState::Port::~Port() {
577 std::string
MidiManagerAlsa::AlsaSeqState::Port::name() const {
581 MidiManagerAlsa::AlsaSeqState::PortDirection
582 MidiManagerAlsa::AlsaSeqState::Port::direction() const {
586 bool MidiManagerAlsa::AlsaSeqState::Port::midi() const {
590 MidiManagerAlsa::AlsaSeqState::Client::Client(const std::string
& name
,
591 snd_seq_client_type_t type
)
592 : name_(name
), type_(type
), ports_deleter_(&ports_
) {
595 MidiManagerAlsa::AlsaSeqState::Client::~Client() {
598 std::string
MidiManagerAlsa::AlsaSeqState::Client::name() const {
602 snd_seq_client_type_t
MidiManagerAlsa::AlsaSeqState::Client::type() const {
606 void MidiManagerAlsa::AlsaSeqState::Client::AddPort(int addr
,
607 scoped_ptr
<Port
> port
) {
609 ports_
[addr
] = port
.release();
612 void MidiManagerAlsa::AlsaSeqState::Client::RemovePort(int addr
) {
613 auto it
= ports_
.find(addr
);
614 if (it
!= ports_
.end()) {
620 MidiManagerAlsa::AlsaSeqState::Client::PortMap::const_iterator
621 MidiManagerAlsa::AlsaSeqState::Client::begin() const {
622 return ports_
.begin();
625 MidiManagerAlsa::AlsaSeqState::Client::PortMap::const_iterator
626 MidiManagerAlsa::AlsaSeqState::Client::end() const {
631 std::string
MidiManagerAlsa::ExtractManufacturerString(
632 const std::string
& udev_id_vendor
,
633 const std::string
& udev_id_vendor_id
,
634 const std::string
& udev_id_vendor_from_database
,
635 const std::string
& alsa_name
,
636 const std::string
& alsa_longname
) {
637 // Let's try to determine the manufacturer. Here is the ordered preference
639 // 1. Vendor name from the hardware device string, from udev properties
641 // 2. Vendor name from the udev database (property ID_VENDOR_FROM_DATABASE).
642 // 3. Heuristic from ALSA.
644 // Is the vendor string present and not just the vendor hex id?
645 if (!udev_id_vendor
.empty() && (udev_id_vendor
!= udev_id_vendor_id
)) {
646 return udev_id_vendor
;
649 // Is there a vendor string in the hardware database?
650 if (!udev_id_vendor_from_database
.empty()) {
651 return udev_id_vendor_from_database
;
654 // Ok, udev gave us nothing useful, or was unavailable. So try a heuristic.
655 // We assume that card longname is in the format of
656 // "<manufacturer> <name> at <bus>". Otherwise, we give up to detect
657 // a manufacturer name here.
658 size_t at_index
= alsa_longname
.rfind(" at ");
659 if (at_index
&& at_index
!= std::string::npos
) {
660 size_t name_index
= alsa_longname
.rfind(alsa_name
, at_index
- 1);
661 if (name_index
&& name_index
!= std::string::npos
)
662 return alsa_longname
.substr(0, name_index
- 1);
669 void MidiManagerAlsa::SendMidiData(uint32 port_index
,
670 const std::vector
<uint8
>& data
) {
671 DCHECK(send_thread_
.message_loop_proxy()->BelongsToCurrentThread());
673 snd_midi_event_t
* encoder
;
674 snd_midi_event_new(kSendBufferSize
, &encoder
);
675 for (unsigned int i
= 0; i
< data
.size(); i
++) {
676 snd_seq_event_t event
;
677 int result
= snd_midi_event_encode_byte(encoder
, data
[i
], &event
);
679 // Full event, send it.
680 base::AutoLock
lock(out_ports_lock_
);
681 auto it
= out_ports_
.find(port_index
);
682 if (it
!= out_ports_
.end()) {
683 snd_seq_ev_set_source(&event
, it
->second
);
684 snd_seq_ev_set_subs(&event
);
685 snd_seq_ev_set_direct(&event
);
686 snd_seq_event_output_direct(out_client_
, &event
);
690 snd_midi_event_free(encoder
);
693 void MidiManagerAlsa::ScheduleEventLoop() {
694 event_thread_
.message_loop()->PostTask(
696 base::Bind(&MidiManagerAlsa::EventLoop
, base::Unretained(this)));
699 void MidiManagerAlsa::EventLoop() {
700 // Read available incoming MIDI data.
701 snd_seq_event_t
* event
;
702 int err
= snd_seq_event_input(in_client_
, &event
);
703 double timestamp
= (base::TimeTicks::Now() - base::TimeTicks()).InSecondsF();
706 if (err
== -ENOSPC
) {
707 VLOG(1) << "snd_seq_event_input detected buffer overrun";
708 // We've lost events: check another way to see if we need to shut down.
709 base::AutoLock
lock(shutdown_lock_
);
710 if (!event_thread_shutdown_
)
713 } else if (err
< 0) {
714 VLOG(1) << "snd_seq_event_input fails: " << snd_strerror(err
);
715 // TODO(agoode): Use RecordAction() or similar to log this.
719 // Handle announce events.
720 if (event
->source
.client
== SND_SEQ_CLIENT_SYSTEM
&&
721 event
->source
.port
== SND_SEQ_PORT_SYSTEM_ANNOUNCE
) {
722 switch (event
->type
) {
723 case SND_SEQ_EVENT_PORT_START
:
724 // Don't use SND_SEQ_EVENT_CLIENT_START because the client name may not
725 // be set by the time we query it. It should be set by the time ports
727 ProcessClientStartEvent(event
->data
.addr
.client
);
728 ProcessPortStartEvent(event
->data
.addr
);
731 case SND_SEQ_EVENT_CLIENT_EXIT
:
732 // Check for disconnection of our "out" client. This means "shut down".
733 if (event
->data
.addr
.client
== out_client_id_
)
736 ProcessClientExitEvent(event
->data
.addr
);
739 case SND_SEQ_EVENT_PORT_EXIT
:
740 ProcessPortExitEvent(event
->data
.addr
);
744 ProcessSingleEvent(event
, timestamp
);
751 void MidiManagerAlsa::ProcessSingleEvent(snd_seq_event_t
* event
,
754 source_map_
.find(AddrToInt(event
->source
.client
, event
->source
.port
));
755 if (source_it
!= source_map_
.end()) {
756 uint32 source
= source_it
->second
;
757 if (event
->type
== SND_SEQ_EVENT_SYSEX
) {
758 // Special! Variable-length sysex.
759 ReceiveMidiData(source
, static_cast<const uint8
*>(event
->data
.ext
.ptr
),
760 event
->data
.ext
.len
, timestamp
);
762 // Otherwise, decode this and send that on.
763 unsigned char buf
[12];
764 long count
= snd_midi_event_decode(decoder_
, buf
, sizeof(buf
), event
);
766 if (count
!= -ENOENT
) {
767 // ENOENT means that it's not a MIDI message, which is not an
768 // error, but other negative values are errors for us.
769 VLOG(1) << "snd_midi_event_decoder fails " << snd_strerror(count
);
770 // TODO(agoode): Record this failure.
773 ReceiveMidiData(source
, buf
, count
, timestamp
);
779 void MidiManagerAlsa::ProcessClientStartEvent(int client_id
) {
780 // Ignore if client is already started.
781 if (alsa_seq_state_
.ClientStarted(client_id
))
784 snd_seq_client_info_t
* client_info
;
785 snd_seq_client_info_alloca(&client_info
);
786 int err
= snd_seq_get_any_client_info(in_client_
, client_id
, client_info
);
790 // Skip our own clients.
791 if ((client_id
== in_client_id_
) || (client_id
== out_client_id_
))
794 // Update our view of ALSA seq state.
795 alsa_seq_state_
.ClientStart(client_id
,
796 snd_seq_client_info_get_name(client_info
),
797 snd_seq_client_info_get_type(client_info
));
799 // Generate Web MIDI events.
800 UpdatePortStateAndGenerateEvents();
803 void MidiManagerAlsa::ProcessPortStartEvent(const snd_seq_addr_t
& addr
) {
804 snd_seq_port_info_t
* port_info
;
805 snd_seq_port_info_alloca(&port_info
);
807 snd_seq_get_any_port_info(in_client_
, addr
.client
, addr
.port
, port_info
);
811 unsigned int caps
= snd_seq_port_info_get_capability(port_info
);
812 bool input
= (caps
& kRequiredInputPortCaps
) == kRequiredInputPortCaps
;
813 bool output
= (caps
& kRequiredOutputPortCaps
) == kRequiredOutputPortCaps
;
814 AlsaSeqState::PortDirection direction
;
816 direction
= AlsaSeqState::PortDirection::kDuplex
;
818 direction
= AlsaSeqState::PortDirection::kInput
;
820 direction
= AlsaSeqState::PortDirection::kOutput
;
824 // Update our view of ALSA seq state.
825 alsa_seq_state_
.PortStart(
826 addr
.client
, addr
.port
, snd_seq_port_info_get_name(port_info
), direction
,
827 snd_seq_port_info_get_type(port_info
) & SND_SEQ_PORT_TYPE_MIDI_GENERIC
);
828 // Generate Web MIDI events.
829 UpdatePortStateAndGenerateEvents();
832 void MidiManagerAlsa::ProcessClientExitEvent(const snd_seq_addr_t
& addr
) {
833 // Update our view of ALSA seq state.
834 alsa_seq_state_
.ClientExit(addr
.client
);
835 // Generate Web MIDI events.
836 UpdatePortStateAndGenerateEvents();
839 void MidiManagerAlsa::ProcessPortExitEvent(const snd_seq_addr_t
& addr
) {
840 // Update our view of ALSA seq state.
841 alsa_seq_state_
.PortExit(addr
.client
, addr
.port
);
842 // Generate Web MIDI events.
843 UpdatePortStateAndGenerateEvents();
846 void MidiManagerAlsa::UpdatePortStateAndGenerateEvents() {
847 // Generate new port state.
848 auto new_port_state
= alsa_seq_state_
.ToMidiPortState();
850 // Disconnect any connected old ports that are now missing.
851 for (auto* old_port
: port_state_
) {
852 if (old_port
->connected() &&
853 (new_port_state
->FindConnected(*old_port
) == new_port_state
->end())) {
854 old_port
->set_connected(false);
855 uint32 web_port_index
= old_port
->web_port_index();
856 switch (old_port
->type()) {
857 case MidiPort::Type::kInput
:
859 AddrToInt(old_port
->client_id(), old_port
->port_id()));
860 SetInputPortState(web_port_index
, MIDI_PORT_DISCONNECTED
);
862 case MidiPort::Type::kOutput
:
863 DeleteAlsaOutputPort(web_port_index
);
864 SetOutputPortState(web_port_index
, MIDI_PORT_DISCONNECTED
);
870 // Reconnect or add new ports.
871 auto it
= new_port_state
->begin();
872 while (it
!= new_port_state
->end()) {
873 auto* new_port
= *it
;
874 auto old_port
= port_state_
.Find(*new_port
);
875 if (old_port
== port_state_
.end()) {
877 uint32 web_port_index
=
878 port_state_
.Insert(scoped_ptr
<MidiPort
>(new_port
));
879 MidiPortInfo
info(new_port
->OpaqueKey(), new_port
->manufacturer(),
880 new_port
->port_name(), new_port
->version(),
882 switch (new_port
->type()) {
883 case MidiPort::Type::kInput
:
884 if (Subscribe(web_port_index
, new_port
->client_id(),
885 new_port
->port_id()))
889 case MidiPort::Type::kOutput
:
890 if (CreateAlsaOutputPort(web_port_index
, new_port
->client_id(),
891 new_port
->port_id()))
895 it
= new_port_state
->weak_erase(it
);
896 } else if (!(*old_port
)->connected()) {
898 uint32 web_port_index
= (*old_port
)->web_port_index();
899 (*old_port
)->Update(new_port
->path(), new_port
->client_id(),
900 new_port
->port_id(), new_port
->client_name(),
901 new_port
->port_name(), new_port
->manufacturer(),
902 new_port
->version());
903 switch ((*old_port
)->type()) {
904 case MidiPort::Type::kInput
:
905 if (Subscribe(web_port_index
, (*old_port
)->client_id(),
906 (*old_port
)->port_id()))
907 SetInputPortState(web_port_index
, MIDI_PORT_OPENED
);
910 case MidiPort::Type::kOutput
:
911 if (CreateAlsaOutputPort(web_port_index
, (*old_port
)->client_id(),
912 (*old_port
)->port_id()))
913 SetOutputPortState(web_port_index
, MIDI_PORT_OPENED
);
916 (*old_port
)->set_connected(true);
924 void MidiManagerAlsa::EnumerateAlsaPorts() {
925 snd_seq_client_info_t
* client_info
;
926 snd_seq_client_info_alloca(&client_info
);
927 snd_seq_port_info_t
* port_info
;
928 snd_seq_port_info_alloca(&port_info
);
930 // Enumerate clients.
931 snd_seq_client_info_set_client(client_info
, -1);
932 while (!snd_seq_query_next_client(in_client_
, client_info
)) {
933 int client_id
= snd_seq_client_info_get_client(client_info
);
934 ProcessClientStartEvent(client_id
);
937 snd_seq_port_info_set_client(port_info
, client_id
);
938 snd_seq_port_info_set_port(port_info
, -1);
939 while (!snd_seq_query_next_port(in_client_
, port_info
)) {
940 const snd_seq_addr_t
* addr
= snd_seq_port_info_get_addr(port_info
);
941 ProcessPortStartEvent(*addr
);
946 bool MidiManagerAlsa::CreateAlsaOutputPort(uint32 port_index
,
950 int out_port
= snd_seq_create_simple_port(
951 out_client_
, NULL
, kCreateOutputPortCaps
, kCreatePortType
);
953 VLOG(1) << "snd_seq_create_simple_port fails: " << snd_strerror(out_port
);
956 // Activate port subscription.
957 snd_seq_port_subscribe_t
* subs
;
958 snd_seq_port_subscribe_alloca(&subs
);
959 snd_seq_addr_t sender
;
960 sender
.client
= out_client_id_
;
961 sender
.port
= out_port
;
962 snd_seq_port_subscribe_set_sender(subs
, &sender
);
964 dest
.client
= client_id
;
966 snd_seq_port_subscribe_set_dest(subs
, &dest
);
967 int err
= snd_seq_subscribe_port(out_client_
, subs
);
969 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err
);
970 snd_seq_delete_simple_port(out_client_
, out_port
);
975 base::AutoLock
lock(out_ports_lock_
);
976 out_ports_
[port_index
] = out_port
;
980 void MidiManagerAlsa::DeleteAlsaOutputPort(uint32 port_index
) {
981 base::AutoLock
lock(out_ports_lock_
);
982 auto it
= out_ports_
.find(port_index
);
983 if (it
== out_ports_
.end())
986 int alsa_port
= it
->second
;
987 snd_seq_delete_simple_port(out_client_
, alsa_port
);
988 out_ports_
.erase(it
);
991 bool MidiManagerAlsa::Subscribe(uint32 port_index
, int client_id
, int port_id
) {
992 // Activate port subscription.
993 snd_seq_port_subscribe_t
* subs
;
994 snd_seq_port_subscribe_alloca(&subs
);
995 snd_seq_addr_t sender
;
996 sender
.client
= client_id
;
997 sender
.port
= port_id
;
998 snd_seq_port_subscribe_set_sender(subs
, &sender
);
1000 dest
.client
= in_client_id_
;
1001 dest
.port
= in_port_id_
;
1002 snd_seq_port_subscribe_set_dest(subs
, &dest
);
1003 int err
= snd_seq_subscribe_port(in_client_
, subs
);
1005 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err
);
1010 source_map_
[AddrToInt(client_id
, port_id
)] = port_index
;
1014 MidiManager
* MidiManager::Create() {
1015 return new MidiManagerAlsa();
1018 } // namespace media