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 CardInfo(const std::string name
, const std::string manufacturer
,
47 const std::string driver
)
48 : name_(name
), manufacturer_(manufacturer
), driver_(driver
) {
50 const std::string name_
;
51 const std::string manufacturer_
;
52 const std::string driver_
;
57 MidiManagerAlsa::MidiManagerAlsa()
63 send_thread_("MidiSendThread"),
64 event_thread_("MidiEventThread"),
65 event_thread_shutdown_(false) {
66 // Initialize decoder.
67 snd_midi_event_new(0, &decoder_
);
68 snd_midi_event_no_status(decoder_
, 1);
71 void MidiManagerAlsa::StartInitialization() {
72 // TODO(agoode): Move off I/O thread. See http://crbug.com/374341.
74 // Create client handles.
75 int err
= snd_seq_open(&in_client_
, "hw", SND_SEQ_OPEN_INPUT
, 0);
77 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err
);
78 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
80 int in_client_id
= snd_seq_client_id(in_client_
);
81 err
= snd_seq_open(&out_client_
, "hw", SND_SEQ_OPEN_OUTPUT
, 0);
83 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err
);
84 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
86 out_client_id_
= snd_seq_client_id(out_client_
);
89 err
= snd_seq_set_client_name(in_client_
, "Chrome (input)");
91 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err
);
92 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
94 err
= snd_seq_set_client_name(out_client_
, "Chrome (output)");
96 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err
);
97 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
100 // Create input port.
101 in_port_
= snd_seq_create_simple_port(in_client_
, NULL
,
102 SND_SEQ_PORT_CAP_WRITE
|
103 SND_SEQ_PORT_CAP_NO_EXPORT
,
104 SND_SEQ_PORT_TYPE_MIDI_GENERIC
|
105 SND_SEQ_PORT_TYPE_APPLICATION
);
107 VLOG(1) << "snd_seq_create_simple_port fails: " << snd_strerror(in_port_
);
108 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
111 // Subscribe to the announce port.
112 snd_seq_port_subscribe_t
* subs
;
113 snd_seq_port_subscribe_alloca(&subs
);
114 snd_seq_addr_t announce_sender
;
115 snd_seq_addr_t announce_dest
;
116 announce_sender
.client
= SND_SEQ_CLIENT_SYSTEM
;
117 announce_sender
.port
= SND_SEQ_PORT_SYSTEM_ANNOUNCE
;
118 announce_dest
.client
= in_client_id
;
119 announce_dest
.port
= in_port_
;
120 snd_seq_port_subscribe_set_sender(subs
, &announce_sender
);
121 snd_seq_port_subscribe_set_dest(subs
, &announce_dest
);
122 err
= snd_seq_subscribe_port(in_client_
, subs
);
124 VLOG(1) << "snd_seq_subscribe_port on the announce port fails: "
125 << snd_strerror(err
);
126 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
129 // Use a heuristic to extract the list of manufacturers for the hardware MIDI
130 // devices. This won't work for all devices. It is also brittle until
131 // hotplug is implemented. (See http://crbug.com/279097.)
132 // TODO(agoode): Make manufacturer extraction simple and reliable.
133 // http://crbug.com/377250.
134 ScopedVector
<CardInfo
> cards
;
135 snd_ctl_card_info_t
* card
;
136 snd_rawmidi_info_t
* midi_out
;
137 snd_rawmidi_info_t
* midi_in
;
138 snd_ctl_card_info_alloca(&card
);
139 snd_rawmidi_info_alloca(&midi_out
);
140 snd_rawmidi_info_alloca(&midi_in
);
141 for (int index
= -1; !snd_card_next(&index
) && index
>= 0; ) {
142 const std::string id
= base::StringPrintf("hw:CARD=%i", index
);
144 int err
= snd_ctl_open(&handle
, id
.c_str(), 0);
146 VLOG(1) << "snd_ctl_open fails: " << snd_strerror(err
);
149 err
= snd_ctl_card_info(handle
, card
);
151 VLOG(1) << "snd_ctl_card_info fails: " << snd_strerror(err
);
152 snd_ctl_close(handle
);
155 // Enumerate any rawmidi devices (not subdevices) and extract CardInfo.
156 for (int device
= -1;
157 !snd_ctl_rawmidi_next_device(handle
, &device
) && device
>= 0; ) {
160 snd_rawmidi_info_set_device(midi_out
, device
);
161 snd_rawmidi_info_set_subdevice(midi_out
, 0);
162 snd_rawmidi_info_set_stream(midi_out
, SND_RAWMIDI_STREAM_OUTPUT
);
163 output
= snd_ctl_rawmidi_info(handle
, midi_out
) == 0;
164 snd_rawmidi_info_set_device(midi_in
, device
);
165 snd_rawmidi_info_set_subdevice(midi_in
, 0);
166 snd_rawmidi_info_set_stream(midi_in
, SND_RAWMIDI_STREAM_INPUT
);
167 input
= snd_ctl_rawmidi_info(handle
, midi_in
) == 0;
168 if (!output
&& !input
)
171 snd_rawmidi_info_t
* midi
= midi_out
? midi_out
: midi_in
;
172 const std::string name
= snd_rawmidi_info_get_name(midi
);
173 // We assume that card longname is in the format of
174 // "<manufacturer> <name> at <bus>". Otherwise, we give up to detect
175 // a manufacturer name here.
176 std::string manufacturer
;
177 const std::string card_name
= snd_ctl_card_info_get_longname(card
);
178 size_t at_index
= card_name
.rfind(" at ");
179 if (std::string::npos
!= at_index
) {
180 size_t name_index
= card_name
.rfind(name
, at_index
- 1);
181 if (std::string::npos
!= name_index
)
182 manufacturer
= card_name
.substr(0, name_index
- 1);
184 const std::string driver
= snd_ctl_card_info_get_driver(card
);
185 cards
.push_back(new CardInfo(name
, manufacturer
, driver
));
187 snd_ctl_close(handle
);
190 // Enumerate all ports in all clients.
191 snd_seq_client_info_t
* client_info
;
192 snd_seq_client_info_alloca(&client_info
);
193 snd_seq_port_info_t
* port_info
;
194 snd_seq_port_info_alloca(&port_info
);
196 snd_seq_client_info_set_client(client_info
, -1);
197 // Enumerate clients.
198 uint32 current_input
= 0;
199 unsigned int current_card
= 0;
200 while (!snd_seq_query_next_client(in_client_
, client_info
)) {
201 int client_id
= snd_seq_client_info_get_client(client_info
);
202 if ((client_id
== in_client_id
) || (client_id
== out_client_id_
)) {
203 // Skip our own clients.
206 const std::string client_name
= snd_seq_client_info_get_name(client_info
);
207 snd_seq_port_info_set_client(port_info
, client_id
);
208 snd_seq_port_info_set_port(port_info
, -1);
210 std::string manufacturer
;
212 // In the current Alsa kernel implementation, hardware clients match the
213 // cards in the same order.
214 if ((snd_seq_client_info_get_type(client_info
) == SND_SEQ_KERNEL_CLIENT
) &&
215 (current_card
< cards
.size())) {
216 const CardInfo
* info
= cards
[current_card
];
217 if (info
->name_
== client_name
) {
218 manufacturer
= info
->manufacturer_
;
219 driver
= info
->driver_
;
224 while (!snd_seq_query_next_port(in_client_
, port_info
)) {
225 unsigned int port_type
= snd_seq_port_info_get_type(port_info
);
226 if (port_type
& SND_SEQ_PORT_TYPE_MIDI_GENERIC
) {
227 const snd_seq_addr_t
* addr
= snd_seq_port_info_get_addr(port_info
);
228 const std::string name
= snd_seq_port_info_get_name(port_info
);
229 const std::string id
= base::StringPrintf("%d:%d %s",
235 version
= driver
+ " / ";
237 version
+= base::StringPrintf("ALSA library version %d.%d.%d",
241 unsigned int caps
= snd_seq_port_info_get_capability(port_info
);
242 if ((caps
& kRequiredInputPortCaps
) == kRequiredInputPortCaps
) {
243 // Subscribe to this port.
244 const snd_seq_addr_t
* sender
= snd_seq_port_info_get_addr(port_info
);
246 dest
.client
= snd_seq_client_id(in_client_
);
247 dest
.port
= in_port_
;
248 snd_seq_port_subscribe_set_sender(subs
, sender
);
249 snd_seq_port_subscribe_set_dest(subs
, &dest
);
250 err
= snd_seq_subscribe_port(in_client_
, subs
);
252 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err
);
254 source_map_
[AddrToInt(sender
)] = current_input
++;
255 AddInputPort(MidiPortInfo(id
, manufacturer
, name
, version
));
258 if ((caps
& kRequiredOutputPortCaps
) == kRequiredOutputPortCaps
) {
259 // Create a port for us to send on.
261 snd_seq_create_simple_port(out_client_
, NULL
,
262 SND_SEQ_PORT_CAP_READ
|
263 SND_SEQ_PORT_CAP_NO_EXPORT
,
264 SND_SEQ_PORT_TYPE_MIDI_GENERIC
|
265 SND_SEQ_PORT_TYPE_APPLICATION
);
267 VLOG(1) << "snd_seq_create_simple_port fails: "
268 << snd_strerror(out_port
);
269 // Skip this output port for now.
273 // Activate port subscription.
274 snd_seq_addr_t sender
;
275 const snd_seq_addr_t
* dest
= snd_seq_port_info_get_addr(port_info
);
276 sender
.client
= snd_seq_client_id(out_client_
);
277 sender
.port
= out_port
;
278 snd_seq_port_subscribe_set_sender(subs
, &sender
);
279 snd_seq_port_subscribe_set_dest(subs
, dest
);
280 err
= snd_seq_subscribe_port(out_client_
, subs
);
282 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err
);
283 snd_seq_delete_simple_port(out_client_
, out_port
);
285 snd_midi_event_t
* encoder
;
286 snd_midi_event_new(kSendBufferSize
, &encoder
);
287 encoders_
.push_back(encoder
);
288 out_ports_
.push_back(out_port
);
289 AddOutputPort(MidiPortInfo(id
, manufacturer
, name
, version
));
296 event_thread_
.Start();
297 event_thread_
.message_loop()->PostTask(
299 base::Bind(&MidiManagerAlsa::EventReset
, base::Unretained(this)));
301 CompleteInitialization(MIDI_OK
);
304 MidiManagerAlsa::~MidiManagerAlsa() {
305 // Tell the event thread it will soon be time to shut down. This gives
306 // us assurance the thread will stop in case the SND_SEQ_EVENT_CLIENT_EXIT
309 base::AutoLock
lock(shutdown_lock_
);
310 event_thread_shutdown_
= true;
313 // Stop the send thread.
316 // Close the out client. This will trigger the event thread to stop,
317 // because of SND_SEQ_EVENT_CLIENT_EXIT.
319 snd_seq_close(out_client_
);
321 // Wait for the event thread to stop.
322 event_thread_
.Stop();
324 // Close the in client.
326 snd_seq_close(in_client_
);
329 snd_midi_event_free(decoder_
);
331 // Free the encoders.
332 for (EncoderList::iterator i
= encoders_
.begin(); i
!= encoders_
.end(); ++i
)
333 snd_midi_event_free(*i
);
336 void MidiManagerAlsa::SendMidiData(uint32 port_index
,
337 const std::vector
<uint8
>& data
) {
338 DCHECK(send_thread_
.message_loop_proxy()->BelongsToCurrentThread());
340 snd_midi_event_t
* encoder
= encoders_
[port_index
];
341 for (unsigned int i
= 0; i
< data
.size(); i
++) {
342 snd_seq_event_t event
;
343 int result
= snd_midi_event_encode_byte(encoder
, data
[i
], &event
);
345 // Full event, send it.
346 snd_seq_ev_set_source(&event
, out_ports_
[port_index
]);
347 snd_seq_ev_set_subs(&event
);
348 snd_seq_ev_set_direct(&event
);
349 snd_seq_event_output_direct(out_client_
, &event
);
354 void MidiManagerAlsa::DispatchSendMidiData(MidiManagerClient
* client
,
356 const std::vector
<uint8
>& data
,
358 if (out_ports_
.size() <= port_index
)
361 // Not correct right now. http://crbug.com/374341.
362 if (!send_thread_
.IsRunning())
363 send_thread_
.Start();
365 base::TimeDelta delay
;
366 if (timestamp
!= 0.0) {
367 base::TimeTicks time_to_send
=
368 base::TimeTicks() + base::TimeDelta::FromMicroseconds(
369 timestamp
* base::Time::kMicrosecondsPerSecond
);
370 delay
= std::max(time_to_send
- base::TimeTicks::Now(), base::TimeDelta());
373 send_thread_
.message_loop()->PostDelayedTask(
375 base::Bind(&MidiManagerAlsa::SendMidiData
, base::Unretained(this),
376 port_index
, data
), delay
);
379 send_thread_
.message_loop()->PostTask(
381 base::Bind(&MidiManagerClient::AccumulateMidiBytesSent
,
382 base::Unretained(client
), data
.size()));
385 void MidiManagerAlsa::EventReset() {
386 event_thread_
.message_loop()->PostTask(
388 base::Bind(&MidiManagerAlsa::EventLoop
, base::Unretained(this)));
391 void MidiManagerAlsa::EventLoop() {
392 // Read available incoming MIDI data.
393 snd_seq_event_t
* event
;
394 int err
= snd_seq_event_input(in_client_
, &event
);
396 (base::TimeTicks::HighResNow() - base::TimeTicks()).InSecondsF();
397 if (err
== -ENOSPC
) {
398 VLOG(1) << "snd_seq_event_input detected buffer overrun";
400 // We've lost events: check another way to see if we need to shut down.
401 base::AutoLock
lock(shutdown_lock_
);
402 if (event_thread_shutdown_
) {
405 } else if (err
< 0) {
406 VLOG(1) << "snd_seq_event_input fails: " << snd_strerror(err
);
409 // Check for disconnection of out client. This means "shut down".
410 if (event
->source
.client
== SND_SEQ_CLIENT_SYSTEM
&&
411 event
->source
.port
== SND_SEQ_PORT_SYSTEM_ANNOUNCE
&&
412 event
->type
== SND_SEQ_EVENT_CLIENT_EXIT
&&
413 event
->data
.addr
.client
== out_client_id_
) {
417 std::map
<int, uint32
>::iterator source_it
=
418 source_map_
.find(AddrToInt(&event
->source
));
419 if (source_it
!= source_map_
.end()) {
420 uint32 source
= source_it
->second
;
421 if (event
->type
== SND_SEQ_EVENT_SYSEX
) {
422 // Special! Variable-length sysex.
423 ReceiveMidiData(source
, static_cast<const uint8
*>(event
->data
.ext
.ptr
),
427 // Otherwise, decode this and send that on.
428 unsigned char buf
[12];
429 long count
= snd_midi_event_decode(decoder_
, buf
, sizeof(buf
), event
);
431 if (count
!= -ENOENT
) {
432 // ENOENT means that it's not a MIDI message, which is not an
433 // error, but other negative values are errors for us.
434 VLOG(1) << "snd_midi_event_decoder fails " << snd_strerror(count
);
437 ReceiveMidiData(source
, buf
, count
, timestamp
);
444 event_thread_
.message_loop()->PostTask(
446 base::Bind(&MidiManagerAlsa::EventLoop
, base::Unretained(this)));
449 MidiManager
* MidiManager::Create() {
450 return new MidiManagerAlsa();