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/logging.h"
14 #include "base/memory/ref_counted.h"
15 #include "base/memory/scoped_vector.h"
16 #include "base/message_loop/message_loop.h"
17 #include "base/posix/eintr_wrapper.h"
18 #include "base/strings/stringprintf.h"
19 #include "base/threading/thread.h"
20 #include "base/time/time.h"
21 #include "media/midi/midi_port_info.h"
27 // Per-output buffer. This can be smaller, but then large sysex messages
28 // will be (harmlessly) split across multiple seq events. This should
29 // not have any real practical effect, except perhaps to slightly reorder
30 // realtime messages with respect to sysex.
31 const size_t kSendBufferSize
= 256;
33 // Constants for the capabilities we search for in inputs and outputs.
34 // See http://www.alsa-project.org/alsa-doc/alsa-lib/seq.html.
35 const unsigned int kRequiredInputPortCaps
=
36 SND_SEQ_PORT_CAP_READ
| SND_SEQ_PORT_CAP_SUBS_READ
;
37 const unsigned int kRequiredOutputPortCaps
=
38 SND_SEQ_PORT_CAP_WRITE
| SND_SEQ_PORT_CAP_SUBS_WRITE
;
40 int AddrToInt(const snd_seq_addr_t
* addr
) {
41 return (addr
->client
<< 8) | addr
->port
;
46 MidiManagerAlsa::MidiManagerAlsa()
53 udev_(device::udev_new()),
54 #endif // defined(USE_UDEV)
55 send_thread_("MidiSendThread"),
56 event_thread_("MidiEventThread"),
57 event_thread_shutdown_(false) {
58 // Initialize decoder.
59 snd_midi_event_new(0, &decoder_
);
60 snd_midi_event_no_status(decoder_
, 1);
63 void MidiManagerAlsa::StartInitialization() {
64 // TODO(agoode): Move off I/O thread. See http://crbug.com/374341.
66 // Create client handles.
67 int err
= snd_seq_open(&in_client_
, "hw", SND_SEQ_OPEN_INPUT
, 0);
69 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err
);
70 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
72 int in_client_id
= snd_seq_client_id(in_client_
);
73 err
= snd_seq_open(&out_client_
, "hw", SND_SEQ_OPEN_OUTPUT
, 0);
75 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err
);
76 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
78 out_client_id_
= snd_seq_client_id(out_client_
);
81 err
= snd_seq_set_client_name(in_client_
, "Chrome (input)");
83 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err
);
84 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
86 err
= snd_seq_set_client_name(out_client_
, "Chrome (output)");
88 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err
);
89 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
93 in_port_
= snd_seq_create_simple_port(in_client_
, NULL
,
94 SND_SEQ_PORT_CAP_WRITE
|
95 SND_SEQ_PORT_CAP_NO_EXPORT
,
96 SND_SEQ_PORT_TYPE_MIDI_GENERIC
|
97 SND_SEQ_PORT_TYPE_APPLICATION
);
99 VLOG(1) << "snd_seq_create_simple_port fails: " << snd_strerror(in_port_
);
100 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
103 // Subscribe to the announce port.
104 snd_seq_port_subscribe_t
* subs
;
105 snd_seq_port_subscribe_alloca(&subs
);
106 snd_seq_addr_t announce_sender
;
107 snd_seq_addr_t announce_dest
;
108 announce_sender
.client
= SND_SEQ_CLIENT_SYSTEM
;
109 announce_sender
.port
= SND_SEQ_PORT_SYSTEM_ANNOUNCE
;
110 announce_dest
.client
= in_client_id
;
111 announce_dest
.port
= in_port_
;
112 snd_seq_port_subscribe_set_sender(subs
, &announce_sender
);
113 snd_seq_port_subscribe_set_dest(subs
, &announce_dest
);
114 err
= snd_seq_subscribe_port(in_client_
, subs
);
116 VLOG(1) << "snd_seq_subscribe_port on the announce port fails: "
117 << snd_strerror(err
);
118 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
121 // Extract the list of manufacturers for the hardware MIDI
122 // devices. This won't work for all devices. It is also brittle until
123 // hotplug is implemented. (See http://crbug.com/431489.)
124 ScopedVector
<CardInfo
> cards
;
125 snd_ctl_card_info_t
* card
;
126 snd_rawmidi_info_t
* midi_out
;
127 snd_rawmidi_info_t
* midi_in
;
128 snd_ctl_card_info_alloca(&card
);
129 snd_rawmidi_info_alloca(&midi_out
);
130 snd_rawmidi_info_alloca(&midi_in
);
131 for (int card_index
= -1; !snd_card_next(&card_index
) && card_index
>= 0; ) {
132 const std::string id
= base::StringPrintf("hw:CARD=%i", card_index
);
134 int err
= snd_ctl_open(&handle
, id
.c_str(), 0);
136 VLOG(1) << "snd_ctl_open fails: " << snd_strerror(err
);
139 err
= snd_ctl_card_info(handle
, card
);
141 VLOG(1) << "snd_ctl_card_info fails: " << snd_strerror(err
);
142 snd_ctl_close(handle
);
145 // Enumerate any rawmidi devices (not subdevices) and extract CardInfo.
146 for (int device
= -1;
147 !snd_ctl_rawmidi_next_device(handle
, &device
) && device
>= 0; ) {
150 snd_rawmidi_info_set_device(midi_out
, device
);
151 snd_rawmidi_info_set_subdevice(midi_out
, 0);
152 snd_rawmidi_info_set_stream(midi_out
, SND_RAWMIDI_STREAM_OUTPUT
);
153 output
= snd_ctl_rawmidi_info(handle
, midi_out
) == 0;
154 snd_rawmidi_info_set_device(midi_in
, device
);
155 snd_rawmidi_info_set_subdevice(midi_in
, 0);
156 snd_rawmidi_info_set_stream(midi_in
, SND_RAWMIDI_STREAM_INPUT
);
157 input
= snd_ctl_rawmidi_info(handle
, midi_in
) == 0;
158 if (!output
&& !input
)
161 // Compute and save Alsa and udev properties.
162 snd_rawmidi_info_t
* midi
= midi_out
? midi_out
: midi_in
;
163 cards
.push_back(new CardInfo(
165 snd_rawmidi_info_get_name(midi
),
166 snd_ctl_card_info_get_longname(card
),
167 snd_ctl_card_info_get_driver(card
),
170 snd_ctl_close(handle
);
173 // Enumerate all ports in all clients.
174 snd_seq_client_info_t
* client_info
;
175 snd_seq_client_info_alloca(&client_info
);
176 snd_seq_port_info_t
* port_info
;
177 snd_seq_port_info_alloca(&port_info
);
179 snd_seq_client_info_set_client(client_info
, -1);
180 // Enumerate clients.
181 uint32 current_input
= 0;
182 unsigned int current_card
= 0;
183 while (!snd_seq_query_next_client(in_client_
, client_info
)) {
184 int client_id
= snd_seq_client_info_get_client(client_info
);
185 if ((client_id
== in_client_id
) || (client_id
== out_client_id_
)) {
186 // Skip our own clients.
189 const std::string client_name
= snd_seq_client_info_get_name(client_info
);
190 snd_seq_port_info_set_client(port_info
, client_id
);
191 snd_seq_port_info_set_port(port_info
, -1);
193 std::string manufacturer
;
195 // In the current Alsa kernel implementation, hardware clients match the
196 // cards in the same order.
197 if ((snd_seq_client_info_get_type(client_info
) == SND_SEQ_KERNEL_CLIENT
) &&
198 (current_card
< cards
.size())) {
199 const CardInfo
* info
= cards
[current_card
];
200 if (info
->alsa_name() == client_name
) {
201 manufacturer
= info
->manufacturer();
202 driver
= info
->alsa_driver();
207 while (!snd_seq_query_next_port(in_client_
, port_info
)) {
208 unsigned int port_type
= snd_seq_port_info_get_type(port_info
);
209 if (port_type
& SND_SEQ_PORT_TYPE_MIDI_GENERIC
) {
210 const snd_seq_addr_t
* addr
= snd_seq_port_info_get_addr(port_info
);
211 const std::string name
= snd_seq_port_info_get_name(port_info
);
212 const std::string id
= base::StringPrintf("%d:%d %s",
217 if (!driver
.empty()) {
218 version
= driver
+ " / ";
220 version
+= base::StringPrintf("ALSA library version %d.%d.%d",
224 unsigned int caps
= snd_seq_port_info_get_capability(port_info
);
225 if ((caps
& kRequiredInputPortCaps
) == kRequiredInputPortCaps
) {
226 // Subscribe to this port.
227 const snd_seq_addr_t
* sender
= snd_seq_port_info_get_addr(port_info
);
229 dest
.client
= snd_seq_client_id(in_client_
);
230 dest
.port
= in_port_
;
231 snd_seq_port_subscribe_set_sender(subs
, sender
);
232 snd_seq_port_subscribe_set_dest(subs
, &dest
);
233 err
= snd_seq_subscribe_port(in_client_
, subs
);
235 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err
);
237 source_map_
[AddrToInt(sender
)] = current_input
++;
238 AddInputPort(MidiPortInfo(
239 id
, manufacturer
, name
, version
, MIDI_PORT_OPENED
));
242 if ((caps
& kRequiredOutputPortCaps
) == kRequiredOutputPortCaps
) {
243 // Create a port for us to send on.
245 snd_seq_create_simple_port(out_client_
, NULL
,
246 SND_SEQ_PORT_CAP_READ
|
247 SND_SEQ_PORT_CAP_NO_EXPORT
,
248 SND_SEQ_PORT_TYPE_MIDI_GENERIC
|
249 SND_SEQ_PORT_TYPE_APPLICATION
);
251 VLOG(1) << "snd_seq_create_simple_port fails: "
252 << snd_strerror(out_port
);
253 // Skip this output port for now.
257 // Activate port subscription.
258 snd_seq_addr_t sender
;
259 const snd_seq_addr_t
* dest
= snd_seq_port_info_get_addr(port_info
);
260 sender
.client
= snd_seq_client_id(out_client_
);
261 sender
.port
= out_port
;
262 snd_seq_port_subscribe_set_sender(subs
, &sender
);
263 snd_seq_port_subscribe_set_dest(subs
, dest
);
264 err
= snd_seq_subscribe_port(out_client_
, subs
);
266 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err
);
267 snd_seq_delete_simple_port(out_client_
, out_port
);
269 snd_midi_event_t
* encoder
;
270 snd_midi_event_new(kSendBufferSize
, &encoder
);
271 encoders_
.push_back(encoder
);
272 out_ports_
.push_back(out_port
);
273 AddOutputPort(MidiPortInfo(
274 id
, manufacturer
, name
, version
, MIDI_PORT_OPENED
));
281 event_thread_
.Start();
282 event_thread_
.message_loop()->PostTask(
284 base::Bind(&MidiManagerAlsa::EventReset
, base::Unretained(this)));
286 CompleteInitialization(MIDI_OK
);
289 MidiManagerAlsa::~MidiManagerAlsa() {
290 // Tell the event thread it will soon be time to shut down. This gives
291 // us assurance the thread will stop in case the SND_SEQ_EVENT_CLIENT_EXIT
294 base::AutoLock
lock(shutdown_lock_
);
295 event_thread_shutdown_
= true;
298 // Stop the send thread.
301 // Close the out client. This will trigger the event thread to stop,
302 // because of SND_SEQ_EVENT_CLIENT_EXIT.
304 snd_seq_close(out_client_
);
306 // Wait for the event thread to stop.
307 event_thread_
.Stop();
309 // Close the in client.
311 snd_seq_close(in_client_
);
314 snd_midi_event_free(decoder_
);
316 // Free the encoders.
317 for (EncoderList::iterator i
= encoders_
.begin(); i
!= encoders_
.end(); ++i
)
318 snd_midi_event_free(*i
);
321 MidiManagerAlsa::CardInfo::CardInfo(
322 const MidiManagerAlsa
* outer
,
323 const std::string
& alsa_name
, const std::string
& alsa_longname
,
324 const std::string
& alsa_driver
, int card_index
)
325 : alsa_name_(alsa_name
), alsa_driver_(alsa_driver
) {
326 // Get udev properties if available.
327 std::string udev_id_vendor
;
328 std::string udev_id_vendor_id
;
329 std::string udev_id_vendor_from_database
;
331 #if defined(USE_UDEV)
332 const std::string sysname
= base::StringPrintf("card%i", card_index
);
333 device::ScopedUdevDevicePtr
udev_device(
334 device::udev_device_new_from_subsystem_sysname(
335 outer
->udev_
.get(), "sound", sysname
.c_str()));
336 udev_id_vendor
= device::UdevDecodeString(device::UdevDeviceGetPropertyValue(
337 udev_device
.get(), "ID_VENDOR_ENC"));
338 udev_id_vendor_id
= device::UdevDeviceGetPropertyValue(
339 udev_device
.get(), "ID_VENDOR_ID");
340 udev_id_vendor_from_database
= device::UdevDeviceGetPropertyValue(
341 udev_device
.get(), "ID_VENDOR_FROM_DATABASE");
343 udev_id_path_
= device::UdevDeviceGetPropertyValue(
344 udev_device
.get(), "ID_PATH");
345 udev_id_id_
= device::UdevDeviceGetPropertyValue(
346 udev_device
.get(), "ID_ID");
347 #endif // defined(USE_UDEV)
349 manufacturer_
= ExtractManufacturerString(
350 udev_id_vendor
, udev_id_vendor_id
, udev_id_vendor_from_database
,
351 alsa_name
, alsa_longname
);
354 MidiManagerAlsa::CardInfo::~CardInfo() {
357 const std::string
MidiManagerAlsa::CardInfo::alsa_name() const {
361 const std::string
MidiManagerAlsa::CardInfo::manufacturer() const {
362 return manufacturer_
;
365 const std::string
MidiManagerAlsa::CardInfo::alsa_driver() const {
369 const std::string
MidiManagerAlsa::CardInfo::udev_id_path() const {
370 return udev_id_path_
;
373 const std::string
MidiManagerAlsa::CardInfo::udev_id_id() const {
377 void MidiManagerAlsa::SendMidiData(uint32 port_index
,
378 const std::vector
<uint8
>& data
) {
379 DCHECK(send_thread_
.message_loop_proxy()->BelongsToCurrentThread());
381 snd_midi_event_t
* encoder
= encoders_
[port_index
];
382 for (unsigned int i
= 0; i
< data
.size(); i
++) {
383 snd_seq_event_t event
;
384 int result
= snd_midi_event_encode_byte(encoder
, data
[i
], &event
);
386 // Full event, send it.
387 snd_seq_ev_set_source(&event
, out_ports_
[port_index
]);
388 snd_seq_ev_set_subs(&event
);
389 snd_seq_ev_set_direct(&event
);
390 snd_seq_event_output_direct(out_client_
, &event
);
395 void MidiManagerAlsa::DispatchSendMidiData(MidiManagerClient
* client
,
397 const std::vector
<uint8
>& data
,
399 if (out_ports_
.size() <= port_index
)
402 // Not correct right now. http://crbug.com/374341.
403 if (!send_thread_
.IsRunning())
404 send_thread_
.Start();
406 base::TimeDelta delay
;
407 if (timestamp
!= 0.0) {
408 base::TimeTicks time_to_send
=
409 base::TimeTicks() + base::TimeDelta::FromMicroseconds(
410 timestamp
* base::Time::kMicrosecondsPerSecond
);
411 delay
= std::max(time_to_send
- base::TimeTicks::Now(), base::TimeDelta());
414 send_thread_
.message_loop()->PostDelayedTask(
416 base::Bind(&MidiManagerAlsa::SendMidiData
, base::Unretained(this),
417 port_index
, data
), delay
);
420 send_thread_
.message_loop()->PostTask(
422 base::Bind(&MidiManagerClient::AccumulateMidiBytesSent
,
423 base::Unretained(client
), data
.size()));
426 void MidiManagerAlsa::EventReset() {
427 event_thread_
.message_loop()->PostTask(
429 base::Bind(&MidiManagerAlsa::EventLoop
, base::Unretained(this)));
432 void MidiManagerAlsa::EventLoop() {
433 // Read available incoming MIDI data.
434 snd_seq_event_t
* event
;
435 int err
= snd_seq_event_input(in_client_
, &event
);
436 double timestamp
= (base::TimeTicks::Now() - base::TimeTicks()).InSecondsF();
437 if (err
== -ENOSPC
) {
438 VLOG(1) << "snd_seq_event_input detected buffer overrun";
440 // We've lost events: check another way to see if we need to shut down.
441 base::AutoLock
lock(shutdown_lock_
);
442 if (event_thread_shutdown_
) {
445 } else if (err
< 0) {
446 VLOG(1) << "snd_seq_event_input fails: " << snd_strerror(err
);
449 // Check for disconnection of out client. This means "shut down".
450 if (event
->source
.client
== SND_SEQ_CLIENT_SYSTEM
&&
451 event
->source
.port
== SND_SEQ_PORT_SYSTEM_ANNOUNCE
&&
452 event
->type
== SND_SEQ_EVENT_CLIENT_EXIT
&&
453 event
->data
.addr
.client
== out_client_id_
) {
457 std::map
<int, uint32
>::iterator source_it
=
458 source_map_
.find(AddrToInt(&event
->source
));
459 if (source_it
!= source_map_
.end()) {
460 uint32 source
= source_it
->second
;
461 if (event
->type
== SND_SEQ_EVENT_SYSEX
) {
462 // Special! Variable-length sysex.
463 ReceiveMidiData(source
, static_cast<const uint8
*>(event
->data
.ext
.ptr
),
467 // Otherwise, decode this and send that on.
468 unsigned char buf
[12];
469 long count
= snd_midi_event_decode(decoder_
, buf
, sizeof(buf
), event
);
471 if (count
!= -ENOENT
) {
472 // ENOENT means that it's not a MIDI message, which is not an
473 // error, but other negative values are errors for us.
474 VLOG(1) << "snd_midi_event_decoder fails " << snd_strerror(count
);
477 ReceiveMidiData(source
, buf
, count
, timestamp
);
484 event_thread_
.message_loop()->PostTask(
486 base::Bind(&MidiManagerAlsa::EventLoop
, base::Unretained(this)));
490 std::string
MidiManagerAlsa::CardInfo::ExtractManufacturerString(
491 const std::string
& udev_id_vendor
,
492 const std::string
& udev_id_vendor_id
,
493 const std::string
& udev_id_vendor_from_database
,
494 const std::string
& alsa_name
,
495 const std::string
& alsa_longname
) {
496 // Let's try to determine the manufacturer. Here is the ordered preference
498 // 1. Vendor name from the USB device iManufacturer string, from
499 // the udev property ID_VENDOR_ENC.
500 // 2. Vendor name from the udev database (property ID_VENDOR_FROM_DATABASE).
501 // 3. Heuristic from ALSA.
503 // Is the vendor string not just the USB vendor hex id?
504 if (udev_id_vendor
!= udev_id_vendor_id
) {
505 return udev_id_vendor
;
508 // Is there a vendor string in the hardware database?
509 if (!udev_id_vendor_from_database
.empty()) {
510 return udev_id_vendor_from_database
;
513 // Ok, udev gave us nothing useful, or was unavailable. So try a heuristic.
514 // We assume that card longname is in the format of
515 // "<manufacturer> <name> at <bus>". Otherwise, we give up to detect
516 // a manufacturer name here.
517 size_t at_index
= alsa_longname
.rfind(" at ");
518 if (std::string::npos
!= at_index
) {
519 size_t name_index
= alsa_longname
.rfind(alsa_name
, at_index
- 1);
520 if (std::string::npos
!= name_index
)
521 return alsa_longname
.substr(0, name_index
- 1);
528 MidiManager
* MidiManager::Create() {
529 return new MidiManagerAlsa();