blob: a21f8919d37f75fad03cceda590a7a52826d8b28 [file] [log] [blame]
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +00001// 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.
4
dnicoara@chromium.org9f2a6f02014-01-03 21:25:00 +00005#include "media/midi/midi_manager_alsa.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +00006
7#include <alsa/asoundlib.h>
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +00008#include <stdlib.h>
yhirano@chromium.orgcfa642c2014-05-01 08:54:41 +00009#include <algorithm>
10#include <string>
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000011
12#include "base/bind.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000013#include "base/logging.h"
14#include "base/memory/ref_counted.h"
agoode@chromium.org25227512014-06-08 05:12:05 +000015#include "base/memory/scoped_vector.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000016#include "base/message_loop/message_loop.h"
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000017#include "base/posix/eintr_wrapper.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000018#include "base/strings/stringprintf.h"
19#include "base/threading/thread.h"
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000020#include "base/time/time.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000021#include "media/midi/midi_port_info.h"
22
23namespace media {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000024
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000025namespace {
26
agoode@chromium.org25227512014-06-08 05:12:05 +000027// 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.
31const size_t kSendBufferSize = 256;
32
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.
35const unsigned int kRequiredInputPortCaps =
36 SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ;
37const unsigned int kRequiredOutputPortCaps =
38 SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE;
39
40int AddrToInt(const snd_seq_addr_t* addr) {
41 return (addr->client << 8) | addr->port;
42}
43
44class CardInfo {
45 public:
46 CardInfo(const std::string name, const std::string manufacturer,
47 const std::string driver)
48 : name_(name), manufacturer_(manufacturer), driver_(driver) {
49 }
50 const std::string name_;
51 const std::string manufacturer_;
52 const std::string driver_;
53};
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000054
55} // namespace
56
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +000057MidiManagerAlsa::MidiManagerAlsa()
agoode@chromium.org25227512014-06-08 05:12:05 +000058 : in_client_(NULL),
59 out_client_(NULL),
60 out_client_id_(-1),
61 in_port_(-1),
62 decoder_(NULL),
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);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000069}
70
toyoshim@chromium.org51c7f532014-05-01 17:17:32 +000071void MidiManagerAlsa::StartInitialization() {
agoode@chromium.org25227512014-06-08 05:12:05 +000072 // TODO(agoode): Move off I/O thread. See http://crbug.com/374341.
73
74 // Create client handles.
75 int err = snd_seq_open(&in_client_, "hw", SND_SEQ_OPEN_INPUT, 0);
76 if (err != 0) {
77 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err);
78 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
79 }
80 int in_client_id = snd_seq_client_id(in_client_);
81 err = snd_seq_open(&out_client_, "hw", SND_SEQ_OPEN_OUTPUT, 0);
82 if (err != 0) {
83 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err);
84 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
85 }
86 out_client_id_ = snd_seq_client_id(out_client_);
87
88 // Name the clients.
89 err = snd_seq_set_client_name(in_client_, "Chrome (input)");
90 if (err != 0) {
91 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err);
92 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
93 }
94 err = snd_seq_set_client_name(out_client_, "Chrome (output)");
95 if (err != 0) {
96 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err);
97 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
98 }
99
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);
106 if (in_port_ < 0) {
107 VLOG(1) << "snd_seq_create_simple_port fails: " << snd_strerror(in_port_);
108 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
109 }
110
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);
123 if (err != 0) {
124 VLOG(1) << "snd_seq_subscribe_port on the announce port fails: "
125 << snd_strerror(err);
126 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
127 }
128
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;
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000135 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);
143 snd_ctl_t* handle;
144 int err = snd_ctl_open(&handle, id.c_str(), 0);
145 if (err != 0) {
toyoshim@chromium.org2b058e82014-02-26 06:10:46 +0000146 VLOG(1) << "snd_ctl_open fails: " << snd_strerror(err);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000147 continue;
148 }
149 err = snd_ctl_card_info(handle, card);
150 if (err != 0) {
toyoshim@chromium.org2b058e82014-02-26 06:10:46 +0000151 VLOG(1) << "snd_ctl_card_info fails: " << snd_strerror(err);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000152 snd_ctl_close(handle);
153 continue;
154 }
agoode@chromium.org25227512014-06-08 05:12:05 +0000155 // Enumerate any rawmidi devices (not subdevices) and extract CardInfo.
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000156 for (int device = -1;
agoode@chromium.org25227512014-06-08 05:12:05 +0000157 !snd_ctl_rawmidi_next_device(handle, &device) && device >= 0; ) {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000158 bool output;
159 bool input;
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)
169 continue;
agoode@chromium.org25227512014-06-08 05:12:05 +0000170
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);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000183 }
agoode@chromium.org25227512014-06-08 05:12:05 +0000184 const std::string driver = snd_ctl_card_info_get_driver(card);
185 cards.push_back(new CardInfo(name, manufacturer, driver));
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000186 }
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000187 }
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000188
agoode@chromium.org25227512014-06-08 05:12:05 +0000189 // Enumerate all ports in all clients.
190 snd_seq_client_info_t* client_info;
191 snd_seq_client_info_alloca(&client_info);
192 snd_seq_port_info_t* port_info;
193 snd_seq_port_info_alloca(&port_info);
194
195 snd_seq_client_info_set_client(client_info, -1);
196 // Enumerate clients.
197 uint32 current_input = 0;
198 unsigned int current_card = 0;
199 while (!snd_seq_query_next_client(in_client_, client_info)) {
200 int client_id = snd_seq_client_info_get_client(client_info);
201 if ((client_id == in_client_id) || (client_id == out_client_id_)) {
202 // Skip our own clients.
203 continue;
204 }
205 const std::string client_name = snd_seq_client_info_get_name(client_info);
206 snd_seq_port_info_set_client(port_info, client_id);
207 snd_seq_port_info_set_port(port_info, -1);
208
209 std::string manufacturer;
210 std::string driver;
211 // In the current Alsa kernel implementation, hardware clients match the
212 // cards in the same order.
213 if ((snd_seq_client_info_get_type(client_info) == SND_SEQ_KERNEL_CLIENT) &&
214 (current_card < cards.size())) {
215 const CardInfo* info = cards[current_card];
216 if (info->name_ == client_name) {
217 manufacturer = info->manufacturer_;
218 driver = info->driver_;
219 current_card++;
220 }
221 }
222 // Enumerate ports.
223 while (!snd_seq_query_next_port(in_client_, port_info)) {
224 unsigned int port_type = snd_seq_port_info_get_type(port_info);
225 if (port_type & SND_SEQ_PORT_TYPE_MIDI_GENERIC) {
226 const snd_seq_addr_t* addr = snd_seq_port_info_get_addr(port_info);
227 const std::string name = snd_seq_port_info_get_name(port_info);
228 const std::string id = base::StringPrintf("%d:%d %s",
229 addr->client,
230 addr->port,
231 name.c_str());
232 std::string version;
233 if (driver != "") {
234 version = driver + " / ";
235 }
236 version += base::StringPrintf("ALSA library version %d.%d.%d",
237 SND_LIB_MAJOR,
238 SND_LIB_MINOR,
239 SND_LIB_SUBMINOR);
240 unsigned int caps = snd_seq_port_info_get_capability(port_info);
241 if ((caps & kRequiredInputPortCaps) == kRequiredInputPortCaps) {
242 // Subscribe to this port.
243 const snd_seq_addr_t* sender = snd_seq_port_info_get_addr(port_info);
244 snd_seq_addr_t dest;
245 dest.client = snd_seq_client_id(in_client_);
246 dest.port = in_port_;
247 snd_seq_port_subscribe_set_sender(subs, sender);
248 snd_seq_port_subscribe_set_dest(subs, &dest);
249 err = snd_seq_subscribe_port(in_client_, subs);
250 if (err != 0) {
251 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
252 } else {
253 source_map_[AddrToInt(sender)] = current_input++;
254 AddInputPort(MidiPortInfo(id, manufacturer, name, version));
255 }
256 }
257 if ((caps & kRequiredOutputPortCaps) == kRequiredOutputPortCaps) {
258 // Create a port for us to send on.
259 int out_port =
260 snd_seq_create_simple_port(out_client_, NULL,
261 SND_SEQ_PORT_CAP_READ |
262 SND_SEQ_PORT_CAP_NO_EXPORT,
263 SND_SEQ_PORT_TYPE_MIDI_GENERIC |
264 SND_SEQ_PORT_TYPE_APPLICATION);
265 if (out_port < 0) {
266 VLOG(1) << "snd_seq_create_simple_port fails: "
267 << snd_strerror(out_port);
268 // Skip this output port for now.
269 continue;
270 }
271
272 // Activate port subscription.
273 snd_seq_addr_t sender;
274 const snd_seq_addr_t* dest = snd_seq_port_info_get_addr(port_info);
275 sender.client = snd_seq_client_id(out_client_);
276 sender.port = out_port;
277 snd_seq_port_subscribe_set_sender(subs, &sender);
278 snd_seq_port_subscribe_set_dest(subs, dest);
279 err = snd_seq_subscribe_port(out_client_, subs);
280 if (err != 0) {
281 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
282 snd_seq_delete_simple_port(out_client_, out_port);
283 } else {
284 snd_midi_event_t* encoder;
285 snd_midi_event_new(kSendBufferSize, &encoder);
286 encoders_.push_back(encoder);
287 out_ports_.push_back(out_port);
288 AddOutputPort(MidiPortInfo(id, manufacturer, name, version));
289 }
290 }
291 }
292 }
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000293 }
agoode@chromium.org25227512014-06-08 05:12:05 +0000294
295 event_thread_.Start();
296 event_thread_.message_loop()->PostTask(
297 FROM_HERE,
298 base::Bind(&MidiManagerAlsa::EventReset, base::Unretained(this)));
299
300 CompleteInitialization(MIDI_OK);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000301}
302
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000303MidiManagerAlsa::~MidiManagerAlsa() {
agoode@chromium.org25227512014-06-08 05:12:05 +0000304 // Tell the event thread it will soon be time to shut down. This gives
305 // us assurance the thread will stop in case the SND_SEQ_EVENT_CLIENT_EXIT
306 // message is lost.
307 {
308 base::AutoLock lock(shutdown_lock_);
309 event_thread_shutdown_ = true;
310 }
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000311
agoode@chromium.org25227512014-06-08 05:12:05 +0000312 // Stop the send thread.
313 send_thread_.Stop();
314
315 // Close the out client. This will trigger the event thread to stop,
316 // because of SND_SEQ_EVENT_CLIENT_EXIT.
317 if (out_client_)
318 snd_seq_close(out_client_);
319
320 // Wait for the event thread to stop.
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000321 event_thread_.Stop();
322
agoode@chromium.org25227512014-06-08 05:12:05 +0000323 // Close the in client.
324 if (in_client_)
325 snd_seq_close(in_client_);
326
327 // Free the decoder.
328 snd_midi_event_free(decoder_);
329
330 // Free the encoders.
331 for (EncoderList::iterator i = encoders_.begin(); i != encoders_.end(); ++i)
332 snd_midi_event_free(*i);
333}
334
335void MidiManagerAlsa::SendMidiData(uint32 port_index,
336 const std::vector<uint8>& data) {
337 DCHECK(send_thread_.message_loop_proxy()->BelongsToCurrentThread());
338
339 snd_midi_event_t* encoder = encoders_[port_index];
340 for (unsigned int i = 0; i < data.size(); i++) {
341 snd_seq_event_t event;
342 int result = snd_midi_event_encode_byte(encoder, data[i], &event);
343 if (result == 1) {
344 // Full event, send it.
345 snd_seq_ev_set_source(&event, out_ports_[port_index]);
346 snd_seq_ev_set_subs(&event);
347 snd_seq_ev_set_direct(&event);
348 snd_seq_event_output_direct(out_client_, &event);
349 }
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000350 }
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000351}
352
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000353void MidiManagerAlsa::DispatchSendMidiData(MidiManagerClient* client,
dnicoara@chromium.org9f2a6f02014-01-03 21:25:00 +0000354 uint32 port_index,
355 const std::vector<uint8>& data,
356 double timestamp) {
agoode@chromium.org25227512014-06-08 05:12:05 +0000357 if (out_ports_.size() <= port_index)
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000358 return;
359
agoode@chromium.org25227512014-06-08 05:12:05 +0000360 // Not correct right now. http://crbug.com/374341.
361 if (!send_thread_.IsRunning())
362 send_thread_.Start();
363
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000364 base::TimeDelta delay;
365 if (timestamp != 0.0) {
366 base::TimeTicks time_to_send =
367 base::TimeTicks() + base::TimeDelta::FromMicroseconds(
368 timestamp * base::Time::kMicrosecondsPerSecond);
369 delay = std::max(time_to_send - base::TimeTicks::Now(), base::TimeDelta());
370 }
371
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000372 send_thread_.message_loop()->PostDelayedTask(
373 FROM_HERE,
agoode@chromium.org25227512014-06-08 05:12:05 +0000374 base::Bind(&MidiManagerAlsa::SendMidiData, base::Unretained(this),
375 port_index, data), delay);
376
377 // Acknowledge send.
378 send_thread_.message_loop()->PostTask(
379 FROM_HERE,
380 base::Bind(&MidiManagerClient::AccumulateMidiBytesSent,
381 base::Unretained(client), data.size()));
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000382}
383
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000384void MidiManagerAlsa::EventReset() {
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000385 event_thread_.message_loop()->PostTask(
386 FROM_HERE,
387 base::Bind(&MidiManagerAlsa::EventLoop, base::Unretained(this)));
388}
389
390void MidiManagerAlsa::EventLoop() {
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000391 // Read available incoming MIDI data.
agoode@chromium.org25227512014-06-08 05:12:05 +0000392 snd_seq_event_t* event;
393 int err = snd_seq_event_input(in_client_, &event);
394 double timestamp =
395 (base::TimeTicks::HighResNow() - base::TimeTicks()).InSecondsF();
396 if (err == -ENOSPC) {
397 VLOG(1) << "snd_seq_event_input detected buffer overrun";
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000398
agoode@chromium.org25227512014-06-08 05:12:05 +0000399 // We've lost events: check another way to see if we need to shut down.
400 base::AutoLock lock(shutdown_lock_);
401 if (event_thread_shutdown_) {
402 return;
403 }
404 } else if (err < 0) {
405 VLOG(1) << "snd_seq_event_input fails: " << snd_strerror(err);
406 return;
407 } else {
408 // Check for disconnection of out client. This means "shut down".
409 if (event->source.client == SND_SEQ_CLIENT_SYSTEM &&
410 event->source.port == SND_SEQ_PORT_SYSTEM_ANNOUNCE &&
411 event->type == SND_SEQ_EVENT_CLIENT_EXIT &&
412 event->data.addr.client == out_client_id_) {
413 return;
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000414 }
agoode@chromium.org25227512014-06-08 05:12:05 +0000415
416 std::map<int, uint32>::iterator source_it =
417 source_map_.find(AddrToInt(&event->source));
418 if (source_it != source_map_.end()) {
419 uint32 source = source_it->second;
420 if (event->type == SND_SEQ_EVENT_SYSEX) {
421 // Special! Variable-length sysex.
422 ReceiveMidiData(source, static_cast<const uint8*>(event->data.ext.ptr),
423 event->data.ext.len,
424 timestamp);
425 } else {
426 // Otherwise, decode this and send that on.
427 unsigned char buf[12];
428 long count = snd_midi_event_decode(decoder_, buf, sizeof(buf), event);
429 if (count <= 0) {
430 if (count != -ENOENT) {
431 // ENOENT means that it's not a MIDI message, which is not an
432 // error, but other negative values are errors for us.
433 VLOG(1) << "snd_midi_event_decoder fails " << snd_strerror(count);
434 }
435 } else {
436 ReceiveMidiData(source, buf, count, timestamp);
437 }
438 }
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000439 }
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000440 }
441
442 // Do again.
443 event_thread_.message_loop()->PostTask(
444 FROM_HERE,
445 base::Bind(&MidiManagerAlsa::EventLoop, base::Unretained(this)));
446}
447
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000448MidiManager* MidiManager::Create() {
449 return new MidiManagerAlsa();
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000450}
451
452} // namespace media