blob: d3c4c5e7e2997e0a99a39d2b959bc91436523d17 [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
mostynbbd693172015-11-17 19:06:54 -08007#include <errno.h>
agoode975043d2015-05-11 00:46:17 -07008#include <poll.h>
avi793390d2015-12-22 22:22:36 -08009#include <stddef.h>
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000010#include <stdlib.h>
yhirano@chromium.orgcfa642c2014-05-01 08:54:41 +000011#include <algorithm>
12#include <string>
limasdfe59d0392015-11-19 20:28:57 -080013#include <utility>
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000014
15#include "base/bind.h"
agoodef212b2a2015-03-19 12:53:23 -070016#include "base/json/json_string_value_serializer.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000017#include "base/logging.h"
avi793390d2015-12-22 22:22:36 -080018#include "base/macros.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000019#include "base/message_loop/message_loop.h"
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000020#include "base/posix/eintr_wrapper.h"
brettwf7f870f2015-06-09 11:05:24 -070021#include "base/posix/safe_strerror.h"
agoodef212b2a2015-03-19 12:53:23 -070022#include "base/strings/string_number_conversions.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000023#include "base/strings/stringprintf.h"
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000024#include "base/time/time.h"
agoodef212b2a2015-03-19 12:53:23 -070025#include "crypto/sha2.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000026#include "media/midi/midi_port_info.h"
27
28namespace media {
toyoshime147c5e2015-05-07 21:58:31 -070029namespace midi {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000030
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000031namespace {
32
agoode@chromium.org25227512014-06-08 05:12:05 +000033// Per-output buffer. This can be smaller, but then large sysex messages
34// will be (harmlessly) split across multiple seq events. This should
35// not have any real practical effect, except perhaps to slightly reorder
36// realtime messages with respect to sysex.
37const size_t kSendBufferSize = 256;
38
agoodeb09423b2015-05-11 11:39:57 -070039// Minimum client id for which we will have ALSA card devices for. When we
40// are searching for card devices (used to get the path, id, and manufacturer),
41// we don't want to get confused by kernel clients that do not have a card.
42// See seq_clientmgr.c in the ALSA code for this.
43// TODO(agoode): Add proper client -> card export from the kernel to avoid
44// hardcoding.
45const int kMinimumClientIdForCards = 16;
46
agoodef212b2a2015-03-19 12:53:23 -070047// ALSA constants.
48const char kAlsaHw[] = "hw";
49
agoode975043d2015-05-11 00:46:17 -070050// udev constants.
51const char kUdev[] = "udev";
52const char kUdevSubsystemSound[] = "sound";
53const char kUdevPropertySoundInitialized[] = "SOUND_INITIALIZED";
54const char kUdevActionChange[] = "change";
55const char kUdevActionRemove[] = "remove";
56
agoodeb09423b2015-05-11 11:39:57 -070057const char kUdevIdVendor[] = "ID_VENDOR";
58const char kUdevIdVendorEnc[] = "ID_VENDOR_ENC";
59const char kUdevIdVendorFromDatabase[] = "ID_VENDOR_FROM_DATABASE";
60const char kUdevIdVendorId[] = "ID_VENDOR_ID";
61const char kUdevIdModelId[] = "ID_MODEL_ID";
62const char kUdevIdBus[] = "ID_BUS";
63const char kUdevIdPath[] = "ID_PATH";
64const char kUdevIdUsbInterfaceNum[] = "ID_USB_INTERFACE_NUM";
65const char kUdevIdSerialShort[] = "ID_SERIAL_SHORT";
66
67const char kSysattrVendorName[] = "vendor_name";
68const char kSysattrVendor[] = "vendor";
69const char kSysattrModel[] = "model";
70const char kSysattrGuid[] = "guid";
71
72const char kCardSyspath[] = "/card";
73
agoode@chromium.org25227512014-06-08 05:12:05 +000074// Constants for the capabilities we search for in inputs and outputs.
75// See http://www.alsa-project.org/alsa-doc/alsa-lib/seq.html.
76const unsigned int kRequiredInputPortCaps =
77 SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ;
78const unsigned int kRequiredOutputPortCaps =
79 SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE;
80
agoode99d63292015-04-13 08:39:25 -070081const unsigned int kCreateOutputPortCaps =
82 SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_NO_EXPORT;
83const unsigned int kCreateInputPortCaps =
84 SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_NO_EXPORT;
85const unsigned int kCreatePortType =
86 SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION;
agoode@chromium.org25227512014-06-08 05:12:05 +000087
agoode99d63292015-04-13 08:39:25 -070088int AddrToInt(int client, int port) {
89 return (client << 8) | port;
agoodef212b2a2015-03-19 12:53:23 -070090}
agoodef212b2a2015-03-19 12:53:23 -070091
agoodeb09423b2015-05-11 11:39:57 -070092// Returns true if this client has an ALSA card associated with it.
93bool IsCardClient(snd_seq_client_type_t type, int client_id) {
94 return (type == SND_SEQ_KERNEL_CLIENT) &&
95 (client_id >= kMinimumClientIdForCards);
96}
97
98// TODO(agoode): Move this to device/udev_linux.
99const std::string UdevDeviceGetPropertyOrSysattr(
100 struct udev_device* udev_device,
101 const char* property_key,
102 const char* sysattr_key) {
103 // First try the property.
104 std::string value =
105 device::UdevDeviceGetPropertyValue(udev_device, property_key);
106
107 // If no property, look for sysattrs and walk up the parent devices too.
108 while (value.empty() && udev_device) {
109 value = device::UdevDeviceGetSysattrValue(udev_device, sysattr_key);
110 udev_device = device::udev_device_get_parent(udev_device);
111 }
112 return value;
113}
114
115int GetCardNumber(udev_device* dev) {
116 const char* syspath = device::udev_device_get_syspath(dev);
117 if (!syspath)
118 return -1;
119
120 std::string syspath_str(syspath);
121 size_t i = syspath_str.rfind(kCardSyspath);
122 if (i == std::string::npos)
123 return -1;
124
125 int number;
126 if (!base::StringToInt(syspath_str.substr(i + strlen(kCardSyspath)), &number))
127 return -1;
128 return number;
129}
130
agooded87fc0f2015-05-21 08:29:31 -0700131std::string GetVendor(udev_device* dev) {
132 // Try to get the vendor string. Sometimes it is encoded.
133 std::string vendor = device::UdevDecodeString(
134 device::UdevDeviceGetPropertyValue(dev, kUdevIdVendorEnc));
135 // Sometimes it is not encoded.
136 if (vendor.empty())
137 vendor =
138 UdevDeviceGetPropertyOrSysattr(dev, kUdevIdVendor, kSysattrVendorName);
139 return vendor;
140}
141
agoode8caab0b2015-03-23 18:48:02 -0700142void SetStringIfNonEmpty(base::DictionaryValue* value,
143 const std::string& path,
144 const std::string& in_value) {
145 if (!in_value.empty())
146 value->SetString(path, in_value);
147}
148
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000149} // namespace
150
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000151MidiManagerAlsa::MidiManagerAlsa()
agoodeb2ead822016-03-11 12:14:35 -0800152 : event_thread_("MidiEventThread"), send_thread_("MidiSendThread") {}
153
154MidiManagerAlsa::~MidiManagerAlsa() {
155 // Take lock to ensure that the members initialized on the IO thread
156 // are not destructed here.
157 base::AutoLock lock(lazy_init_member_lock_);
158
159 // Extra DCHECK to verify all members are already reset.
160 DCHECK(!initialization_thread_checker_);
161 DCHECK(!in_client_);
162 DCHECK(!out_client_);
163 DCHECK(!decoder_);
164 DCHECK(!udev_);
165 DCHECK(!udev_monitor_);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000166}
167
toyoshim@chromium.org51c7f532014-05-01 17:17:32 +0000168void MidiManagerAlsa::StartInitialization() {
agoodeb2ead822016-03-11 12:14:35 -0800169 base::AutoLock lock(lazy_init_member_lock_);
agoode5a1aa112015-06-21 20:51:00 -0700170
agoodeb2ead822016-03-11 12:14:35 -0800171 initialization_thread_checker_.reset(new base::ThreadChecker());
172
173 // Create client handles.
174 snd_seq_t* tmp_seq = nullptr;
175 int err =
176 snd_seq_open(&tmp_seq, kAlsaHw, SND_SEQ_OPEN_INPUT, SND_SEQ_NONBLOCK);
agoode@chromium.org25227512014-06-08 05:12:05 +0000177 if (err != 0) {
178 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err);
toyoshimf1b88962015-07-09 14:14:51 -0700179 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode@chromium.org25227512014-06-08 05:12:05 +0000180 }
agoodeb2ead822016-03-11 12:14:35 -0800181 ScopedSndSeqPtr in_client(tmp_seq);
182 tmp_seq = nullptr;
183 in_client_id_ = snd_seq_client_id(in_client.get());
184
185 err = snd_seq_open(&tmp_seq, kAlsaHw, SND_SEQ_OPEN_OUTPUT, 0);
186 if (err != 0) {
187 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err);
188 return CompleteInitialization(Result::INITIALIZATION_ERROR);
189 }
190 ScopedSndSeqPtr out_client(tmp_seq);
191 tmp_seq = nullptr;
192 out_client_id_ = snd_seq_client_id(out_client.get());
agoode@chromium.org25227512014-06-08 05:12:05 +0000193
194 // Name the clients.
agoodeb2ead822016-03-11 12:14:35 -0800195 err = snd_seq_set_client_name(in_client.get(), "Chrome (input)");
agoode@chromium.org25227512014-06-08 05:12:05 +0000196 if (err != 0) {
197 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err);
toyoshimf1b88962015-07-09 14:14:51 -0700198 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode@chromium.org25227512014-06-08 05:12:05 +0000199 }
agoodeb2ead822016-03-11 12:14:35 -0800200 err = snd_seq_set_client_name(out_client.get(), "Chrome (output)");
agoode@chromium.org25227512014-06-08 05:12:05 +0000201 if (err != 0) {
202 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err);
toyoshimf1b88962015-07-09 14:14:51 -0700203 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode@chromium.org25227512014-06-08 05:12:05 +0000204 }
205
206 // Create input port.
agoode99d63292015-04-13 08:39:25 -0700207 in_port_id_ = snd_seq_create_simple_port(
agoodeb2ead822016-03-11 12:14:35 -0800208 in_client.get(), NULL, kCreateInputPortCaps, kCreatePortType);
agoode99d63292015-04-13 08:39:25 -0700209 if (in_port_id_ < 0) {
210 VLOG(1) << "snd_seq_create_simple_port fails: "
211 << snd_strerror(in_port_id_);
toyoshimf1b88962015-07-09 14:14:51 -0700212 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode@chromium.org25227512014-06-08 05:12:05 +0000213 }
214
215 // Subscribe to the announce port.
216 snd_seq_port_subscribe_t* subs;
217 snd_seq_port_subscribe_alloca(&subs);
218 snd_seq_addr_t announce_sender;
219 snd_seq_addr_t announce_dest;
220 announce_sender.client = SND_SEQ_CLIENT_SYSTEM;
221 announce_sender.port = SND_SEQ_PORT_SYSTEM_ANNOUNCE;
agoode99d63292015-04-13 08:39:25 -0700222 announce_dest.client = in_client_id_;
223 announce_dest.port = in_port_id_;
agoode@chromium.org25227512014-06-08 05:12:05 +0000224 snd_seq_port_subscribe_set_sender(subs, &announce_sender);
225 snd_seq_port_subscribe_set_dest(subs, &announce_dest);
agoodeb2ead822016-03-11 12:14:35 -0800226 err = snd_seq_subscribe_port(in_client.get(), subs);
agoode@chromium.org25227512014-06-08 05:12:05 +0000227 if (err != 0) {
228 VLOG(1) << "snd_seq_subscribe_port on the announce port fails: "
229 << snd_strerror(err);
toyoshimf1b88962015-07-09 14:14:51 -0700230 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode@chromium.org25227512014-06-08 05:12:05 +0000231 }
232
agoodeb2ead822016-03-11 12:14:35 -0800233 // Initialize decoder.
234 snd_midi_event_t* tmp_decoder = nullptr;
235 snd_midi_event_new(0, &tmp_decoder);
236 ScopedSndMidiEventPtr decoder(tmp_decoder);
237 tmp_decoder = nullptr;
238 snd_midi_event_no_status(decoder.get(), 1);
agoode@chromium.org25227512014-06-08 05:12:05 +0000239
agoodeb2ead822016-03-11 12:14:35 -0800240 // Initialize udev and monitor.
241 device::ScopedUdevPtr udev(device::udev_new());
242 device::ScopedUdevMonitorPtr udev_monitor(
243 device::udev_monitor_new_from_netlink(udev.get(), kUdev));
244 if (!udev_monitor.get()) {
agoode975043d2015-05-11 00:46:17 -0700245 VLOG(1) << "udev_monitor_new_from_netlink fails";
toyoshimf1b88962015-07-09 14:14:51 -0700246 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode975043d2015-05-11 00:46:17 -0700247 }
248 err = device::udev_monitor_filter_add_match_subsystem_devtype(
agoodeb2ead822016-03-11 12:14:35 -0800249 udev_monitor.get(), kUdevSubsystemSound, nullptr);
agoode975043d2015-05-11 00:46:17 -0700250 if (err != 0) {
251 VLOG(1) << "udev_monitor_add_match_subsystem fails: "
brettwf7f870f2015-06-09 11:05:24 -0700252 << base::safe_strerror(-err);
toyoshimf1b88962015-07-09 14:14:51 -0700253 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode975043d2015-05-11 00:46:17 -0700254 }
agoodeb2ead822016-03-11 12:14:35 -0800255 err = device::udev_monitor_enable_receiving(udev_monitor.get());
agoode975043d2015-05-11 00:46:17 -0700256 if (err != 0) {
brettwf7f870f2015-06-09 11:05:24 -0700257 VLOG(1) << "udev_monitor_enable_receiving fails: "
258 << base::safe_strerror(-err);
toyoshimf1b88962015-07-09 14:14:51 -0700259 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode975043d2015-05-11 00:46:17 -0700260 }
261
agoodeb2ead822016-03-11 12:14:35 -0800262 // Success! Now, initialize members from the temporaries. Do not
263 // initialize these earlier, since they need to be destroyed by the
264 // thread that calls Finalize(), not the destructor thread (and we
265 // check this in the destructor).
266 in_client_.reset(in_client.release());
267 out_client_.reset(out_client.release());
268 decoder_.reset(decoder.release());
269 udev_.reset(udev.release());
270 udev_monitor_.reset(udev_monitor_.release());
271
272 // Generate hotplug events for existing ports.
273 // TODO(agoode): Check the return value for failure.
274 EnumerateAlsaPorts();
275
276 // Generate hotplug events for existing udev devices. This must be done
277 // after udev_monitor_enable_receiving() is called. See the algorithm
278 // at http://www.signal11.us/oss/udev/.
agoode975043d2015-05-11 00:46:17 -0700279 EnumerateUdevCards();
280
agoodeb2ead822016-03-11 12:14:35 -0800281 // Start processing events. Don't do this before enumeration of both
282 // ALSA and udev.
agoode@chromium.org25227512014-06-08 05:12:05 +0000283 event_thread_.Start();
284 event_thread_.message_loop()->PostTask(
285 FROM_HERE,
agoodebd4be9b2015-03-16 19:17:25 -0700286 base::Bind(&MidiManagerAlsa::ScheduleEventLoop, base::Unretained(this)));
agoodeb2ead822016-03-11 12:14:35 -0800287 send_thread_.Start();
agoode@chromium.org25227512014-06-08 05:12:05 +0000288
toyoshimf1b88962015-07-09 14:14:51 -0700289 CompleteInitialization(Result::OK);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000290}
291
toyoshim8e7d6e02015-10-06 08:47:17 -0700292void MidiManagerAlsa::Finalize() {
agoodeb2ead822016-03-11 12:14:35 -0800293 base::AutoLock lock(lazy_init_member_lock_);
294 DCHECK(initialization_thread_checker_->CalledOnValidThread());
295
toyoshim8e7d6e02015-10-06 08:47:17 -0700296 // Tell the event thread it will soon be time to shut down. This gives
297 // us assurance the thread will stop in case the SND_SEQ_EVENT_CLIENT_EXIT
298 // message is lost.
299 {
300 base::AutoLock lock(shutdown_lock_);
301 event_thread_shutdown_ = true;
302 }
303
304 // Stop the send thread.
305 send_thread_.Stop();
306
307 // Close the out client. This will trigger the event thread to stop,
308 // because of SND_SEQ_EVENT_CLIENT_EXIT.
agoodeb2ead822016-03-11 12:14:35 -0800309 out_client_.reset();
toyoshim8e7d6e02015-10-06 08:47:17 -0700310
311 // Wait for the event thread to stop.
312 event_thread_.Stop();
agoodeb2ead822016-03-11 12:14:35 -0800313
314 // Destruct the other stuff we initialized in StartInitialization().
315 udev_monitor_.reset();
316 udev_.reset();
317 decoder_.reset();
318 in_client_.reset();
319 initialization_thread_checker_.reset();
toyoshim8e7d6e02015-10-06 08:47:17 -0700320}
321
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000322void MidiManagerAlsa::DispatchSendMidiData(MidiManagerClient* client,
Avi Drissman3528fd02015-12-18 20:11:31 -0500323 uint32_t port_index,
324 const std::vector<uint8_t>& data,
dnicoara@chromium.org9f2a6f02014-01-03 21:25:00 +0000325 double timestamp) {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000326 base::TimeDelta delay;
327 if (timestamp != 0.0) {
328 base::TimeTicks time_to_send =
329 base::TimeTicks() + base::TimeDelta::FromMicroseconds(
agoodebd4be9b2015-03-16 19:17:25 -0700330 timestamp * base::Time::kMicrosecondsPerSecond);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000331 delay = std::max(time_to_send - base::TimeTicks::Now(), base::TimeDelta());
332 }
333
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000334 send_thread_.message_loop()->PostDelayedTask(
agoodebd4be9b2015-03-16 19:17:25 -0700335 FROM_HERE, base::Bind(&MidiManagerAlsa::SendMidiData,
336 base::Unretained(this), port_index, data),
337 delay);
agoode@chromium.org25227512014-06-08 05:12:05 +0000338
339 // Acknowledge send.
340 send_thread_.message_loop()->PostTask(
agoodead116b22015-12-07 00:00:35 -0800341 FROM_HERE, base::Bind(&MidiManagerAlsa::AccumulateMidiBytesSent,
342 base::Unretained(this), client, data.size()));
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000343}
344
agooded87fc0f2015-05-21 08:29:31 -0700345MidiManagerAlsa::MidiPort::Id::Id() = default;
346
347MidiManagerAlsa::MidiPort::Id::Id(const std::string& bus,
348 const std::string& vendor_id,
349 const std::string& model_id,
350 const std::string& usb_interface_num,
351 const std::string& serial)
352 : bus_(bus),
353 vendor_id_(vendor_id),
354 model_id_(model_id),
355 usb_interface_num_(usb_interface_num),
356 serial_(serial) {
357}
358
359MidiManagerAlsa::MidiPort::Id::Id(const Id&) = default;
360
361MidiManagerAlsa::MidiPort::Id::~Id() = default;
362
363bool MidiManagerAlsa::MidiPort::Id::operator==(const Id& rhs) const {
364 return (bus_ == rhs.bus_) && (vendor_id_ == rhs.vendor_id_) &&
365 (model_id_ == rhs.model_id_) &&
366 (usb_interface_num_ == rhs.usb_interface_num_) &&
367 (serial_ == rhs.serial_);
368}
369
370bool MidiManagerAlsa::MidiPort::Id::empty() const {
371 return bus_.empty() && vendor_id_.empty() && model_id_.empty() &&
372 usb_interface_num_.empty() && serial_.empty();
373}
374
agoode99d63292015-04-13 08:39:25 -0700375MidiManagerAlsa::MidiPort::MidiPort(const std::string& path,
agooded87fc0f2015-05-21 08:29:31 -0700376 const Id& id,
agoode99d63292015-04-13 08:39:25 -0700377 int client_id,
378 int port_id,
379 int midi_device,
380 const std::string& client_name,
381 const std::string& port_name,
382 const std::string& manufacturer,
383 const std::string& version,
384 Type type)
385 : id_(id),
386 midi_device_(midi_device),
387 type_(type),
388 path_(path),
389 client_id_(client_id),
390 port_id_(port_id),
391 client_name_(client_name),
392 port_name_(port_name),
393 manufacturer_(manufacturer),
agoode5a1aa112015-06-21 20:51:00 -0700394 version_(version) {
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000395}
396
agoodeb0582872015-05-20 05:22:24 -0700397MidiManagerAlsa::MidiPort::~MidiPort() = default;
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000398
agoode99d63292015-04-13 08:39:25 -0700399// Note: keep synchronized with the MidiPort::Match* methods.
400scoped_ptr<base::Value> MidiManagerAlsa::MidiPort::Value() const {
401 scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue);
402
403 std::string type;
404 switch (type_) {
405 case Type::kInput:
406 type = "input";
407 break;
agoode99d63292015-04-13 08:39:25 -0700408 case Type::kOutput:
409 type = "output";
410 break;
411 }
412 value->SetString("type", type);
413 SetStringIfNonEmpty(value.get(), "path", path_);
agoode99d63292015-04-13 08:39:25 -0700414 SetStringIfNonEmpty(value.get(), "clientName", client_name_);
415 SetStringIfNonEmpty(value.get(), "portName", port_name_);
416 value->SetInteger("clientId", client_id_);
417 value->SetInteger("portId", port_id_);
418 value->SetInteger("midiDevice", midi_device_);
419
agooded87fc0f2015-05-21 08:29:31 -0700420 // Flatten id fields.
421 SetStringIfNonEmpty(value.get(), "bus", id_.bus());
422 SetStringIfNonEmpty(value.get(), "vendorId", id_.vendor_id());
423 SetStringIfNonEmpty(value.get(), "modelId", id_.model_id());
424 SetStringIfNonEmpty(value.get(), "usbInterfaceNum", id_.usb_interface_num());
425 SetStringIfNonEmpty(value.get(), "serial", id_.serial());
426
dchengc2aeece2015-12-27 00:54:00 -0800427 return std::move(value);
agoodebd4be9b2015-03-16 19:17:25 -0700428}
agoode@chromium.org25227512014-06-08 05:12:05 +0000429
agoode99d63292015-04-13 08:39:25 -0700430std::string MidiManagerAlsa::MidiPort::JSONValue() const {
431 std::string json;
432 JSONStringValueSerializer serializer(&json);
433 serializer.Serialize(*Value().get());
434 return json;
agoodef212b2a2015-03-19 12:53:23 -0700435}
436
agoode99d63292015-04-13 08:39:25 -0700437// TODO(agoode): Do not use SHA256 here. Instead store a persistent
438// mapping and just use a UUID or other random string.
439// http://crbug.com/465320
440std::string MidiManagerAlsa::MidiPort::OpaqueKey() const {
Avi Drissman3528fd02015-12-18 20:11:31 -0500441 uint8_t hash[crypto::kSHA256Length];
agoode99d63292015-04-13 08:39:25 -0700442 crypto::SHA256HashString(JSONValue(), &hash, sizeof(hash));
443 return base::HexEncode(&hash, sizeof(hash));
agoodebd4be9b2015-03-16 19:17:25 -0700444}
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000445
agoode99d63292015-04-13 08:39:25 -0700446bool MidiManagerAlsa::MidiPort::MatchConnected(const MidiPort& query) const {
447 // Matches on:
448 // connected == true
449 // type
450 // path
451 // id
452 // client_id
453 // port_id
454 // midi_device
455 // client_name
456 // port_name
457 return connected() && (type() == query.type()) && (path() == query.path()) &&
458 (id() == query.id()) && (client_id() == query.client_id()) &&
459 (port_id() == query.port_id()) &&
460 (midi_device() == query.midi_device()) &&
461 (client_name() == query.client_name()) &&
462 (port_name() == query.port_name());
agoodebd4be9b2015-03-16 19:17:25 -0700463}
464
agoode99d63292015-04-13 08:39:25 -0700465bool MidiManagerAlsa::MidiPort::MatchCardPass1(const MidiPort& query) const {
466 // Matches on:
467 // connected == false
468 // type
469 // path
470 // id
471 // port_id
472 // midi_device
473 return MatchCardPass2(query) && (path() == query.path());
agoodebd4be9b2015-03-16 19:17:25 -0700474}
475
agoode99d63292015-04-13 08:39:25 -0700476bool MidiManagerAlsa::MidiPort::MatchCardPass2(const MidiPort& query) const {
477 // Matches on:
478 // connected == false
479 // type
480 // id
481 // port_id
482 // midi_device
483 return !connected() && (type() == query.type()) && (id() == query.id()) &&
484 (port_id() == query.port_id()) &&
485 (midi_device() == query.midi_device());
agoodef212b2a2015-03-19 12:53:23 -0700486}
487
agoode99d63292015-04-13 08:39:25 -0700488bool MidiManagerAlsa::MidiPort::MatchNoCardPass1(const MidiPort& query) const {
489 // Matches on:
490 // connected == false
491 // type
492 // path.empty(), for both this and query
493 // id.empty(), for both this and query
494 // client_id
495 // port_id
496 // client_name
497 // port_name
498 // midi_device == -1, for both this and query
499 return MatchNoCardPass2(query) && (client_id() == query.client_id());
agoodef212b2a2015-03-19 12:53:23 -0700500}
501
agoode99d63292015-04-13 08:39:25 -0700502bool MidiManagerAlsa::MidiPort::MatchNoCardPass2(const MidiPort& query) const {
503 // Matches on:
504 // connected == false
505 // type
506 // path.empty(), for both this and query
507 // id.empty(), for both this and query
508 // port_id
509 // client_name
510 // port_name
511 // midi_device == -1, for both this and query
512 return !connected() && (type() == query.type()) && path().empty() &&
513 query.path().empty() && id().empty() && query.id().empty() &&
514 (port_id() == query.port_id()) &&
515 (client_name() == query.client_name()) &&
516 (port_name() == query.port_name()) && (midi_device() == -1) &&
517 (query.midi_device() == -1);
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000518}
519
agoodeb0582872015-05-20 05:22:24 -0700520MidiManagerAlsa::MidiPortStateBase::~MidiPortStateBase() = default;
agoode99d63292015-04-13 08:39:25 -0700521
522MidiManagerAlsa::MidiPortStateBase::iterator
523MidiManagerAlsa::MidiPortStateBase::Find(
524 const MidiManagerAlsa::MidiPort& port) {
525 auto result = FindConnected(port);
526 if (result == end())
527 result = FindDisconnected(port);
528 return result;
529}
530
531MidiManagerAlsa::MidiPortStateBase::iterator
532MidiManagerAlsa::MidiPortStateBase::FindConnected(
533 const MidiManagerAlsa::MidiPort& port) {
534 // Exact match required for connected ports.
agoode5ebc4932015-12-01 08:25:12 -0800535 auto it = std::find_if(
536 ports_.begin(), ports_.end(),
537 [&port](scoped_ptr<MidiPort>& p) { return p->MatchConnected(port); });
agoode99d63292015-04-13 08:39:25 -0700538 return it;
539}
540
541MidiManagerAlsa::MidiPortStateBase::iterator
542MidiManagerAlsa::MidiPortStateBase::FindDisconnected(
543 const MidiManagerAlsa::MidiPort& port) {
544 // Always match on:
545 // type
546 // Possible things to match on:
547 // path
548 // id
549 // client_id
550 // port_id
551 // midi_device
552 // client_name
553 // port_name
554
555 if (!port.path().empty()) {
556 // If path is present, then we have a card-based client.
557
558 // Pass 1. Match on path, id, midi_device, port_id.
559 // This is the best possible match for hardware card-based clients.
560 // This will also match the empty id correctly for devices without an id.
agoode5ebc4932015-12-01 08:25:12 -0800561 auto it = std::find_if(
562 ports_.begin(), ports_.end(),
563 [&port](scoped_ptr<MidiPort>& p) { return p->MatchCardPass1(port); });
agoode99d63292015-04-13 08:39:25 -0700564 if (it != ports_.end())
565 return it;
566
567 if (!port.id().empty()) {
568 // Pass 2. Match on id, midi_device, port_id.
569 // This will give us a high-confidence match when a user moves a device to
570 // another USB/Firewire/Thunderbolt/etc port, but only works if the device
571 // has a hardware id.
agoode5ebc4932015-12-01 08:25:12 -0800572 it = std::find_if(
573 ports_.begin(), ports_.end(),
574 [&port](scoped_ptr<MidiPort>& p) { return p->MatchCardPass2(port); });
agoode99d63292015-04-13 08:39:25 -0700575 if (it != ports_.end())
576 return it;
577 }
578 } else {
579 // Else, we have a non-card-based client.
580 // Pass 1. Match on client_id, port_id, client_name, port_name.
581 // This will give us a reasonably good match.
agoode5ebc4932015-12-01 08:25:12 -0800582 auto it = std::find_if(
583 ports_.begin(), ports_.end(),
584 [&port](scoped_ptr<MidiPort>& p) { return p->MatchNoCardPass1(port); });
agoode99d63292015-04-13 08:39:25 -0700585 if (it != ports_.end())
586 return it;
587
588 // Pass 2. Match on port_id, client_name, port_name.
589 // This is weaker but similar to pass 2 in the hardware card-based clients
590 // match.
agoode5ebc4932015-12-01 08:25:12 -0800591 it = std::find_if(
592 ports_.begin(), ports_.end(),
593 [&port](scoped_ptr<MidiPort>& p) { return p->MatchNoCardPass2(port); });
agoode99d63292015-04-13 08:39:25 -0700594 if (it != ports_.end())
595 return it;
596 }
597
598 // No match.
599 return ports_.end();
600}
601
agoodeb0582872015-05-20 05:22:24 -0700602MidiManagerAlsa::MidiPortStateBase::MidiPortStateBase() = default;
agoode99d63292015-04-13 08:39:25 -0700603
agoodedf1b9ff2015-06-25 18:14:50 -0700604MidiManagerAlsa::MidiPortState::MidiPortState() = default;
agoode99d63292015-04-13 08:39:25 -0700605
Avi Drissman3528fd02015-12-18 20:11:31 -0500606uint32_t MidiManagerAlsa::MidiPortState::push_back(scoped_ptr<MidiPort> port) {
agoode99d63292015-04-13 08:39:25 -0700607 // Add the web midi index.
Avi Drissman3528fd02015-12-18 20:11:31 -0500608 uint32_t web_port_index = 0;
agoode99d63292015-04-13 08:39:25 -0700609 switch (port->type()) {
610 case MidiPort::Type::kInput:
611 web_port_index = num_input_ports_++;
612 break;
613 case MidiPort::Type::kOutput:
614 web_port_index = num_output_ports_++;
615 break;
616 }
617 port->set_web_port_index(web_port_index);
dchengc2aeece2015-12-27 00:54:00 -0800618 MidiPortStateBase::push_back(std::move(port));
agoode99d63292015-04-13 08:39:25 -0700619 return web_port_index;
620}
621
agoodedf1b9ff2015-06-25 18:14:50 -0700622MidiManagerAlsa::AlsaSeqState::AlsaSeqState() = default;
agoode99d63292015-04-13 08:39:25 -0700623
agoodeb0582872015-05-20 05:22:24 -0700624MidiManagerAlsa::AlsaSeqState::~AlsaSeqState() = default;
agoode99d63292015-04-13 08:39:25 -0700625
626void MidiManagerAlsa::AlsaSeqState::ClientStart(int client_id,
627 const std::string& client_name,
628 snd_seq_client_type_t type) {
629 ClientExit(client_id);
limasdfe59d0392015-11-19 20:28:57 -0800630 clients_.insert(std::make_pair(
631 client_id, make_scoped_ptr(new Client(client_name, type))));
agoodeb09423b2015-05-11 11:39:57 -0700632 if (IsCardClient(type, client_id))
633 ++card_client_count_;
agoode99d63292015-04-13 08:39:25 -0700634}
635
636bool MidiManagerAlsa::AlsaSeqState::ClientStarted(int client_id) {
637 return clients_.find(client_id) != clients_.end();
638}
639
640void MidiManagerAlsa::AlsaSeqState::ClientExit(int client_id) {
641 auto it = clients_.find(client_id);
642 if (it != clients_.end()) {
agoodeb09423b2015-05-11 11:39:57 -0700643 if (IsCardClient(it->second->type(), client_id))
644 --card_client_count_;
agoode99d63292015-04-13 08:39:25 -0700645 clients_.erase(it);
646 }
647}
648
649void MidiManagerAlsa::AlsaSeqState::PortStart(
650 int client_id,
651 int port_id,
652 const std::string& port_name,
653 MidiManagerAlsa::AlsaSeqState::PortDirection direction,
654 bool midi) {
655 auto it = clients_.find(client_id);
656 if (it != clients_.end())
657 it->second->AddPort(port_id,
agoode5a1aa112015-06-21 20:51:00 -0700658 make_scoped_ptr(new Port(port_name, direction, midi)));
agoode99d63292015-04-13 08:39:25 -0700659}
660
661void MidiManagerAlsa::AlsaSeqState::PortExit(int client_id, int port_id) {
662 auto it = clients_.find(client_id);
663 if (it != clients_.end())
664 it->second->RemovePort(port_id);
665}
666
667snd_seq_client_type_t MidiManagerAlsa::AlsaSeqState::ClientType(
668 int client_id) const {
669 auto it = clients_.find(client_id);
670 if (it == clients_.end())
671 return SND_SEQ_USER_CLIENT;
672 return it->second->type();
673}
674
675scoped_ptr<MidiManagerAlsa::TemporaryMidiPortState>
agoodeb09423b2015-05-11 11:39:57 -0700676MidiManagerAlsa::AlsaSeqState::ToMidiPortState(const AlsaCardMap& alsa_cards) {
agoode99d63292015-04-13 08:39:25 -0700677 scoped_ptr<MidiManagerAlsa::TemporaryMidiPortState> midi_ports(
678 new TemporaryMidiPortState);
agoodeb09423b2015-05-11 11:39:57 -0700679 auto card_it = alsa_cards.begin();
agoode99d63292015-04-13 08:39:25 -0700680
agoodeb09423b2015-05-11 11:39:57 -0700681 int card_midi_device = -1;
agoode99d63292015-04-13 08:39:25 -0700682 for (const auto& client_pair : clients_) {
683 int client_id = client_pair.first;
limasdfe59d0392015-11-19 20:28:57 -0800684 const auto& client = client_pair.second.get();
agoode99d63292015-04-13 08:39:25 -0700685
686 // Get client metadata.
687 const std::string client_name = client->name();
688 std::string manufacturer;
689 std::string driver;
690 std::string path;
agooded87fc0f2015-05-21 08:29:31 -0700691 MidiPort::Id id;
agoode99d63292015-04-13 08:39:25 -0700692 std::string card_name;
693 std::string card_longname;
694 int midi_device = -1;
695
agoodeb09423b2015-05-11 11:39:57 -0700696 if (IsCardClient(client->type(), client_id)) {
697 auto& card = card_it->second;
698 if (card_midi_device == -1)
699 card_midi_device = 0;
700
701 manufacturer = card->manufacturer();
agooded87fc0f2015-05-21 08:29:31 -0700702 path = card->path();
703 id = MidiPort::Id(card->bus(), card->vendor_id(), card->model_id(),
704 card->usb_interface_num(), card->serial());
705 card_name = card->name();
706 card_longname = card->longname();
agoodeb09423b2015-05-11 11:39:57 -0700707 midi_device = card_midi_device;
708
709 ++card_midi_device;
710 if (card_midi_device >= card->midi_device_count()) {
711 card_midi_device = -1;
712 ++card_it;
713 }
714 }
715
agoode99d63292015-04-13 08:39:25 -0700716 for (const auto& port_pair : *client) {
717 int port_id = port_pair.first;
718 const auto& port = port_pair.second;
719
720 if (port->midi()) {
721 std::string version;
722 if (!driver.empty()) {
723 version = driver + " / ";
724 }
725 version +=
726 base::StringPrintf("ALSA library version %d.%d.%d", SND_LIB_MAJOR,
727 SND_LIB_MINOR, SND_LIB_SUBMINOR);
728 PortDirection direction = port->direction();
729 if (direction == PortDirection::kInput ||
730 direction == PortDirection::kDuplex) {
agoode5ebc4932015-12-01 08:25:12 -0800731 midi_ports->push_back(make_scoped_ptr(new MidiPort(
agoode99d63292015-04-13 08:39:25 -0700732 path, id, client_id, port_id, midi_device, client->name(),
733 port->name(), manufacturer, version, MidiPort::Type::kInput)));
734 }
735 if (direction == PortDirection::kOutput ||
736 direction == PortDirection::kDuplex) {
agoode5ebc4932015-12-01 08:25:12 -0800737 midi_ports->push_back(make_scoped_ptr(new MidiPort(
agoode99d63292015-04-13 08:39:25 -0700738 path, id, client_id, port_id, midi_device, client->name(),
739 port->name(), manufacturer, version, MidiPort::Type::kOutput)));
740 }
741 }
742 }
743 }
744
dchengc2aeece2015-12-27 00:54:00 -0800745 return midi_ports;
agoode99d63292015-04-13 08:39:25 -0700746}
747
748MidiManagerAlsa::AlsaSeqState::Port::Port(
749 const std::string& name,
750 MidiManagerAlsa::AlsaSeqState::PortDirection direction,
751 bool midi)
752 : name_(name), direction_(direction), midi_(midi) {
753}
754
agoodeb0582872015-05-20 05:22:24 -0700755MidiManagerAlsa::AlsaSeqState::Port::~Port() = default;
agoode99d63292015-04-13 08:39:25 -0700756
757MidiManagerAlsa::AlsaSeqState::Client::Client(const std::string& name,
758 snd_seq_client_type_t type)
mgiucad9af8452015-06-25 02:11:57 -0700759 : name_(name), type_(type) {
agoode99d63292015-04-13 08:39:25 -0700760}
761
agoodeb0582872015-05-20 05:22:24 -0700762MidiManagerAlsa::AlsaSeqState::Client::~Client() = default;
agoode99d63292015-04-13 08:39:25 -0700763
764void MidiManagerAlsa::AlsaSeqState::Client::AddPort(int addr,
765 scoped_ptr<Port> port) {
limasdfe59d0392015-11-19 20:28:57 -0800766 ports_[addr] = std::move(port);
agoode99d63292015-04-13 08:39:25 -0700767}
768
769void MidiManagerAlsa::AlsaSeqState::Client::RemovePort(int addr) {
mgiucad9af8452015-06-25 02:11:57 -0700770 ports_.erase(addr);
agoode99d63292015-04-13 08:39:25 -0700771}
772
773MidiManagerAlsa::AlsaSeqState::Client::PortMap::const_iterator
774MidiManagerAlsa::AlsaSeqState::Client::begin() const {
775 return ports_.begin();
776}
777
778MidiManagerAlsa::AlsaSeqState::Client::PortMap::const_iterator
779MidiManagerAlsa::AlsaSeqState::Client::end() const {
780 return ports_.end();
agoodeaf6e9f52015-03-24 10:23:49 -0700781}
782
agoodeb09423b2015-05-11 11:39:57 -0700783MidiManagerAlsa::AlsaCard::AlsaCard(udev_device* dev,
agooded87fc0f2015-05-21 08:29:31 -0700784 const std::string& name,
785 const std::string& longname,
786 const std::string& driver,
agoodeb09423b2015-05-11 11:39:57 -0700787 int midi_device_count)
agooded87fc0f2015-05-21 08:29:31 -0700788 : name_(name),
789 longname_(longname),
790 driver_(driver),
791 path_(device::UdevDeviceGetPropertyValue(dev, kUdevIdPath)),
792 bus_(device::UdevDeviceGetPropertyValue(dev, kUdevIdBus)),
793 vendor_id_(
794 UdevDeviceGetPropertyOrSysattr(dev, kUdevIdVendorId, kSysattrVendor)),
795 model_id_(
796 UdevDeviceGetPropertyOrSysattr(dev, kUdevIdModelId, kSysattrModel)),
797 usb_interface_num_(
798 device::UdevDeviceGetPropertyValue(dev, kUdevIdUsbInterfaceNum)),
799 serial_(UdevDeviceGetPropertyOrSysattr(dev,
800 kUdevIdSerialShort,
801 kSysattrGuid)),
802 midi_device_count_(midi_device_count),
803 manufacturer_(ExtractManufacturerString(
804 GetVendor(dev),
805 vendor_id_,
806 device::UdevDeviceGetPropertyValue(dev, kUdevIdVendorFromDatabase),
807 name,
808 longname)) {
agoodeb09423b2015-05-11 11:39:57 -0700809}
810
agoodeb0582872015-05-20 05:22:24 -0700811MidiManagerAlsa::AlsaCard::~AlsaCard() = default;
agoodeb09423b2015-05-11 11:39:57 -0700812
agoode55a8b522015-03-08 12:40:17 -0700813// static
agoodeb09423b2015-05-11 11:39:57 -0700814std::string MidiManagerAlsa::AlsaCard::ExtractManufacturerString(
agoode5e4e9cd2015-03-09 12:34:24 -0700815 const std::string& udev_id_vendor,
agoode55a8b522015-03-08 12:40:17 -0700816 const std::string& udev_id_vendor_id,
817 const std::string& udev_id_vendor_from_database,
818 const std::string& alsa_name,
819 const std::string& alsa_longname) {
820 // Let's try to determine the manufacturer. Here is the ordered preference
821 // in extraction:
agoodef212b2a2015-03-19 12:53:23 -0700822 // 1. Vendor name from the hardware device string, from udev properties
823 // or sysattrs.
agoode5e4e9cd2015-03-09 12:34:24 -0700824 // 2. Vendor name from the udev database (property ID_VENDOR_FROM_DATABASE).
agoode55a8b522015-03-08 12:40:17 -0700825 // 3. Heuristic from ALSA.
826
agoodee83758c2015-03-23 22:07:54 -0700827 // Is the vendor string present and not just the vendor hex id?
828 if (!udev_id_vendor.empty() && (udev_id_vendor != udev_id_vendor_id)) {
agoode55a8b522015-03-08 12:40:17 -0700829 return udev_id_vendor;
830 }
831
832 // Is there a vendor string in the hardware database?
833 if (!udev_id_vendor_from_database.empty()) {
834 return udev_id_vendor_from_database;
835 }
836
837 // Ok, udev gave us nothing useful, or was unavailable. So try a heuristic.
838 // We assume that card longname is in the format of
839 // "<manufacturer> <name> at <bus>". Otherwise, we give up to detect
840 // a manufacturer name here.
841 size_t at_index = alsa_longname.rfind(" at ");
agoodef212b2a2015-03-19 12:53:23 -0700842 if (at_index && at_index != std::string::npos) {
agoode55a8b522015-03-08 12:40:17 -0700843 size_t name_index = alsa_longname.rfind(alsa_name, at_index - 1);
agoodef212b2a2015-03-19 12:53:23 -0700844 if (name_index && name_index != std::string::npos)
agoode55a8b522015-03-08 12:40:17 -0700845 return alsa_longname.substr(0, name_index - 1);
846 }
847
848 // Failure.
849 return "";
850}
851
Avi Drissman3528fd02015-12-18 20:11:31 -0500852void MidiManagerAlsa::SendMidiData(uint32_t port_index,
853 const std::vector<uint8_t>& data) {
skyostil93e2ec22015-06-17 08:49:09 -0700854 DCHECK(send_thread_.task_runner()->BelongsToCurrentThread());
agoodebd4be9b2015-03-16 19:17:25 -0700855
agoodef212b2a2015-03-19 12:53:23 -0700856 snd_midi_event_t* encoder;
857 snd_midi_event_new(kSendBufferSize, &encoder);
agoode5a1aa112015-06-21 20:51:00 -0700858 for (const auto datum : data) {
agoodebd4be9b2015-03-16 19:17:25 -0700859 snd_seq_event_t event;
agoode5a1aa112015-06-21 20:51:00 -0700860 int result = snd_midi_event_encode_byte(encoder, datum, &event);
agoodebd4be9b2015-03-16 19:17:25 -0700861 if (result == 1) {
862 // Full event, send it.
agoode99d63292015-04-13 08:39:25 -0700863 base::AutoLock lock(out_ports_lock_);
864 auto it = out_ports_.find(port_index);
865 if (it != out_ports_.end()) {
866 snd_seq_ev_set_source(&event, it->second);
867 snd_seq_ev_set_subs(&event);
868 snd_seq_ev_set_direct(&event);
agoode5a1aa112015-06-21 20:51:00 -0700869 snd_seq_event_output_direct(out_client_.get(), &event);
agoode99d63292015-04-13 08:39:25 -0700870 }
agoodebd4be9b2015-03-16 19:17:25 -0700871 }
872 }
agoodef212b2a2015-03-19 12:53:23 -0700873 snd_midi_event_free(encoder);
agoodebd4be9b2015-03-16 19:17:25 -0700874}
875
876void MidiManagerAlsa::ScheduleEventLoop() {
877 event_thread_.message_loop()->PostTask(
878 FROM_HERE,
879 base::Bind(&MidiManagerAlsa::EventLoop, base::Unretained(this)));
880}
881
882void MidiManagerAlsa::EventLoop() {
agoode975043d2015-05-11 00:46:17 -0700883 bool loop_again = true;
agoodebd4be9b2015-03-16 19:17:25 -0700884
agoode975043d2015-05-11 00:46:17 -0700885 struct pollfd pfd[2];
agoode5a1aa112015-06-21 20:51:00 -0700886 snd_seq_poll_descriptors(in_client_.get(), &pfd[0], 1, POLLIN);
agoode975043d2015-05-11 00:46:17 -0700887 pfd[1].fd = device::udev_monitor_get_fd(udev_monitor_.get());
888 pfd[1].events = POLLIN;
agoodebd4be9b2015-03-16 19:17:25 -0700889
agoode975043d2015-05-11 00:46:17 -0700890 int err = HANDLE_EINTR(poll(pfd, arraysize(pfd), -1));
891 if (err < 0) {
brettwf7f870f2015-06-09 11:05:24 -0700892 VLOG(1) << "poll fails: " << base::safe_strerror(errno);
agoode975043d2015-05-11 00:46:17 -0700893 loop_again = false;
agoodeaf6e9f52015-03-24 10:23:49 -0700894 } else {
agoode975043d2015-05-11 00:46:17 -0700895 if (pfd[0].revents & POLLIN) {
896 // Read available incoming MIDI data.
897 int remaining;
898 double timestamp =
899 (base::TimeTicks::Now() - base::TimeTicks()).InSecondsF();
900 do {
901 snd_seq_event_t* event;
agoode5a1aa112015-06-21 20:51:00 -0700902 err = snd_seq_event_input(in_client_.get(), &event);
903 remaining = snd_seq_event_input_pending(in_client_.get(), 0);
agoode975043d2015-05-11 00:46:17 -0700904
905 if (err == -ENOSPC) {
906 // Handle out of space error.
907 VLOG(1) << "snd_seq_event_input detected buffer overrun";
908 // We've lost events: check another way to see if we need to shut
909 // down.
910 base::AutoLock lock(shutdown_lock_);
911 if (event_thread_shutdown_)
912 loop_again = false;
913 } else if (err == -EAGAIN) {
914 // We've read all the data.
915 } else if (err < 0) {
916 // Handle other errors.
917 VLOG(1) << "snd_seq_event_input fails: " << snd_strerror(err);
918 // TODO(agoode): Use RecordAction() or similar to log this.
919 loop_again = false;
920 } else if (event->source.client == SND_SEQ_CLIENT_SYSTEM &&
921 event->source.port == SND_SEQ_PORT_SYSTEM_ANNOUNCE) {
922 // Handle announce events.
923 switch (event->type) {
924 case SND_SEQ_EVENT_PORT_START:
925 // Don't use SND_SEQ_EVENT_CLIENT_START because the
926 // client name may not be set by the time we query
927 // it. It should be set by the time ports are made.
928 ProcessClientStartEvent(event->data.addr.client);
929 ProcessPortStartEvent(event->data.addr);
930 break;
931 case SND_SEQ_EVENT_CLIENT_EXIT:
932 // Check for disconnection of our "out" client. This means "shut
933 // down".
934 if (event->data.addr.client == out_client_id_) {
935 loop_again = false;
936 remaining = 0;
937 } else
938 ProcessClientExitEvent(event->data.addr);
939 break;
940 case SND_SEQ_EVENT_PORT_EXIT:
941 ProcessPortExitEvent(event->data.addr);
942 break;
943 }
944 } else {
945 // Normal operation.
946 ProcessSingleEvent(event, timestamp);
947 }
948 } while (remaining > 0);
949 }
950 if (pfd[1].revents & POLLIN) {
951 device::ScopedUdevDevicePtr dev(
952 device::udev_monitor_receive_device(udev_monitor_.get()));
953 if (dev.get())
954 ProcessUdevEvent(dev.get());
955 else
956 VLOG(1) << "udev_monitor_receive_device fails";
957 }
agoodebd4be9b2015-03-16 19:17:25 -0700958 }
959
agoodebd4be9b2015-03-16 19:17:25 -0700960 // Do again.
agoode975043d2015-05-11 00:46:17 -0700961 if (loop_again)
962 ScheduleEventLoop();
agoodebd4be9b2015-03-16 19:17:25 -0700963}
964
965void MidiManagerAlsa::ProcessSingleEvent(snd_seq_event_t* event,
966 double timestamp) {
agoode99d63292015-04-13 08:39:25 -0700967 auto source_it =
968 source_map_.find(AddrToInt(event->source.client, event->source.port));
agoodebd4be9b2015-03-16 19:17:25 -0700969 if (source_it != source_map_.end()) {
Avi Drissman3528fd02015-12-18 20:11:31 -0500970 uint32_t source = source_it->second;
agoodebd4be9b2015-03-16 19:17:25 -0700971 if (event->type == SND_SEQ_EVENT_SYSEX) {
972 // Special! Variable-length sysex.
Avi Drissman3528fd02015-12-18 20:11:31 -0500973 ReceiveMidiData(source, static_cast<const uint8_t*>(event->data.ext.ptr),
agoodebd4be9b2015-03-16 19:17:25 -0700974 event->data.ext.len, timestamp);
975 } else {
976 // Otherwise, decode this and send that on.
977 unsigned char buf[12];
agoode5a1aa112015-06-21 20:51:00 -0700978 long count =
979 snd_midi_event_decode(decoder_.get(), buf, sizeof(buf), event);
agoodebd4be9b2015-03-16 19:17:25 -0700980 if (count <= 0) {
981 if (count != -ENOENT) {
982 // ENOENT means that it's not a MIDI message, which is not an
983 // error, but other negative values are errors for us.
984 VLOG(1) << "snd_midi_event_decoder fails " << snd_strerror(count);
985 // TODO(agoode): Record this failure.
986 }
987 } else {
988 ReceiveMidiData(source, buf, count, timestamp);
989 }
990 }
991 }
992}
993
agoode99d63292015-04-13 08:39:25 -0700994void MidiManagerAlsa::ProcessClientStartEvent(int client_id) {
995 // Ignore if client is already started.
996 if (alsa_seq_state_.ClientStarted(client_id))
997 return;
998
999 snd_seq_client_info_t* client_info;
1000 snd_seq_client_info_alloca(&client_info);
agoode5a1aa112015-06-21 20:51:00 -07001001 int err =
1002 snd_seq_get_any_client_info(in_client_.get(), client_id, client_info);
agoode99d63292015-04-13 08:39:25 -07001003 if (err != 0)
1004 return;
1005
1006 // Skip our own clients.
1007 if ((client_id == in_client_id_) || (client_id == out_client_id_))
1008 return;
1009
1010 // Update our view of ALSA seq state.
1011 alsa_seq_state_.ClientStart(client_id,
1012 snd_seq_client_info_get_name(client_info),
1013 snd_seq_client_info_get_type(client_info));
1014
1015 // Generate Web MIDI events.
1016 UpdatePortStateAndGenerateEvents();
1017}
1018
1019void MidiManagerAlsa::ProcessPortStartEvent(const snd_seq_addr_t& addr) {
1020 snd_seq_port_info_t* port_info;
1021 snd_seq_port_info_alloca(&port_info);
agoode5a1aa112015-06-21 20:51:00 -07001022 int err = snd_seq_get_any_port_info(in_client_.get(), addr.client, addr.port,
1023 port_info);
agoode99d63292015-04-13 08:39:25 -07001024 if (err != 0)
1025 return;
1026
1027 unsigned int caps = snd_seq_port_info_get_capability(port_info);
1028 bool input = (caps & kRequiredInputPortCaps) == kRequiredInputPortCaps;
1029 bool output = (caps & kRequiredOutputPortCaps) == kRequiredOutputPortCaps;
1030 AlsaSeqState::PortDirection direction;
1031 if (input && output)
1032 direction = AlsaSeqState::PortDirection::kDuplex;
1033 else if (input)
1034 direction = AlsaSeqState::PortDirection::kInput;
1035 else if (output)
1036 direction = AlsaSeqState::PortDirection::kOutput;
1037 else
1038 return;
1039
1040 // Update our view of ALSA seq state.
1041 alsa_seq_state_.PortStart(
1042 addr.client, addr.port, snd_seq_port_info_get_name(port_info), direction,
1043 snd_seq_port_info_get_type(port_info) & SND_SEQ_PORT_TYPE_MIDI_GENERIC);
1044 // Generate Web MIDI events.
1045 UpdatePortStateAndGenerateEvents();
1046}
1047
1048void MidiManagerAlsa::ProcessClientExitEvent(const snd_seq_addr_t& addr) {
1049 // Update our view of ALSA seq state.
1050 alsa_seq_state_.ClientExit(addr.client);
1051 // Generate Web MIDI events.
1052 UpdatePortStateAndGenerateEvents();
1053}
1054
1055void MidiManagerAlsa::ProcessPortExitEvent(const snd_seq_addr_t& addr) {
1056 // Update our view of ALSA seq state.
1057 alsa_seq_state_.PortExit(addr.client, addr.port);
1058 // Generate Web MIDI events.
1059 UpdatePortStateAndGenerateEvents();
1060}
1061
agoode975043d2015-05-11 00:46:17 -07001062void MidiManagerAlsa::ProcessUdevEvent(udev_device* dev) {
1063 // Only card devices have this property set, and only when they are
1064 // fully initialized.
1065 if (!device::udev_device_get_property_value(dev,
1066 kUdevPropertySoundInitialized))
1067 return;
1068
1069 // Get the action. If no action, then we are doing first time enumeration
1070 // and the device is treated as new.
1071 const char* action = device::udev_device_get_action(dev);
1072 if (!action)
1073 action = kUdevActionChange;
1074
1075 if (strcmp(action, kUdevActionChange) == 0) {
agoodeb09423b2015-05-11 11:39:57 -07001076 AddCard(dev);
1077 // Generate Web MIDI events.
1078 UpdatePortStateAndGenerateEvents();
agoode975043d2015-05-11 00:46:17 -07001079 } else if (strcmp(action, kUdevActionRemove) == 0) {
agoodeb09423b2015-05-11 11:39:57 -07001080 RemoveCard(GetCardNumber(dev));
1081 // Generate Web MIDI events.
1082 UpdatePortStateAndGenerateEvents();
agoode975043d2015-05-11 00:46:17 -07001083 }
1084}
1085
agoodeb09423b2015-05-11 11:39:57 -07001086void MidiManagerAlsa::AddCard(udev_device* dev) {
1087 int number = GetCardNumber(dev);
1088 if (number == -1)
1089 return;
1090
1091 RemoveCard(number);
1092
1093 snd_ctl_card_info_t* card;
1094 snd_hwdep_info_t* hwdep;
1095 snd_ctl_card_info_alloca(&card);
1096 snd_hwdep_info_alloca(&hwdep);
1097 const std::string id = base::StringPrintf("hw:CARD=%i", number);
1098 snd_ctl_t* handle;
1099 int err = snd_ctl_open(&handle, id.c_str(), 0);
1100 if (err != 0) {
1101 VLOG(1) << "snd_ctl_open fails: " << snd_strerror(err);
1102 return;
1103 }
1104 err = snd_ctl_card_info(handle, card);
1105 if (err != 0) {
1106 VLOG(1) << "snd_ctl_card_info fails: " << snd_strerror(err);
1107 snd_ctl_close(handle);
1108 return;
1109 }
1110 std::string name = snd_ctl_card_info_get_name(card);
1111 std::string longname = snd_ctl_card_info_get_longname(card);
1112 std::string driver = snd_ctl_card_info_get_driver(card);
1113
1114 // Count rawmidi devices (not subdevices).
1115 int midi_count = 0;
1116 for (int device = -1;
1117 !snd_ctl_rawmidi_next_device(handle, &device) && device >= 0;)
1118 ++midi_count;
1119
1120 // Count any hwdep synths that become MIDI devices outside of rawmidi.
1121 //
1122 // Explanation:
1123 // Any kernel driver can create an ALSA client (visible to us).
1124 // With modern hardware, only rawmidi devices do this. Kernel
1125 // drivers create rawmidi devices and the rawmidi subsystem makes
1126 // the seq clients. But the OPL3 driver is special, it does not
1127 // make a rawmidi device but a seq client directly. (This is the
1128 // only one to worry about in the kernel code, as of 2015-03-23.)
1129 //
1130 // OPL3 is very old (but still possible to get in new
1131 // hardware). It is unlikely that new drivers would not use
1132 // rawmidi and defeat our heuristic.
1133 //
1134 // Longer term, support should be added in the kernel to expose a
1135 // direct link from card->client (or client->card) so that all
1136 // these heuristics will be obsolete. Once that is there, we can
1137 // assume our old heuristics will work on old kernels and the new
1138 // robust code will be used on new. Then we will not need to worry
1139 // about changes to kernel internals breaking our code.
1140 // See the TODO above at kMinimumClientIdForCards.
1141 for (int device = -1;
1142 !snd_ctl_hwdep_next_device(handle, &device) && device >= 0;) {
1143 err = snd_ctl_hwdep_info(handle, hwdep);
1144 if (err != 0) {
1145 VLOG(1) << "snd_ctl_hwdep_info fails: " << snd_strerror(err);
1146 continue;
1147 }
1148 snd_hwdep_iface_t iface = snd_hwdep_info_get_iface(hwdep);
1149 if (iface == SND_HWDEP_IFACE_OPL2 || iface == SND_HWDEP_IFACE_OPL3 ||
1150 iface == SND_HWDEP_IFACE_OPL4)
1151 ++midi_count;
1152 }
1153 snd_ctl_close(handle);
1154
mgiucad9af8452015-06-25 02:11:57 -07001155 if (midi_count > 0) {
1156 scoped_ptr<AlsaCard> card(
1157 new AlsaCard(dev, name, longname, driver, midi_count));
limasdfe59d0392015-11-19 20:28:57 -08001158 alsa_cards_.insert(std::make_pair(number, std::move(card)));
mgiucad9af8452015-06-25 02:11:57 -07001159 alsa_card_midi_count_ += midi_count;
1160 }
agoodeb09423b2015-05-11 11:39:57 -07001161}
1162
1163void MidiManagerAlsa::RemoveCard(int number) {
1164 auto it = alsa_cards_.find(number);
1165 if (it == alsa_cards_.end())
1166 return;
1167
1168 alsa_card_midi_count_ -= it->second->midi_device_count();
agoodeb09423b2015-05-11 11:39:57 -07001169 alsa_cards_.erase(it);
1170}
1171
agoode99d63292015-04-13 08:39:25 -07001172void MidiManagerAlsa::UpdatePortStateAndGenerateEvents() {
agoodeb09423b2015-05-11 11:39:57 -07001173 // Verify that our information from ALSA and udev are in sync. If
1174 // not, we cannot generate events right now.
1175 if (alsa_card_midi_count_ != alsa_seq_state_.card_client_count())
1176 return;
1177
agoode99d63292015-04-13 08:39:25 -07001178 // Generate new port state.
agoodeb09423b2015-05-11 11:39:57 -07001179 auto new_port_state = alsa_seq_state_.ToMidiPortState(alsa_cards_);
agoode99d63292015-04-13 08:39:25 -07001180
1181 // Disconnect any connected old ports that are now missing.
agoode5ebc4932015-12-01 08:25:12 -08001182 for (auto& old_port : port_state_) {
agoode99d63292015-04-13 08:39:25 -07001183 if (old_port->connected() &&
1184 (new_port_state->FindConnected(*old_port) == new_port_state->end())) {
1185 old_port->set_connected(false);
Avi Drissman3528fd02015-12-18 20:11:31 -05001186 uint32_t web_port_index = old_port->web_port_index();
agoode99d63292015-04-13 08:39:25 -07001187 switch (old_port->type()) {
1188 case MidiPort::Type::kInput:
1189 source_map_.erase(
1190 AddrToInt(old_port->client_id(), old_port->port_id()));
1191 SetInputPortState(web_port_index, MIDI_PORT_DISCONNECTED);
1192 break;
1193 case MidiPort::Type::kOutput:
1194 DeleteAlsaOutputPort(web_port_index);
1195 SetOutputPortState(web_port_index, MIDI_PORT_DISCONNECTED);
1196 break;
1197 }
1198 }
1199 }
1200
1201 // Reconnect or add new ports.
1202 auto it = new_port_state->begin();
1203 while (it != new_port_state->end()) {
agoode5ebc4932015-12-01 08:25:12 -08001204 auto& new_port = *it;
agoode99d63292015-04-13 08:39:25 -07001205 auto old_port = port_state_.Find(*new_port);
1206 if (old_port == port_state_.end()) {
1207 // Add new port.
agoode5ebc4932015-12-01 08:25:12 -08001208 const auto& opaque_key = new_port->OpaqueKey();
1209 const auto& manufacturer = new_port->manufacturer();
1210 const auto& port_name = new_port->port_name();
1211 const auto& version = new_port->version();
1212 const auto& type = new_port->type();
1213 const auto& client_id = new_port->client_id();
1214 const auto& port_id = new_port->port_id();
1215
dchengc2aeece2015-12-27 00:54:00 -08001216 uint32_t web_port_index = port_state_.push_back(std::move(new_port));
agoode5ebc4932015-12-01 08:25:12 -08001217 it = new_port_state->erase(it);
1218
1219 MidiPortInfo info(opaque_key, manufacturer, port_name, version,
agoode99d63292015-04-13 08:39:25 -07001220 MIDI_PORT_OPENED);
agoode5ebc4932015-12-01 08:25:12 -08001221 switch (type) {
agoode99d63292015-04-13 08:39:25 -07001222 case MidiPort::Type::kInput:
agoode5ebc4932015-12-01 08:25:12 -08001223 if (Subscribe(web_port_index, client_id, port_id))
agoode99d63292015-04-13 08:39:25 -07001224 AddInputPort(info);
1225 break;
agoode99d63292015-04-13 08:39:25 -07001226 case MidiPort::Type::kOutput:
agoode5ebc4932015-12-01 08:25:12 -08001227 if (CreateAlsaOutputPort(web_port_index, client_id, port_id))
agoode99d63292015-04-13 08:39:25 -07001228 AddOutputPort(info);
1229 break;
1230 }
agoode99d63292015-04-13 08:39:25 -07001231 } else if (!(*old_port)->connected()) {
1232 // Reconnect.
Avi Drissman3528fd02015-12-18 20:11:31 -05001233 uint32_t web_port_index = (*old_port)->web_port_index();
agoode99d63292015-04-13 08:39:25 -07001234 (*old_port)->Update(new_port->path(), new_port->client_id(),
1235 new_port->port_id(), new_port->client_name(),
1236 new_port->port_name(), new_port->manufacturer(),
1237 new_port->version());
1238 switch ((*old_port)->type()) {
1239 case MidiPort::Type::kInput:
1240 if (Subscribe(web_port_index, (*old_port)->client_id(),
1241 (*old_port)->port_id()))
1242 SetInputPortState(web_port_index, MIDI_PORT_OPENED);
1243 break;
agoode99d63292015-04-13 08:39:25 -07001244 case MidiPort::Type::kOutput:
1245 if (CreateAlsaOutputPort(web_port_index, (*old_port)->client_id(),
1246 (*old_port)->port_id()))
1247 SetOutputPortState(web_port_index, MIDI_PORT_OPENED);
1248 break;
1249 }
1250 (*old_port)->set_connected(true);
1251 ++it;
1252 } else {
1253 ++it;
1254 }
1255 }
1256}
1257
agoode975043d2015-05-11 00:46:17 -07001258// TODO(agoode): return false on failure.
agoode99d63292015-04-13 08:39:25 -07001259void MidiManagerAlsa::EnumerateAlsaPorts() {
1260 snd_seq_client_info_t* client_info;
1261 snd_seq_client_info_alloca(&client_info);
1262 snd_seq_port_info_t* port_info;
1263 snd_seq_port_info_alloca(&port_info);
1264
1265 // Enumerate clients.
1266 snd_seq_client_info_set_client(client_info, -1);
agoode5a1aa112015-06-21 20:51:00 -07001267 while (!snd_seq_query_next_client(in_client_.get(), client_info)) {
agoode99d63292015-04-13 08:39:25 -07001268 int client_id = snd_seq_client_info_get_client(client_info);
1269 ProcessClientStartEvent(client_id);
1270
1271 // Enumerate ports.
1272 snd_seq_port_info_set_client(port_info, client_id);
1273 snd_seq_port_info_set_port(port_info, -1);
agoode5a1aa112015-06-21 20:51:00 -07001274 while (!snd_seq_query_next_port(in_client_.get(), port_info)) {
agoode99d63292015-04-13 08:39:25 -07001275 const snd_seq_addr_t* addr = snd_seq_port_info_get_addr(port_info);
1276 ProcessPortStartEvent(*addr);
1277 }
1278 }
1279}
1280
agoode975043d2015-05-11 00:46:17 -07001281bool MidiManagerAlsa::EnumerateUdevCards() {
1282 int err;
1283
1284 device::ScopedUdevEnumeratePtr enumerate(
1285 device::udev_enumerate_new(udev_.get()));
1286 if (!enumerate.get()) {
1287 VLOG(1) << "udev_enumerate_new fails";
1288 return false;
1289 }
1290
1291 err = device::udev_enumerate_add_match_subsystem(enumerate.get(),
1292 kUdevSubsystemSound);
1293 if (err) {
1294 VLOG(1) << "udev_enumerate_add_match_subsystem fails: "
brettwf7f870f2015-06-09 11:05:24 -07001295 << base::safe_strerror(-err);
agoode975043d2015-05-11 00:46:17 -07001296 return false;
1297 }
1298
1299 err = device::udev_enumerate_scan_devices(enumerate.get());
1300 if (err) {
brettwf7f870f2015-06-09 11:05:24 -07001301 VLOG(1) << "udev_enumerate_scan_devices fails: "
1302 << base::safe_strerror(-err);
agoode975043d2015-05-11 00:46:17 -07001303 return false;
1304 }
1305
1306 udev_list_entry* list_entry;
1307 auto* devices = device::udev_enumerate_get_list_entry(enumerate.get());
1308 udev_list_entry_foreach(list_entry, devices) {
1309 const char* path = device::udev_list_entry_get_name(list_entry);
1310 device::ScopedUdevDevicePtr dev(
1311 device::udev_device_new_from_syspath(udev_.get(), path));
1312 if (dev.get())
1313 ProcessUdevEvent(dev.get());
1314 }
1315
1316 return true;
1317}
1318
Avi Drissman3528fd02015-12-18 20:11:31 -05001319bool MidiManagerAlsa::CreateAlsaOutputPort(uint32_t port_index,
agoode99d63292015-04-13 08:39:25 -07001320 int client_id,
1321 int port_id) {
1322 // Create the port.
1323 int out_port = snd_seq_create_simple_port(
agoode5a1aa112015-06-21 20:51:00 -07001324 out_client_.get(), NULL, kCreateOutputPortCaps, kCreatePortType);
agoode99d63292015-04-13 08:39:25 -07001325 if (out_port < 0) {
1326 VLOG(1) << "snd_seq_create_simple_port fails: " << snd_strerror(out_port);
1327 return false;
1328 }
1329 // Activate port subscription.
1330 snd_seq_port_subscribe_t* subs;
1331 snd_seq_port_subscribe_alloca(&subs);
1332 snd_seq_addr_t sender;
1333 sender.client = out_client_id_;
1334 sender.port = out_port;
1335 snd_seq_port_subscribe_set_sender(subs, &sender);
1336 snd_seq_addr_t dest;
1337 dest.client = client_id;
1338 dest.port = port_id;
1339 snd_seq_port_subscribe_set_dest(subs, &dest);
agoode5a1aa112015-06-21 20:51:00 -07001340 int err = snd_seq_subscribe_port(out_client_.get(), subs);
agoode99d63292015-04-13 08:39:25 -07001341 if (err != 0) {
1342 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
agoode5a1aa112015-06-21 20:51:00 -07001343 snd_seq_delete_simple_port(out_client_.get(), out_port);
agoode99d63292015-04-13 08:39:25 -07001344 return false;
1345 }
1346
1347 // Update our map.
1348 base::AutoLock lock(out_ports_lock_);
1349 out_ports_[port_index] = out_port;
1350 return true;
1351}
1352
Avi Drissman3528fd02015-12-18 20:11:31 -05001353void MidiManagerAlsa::DeleteAlsaOutputPort(uint32_t port_index) {
agoode99d63292015-04-13 08:39:25 -07001354 base::AutoLock lock(out_ports_lock_);
1355 auto it = out_ports_.find(port_index);
1356 if (it == out_ports_.end())
1357 return;
1358
1359 int alsa_port = it->second;
agoode5a1aa112015-06-21 20:51:00 -07001360 snd_seq_delete_simple_port(out_client_.get(), alsa_port);
agoode99d63292015-04-13 08:39:25 -07001361 out_ports_.erase(it);
1362}
1363
Avi Drissman3528fd02015-12-18 20:11:31 -05001364bool MidiManagerAlsa::Subscribe(uint32_t port_index,
1365 int client_id,
1366 int port_id) {
agoode99d63292015-04-13 08:39:25 -07001367 // Activate port subscription.
1368 snd_seq_port_subscribe_t* subs;
1369 snd_seq_port_subscribe_alloca(&subs);
1370 snd_seq_addr_t sender;
1371 sender.client = client_id;
1372 sender.port = port_id;
1373 snd_seq_port_subscribe_set_sender(subs, &sender);
1374 snd_seq_addr_t dest;
1375 dest.client = in_client_id_;
1376 dest.port = in_port_id_;
1377 snd_seq_port_subscribe_set_dest(subs, &dest);
agoode5a1aa112015-06-21 20:51:00 -07001378 int err = snd_seq_subscribe_port(in_client_.get(), subs);
agoode99d63292015-04-13 08:39:25 -07001379 if (err != 0) {
1380 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
1381 return false;
1382 }
1383
1384 // Update our map.
1385 source_map_[AddrToInt(client_id, port_id)] = port_index;
1386 return true;
1387}
1388
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +00001389MidiManager* MidiManager::Create() {
1390 return new MidiManagerAlsa();
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +00001391}
1392
toyoshime147c5e2015-05-07 21:58:31 -07001393} // namespace midi
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +00001394} // namespace media