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;
37 // Minimum client id for which we will have ALSA card devices for. When we
38 // are searching for card devices (used to get the path, id, and manufacturer),
39 // we don't want to get confused by kernel clients that do not have a card.
40 // See seq_clientmgr.c in the ALSA code for this.
41 // TODO(agoode): Add proper client -> card export from the kernel to avoid
43 const int kMinimumClientIdForCards
= 16;
45 // udev key constants.
47 const char kSoundClass
[] = "sound";
48 const char kIdVendor
[] = "ID_VENDOR";
49 const char kIdVendorEnc
[] = "ID_VENDOR_ENC";
50 const char kIdVendorFromDatabase
[] = "ID_VENDOR_FROM_DATABASE";
51 const char kSysattrVendorName
[] = "vendor_name";
52 const char kIdVendorId
[] = "ID_VENDOR_ID";
53 const char kSysattrVendor
[] = "vendor";
54 const char kIdModelId
[] = "ID_MODEL_ID";
55 const char kSysattrModel
[] = "model";
56 const char kIdBus
[] = "ID_BUS";
57 const char kIdPath
[] = "ID_PATH";
58 const char kUsbInterfaceNum
[] = "ID_USB_INTERFACE_NUM";
59 #endif // defined(USE_UDEV)
62 const char kAlsaHw
[] = "hw";
64 // Constants for the capabilities we search for in inputs and outputs.
65 // See http://www.alsa-project.org/alsa-doc/alsa-lib/seq.html.
66 const unsigned int kRequiredInputPortCaps
=
67 SND_SEQ_PORT_CAP_READ
| SND_SEQ_PORT_CAP_SUBS_READ
;
68 const unsigned int kRequiredOutputPortCaps
=
69 SND_SEQ_PORT_CAP_WRITE
| SND_SEQ_PORT_CAP_SUBS_WRITE
;
71 int AddrToInt(const snd_seq_addr_t
* addr
) {
72 return (addr
->client
<< 8) | addr
->port
;
75 // TODO(agoode): Move this to device/udev_linux.
77 const std::string
UdevDeviceGetPropertyOrSysattr(
78 struct udev_device
* udev_device
,
79 const char* property_key
,
80 const char* sysattr_key
) {
81 // First try the property.
83 device::UdevDeviceGetPropertyValue(udev_device
, property_key
);
85 // If no property, look for sysattrs and walk up the parent devices too.
86 while (value
.empty() && udev_device
) {
87 value
= device::UdevDeviceGetSysattrValue(udev_device
, sysattr_key
);
88 udev_device
= device::udev_device_get_parent(udev_device
);
92 #endif // defined(USE_UDEV)
94 void SetStringIfNonEmpty(base::DictionaryValue
* value
,
95 const std::string
& path
,
96 const std::string
& in_value
) {
97 if (!in_value
.empty())
98 value
->SetString(path
, in_value
);
103 MidiManagerAlsa::MidiManagerAlsa()
109 #if defined(USE_UDEV)
110 udev_(device::udev_new()),
111 #endif // defined(USE_UDEV)
112 send_thread_("MidiSendThread"),
113 event_thread_("MidiEventThread"),
114 event_thread_shutdown_(false) {
115 // Initialize decoder.
116 snd_midi_event_new(0, &decoder_
);
117 snd_midi_event_no_status(decoder_
, 1);
120 MidiManagerAlsa::~MidiManagerAlsa() {
121 // Tell the event thread it will soon be time to shut down. This gives
122 // us assurance the thread will stop in case the SND_SEQ_EVENT_CLIENT_EXIT
125 base::AutoLock
lock(shutdown_lock_
);
126 event_thread_shutdown_
= true;
129 // Stop the send thread.
132 // Close the out client. This will trigger the event thread to stop,
133 // because of SND_SEQ_EVENT_CLIENT_EXIT.
135 snd_seq_close(out_client_
);
137 // Wait for the event thread to stop.
138 event_thread_
.Stop();
140 // Close the in client.
142 snd_seq_close(in_client_
);
145 snd_midi_event_free(decoder_
);
148 void MidiManagerAlsa::StartInitialization() {
149 // TODO(agoode): Move off I/O thread. See http://crbug.com/374341.
151 // Create client handles.
152 int err
= snd_seq_open(&in_client_
, kAlsaHw
, SND_SEQ_OPEN_INPUT
, 0);
154 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err
);
155 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
157 int in_client_id
= snd_seq_client_id(in_client_
);
158 err
= snd_seq_open(&out_client_
, kAlsaHw
, SND_SEQ_OPEN_OUTPUT
, 0);
160 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err
);
161 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
163 out_client_id_
= snd_seq_client_id(out_client_
);
166 err
= snd_seq_set_client_name(in_client_
, "Chrome (input)");
168 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err
);
169 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
171 err
= snd_seq_set_client_name(out_client_
, "Chrome (output)");
173 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err
);
174 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
177 // Create input port.
178 in_port_
= snd_seq_create_simple_port(in_client_
, NULL
,
179 SND_SEQ_PORT_CAP_WRITE
|
180 SND_SEQ_PORT_CAP_NO_EXPORT
,
181 SND_SEQ_PORT_TYPE_MIDI_GENERIC
|
182 SND_SEQ_PORT_TYPE_APPLICATION
);
184 VLOG(1) << "snd_seq_create_simple_port fails: " << snd_strerror(in_port_
);
185 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
188 // Subscribe to the announce port.
189 snd_seq_port_subscribe_t
* subs
;
190 snd_seq_port_subscribe_alloca(&subs
);
191 snd_seq_addr_t announce_sender
;
192 snd_seq_addr_t announce_dest
;
193 announce_sender
.client
= SND_SEQ_CLIENT_SYSTEM
;
194 announce_sender
.port
= SND_SEQ_PORT_SYSTEM_ANNOUNCE
;
195 announce_dest
.client
= in_client_id
;
196 announce_dest
.port
= in_port_
;
197 snd_seq_port_subscribe_set_sender(subs
, &announce_sender
);
198 snd_seq_port_subscribe_set_dest(subs
, &announce_dest
);
199 err
= snd_seq_subscribe_port(in_client_
, subs
);
201 VLOG(1) << "snd_seq_subscribe_port on the announce port fails: "
202 << snd_strerror(err
);
203 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
208 event_thread_
.Start();
209 event_thread_
.message_loop()->PostTask(
211 base::Bind(&MidiManagerAlsa::ScheduleEventLoop
, base::Unretained(this)));
213 CompleteInitialization(MIDI_OK
);
216 void MidiManagerAlsa::DispatchSendMidiData(MidiManagerClient
* client
,
218 const std::vector
<uint8
>& data
,
220 if (out_ports_
.size() <= port_index
)
223 // Not correct right now. http://crbug.com/374341.
224 if (!send_thread_
.IsRunning())
225 send_thread_
.Start();
227 base::TimeDelta delay
;
228 if (timestamp
!= 0.0) {
229 base::TimeTicks time_to_send
=
230 base::TimeTicks() + base::TimeDelta::FromMicroseconds(
231 timestamp
* base::Time::kMicrosecondsPerSecond
);
232 delay
= std::max(time_to_send
- base::TimeTicks::Now(), base::TimeDelta());
235 send_thread_
.message_loop()->PostDelayedTask(
236 FROM_HERE
, base::Bind(&MidiManagerAlsa::SendMidiData
,
237 base::Unretained(this), port_index
, data
),
241 send_thread_
.message_loop()->PostTask(
242 FROM_HERE
, base::Bind(&MidiManagerClient::AccumulateMidiBytesSent
,
243 base::Unretained(client
), data
.size()));
246 MidiManagerAlsa::AlsaCard::AlsaCard(const MidiManagerAlsa
* outer
,
247 const std::string
& alsa_name
,
248 const std::string
& alsa_longname
,
249 const std::string
& alsa_driver
,
252 : alsa_name_(alsa_name
),
253 alsa_longname_(alsa_longname
),
254 alsa_driver_(alsa_driver
),
255 midi_count_(midi_count
) {
256 // Get udev properties if available.
258 std::string vendor_from_database
;
260 #if defined(USE_UDEV)
261 const std::string sysname
= base::StringPrintf("card%i", card_index
);
262 device::ScopedUdevDevicePtr
udev_device(
263 device::udev_device_new_from_subsystem_sysname(
264 outer
->udev_
.get(), kSoundClass
, sysname
.c_str()));
266 // TODO(agoode): Move this to a new utility class in device/udev_linux?
268 // Try to get the vendor string. Sometimes it is encoded.
269 vendor
= device::UdevDecodeString(
270 device::UdevDeviceGetPropertyValue(udev_device
.get(), kIdVendorEnc
));
271 // Sometimes it is not encoded.
273 vendor
= UdevDeviceGetPropertyOrSysattr(udev_device
.get(), kIdVendor
,
275 // Also get the vendor string from the hardware database.
276 vendor_from_database
= device::UdevDeviceGetPropertyValue(
277 udev_device
.get(), kIdVendorFromDatabase
);
279 // Get the device path.
280 path_
= device::UdevDeviceGetPropertyValue(udev_device
.get(), kIdPath
);
283 bus_
= device::UdevDeviceGetPropertyValue(udev_device
.get(), kIdBus
);
285 // Get the vendor id, by either property or sysattr.
286 vendor_id_
= UdevDeviceGetPropertyOrSysattr(udev_device
.get(), kIdVendorId
,
289 // Get the model id, by either property or sysattr.
290 model_id_
= UdevDeviceGetPropertyOrSysattr(udev_device
.get(), kIdModelId
,
293 // Get the usb interface number.
295 device::UdevDeviceGetPropertyValue(udev_device
.get(), kUsbInterfaceNum
);
296 #endif // defined(USE_UDEV)
298 manufacturer_
= ExtractManufacturerString(
299 vendor
, vendor_id_
, vendor_from_database
, alsa_name
, alsa_longname
);
302 MidiManagerAlsa::AlsaCard::~AlsaCard() {
305 const std::string
MidiManagerAlsa::AlsaCard::alsa_name() const {
309 const std::string
MidiManagerAlsa::AlsaCard::alsa_longname() const {
310 return alsa_longname_
;
313 const std::string
MidiManagerAlsa::AlsaCard::manufacturer() const {
314 return manufacturer_
;
317 const std::string
MidiManagerAlsa::AlsaCard::alsa_driver() const {
321 const std::string
MidiManagerAlsa::AlsaCard::path() const {
325 const std::string
MidiManagerAlsa::AlsaCard::bus() const {
329 const std::string
MidiManagerAlsa::AlsaCard::vendor_id() const {
333 const std::string
MidiManagerAlsa::AlsaCard::id() const {
334 std::string id
= vendor_id_
;
335 if (!model_id_
.empty())
336 id
+= ":" + model_id_
;
337 if (!usb_interface_num_
.empty())
338 id
+= ":" + usb_interface_num_
;
342 const int MidiManagerAlsa::AlsaCard::midi_count() const {
347 std::string
MidiManagerAlsa::AlsaCard::ExtractManufacturerString(
348 const std::string
& udev_id_vendor
,
349 const std::string
& udev_id_vendor_id
,
350 const std::string
& udev_id_vendor_from_database
,
351 const std::string
& alsa_name
,
352 const std::string
& alsa_longname
) {
353 // Let's try to determine the manufacturer. Here is the ordered preference
355 // 1. Vendor name from the hardware device string, from udev properties
357 // 2. Vendor name from the udev database (property ID_VENDOR_FROM_DATABASE).
358 // 3. Heuristic from ALSA.
360 // Is the vendor string present and not just the vendor hex id?
361 if (!udev_id_vendor
.empty() && (udev_id_vendor
!= udev_id_vendor_id
)) {
362 return udev_id_vendor
;
365 // Is there a vendor string in the hardware database?
366 if (!udev_id_vendor_from_database
.empty()) {
367 return udev_id_vendor_from_database
;
370 // Ok, udev gave us nothing useful, or was unavailable. So try a heuristic.
371 // We assume that card longname is in the format of
372 // "<manufacturer> <name> at <bus>". Otherwise, we give up to detect
373 // a manufacturer name here.
374 size_t at_index
= alsa_longname
.rfind(" at ");
375 if (at_index
&& at_index
!= std::string::npos
) {
376 size_t name_index
= alsa_longname
.rfind(alsa_name
, at_index
- 1);
377 if (name_index
&& name_index
!= std::string::npos
)
378 return alsa_longname
.substr(0, name_index
- 1);
385 MidiManagerAlsa::AlsaPortMetadata::AlsaPortMetadata(
386 const std::string
& path
,
387 const std::string
& bus
,
388 const std::string
& id
,
389 const snd_seq_addr_t
* address
,
390 const std::string
& client_name
,
391 const std::string
& port_name
,
392 const std::string
& card_name
,
393 const std::string
& card_longname
,
398 client_addr_(address
->client
),
399 port_addr_(address
->port
),
400 client_name_(client_name
),
401 port_name_(port_name
),
402 card_name_(card_name
),
403 card_longname_(card_longname
),
407 MidiManagerAlsa::AlsaPortMetadata::~AlsaPortMetadata() {
410 scoped_ptr
<base::Value
> MidiManagerAlsa::AlsaPortMetadata::Value() const {
411 scoped_ptr
<base::DictionaryValue
> value(new base::DictionaryValue
);
412 SetStringIfNonEmpty(value
.get(), "path", path_
);
413 SetStringIfNonEmpty(value
.get(), "bus", bus_
);
414 SetStringIfNonEmpty(value
.get(), "id", id_
);
415 value
->SetInteger("clientAddr", client_addr_
);
416 value
->SetInteger("portAddr", port_addr_
);
417 SetStringIfNonEmpty(value
.get(), "clientName", client_name_
);
418 SetStringIfNonEmpty(value
.get(), "portName", port_name_
);
419 SetStringIfNonEmpty(value
.get(), "cardName", card_name_
);
420 SetStringIfNonEmpty(value
.get(), "cardLongname", card_longname_
);
431 SetStringIfNonEmpty(value
.get(), "type", type
);
436 std::string
MidiManagerAlsa::AlsaPortMetadata::JSONValue() const {
438 JSONStringValueSerializer
serializer(&json
);
439 serializer
.Serialize(*Value().get());
443 // TODO(agoode): Do not use SHA256 here. Instead store a persistent
444 // mapping and just use a UUID or other random string.
445 // http://crbug.com/465320
446 std::string
MidiManagerAlsa::AlsaPortMetadata::OpaqueKey() const {
447 uint8 hash
[crypto::kSHA256Length
];
448 crypto::SHA256HashString(JSONValue(), &hash
, sizeof(hash
));
449 return base::HexEncode(&hash
, sizeof(hash
));
452 // TODO(agoode): Add a client->card/rawmidi mapping to the kernel to avoid
453 // needing to probe in this way.
454 ScopedVector
<MidiManagerAlsa::AlsaCard
> MidiManagerAlsa::AllMidiCards() {
455 ScopedVector
<AlsaCard
> devices
;
456 snd_ctl_card_info_t
* card
;
457 snd_hwdep_info_t
* hwdep
;
458 snd_ctl_card_info_alloca(&card
);
459 snd_hwdep_info_alloca(&hwdep
);
460 for (int card_index
= -1; !snd_card_next(&card_index
) && card_index
>= 0;) {
462 const std::string id
= base::StringPrintf("hw:CARD=%i", card_index
);
464 int err
= snd_ctl_open(&handle
, id
.c_str(), 0);
466 VLOG(1) << "snd_ctl_open fails: " << snd_strerror(err
);
469 err
= snd_ctl_card_info(handle
, card
);
471 VLOG(1) << "snd_ctl_card_info fails: " << snd_strerror(err
);
472 snd_ctl_close(handle
);
475 std::string name
= snd_ctl_card_info_get_name(card
);
476 std::string longname
= snd_ctl_card_info_get_longname(card
);
477 std::string driver
= snd_ctl_card_info_get_driver(card
);
479 // Count rawmidi devices (not subdevices).
480 for (int device
= -1;
481 !snd_ctl_rawmidi_next_device(handle
, &device
) && device
>= 0;)
484 // Count any hwdep synths that become MIDI devices outside of rawmidi.
487 // Any kernel driver can create an ALSA client (visible to us).
488 // With modern hardware, only rawmidi devices do this. Kernel
489 // drivers create rawmidi devices and the rawmidi subsystem makes
490 // the seq clients. But the OPL3 driver is special, it does not
491 // make a rawmidi device but a seq client directly. (This is the
492 // only one to worry about in the kernel code, as of 2015-03-23.)
494 // OPL3 is very old (but still possible to get in new
495 // hardware). It is unlikely that new drivers would not use
496 // rawmidi and defeat our heuristic.
498 // Longer term, support should be added in the kernel to expose a
499 // direct link from card->client (or client->card) so that all
500 // these heuristics will be obsolete. Once that is there, we can
501 // assume our old heuristics will work on old kernels and the new
502 // robust code will be used on new. Then we will not need to worry
503 // about changes to kernel internals breaking our code.
504 // See the TODO above at kMinimumClientIdForCards.
505 for (int device
= -1;
506 !snd_ctl_hwdep_next_device(handle
, &device
) && device
>= 0;) {
507 snd_ctl_hwdep_info(handle
, hwdep
);
508 snd_hwdep_iface_t iface
= snd_hwdep_info_get_iface(hwdep
);
509 if (iface
== SND_HWDEP_IFACE_OPL2
|| iface
== SND_HWDEP_IFACE_OPL3
||
510 iface
== SND_HWDEP_IFACE_OPL4
)
516 new AlsaCard(this, name
, longname
, driver
, card_index
, midi_count
));
517 snd_ctl_close(handle
);
520 return devices
.Pass();
523 void MidiManagerAlsa::EnumeratePorts() {
524 ScopedVector
<AlsaCard
> cards
= AllMidiCards();
525 std::vector
<const AlsaCard
*> devices
;
526 for (const auto* card
: cards
) {
527 // Insert 1 AlsaCard per number of MIDI devices.
528 for (int n
= 0; n
< card
->midi_count(); ++n
)
529 devices
.push_back(card
);
532 snd_seq_port_subscribe_t
* subs
;
533 snd_seq_port_subscribe_alloca(&subs
);
535 int in_client_id
= snd_seq_client_id(in_client_
);
537 // Enumerate all clients.
538 snd_seq_client_info_t
* client_info
;
539 snd_seq_client_info_alloca(&client_info
);
540 snd_seq_port_info_t
* port_info
;
541 snd_seq_port_info_alloca(&port_info
);
543 // Enumerate clients.
544 snd_seq_client_info_set_client(client_info
, -1);
545 uint32 current_input
= 0;
546 unsigned int current_device
= 0;
547 while (!snd_seq_query_next_client(in_client_
, client_info
)) {
548 int client_id
= snd_seq_client_info_get_client(client_info
);
549 if ((client_id
== in_client_id
) || (client_id
== out_client_id_
)) {
550 // Skip our own clients.
554 // Get client metadata.
555 const std::string client_name
= snd_seq_client_info_get_name(client_info
);
556 snd_seq_port_info_set_client(port_info
, client_id
);
557 snd_seq_port_info_set_port(port_info
, -1);
559 std::string manufacturer
;
563 std::string vendor_id
;
565 std::string card_name
;
566 std::string card_longname
;
568 // Join kernel clients against the list of AlsaCards.
569 // In the current ALSA kernel implementation, kernel clients match the
570 // kernel devices in the same order, for clients with client_id over
571 // kMinimumClientIdForCards.
572 if ((snd_seq_client_info_get_type(client_info
) == SND_SEQ_KERNEL_CLIENT
) &&
573 (current_device
< devices
.size()) &&
574 (client_id
>= kMinimumClientIdForCards
)) {
575 const AlsaCard
* device
= devices
[current_device
];
576 manufacturer
= device
->manufacturer();
577 driver
= device
->alsa_driver();
578 path
= device
->path();
580 vendor_id
= device
->vendor_id();
582 card_name
= device
->alsa_name();
583 card_longname
= device
->alsa_longname();
587 while (!snd_seq_query_next_port(in_client_
, port_info
)) {
588 unsigned int port_type
= snd_seq_port_info_get_type(port_info
);
589 if (port_type
& SND_SEQ_PORT_TYPE_MIDI_GENERIC
) {
590 const snd_seq_addr_t
* addr
= snd_seq_port_info_get_addr(port_info
);
591 const std::string name
= snd_seq_port_info_get_name(port_info
);
593 if (!driver
.empty()) {
594 version
= driver
+ " / ";
596 version
+= base::StringPrintf("ALSA library version %d.%d.%d",
600 unsigned int caps
= snd_seq_port_info_get_capability(port_info
);
601 if ((caps
& kRequiredInputPortCaps
) == kRequiredInputPortCaps
) {
602 // Subscribe to this port.
603 const snd_seq_addr_t
* sender
= snd_seq_port_info_get_addr(port_info
);
605 dest
.client
= snd_seq_client_id(in_client_
);
606 dest
.port
= in_port_
;
607 snd_seq_port_subscribe_set_sender(subs
, sender
);
608 snd_seq_port_subscribe_set_dest(subs
, &dest
);
609 int err
= snd_seq_subscribe_port(in_client_
, subs
);
611 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err
);
613 source_map_
[AddrToInt(sender
)] = current_input
++;
614 const AlsaPortMetadata
metadata(path
, bus
, id
, addr
, client_name
,
615 name
, card_name
, card_longname
,
616 AlsaPortMetadata::Type::kInput
);
617 const std::string id
= metadata
.OpaqueKey();
618 AddInputPort(MidiPortInfo(id
.c_str(), manufacturer
, name
, version
,
622 if ((caps
& kRequiredOutputPortCaps
) == kRequiredOutputPortCaps
) {
623 // Create a port for us to send on.
625 snd_seq_create_simple_port(out_client_
, NULL
,
626 SND_SEQ_PORT_CAP_READ
|
627 SND_SEQ_PORT_CAP_NO_EXPORT
,
628 SND_SEQ_PORT_TYPE_MIDI_GENERIC
|
629 SND_SEQ_PORT_TYPE_APPLICATION
);
631 VLOG(1) << "snd_seq_create_simple_port fails: "
632 << snd_strerror(out_port
);
633 // Skip this output port for now.
637 // Activate port subscription.
638 snd_seq_addr_t sender
;
639 const snd_seq_addr_t
* dest
= snd_seq_port_info_get_addr(port_info
);
640 sender
.client
= snd_seq_client_id(out_client_
);
641 sender
.port
= out_port
;
642 snd_seq_port_subscribe_set_sender(subs
, &sender
);
643 snd_seq_port_subscribe_set_dest(subs
, dest
);
644 int err
= snd_seq_subscribe_port(out_client_
, subs
);
646 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err
);
647 snd_seq_delete_simple_port(out_client_
, out_port
);
649 out_ports_
.push_back(out_port
);
650 const AlsaPortMetadata
metadata(path
, bus
, id
, addr
, client_name
,
651 name
, card_name
, card_longname
,
652 AlsaPortMetadata::Type::kOutput
);
653 const std::string id
= metadata
.OpaqueKey();
654 AddOutputPort(MidiPortInfo(id
.c_str(), manufacturer
, name
, version
,
663 void MidiManagerAlsa::SendMidiData(uint32 port_index
,
664 const std::vector
<uint8
>& data
) {
665 DCHECK(send_thread_
.message_loop_proxy()->BelongsToCurrentThread());
667 snd_midi_event_t
* encoder
;
668 snd_midi_event_new(kSendBufferSize
, &encoder
);
669 for (unsigned int i
= 0; i
< data
.size(); i
++) {
670 snd_seq_event_t event
;
671 int result
= snd_midi_event_encode_byte(encoder
, data
[i
], &event
);
673 // Full event, send it.
674 snd_seq_ev_set_source(&event
, out_ports_
[port_index
]);
675 snd_seq_ev_set_subs(&event
);
676 snd_seq_ev_set_direct(&event
);
677 snd_seq_event_output_direct(out_client_
, &event
);
680 snd_midi_event_free(encoder
);
683 void MidiManagerAlsa::ScheduleEventLoop() {
684 event_thread_
.message_loop()->PostTask(
686 base::Bind(&MidiManagerAlsa::EventLoop
, base::Unretained(this)));
689 void MidiManagerAlsa::EventLoop() {
690 // Read available incoming MIDI data.
691 snd_seq_event_t
* event
;
692 int err
= snd_seq_event_input(in_client_
, &event
);
693 double timestamp
= (base::TimeTicks::Now() - base::TimeTicks()).InSecondsF();
696 if (err
== -ENOSPC
) {
697 VLOG(1) << "snd_seq_event_input detected buffer overrun";
698 // We've lost events: check another way to see if we need to shut down.
699 base::AutoLock
lock(shutdown_lock_
);
700 if (!event_thread_shutdown_
)
703 } else if (err
< 0) {
704 VLOG(1) << "snd_seq_event_input fails: " << snd_strerror(err
);
705 // TODO(agoode): Use RecordAction() or similar to log this.
709 // Handle announce events.
710 if (event
->source
.client
== SND_SEQ_CLIENT_SYSTEM
&&
711 event
->source
.port
== SND_SEQ_PORT_SYSTEM_ANNOUNCE
) {
712 switch (event
->type
) {
713 case SND_SEQ_EVENT_CLIENT_START
:
714 // TODO(agoode): rescan hardware devices.
717 case SND_SEQ_EVENT_PORT_START
:
718 // TODO(agoode): add port.
721 case SND_SEQ_EVENT_CLIENT_EXIT
:
722 // Check for disconnection of our "out" client. This means "shut down".
723 if (event
->data
.addr
.client
== out_client_id_
)
726 // TODO(agoode): remove all ports for a client.
729 case SND_SEQ_EVENT_PORT_EXIT
:
730 // TODO(agoode): remove port.
734 ProcessSingleEvent(event
, timestamp
);
741 void MidiManagerAlsa::ProcessSingleEvent(snd_seq_event_t
* event
,
743 auto source_it
= source_map_
.find(AddrToInt(&event
->source
));
744 if (source_it
!= source_map_
.end()) {
745 uint32 source
= source_it
->second
;
746 if (event
->type
== SND_SEQ_EVENT_SYSEX
) {
747 // Special! Variable-length sysex.
748 ReceiveMidiData(source
, static_cast<const uint8
*>(event
->data
.ext
.ptr
),
749 event
->data
.ext
.len
, timestamp
);
751 // Otherwise, decode this and send that on.
752 unsigned char buf
[12];
753 long count
= snd_midi_event_decode(decoder_
, buf
, sizeof(buf
), event
);
755 if (count
!= -ENOENT
) {
756 // ENOENT means that it's not a MIDI message, which is not an
757 // error, but other negative values are errors for us.
758 VLOG(1) << "snd_midi_event_decoder fails " << snd_strerror(count
);
759 // TODO(agoode): Record this failure.
762 ReceiveMidiData(source
, buf
, count
, timestamp
);
768 MidiManager
* MidiManager::Create() {
769 return new MidiManagerAlsa();