blob: f4b87ba43b446edd2dafcdbe4efccce4750b166d [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>
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +00009#include <stdlib.h>
yhirano@chromium.orgcfa642c2014-05-01 08:54:41 +000010#include <algorithm>
11#include <string>
limasdfe59d0392015-11-19 20:28:57 -080012#include <utility>
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000013
14#include "base/bind.h"
agoodef212b2a2015-03-19 12:53:23 -070015#include "base/json/json_string_value_serializer.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000016#include "base/logging.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000017#include "base/message_loop/message_loop.h"
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000018#include "base/posix/eintr_wrapper.h"
brettwf7f870f2015-06-09 11:05:24 -070019#include "base/posix/safe_strerror.h"
agoodef212b2a2015-03-19 12:53:23 -070020#include "base/strings/string_number_conversions.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000021#include "base/strings/stringprintf.h"
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000022#include "base/time/time.h"
agoodef212b2a2015-03-19 12:53:23 -070023#include "crypto/sha2.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000024#include "media/midi/midi_port_info.h"
25
26namespace media {
toyoshime147c5e2015-05-07 21:58:31 -070027namespace midi {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000028
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000029namespace {
30
agoode@chromium.org25227512014-06-08 05:12:05 +000031// Per-output buffer. This can be smaller, but then large sysex messages
32// will be (harmlessly) split across multiple seq events. This should
33// not have any real practical effect, except perhaps to slightly reorder
34// realtime messages with respect to sysex.
35const size_t kSendBufferSize = 256;
36
agoodeb09423b2015-05-11 11:39:57 -070037// Minimum client id for which we will have ALSA card devices for. When we
38// are searching for card devices (used to get the path, id, and manufacturer),
39// we don't want to get confused by kernel clients that do not have a card.
40// See seq_clientmgr.c in the ALSA code for this.
41// TODO(agoode): Add proper client -> card export from the kernel to avoid
42// hardcoding.
43const int kMinimumClientIdForCards = 16;
44
agoodef212b2a2015-03-19 12:53:23 -070045// ALSA constants.
46const char kAlsaHw[] = "hw";
47
agoode975043d2015-05-11 00:46:17 -070048// udev constants.
49const char kUdev[] = "udev";
50const char kUdevSubsystemSound[] = "sound";
51const char kUdevPropertySoundInitialized[] = "SOUND_INITIALIZED";
52const char kUdevActionChange[] = "change";
53const char kUdevActionRemove[] = "remove";
54
agoodeb09423b2015-05-11 11:39:57 -070055const char kUdevIdVendor[] = "ID_VENDOR";
56const char kUdevIdVendorEnc[] = "ID_VENDOR_ENC";
57const char kUdevIdVendorFromDatabase[] = "ID_VENDOR_FROM_DATABASE";
58const char kUdevIdVendorId[] = "ID_VENDOR_ID";
59const char kUdevIdModelId[] = "ID_MODEL_ID";
60const char kUdevIdBus[] = "ID_BUS";
61const char kUdevIdPath[] = "ID_PATH";
62const char kUdevIdUsbInterfaceNum[] = "ID_USB_INTERFACE_NUM";
63const char kUdevIdSerialShort[] = "ID_SERIAL_SHORT";
64
65const char kSysattrVendorName[] = "vendor_name";
66const char kSysattrVendor[] = "vendor";
67const char kSysattrModel[] = "model";
68const char kSysattrGuid[] = "guid";
69
70const char kCardSyspath[] = "/card";
71
agoode@chromium.org25227512014-06-08 05:12:05 +000072// Constants for the capabilities we search for in inputs and outputs.
73// See http://www.alsa-project.org/alsa-doc/alsa-lib/seq.html.
74const unsigned int kRequiredInputPortCaps =
75 SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ;
76const unsigned int kRequiredOutputPortCaps =
77 SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE;
78
agoode99d63292015-04-13 08:39:25 -070079const unsigned int kCreateOutputPortCaps =
80 SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_NO_EXPORT;
81const unsigned int kCreateInputPortCaps =
82 SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_NO_EXPORT;
83const unsigned int kCreatePortType =
84 SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION;
agoode@chromium.org25227512014-06-08 05:12:05 +000085
agoode99d63292015-04-13 08:39:25 -070086int AddrToInt(int client, int port) {
87 return (client << 8) | port;
agoodef212b2a2015-03-19 12:53:23 -070088}
agoodef212b2a2015-03-19 12:53:23 -070089
agoodeb09423b2015-05-11 11:39:57 -070090// Returns true if this client has an ALSA card associated with it.
91bool IsCardClient(snd_seq_client_type_t type, int client_id) {
92 return (type == SND_SEQ_KERNEL_CLIENT) &&
93 (client_id >= kMinimumClientIdForCards);
94}
95
96// TODO(agoode): Move this to device/udev_linux.
97const std::string UdevDeviceGetPropertyOrSysattr(
98 struct udev_device* udev_device,
99 const char* property_key,
100 const char* sysattr_key) {
101 // First try the property.
102 std::string value =
103 device::UdevDeviceGetPropertyValue(udev_device, property_key);
104
105 // If no property, look for sysattrs and walk up the parent devices too.
106 while (value.empty() && udev_device) {
107 value = device::UdevDeviceGetSysattrValue(udev_device, sysattr_key);
108 udev_device = device::udev_device_get_parent(udev_device);
109 }
110 return value;
111}
112
113int GetCardNumber(udev_device* dev) {
114 const char* syspath = device::udev_device_get_syspath(dev);
115 if (!syspath)
116 return -1;
117
118 std::string syspath_str(syspath);
119 size_t i = syspath_str.rfind(kCardSyspath);
120 if (i == std::string::npos)
121 return -1;
122
123 int number;
124 if (!base::StringToInt(syspath_str.substr(i + strlen(kCardSyspath)), &number))
125 return -1;
126 return number;
127}
128
agooded87fc0f2015-05-21 08:29:31 -0700129std::string GetVendor(udev_device* dev) {
130 // Try to get the vendor string. Sometimes it is encoded.
131 std::string vendor = device::UdevDecodeString(
132 device::UdevDeviceGetPropertyValue(dev, kUdevIdVendorEnc));
133 // Sometimes it is not encoded.
134 if (vendor.empty())
135 vendor =
136 UdevDeviceGetPropertyOrSysattr(dev, kUdevIdVendor, kSysattrVendorName);
137 return vendor;
138}
139
agoode8caab0b2015-03-23 18:48:02 -0700140void SetStringIfNonEmpty(base::DictionaryValue* value,
141 const std::string& path,
142 const std::string& in_value) {
143 if (!in_value.empty())
144 value->SetString(path, in_value);
145}
146
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000147} // namespace
148
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000149MidiManagerAlsa::MidiManagerAlsa()
mgiucad9af8452015-06-25 02:11:57 -0700150 : udev_(device::udev_new()),
agoode@chromium.org25227512014-06-08 05:12:05 +0000151 send_thread_("MidiSendThread"),
agoode5a1aa112015-06-21 20:51:00 -0700152 event_thread_("MidiEventThread") {
agoode@chromium.org25227512014-06-08 05:12:05 +0000153 // Initialize decoder.
agoode5a1aa112015-06-21 20:51:00 -0700154 snd_midi_event_t* decoder;
155 snd_midi_event_new(0, &decoder);
156 decoder_.reset(decoder);
157 snd_midi_event_no_status(decoder_.get(), 1);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000158}
159
toyoshim8e7d6e02015-10-06 08:47:17 -0700160MidiManagerAlsa::~MidiManagerAlsa() = default;
agoodebd4be9b2015-03-16 19:17:25 -0700161
toyoshim@chromium.org51c7f532014-05-01 17:17:32 +0000162void MidiManagerAlsa::StartInitialization() {
agoode@chromium.org25227512014-06-08 05:12:05 +0000163 // Create client handles.
agoode5a1aa112015-06-21 20:51:00 -0700164 snd_seq_t* in_client;
agoode975043d2015-05-11 00:46:17 -0700165 int err =
agoode5a1aa112015-06-21 20:51:00 -0700166 snd_seq_open(&in_client, kAlsaHw, SND_SEQ_OPEN_INPUT, SND_SEQ_NONBLOCK);
agoode@chromium.org25227512014-06-08 05:12:05 +0000167 if (err != 0) {
168 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err);
toyoshimf1b88962015-07-09 14:14:51 -0700169 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode@chromium.org25227512014-06-08 05:12:05 +0000170 }
agoode5a1aa112015-06-21 20:51:00 -0700171 in_client_.reset(in_client);
172 in_client_id_ = snd_seq_client_id(in_client_.get());
173
174 snd_seq_t* out_client;
175 err = snd_seq_open(&out_client, kAlsaHw, SND_SEQ_OPEN_OUTPUT, 0);
agoode@chromium.org25227512014-06-08 05:12:05 +0000176 if (err != 0) {
177 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err);
toyoshimf1b88962015-07-09 14:14:51 -0700178 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode@chromium.org25227512014-06-08 05:12:05 +0000179 }
agoode5a1aa112015-06-21 20:51:00 -0700180 out_client_.reset(out_client);
181 out_client_id_ = snd_seq_client_id(out_client_.get());
agoode@chromium.org25227512014-06-08 05:12:05 +0000182
183 // Name the clients.
agoode5a1aa112015-06-21 20:51:00 -0700184 err = snd_seq_set_client_name(in_client_.get(), "Chrome (input)");
agoode@chromium.org25227512014-06-08 05:12:05 +0000185 if (err != 0) {
186 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err);
toyoshimf1b88962015-07-09 14:14:51 -0700187 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode@chromium.org25227512014-06-08 05:12:05 +0000188 }
agoode5a1aa112015-06-21 20:51:00 -0700189 err = snd_seq_set_client_name(out_client_.get(), "Chrome (output)");
agoode@chromium.org25227512014-06-08 05:12:05 +0000190 if (err != 0) {
191 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err);
toyoshimf1b88962015-07-09 14:14:51 -0700192 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode@chromium.org25227512014-06-08 05:12:05 +0000193 }
194
195 // Create input port.
agoode99d63292015-04-13 08:39:25 -0700196 in_port_id_ = snd_seq_create_simple_port(
agoode5a1aa112015-06-21 20:51:00 -0700197 in_client_.get(), NULL, kCreateInputPortCaps, kCreatePortType);
agoode99d63292015-04-13 08:39:25 -0700198 if (in_port_id_ < 0) {
199 VLOG(1) << "snd_seq_create_simple_port fails: "
200 << snd_strerror(in_port_id_);
toyoshimf1b88962015-07-09 14:14:51 -0700201 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode@chromium.org25227512014-06-08 05:12:05 +0000202 }
203
204 // Subscribe to the announce port.
205 snd_seq_port_subscribe_t* subs;
206 snd_seq_port_subscribe_alloca(&subs);
207 snd_seq_addr_t announce_sender;
208 snd_seq_addr_t announce_dest;
209 announce_sender.client = SND_SEQ_CLIENT_SYSTEM;
210 announce_sender.port = SND_SEQ_PORT_SYSTEM_ANNOUNCE;
agoode99d63292015-04-13 08:39:25 -0700211 announce_dest.client = in_client_id_;
212 announce_dest.port = in_port_id_;
agoode@chromium.org25227512014-06-08 05:12:05 +0000213 snd_seq_port_subscribe_set_sender(subs, &announce_sender);
214 snd_seq_port_subscribe_set_dest(subs, &announce_dest);
agoode5a1aa112015-06-21 20:51:00 -0700215 err = snd_seq_subscribe_port(in_client_.get(), subs);
agoode@chromium.org25227512014-06-08 05:12:05 +0000216 if (err != 0) {
217 VLOG(1) << "snd_seq_subscribe_port on the announce port fails: "
218 << snd_strerror(err);
toyoshimf1b88962015-07-09 14:14:51 -0700219 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode@chromium.org25227512014-06-08 05:12:05 +0000220 }
221
agoode99d63292015-04-13 08:39:25 -0700222 // Generate hotplug events for existing ports.
agoode975043d2015-05-11 00:46:17 -0700223 // TODO(agoode): Check the return value for failure.
agoode99d63292015-04-13 08:39:25 -0700224 EnumerateAlsaPorts();
agoode@chromium.org25227512014-06-08 05:12:05 +0000225
agoode975043d2015-05-11 00:46:17 -0700226 // Initialize udev monitor.
227 udev_monitor_.reset(
228 device::udev_monitor_new_from_netlink(udev_.get(), kUdev));
229 if (!udev_monitor_.get()) {
230 VLOG(1) << "udev_monitor_new_from_netlink fails";
toyoshimf1b88962015-07-09 14:14:51 -0700231 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode975043d2015-05-11 00:46:17 -0700232 }
233 err = device::udev_monitor_filter_add_match_subsystem_devtype(
234 udev_monitor_.get(), kUdevSubsystemSound, nullptr);
235 if (err != 0) {
236 VLOG(1) << "udev_monitor_add_match_subsystem fails: "
brettwf7f870f2015-06-09 11:05:24 -0700237 << base::safe_strerror(-err);
toyoshimf1b88962015-07-09 14:14:51 -0700238 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode975043d2015-05-11 00:46:17 -0700239 }
240 err = device::udev_monitor_enable_receiving(udev_monitor_.get());
241 if (err != 0) {
brettwf7f870f2015-06-09 11:05:24 -0700242 VLOG(1) << "udev_monitor_enable_receiving fails: "
243 << base::safe_strerror(-err);
toyoshimf1b88962015-07-09 14:14:51 -0700244 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode975043d2015-05-11 00:46:17 -0700245 }
246
247 // Generate hotplug events for existing udev devices.
248 EnumerateUdevCards();
249
agoode99d63292015-04-13 08:39:25 -0700250 // Start processing events.
agoode@chromium.org25227512014-06-08 05:12:05 +0000251 event_thread_.Start();
252 event_thread_.message_loop()->PostTask(
253 FROM_HERE,
agoodebd4be9b2015-03-16 19:17:25 -0700254 base::Bind(&MidiManagerAlsa::ScheduleEventLoop, base::Unretained(this)));
agoode@chromium.org25227512014-06-08 05:12:05 +0000255
toyoshimf1b88962015-07-09 14:14:51 -0700256 CompleteInitialization(Result::OK);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000257}
258
toyoshim8e7d6e02015-10-06 08:47:17 -0700259void MidiManagerAlsa::Finalize() {
260 // Tell the event thread it will soon be time to shut down. This gives
261 // us assurance the thread will stop in case the SND_SEQ_EVENT_CLIENT_EXIT
262 // message is lost.
263 {
264 base::AutoLock lock(shutdown_lock_);
265 event_thread_shutdown_ = true;
266 }
267
268 // Stop the send thread.
269 send_thread_.Stop();
270
271 // Close the out client. This will trigger the event thread to stop,
272 // because of SND_SEQ_EVENT_CLIENT_EXIT.
273 if (out_client_.get())
274 snd_seq_close(out_client_.release());
275
276 // Wait for the event thread to stop.
277 event_thread_.Stop();
278}
279
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000280void MidiManagerAlsa::DispatchSendMidiData(MidiManagerClient* client,
Avi Drissman3528fd02015-12-18 20:11:31 -0500281 uint32_t port_index,
282 const std::vector<uint8_t>& data,
dnicoara@chromium.org9f2a6f02014-01-03 21:25:00 +0000283 double timestamp) {
agoode@chromium.org25227512014-06-08 05:12:05 +0000284 if (!send_thread_.IsRunning())
285 send_thread_.Start();
286
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000287 base::TimeDelta delay;
288 if (timestamp != 0.0) {
289 base::TimeTicks time_to_send =
290 base::TimeTicks() + base::TimeDelta::FromMicroseconds(
agoodebd4be9b2015-03-16 19:17:25 -0700291 timestamp * base::Time::kMicrosecondsPerSecond);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000292 delay = std::max(time_to_send - base::TimeTicks::Now(), base::TimeDelta());
293 }
294
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000295 send_thread_.message_loop()->PostDelayedTask(
agoodebd4be9b2015-03-16 19:17:25 -0700296 FROM_HERE, base::Bind(&MidiManagerAlsa::SendMidiData,
297 base::Unretained(this), port_index, data),
298 delay);
agoode@chromium.org25227512014-06-08 05:12:05 +0000299
300 // Acknowledge send.
301 send_thread_.message_loop()->PostTask(
agoodead116b22015-12-07 00:00:35 -0800302 FROM_HERE, base::Bind(&MidiManagerAlsa::AccumulateMidiBytesSent,
303 base::Unretained(this), client, data.size()));
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000304}
305
agooded87fc0f2015-05-21 08:29:31 -0700306MidiManagerAlsa::MidiPort::Id::Id() = default;
307
308MidiManagerAlsa::MidiPort::Id::Id(const std::string& bus,
309 const std::string& vendor_id,
310 const std::string& model_id,
311 const std::string& usb_interface_num,
312 const std::string& serial)
313 : bus_(bus),
314 vendor_id_(vendor_id),
315 model_id_(model_id),
316 usb_interface_num_(usb_interface_num),
317 serial_(serial) {
318}
319
320MidiManagerAlsa::MidiPort::Id::Id(const Id&) = default;
321
322MidiManagerAlsa::MidiPort::Id::~Id() = default;
323
324bool MidiManagerAlsa::MidiPort::Id::operator==(const Id& rhs) const {
325 return (bus_ == rhs.bus_) && (vendor_id_ == rhs.vendor_id_) &&
326 (model_id_ == rhs.model_id_) &&
327 (usb_interface_num_ == rhs.usb_interface_num_) &&
328 (serial_ == rhs.serial_);
329}
330
331bool MidiManagerAlsa::MidiPort::Id::empty() const {
332 return bus_.empty() && vendor_id_.empty() && model_id_.empty() &&
333 usb_interface_num_.empty() && serial_.empty();
334}
335
agoode99d63292015-04-13 08:39:25 -0700336MidiManagerAlsa::MidiPort::MidiPort(const std::string& path,
agooded87fc0f2015-05-21 08:29:31 -0700337 const Id& id,
agoode99d63292015-04-13 08:39:25 -0700338 int client_id,
339 int port_id,
340 int midi_device,
341 const std::string& client_name,
342 const std::string& port_name,
343 const std::string& manufacturer,
344 const std::string& version,
345 Type type)
346 : id_(id),
347 midi_device_(midi_device),
348 type_(type),
349 path_(path),
350 client_id_(client_id),
351 port_id_(port_id),
352 client_name_(client_name),
353 port_name_(port_name),
354 manufacturer_(manufacturer),
agoode5a1aa112015-06-21 20:51:00 -0700355 version_(version) {
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000356}
357
agoodeb0582872015-05-20 05:22:24 -0700358MidiManagerAlsa::MidiPort::~MidiPort() = default;
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000359
agoode99d63292015-04-13 08:39:25 -0700360// Note: keep synchronized with the MidiPort::Match* methods.
361scoped_ptr<base::Value> MidiManagerAlsa::MidiPort::Value() const {
362 scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue);
363
364 std::string type;
365 switch (type_) {
366 case Type::kInput:
367 type = "input";
368 break;
agoode99d63292015-04-13 08:39:25 -0700369 case Type::kOutput:
370 type = "output";
371 break;
372 }
373 value->SetString("type", type);
374 SetStringIfNonEmpty(value.get(), "path", path_);
agoode99d63292015-04-13 08:39:25 -0700375 SetStringIfNonEmpty(value.get(), "clientName", client_name_);
376 SetStringIfNonEmpty(value.get(), "portName", port_name_);
377 value->SetInteger("clientId", client_id_);
378 value->SetInteger("portId", port_id_);
379 value->SetInteger("midiDevice", midi_device_);
380
agooded87fc0f2015-05-21 08:29:31 -0700381 // Flatten id fields.
382 SetStringIfNonEmpty(value.get(), "bus", id_.bus());
383 SetStringIfNonEmpty(value.get(), "vendorId", id_.vendor_id());
384 SetStringIfNonEmpty(value.get(), "modelId", id_.model_id());
385 SetStringIfNonEmpty(value.get(), "usbInterfaceNum", id_.usb_interface_num());
386 SetStringIfNonEmpty(value.get(), "serial", id_.serial());
387
agoode99d63292015-04-13 08:39:25 -0700388 return value.Pass();
agoodebd4be9b2015-03-16 19:17:25 -0700389}
agoode@chromium.org25227512014-06-08 05:12:05 +0000390
agoode99d63292015-04-13 08:39:25 -0700391std::string MidiManagerAlsa::MidiPort::JSONValue() const {
392 std::string json;
393 JSONStringValueSerializer serializer(&json);
394 serializer.Serialize(*Value().get());
395 return json;
agoodef212b2a2015-03-19 12:53:23 -0700396}
397
agoode99d63292015-04-13 08:39:25 -0700398// TODO(agoode): Do not use SHA256 here. Instead store a persistent
399// mapping and just use a UUID or other random string.
400// http://crbug.com/465320
401std::string MidiManagerAlsa::MidiPort::OpaqueKey() const {
Avi Drissman3528fd02015-12-18 20:11:31 -0500402 uint8_t hash[crypto::kSHA256Length];
agoode99d63292015-04-13 08:39:25 -0700403 crypto::SHA256HashString(JSONValue(), &hash, sizeof(hash));
404 return base::HexEncode(&hash, sizeof(hash));
agoodebd4be9b2015-03-16 19:17:25 -0700405}
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000406
agoode99d63292015-04-13 08:39:25 -0700407bool MidiManagerAlsa::MidiPort::MatchConnected(const MidiPort& query) const {
408 // Matches on:
409 // connected == true
410 // type
411 // path
412 // id
413 // client_id
414 // port_id
415 // midi_device
416 // client_name
417 // port_name
418 return connected() && (type() == query.type()) && (path() == query.path()) &&
419 (id() == query.id()) && (client_id() == query.client_id()) &&
420 (port_id() == query.port_id()) &&
421 (midi_device() == query.midi_device()) &&
422 (client_name() == query.client_name()) &&
423 (port_name() == query.port_name());
agoodebd4be9b2015-03-16 19:17:25 -0700424}
425
agoode99d63292015-04-13 08:39:25 -0700426bool MidiManagerAlsa::MidiPort::MatchCardPass1(const MidiPort& query) const {
427 // Matches on:
428 // connected == false
429 // type
430 // path
431 // id
432 // port_id
433 // midi_device
434 return MatchCardPass2(query) && (path() == query.path());
agoodebd4be9b2015-03-16 19:17:25 -0700435}
436
agoode99d63292015-04-13 08:39:25 -0700437bool MidiManagerAlsa::MidiPort::MatchCardPass2(const MidiPort& query) const {
438 // Matches on:
439 // connected == false
440 // type
441 // id
442 // port_id
443 // midi_device
444 return !connected() && (type() == query.type()) && (id() == query.id()) &&
445 (port_id() == query.port_id()) &&
446 (midi_device() == query.midi_device());
agoodef212b2a2015-03-19 12:53:23 -0700447}
448
agoode99d63292015-04-13 08:39:25 -0700449bool MidiManagerAlsa::MidiPort::MatchNoCardPass1(const MidiPort& query) const {
450 // Matches on:
451 // connected == false
452 // type
453 // path.empty(), for both this and query
454 // id.empty(), for both this and query
455 // client_id
456 // port_id
457 // client_name
458 // port_name
459 // midi_device == -1, for both this and query
460 return MatchNoCardPass2(query) && (client_id() == query.client_id());
agoodef212b2a2015-03-19 12:53:23 -0700461}
462
agoode99d63292015-04-13 08:39:25 -0700463bool MidiManagerAlsa::MidiPort::MatchNoCardPass2(const MidiPort& query) const {
464 // Matches on:
465 // connected == false
466 // type
467 // path.empty(), for both this and query
468 // id.empty(), for both this and query
469 // port_id
470 // client_name
471 // port_name
472 // midi_device == -1, for both this and query
473 return !connected() && (type() == query.type()) && path().empty() &&
474 query.path().empty() && id().empty() && query.id().empty() &&
475 (port_id() == query.port_id()) &&
476 (client_name() == query.client_name()) &&
477 (port_name() == query.port_name()) && (midi_device() == -1) &&
478 (query.midi_device() == -1);
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000479}
480
agoodeb0582872015-05-20 05:22:24 -0700481MidiManagerAlsa::MidiPortStateBase::~MidiPortStateBase() = default;
agoode99d63292015-04-13 08:39:25 -0700482
483MidiManagerAlsa::MidiPortStateBase::iterator
484MidiManagerAlsa::MidiPortStateBase::Find(
485 const MidiManagerAlsa::MidiPort& port) {
486 auto result = FindConnected(port);
487 if (result == end())
488 result = FindDisconnected(port);
489 return result;
490}
491
492MidiManagerAlsa::MidiPortStateBase::iterator
493MidiManagerAlsa::MidiPortStateBase::FindConnected(
494 const MidiManagerAlsa::MidiPort& port) {
495 // Exact match required for connected ports.
agoode5ebc4932015-12-01 08:25:12 -0800496 auto it = std::find_if(
497 ports_.begin(), ports_.end(),
498 [&port](scoped_ptr<MidiPort>& p) { return p->MatchConnected(port); });
agoode99d63292015-04-13 08:39:25 -0700499 return it;
500}
501
502MidiManagerAlsa::MidiPortStateBase::iterator
503MidiManagerAlsa::MidiPortStateBase::FindDisconnected(
504 const MidiManagerAlsa::MidiPort& port) {
505 // Always match on:
506 // type
507 // Possible things to match on:
508 // path
509 // id
510 // client_id
511 // port_id
512 // midi_device
513 // client_name
514 // port_name
515
516 if (!port.path().empty()) {
517 // If path is present, then we have a card-based client.
518
519 // Pass 1. Match on path, id, midi_device, port_id.
520 // This is the best possible match for hardware card-based clients.
521 // This will also match the empty id correctly for devices without an id.
agoode5ebc4932015-12-01 08:25:12 -0800522 auto it = std::find_if(
523 ports_.begin(), ports_.end(),
524 [&port](scoped_ptr<MidiPort>& p) { return p->MatchCardPass1(port); });
agoode99d63292015-04-13 08:39:25 -0700525 if (it != ports_.end())
526 return it;
527
528 if (!port.id().empty()) {
529 // Pass 2. Match on id, midi_device, port_id.
530 // This will give us a high-confidence match when a user moves a device to
531 // another USB/Firewire/Thunderbolt/etc port, but only works if the device
532 // has a hardware id.
agoode5ebc4932015-12-01 08:25:12 -0800533 it = std::find_if(
534 ports_.begin(), ports_.end(),
535 [&port](scoped_ptr<MidiPort>& p) { return p->MatchCardPass2(port); });
agoode99d63292015-04-13 08:39:25 -0700536 if (it != ports_.end())
537 return it;
538 }
539 } else {
540 // Else, we have a non-card-based client.
541 // Pass 1. Match on client_id, port_id, client_name, port_name.
542 // This will give us a reasonably good match.
agoode5ebc4932015-12-01 08:25:12 -0800543 auto it = std::find_if(
544 ports_.begin(), ports_.end(),
545 [&port](scoped_ptr<MidiPort>& p) { return p->MatchNoCardPass1(port); });
agoode99d63292015-04-13 08:39:25 -0700546 if (it != ports_.end())
547 return it;
548
549 // Pass 2. Match on port_id, client_name, port_name.
550 // This is weaker but similar to pass 2 in the hardware card-based clients
551 // match.
agoode5ebc4932015-12-01 08:25:12 -0800552 it = std::find_if(
553 ports_.begin(), ports_.end(),
554 [&port](scoped_ptr<MidiPort>& p) { return p->MatchNoCardPass2(port); });
agoode99d63292015-04-13 08:39:25 -0700555 if (it != ports_.end())
556 return it;
557 }
558
559 // No match.
560 return ports_.end();
561}
562
agoodeb0582872015-05-20 05:22:24 -0700563MidiManagerAlsa::MidiPortStateBase::MidiPortStateBase() = default;
agoode99d63292015-04-13 08:39:25 -0700564
agoodedf1b9ff2015-06-25 18:14:50 -0700565MidiManagerAlsa::MidiPortState::MidiPortState() = default;
agoode99d63292015-04-13 08:39:25 -0700566
Avi Drissman3528fd02015-12-18 20:11:31 -0500567uint32_t MidiManagerAlsa::MidiPortState::push_back(scoped_ptr<MidiPort> port) {
agoode99d63292015-04-13 08:39:25 -0700568 // Add the web midi index.
Avi Drissman3528fd02015-12-18 20:11:31 -0500569 uint32_t web_port_index = 0;
agoode99d63292015-04-13 08:39:25 -0700570 switch (port->type()) {
571 case MidiPort::Type::kInput:
572 web_port_index = num_input_ports_++;
573 break;
574 case MidiPort::Type::kOutput:
575 web_port_index = num_output_ports_++;
576 break;
577 }
578 port->set_web_port_index(web_port_index);
agoode5ebc4932015-12-01 08:25:12 -0800579 MidiPortStateBase::push_back(port.Pass());
agoode99d63292015-04-13 08:39:25 -0700580 return web_port_index;
581}
582
agoodedf1b9ff2015-06-25 18:14:50 -0700583MidiManagerAlsa::AlsaSeqState::AlsaSeqState() = default;
agoode99d63292015-04-13 08:39:25 -0700584
agoodeb0582872015-05-20 05:22:24 -0700585MidiManagerAlsa::AlsaSeqState::~AlsaSeqState() = default;
agoode99d63292015-04-13 08:39:25 -0700586
587void MidiManagerAlsa::AlsaSeqState::ClientStart(int client_id,
588 const std::string& client_name,
589 snd_seq_client_type_t type) {
590 ClientExit(client_id);
limasdfe59d0392015-11-19 20:28:57 -0800591 clients_.insert(std::make_pair(
592 client_id, make_scoped_ptr(new Client(client_name, type))));
agoodeb09423b2015-05-11 11:39:57 -0700593 if (IsCardClient(type, client_id))
594 ++card_client_count_;
agoode99d63292015-04-13 08:39:25 -0700595}
596
597bool MidiManagerAlsa::AlsaSeqState::ClientStarted(int client_id) {
598 return clients_.find(client_id) != clients_.end();
599}
600
601void MidiManagerAlsa::AlsaSeqState::ClientExit(int client_id) {
602 auto it = clients_.find(client_id);
603 if (it != clients_.end()) {
agoodeb09423b2015-05-11 11:39:57 -0700604 if (IsCardClient(it->second->type(), client_id))
605 --card_client_count_;
agoode99d63292015-04-13 08:39:25 -0700606 clients_.erase(it);
607 }
608}
609
610void MidiManagerAlsa::AlsaSeqState::PortStart(
611 int client_id,
612 int port_id,
613 const std::string& port_name,
614 MidiManagerAlsa::AlsaSeqState::PortDirection direction,
615 bool midi) {
616 auto it = clients_.find(client_id);
617 if (it != clients_.end())
618 it->second->AddPort(port_id,
agoode5a1aa112015-06-21 20:51:00 -0700619 make_scoped_ptr(new Port(port_name, direction, midi)));
agoode99d63292015-04-13 08:39:25 -0700620}
621
622void MidiManagerAlsa::AlsaSeqState::PortExit(int client_id, int port_id) {
623 auto it = clients_.find(client_id);
624 if (it != clients_.end())
625 it->second->RemovePort(port_id);
626}
627
628snd_seq_client_type_t MidiManagerAlsa::AlsaSeqState::ClientType(
629 int client_id) const {
630 auto it = clients_.find(client_id);
631 if (it == clients_.end())
632 return SND_SEQ_USER_CLIENT;
633 return it->second->type();
634}
635
636scoped_ptr<MidiManagerAlsa::TemporaryMidiPortState>
agoodeb09423b2015-05-11 11:39:57 -0700637MidiManagerAlsa::AlsaSeqState::ToMidiPortState(const AlsaCardMap& alsa_cards) {
agoode99d63292015-04-13 08:39:25 -0700638 scoped_ptr<MidiManagerAlsa::TemporaryMidiPortState> midi_ports(
639 new TemporaryMidiPortState);
agoodeb09423b2015-05-11 11:39:57 -0700640 auto card_it = alsa_cards.begin();
agoode99d63292015-04-13 08:39:25 -0700641
agoodeb09423b2015-05-11 11:39:57 -0700642 int card_midi_device = -1;
agoode99d63292015-04-13 08:39:25 -0700643 for (const auto& client_pair : clients_) {
644 int client_id = client_pair.first;
limasdfe59d0392015-11-19 20:28:57 -0800645 const auto& client = client_pair.second.get();
agoode99d63292015-04-13 08:39:25 -0700646
647 // Get client metadata.
648 const std::string client_name = client->name();
649 std::string manufacturer;
650 std::string driver;
651 std::string path;
agooded87fc0f2015-05-21 08:29:31 -0700652 MidiPort::Id id;
agoode99d63292015-04-13 08:39:25 -0700653 std::string card_name;
654 std::string card_longname;
655 int midi_device = -1;
656
agoodeb09423b2015-05-11 11:39:57 -0700657 if (IsCardClient(client->type(), client_id)) {
658 auto& card = card_it->second;
659 if (card_midi_device == -1)
660 card_midi_device = 0;
661
662 manufacturer = card->manufacturer();
agooded87fc0f2015-05-21 08:29:31 -0700663 path = card->path();
664 id = MidiPort::Id(card->bus(), card->vendor_id(), card->model_id(),
665 card->usb_interface_num(), card->serial());
666 card_name = card->name();
667 card_longname = card->longname();
agoodeb09423b2015-05-11 11:39:57 -0700668 midi_device = card_midi_device;
669
670 ++card_midi_device;
671 if (card_midi_device >= card->midi_device_count()) {
672 card_midi_device = -1;
673 ++card_it;
674 }
675 }
676
agoode99d63292015-04-13 08:39:25 -0700677 for (const auto& port_pair : *client) {
678 int port_id = port_pair.first;
679 const auto& port = port_pair.second;
680
681 if (port->midi()) {
682 std::string version;
683 if (!driver.empty()) {
684 version = driver + " / ";
685 }
686 version +=
687 base::StringPrintf("ALSA library version %d.%d.%d", SND_LIB_MAJOR,
688 SND_LIB_MINOR, SND_LIB_SUBMINOR);
689 PortDirection direction = port->direction();
690 if (direction == PortDirection::kInput ||
691 direction == PortDirection::kDuplex) {
agoode5ebc4932015-12-01 08:25:12 -0800692 midi_ports->push_back(make_scoped_ptr(new MidiPort(
agoode99d63292015-04-13 08:39:25 -0700693 path, id, client_id, port_id, midi_device, client->name(),
694 port->name(), manufacturer, version, MidiPort::Type::kInput)));
695 }
696 if (direction == PortDirection::kOutput ||
697 direction == PortDirection::kDuplex) {
agoode5ebc4932015-12-01 08:25:12 -0800698 midi_ports->push_back(make_scoped_ptr(new MidiPort(
agoode99d63292015-04-13 08:39:25 -0700699 path, id, client_id, port_id, midi_device, client->name(),
700 port->name(), manufacturer, version, MidiPort::Type::kOutput)));
701 }
702 }
703 }
704 }
705
706 return midi_ports.Pass();
707}
708
709MidiManagerAlsa::AlsaSeqState::Port::Port(
710 const std::string& name,
711 MidiManagerAlsa::AlsaSeqState::PortDirection direction,
712 bool midi)
713 : name_(name), direction_(direction), midi_(midi) {
714}
715
agoodeb0582872015-05-20 05:22:24 -0700716MidiManagerAlsa::AlsaSeqState::Port::~Port() = default;
agoode99d63292015-04-13 08:39:25 -0700717
718MidiManagerAlsa::AlsaSeqState::Client::Client(const std::string& name,
719 snd_seq_client_type_t type)
mgiucad9af8452015-06-25 02:11:57 -0700720 : name_(name), type_(type) {
agoode99d63292015-04-13 08:39:25 -0700721}
722
agoodeb0582872015-05-20 05:22:24 -0700723MidiManagerAlsa::AlsaSeqState::Client::~Client() = default;
agoode99d63292015-04-13 08:39:25 -0700724
725void MidiManagerAlsa::AlsaSeqState::Client::AddPort(int addr,
726 scoped_ptr<Port> port) {
limasdfe59d0392015-11-19 20:28:57 -0800727 ports_[addr] = std::move(port);
agoode99d63292015-04-13 08:39:25 -0700728}
729
730void MidiManagerAlsa::AlsaSeqState::Client::RemovePort(int addr) {
mgiucad9af8452015-06-25 02:11:57 -0700731 ports_.erase(addr);
agoode99d63292015-04-13 08:39:25 -0700732}
733
734MidiManagerAlsa::AlsaSeqState::Client::PortMap::const_iterator
735MidiManagerAlsa::AlsaSeqState::Client::begin() const {
736 return ports_.begin();
737}
738
739MidiManagerAlsa::AlsaSeqState::Client::PortMap::const_iterator
740MidiManagerAlsa::AlsaSeqState::Client::end() const {
741 return ports_.end();
agoodeaf6e9f52015-03-24 10:23:49 -0700742}
743
agoodeb09423b2015-05-11 11:39:57 -0700744MidiManagerAlsa::AlsaCard::AlsaCard(udev_device* dev,
agooded87fc0f2015-05-21 08:29:31 -0700745 const std::string& name,
746 const std::string& longname,
747 const std::string& driver,
agoodeb09423b2015-05-11 11:39:57 -0700748 int midi_device_count)
agooded87fc0f2015-05-21 08:29:31 -0700749 : name_(name),
750 longname_(longname),
751 driver_(driver),
752 path_(device::UdevDeviceGetPropertyValue(dev, kUdevIdPath)),
753 bus_(device::UdevDeviceGetPropertyValue(dev, kUdevIdBus)),
754 vendor_id_(
755 UdevDeviceGetPropertyOrSysattr(dev, kUdevIdVendorId, kSysattrVendor)),
756 model_id_(
757 UdevDeviceGetPropertyOrSysattr(dev, kUdevIdModelId, kSysattrModel)),
758 usb_interface_num_(
759 device::UdevDeviceGetPropertyValue(dev, kUdevIdUsbInterfaceNum)),
760 serial_(UdevDeviceGetPropertyOrSysattr(dev,
761 kUdevIdSerialShort,
762 kSysattrGuid)),
763 midi_device_count_(midi_device_count),
764 manufacturer_(ExtractManufacturerString(
765 GetVendor(dev),
766 vendor_id_,
767 device::UdevDeviceGetPropertyValue(dev, kUdevIdVendorFromDatabase),
768 name,
769 longname)) {
agoodeb09423b2015-05-11 11:39:57 -0700770}
771
agoodeb0582872015-05-20 05:22:24 -0700772MidiManagerAlsa::AlsaCard::~AlsaCard() = default;
agoodeb09423b2015-05-11 11:39:57 -0700773
agoode55a8b522015-03-08 12:40:17 -0700774// static
agoodeb09423b2015-05-11 11:39:57 -0700775std::string MidiManagerAlsa::AlsaCard::ExtractManufacturerString(
agoode5e4e9cd2015-03-09 12:34:24 -0700776 const std::string& udev_id_vendor,
agoode55a8b522015-03-08 12:40:17 -0700777 const std::string& udev_id_vendor_id,
778 const std::string& udev_id_vendor_from_database,
779 const std::string& alsa_name,
780 const std::string& alsa_longname) {
781 // Let's try to determine the manufacturer. Here is the ordered preference
782 // in extraction:
agoodef212b2a2015-03-19 12:53:23 -0700783 // 1. Vendor name from the hardware device string, from udev properties
784 // or sysattrs.
agoode5e4e9cd2015-03-09 12:34:24 -0700785 // 2. Vendor name from the udev database (property ID_VENDOR_FROM_DATABASE).
agoode55a8b522015-03-08 12:40:17 -0700786 // 3. Heuristic from ALSA.
787
agoodee83758c2015-03-23 22:07:54 -0700788 // Is the vendor string present and not just the vendor hex id?
789 if (!udev_id_vendor.empty() && (udev_id_vendor != udev_id_vendor_id)) {
agoode55a8b522015-03-08 12:40:17 -0700790 return udev_id_vendor;
791 }
792
793 // Is there a vendor string in the hardware database?
794 if (!udev_id_vendor_from_database.empty()) {
795 return udev_id_vendor_from_database;
796 }
797
798 // Ok, udev gave us nothing useful, or was unavailable. So try a heuristic.
799 // We assume that card longname is in the format of
800 // "<manufacturer> <name> at <bus>". Otherwise, we give up to detect
801 // a manufacturer name here.
802 size_t at_index = alsa_longname.rfind(" at ");
agoodef212b2a2015-03-19 12:53:23 -0700803 if (at_index && at_index != std::string::npos) {
agoode55a8b522015-03-08 12:40:17 -0700804 size_t name_index = alsa_longname.rfind(alsa_name, at_index - 1);
agoodef212b2a2015-03-19 12:53:23 -0700805 if (name_index && name_index != std::string::npos)
agoode55a8b522015-03-08 12:40:17 -0700806 return alsa_longname.substr(0, name_index - 1);
807 }
808
809 // Failure.
810 return "";
811}
812
Avi Drissman3528fd02015-12-18 20:11:31 -0500813void MidiManagerAlsa::SendMidiData(uint32_t port_index,
814 const std::vector<uint8_t>& data) {
skyostil93e2ec22015-06-17 08:49:09 -0700815 DCHECK(send_thread_.task_runner()->BelongsToCurrentThread());
agoodebd4be9b2015-03-16 19:17:25 -0700816
agoodef212b2a2015-03-19 12:53:23 -0700817 snd_midi_event_t* encoder;
818 snd_midi_event_new(kSendBufferSize, &encoder);
agoode5a1aa112015-06-21 20:51:00 -0700819 for (const auto datum : data) {
agoodebd4be9b2015-03-16 19:17:25 -0700820 snd_seq_event_t event;
agoode5a1aa112015-06-21 20:51:00 -0700821 int result = snd_midi_event_encode_byte(encoder, datum, &event);
agoodebd4be9b2015-03-16 19:17:25 -0700822 if (result == 1) {
823 // Full event, send it.
agoode99d63292015-04-13 08:39:25 -0700824 base::AutoLock lock(out_ports_lock_);
825 auto it = out_ports_.find(port_index);
826 if (it != out_ports_.end()) {
827 snd_seq_ev_set_source(&event, it->second);
828 snd_seq_ev_set_subs(&event);
829 snd_seq_ev_set_direct(&event);
agoode5a1aa112015-06-21 20:51:00 -0700830 snd_seq_event_output_direct(out_client_.get(), &event);
agoode99d63292015-04-13 08:39:25 -0700831 }
agoodebd4be9b2015-03-16 19:17:25 -0700832 }
833 }
agoodef212b2a2015-03-19 12:53:23 -0700834 snd_midi_event_free(encoder);
agoodebd4be9b2015-03-16 19:17:25 -0700835}
836
837void MidiManagerAlsa::ScheduleEventLoop() {
838 event_thread_.message_loop()->PostTask(
839 FROM_HERE,
840 base::Bind(&MidiManagerAlsa::EventLoop, base::Unretained(this)));
841}
842
843void MidiManagerAlsa::EventLoop() {
agoode975043d2015-05-11 00:46:17 -0700844 bool loop_again = true;
agoodebd4be9b2015-03-16 19:17:25 -0700845
agoode975043d2015-05-11 00:46:17 -0700846 struct pollfd pfd[2];
agoode5a1aa112015-06-21 20:51:00 -0700847 snd_seq_poll_descriptors(in_client_.get(), &pfd[0], 1, POLLIN);
agoode975043d2015-05-11 00:46:17 -0700848 pfd[1].fd = device::udev_monitor_get_fd(udev_monitor_.get());
849 pfd[1].events = POLLIN;
agoodebd4be9b2015-03-16 19:17:25 -0700850
agoode975043d2015-05-11 00:46:17 -0700851 int err = HANDLE_EINTR(poll(pfd, arraysize(pfd), -1));
852 if (err < 0) {
brettwf7f870f2015-06-09 11:05:24 -0700853 VLOG(1) << "poll fails: " << base::safe_strerror(errno);
agoode975043d2015-05-11 00:46:17 -0700854 loop_again = false;
agoodeaf6e9f52015-03-24 10:23:49 -0700855 } else {
agoode975043d2015-05-11 00:46:17 -0700856 if (pfd[0].revents & POLLIN) {
857 // Read available incoming MIDI data.
858 int remaining;
859 double timestamp =
860 (base::TimeTicks::Now() - base::TimeTicks()).InSecondsF();
861 do {
862 snd_seq_event_t* event;
agoode5a1aa112015-06-21 20:51:00 -0700863 err = snd_seq_event_input(in_client_.get(), &event);
864 remaining = snd_seq_event_input_pending(in_client_.get(), 0);
agoode975043d2015-05-11 00:46:17 -0700865
866 if (err == -ENOSPC) {
867 // Handle out of space error.
868 VLOG(1) << "snd_seq_event_input detected buffer overrun";
869 // We've lost events: check another way to see if we need to shut
870 // down.
871 base::AutoLock lock(shutdown_lock_);
872 if (event_thread_shutdown_)
873 loop_again = false;
874 } else if (err == -EAGAIN) {
875 // We've read all the data.
876 } else if (err < 0) {
877 // Handle other errors.
878 VLOG(1) << "snd_seq_event_input fails: " << snd_strerror(err);
879 // TODO(agoode): Use RecordAction() or similar to log this.
880 loop_again = false;
881 } else if (event->source.client == SND_SEQ_CLIENT_SYSTEM &&
882 event->source.port == SND_SEQ_PORT_SYSTEM_ANNOUNCE) {
883 // Handle announce events.
884 switch (event->type) {
885 case SND_SEQ_EVENT_PORT_START:
886 // Don't use SND_SEQ_EVENT_CLIENT_START because the
887 // client name may not be set by the time we query
888 // it. It should be set by the time ports are made.
889 ProcessClientStartEvent(event->data.addr.client);
890 ProcessPortStartEvent(event->data.addr);
891 break;
892 case SND_SEQ_EVENT_CLIENT_EXIT:
893 // Check for disconnection of our "out" client. This means "shut
894 // down".
895 if (event->data.addr.client == out_client_id_) {
896 loop_again = false;
897 remaining = 0;
898 } else
899 ProcessClientExitEvent(event->data.addr);
900 break;
901 case SND_SEQ_EVENT_PORT_EXIT:
902 ProcessPortExitEvent(event->data.addr);
903 break;
904 }
905 } else {
906 // Normal operation.
907 ProcessSingleEvent(event, timestamp);
908 }
909 } while (remaining > 0);
910 }
911 if (pfd[1].revents & POLLIN) {
912 device::ScopedUdevDevicePtr dev(
913 device::udev_monitor_receive_device(udev_monitor_.get()));
914 if (dev.get())
915 ProcessUdevEvent(dev.get());
916 else
917 VLOG(1) << "udev_monitor_receive_device fails";
918 }
agoodebd4be9b2015-03-16 19:17:25 -0700919 }
920
agoodebd4be9b2015-03-16 19:17:25 -0700921 // Do again.
agoode975043d2015-05-11 00:46:17 -0700922 if (loop_again)
923 ScheduleEventLoop();
agoodebd4be9b2015-03-16 19:17:25 -0700924}
925
926void MidiManagerAlsa::ProcessSingleEvent(snd_seq_event_t* event,
927 double timestamp) {
agoode99d63292015-04-13 08:39:25 -0700928 auto source_it =
929 source_map_.find(AddrToInt(event->source.client, event->source.port));
agoodebd4be9b2015-03-16 19:17:25 -0700930 if (source_it != source_map_.end()) {
Avi Drissman3528fd02015-12-18 20:11:31 -0500931 uint32_t source = source_it->second;
agoodebd4be9b2015-03-16 19:17:25 -0700932 if (event->type == SND_SEQ_EVENT_SYSEX) {
933 // Special! Variable-length sysex.
Avi Drissman3528fd02015-12-18 20:11:31 -0500934 ReceiveMidiData(source, static_cast<const uint8_t*>(event->data.ext.ptr),
agoodebd4be9b2015-03-16 19:17:25 -0700935 event->data.ext.len, timestamp);
936 } else {
937 // Otherwise, decode this and send that on.
938 unsigned char buf[12];
agoode5a1aa112015-06-21 20:51:00 -0700939 long count =
940 snd_midi_event_decode(decoder_.get(), buf, sizeof(buf), event);
agoodebd4be9b2015-03-16 19:17:25 -0700941 if (count <= 0) {
942 if (count != -ENOENT) {
943 // ENOENT means that it's not a MIDI message, which is not an
944 // error, but other negative values are errors for us.
945 VLOG(1) << "snd_midi_event_decoder fails " << snd_strerror(count);
946 // TODO(agoode): Record this failure.
947 }
948 } else {
949 ReceiveMidiData(source, buf, count, timestamp);
950 }
951 }
952 }
953}
954
agoode99d63292015-04-13 08:39:25 -0700955void MidiManagerAlsa::ProcessClientStartEvent(int client_id) {
956 // Ignore if client is already started.
957 if (alsa_seq_state_.ClientStarted(client_id))
958 return;
959
960 snd_seq_client_info_t* client_info;
961 snd_seq_client_info_alloca(&client_info);
agoode5a1aa112015-06-21 20:51:00 -0700962 int err =
963 snd_seq_get_any_client_info(in_client_.get(), client_id, client_info);
agoode99d63292015-04-13 08:39:25 -0700964 if (err != 0)
965 return;
966
967 // Skip our own clients.
968 if ((client_id == in_client_id_) || (client_id == out_client_id_))
969 return;
970
971 // Update our view of ALSA seq state.
972 alsa_seq_state_.ClientStart(client_id,
973 snd_seq_client_info_get_name(client_info),
974 snd_seq_client_info_get_type(client_info));
975
976 // Generate Web MIDI events.
977 UpdatePortStateAndGenerateEvents();
978}
979
980void MidiManagerAlsa::ProcessPortStartEvent(const snd_seq_addr_t& addr) {
981 snd_seq_port_info_t* port_info;
982 snd_seq_port_info_alloca(&port_info);
agoode5a1aa112015-06-21 20:51:00 -0700983 int err = snd_seq_get_any_port_info(in_client_.get(), addr.client, addr.port,
984 port_info);
agoode99d63292015-04-13 08:39:25 -0700985 if (err != 0)
986 return;
987
988 unsigned int caps = snd_seq_port_info_get_capability(port_info);
989 bool input = (caps & kRequiredInputPortCaps) == kRequiredInputPortCaps;
990 bool output = (caps & kRequiredOutputPortCaps) == kRequiredOutputPortCaps;
991 AlsaSeqState::PortDirection direction;
992 if (input && output)
993 direction = AlsaSeqState::PortDirection::kDuplex;
994 else if (input)
995 direction = AlsaSeqState::PortDirection::kInput;
996 else if (output)
997 direction = AlsaSeqState::PortDirection::kOutput;
998 else
999 return;
1000
1001 // Update our view of ALSA seq state.
1002 alsa_seq_state_.PortStart(
1003 addr.client, addr.port, snd_seq_port_info_get_name(port_info), direction,
1004 snd_seq_port_info_get_type(port_info) & SND_SEQ_PORT_TYPE_MIDI_GENERIC);
1005 // Generate Web MIDI events.
1006 UpdatePortStateAndGenerateEvents();
1007}
1008
1009void MidiManagerAlsa::ProcessClientExitEvent(const snd_seq_addr_t& addr) {
1010 // Update our view of ALSA seq state.
1011 alsa_seq_state_.ClientExit(addr.client);
1012 // Generate Web MIDI events.
1013 UpdatePortStateAndGenerateEvents();
1014}
1015
1016void MidiManagerAlsa::ProcessPortExitEvent(const snd_seq_addr_t& addr) {
1017 // Update our view of ALSA seq state.
1018 alsa_seq_state_.PortExit(addr.client, addr.port);
1019 // Generate Web MIDI events.
1020 UpdatePortStateAndGenerateEvents();
1021}
1022
agoode975043d2015-05-11 00:46:17 -07001023void MidiManagerAlsa::ProcessUdevEvent(udev_device* dev) {
1024 // Only card devices have this property set, and only when they are
1025 // fully initialized.
1026 if (!device::udev_device_get_property_value(dev,
1027 kUdevPropertySoundInitialized))
1028 return;
1029
1030 // Get the action. If no action, then we are doing first time enumeration
1031 // and the device is treated as new.
1032 const char* action = device::udev_device_get_action(dev);
1033 if (!action)
1034 action = kUdevActionChange;
1035
1036 if (strcmp(action, kUdevActionChange) == 0) {
agoodeb09423b2015-05-11 11:39:57 -07001037 AddCard(dev);
1038 // Generate Web MIDI events.
1039 UpdatePortStateAndGenerateEvents();
agoode975043d2015-05-11 00:46:17 -07001040 } else if (strcmp(action, kUdevActionRemove) == 0) {
agoodeb09423b2015-05-11 11:39:57 -07001041 RemoveCard(GetCardNumber(dev));
1042 // Generate Web MIDI events.
1043 UpdatePortStateAndGenerateEvents();
agoode975043d2015-05-11 00:46:17 -07001044 }
1045}
1046
agoodeb09423b2015-05-11 11:39:57 -07001047void MidiManagerAlsa::AddCard(udev_device* dev) {
1048 int number = GetCardNumber(dev);
1049 if (number == -1)
1050 return;
1051
1052 RemoveCard(number);
1053
1054 snd_ctl_card_info_t* card;
1055 snd_hwdep_info_t* hwdep;
1056 snd_ctl_card_info_alloca(&card);
1057 snd_hwdep_info_alloca(&hwdep);
1058 const std::string id = base::StringPrintf("hw:CARD=%i", number);
1059 snd_ctl_t* handle;
1060 int err = snd_ctl_open(&handle, id.c_str(), 0);
1061 if (err != 0) {
1062 VLOG(1) << "snd_ctl_open fails: " << snd_strerror(err);
1063 return;
1064 }
1065 err = snd_ctl_card_info(handle, card);
1066 if (err != 0) {
1067 VLOG(1) << "snd_ctl_card_info fails: " << snd_strerror(err);
1068 snd_ctl_close(handle);
1069 return;
1070 }
1071 std::string name = snd_ctl_card_info_get_name(card);
1072 std::string longname = snd_ctl_card_info_get_longname(card);
1073 std::string driver = snd_ctl_card_info_get_driver(card);
1074
1075 // Count rawmidi devices (not subdevices).
1076 int midi_count = 0;
1077 for (int device = -1;
1078 !snd_ctl_rawmidi_next_device(handle, &device) && device >= 0;)
1079 ++midi_count;
1080
1081 // Count any hwdep synths that become MIDI devices outside of rawmidi.
1082 //
1083 // Explanation:
1084 // Any kernel driver can create an ALSA client (visible to us).
1085 // With modern hardware, only rawmidi devices do this. Kernel
1086 // drivers create rawmidi devices and the rawmidi subsystem makes
1087 // the seq clients. But the OPL3 driver is special, it does not
1088 // make a rawmidi device but a seq client directly. (This is the
1089 // only one to worry about in the kernel code, as of 2015-03-23.)
1090 //
1091 // OPL3 is very old (but still possible to get in new
1092 // hardware). It is unlikely that new drivers would not use
1093 // rawmidi and defeat our heuristic.
1094 //
1095 // Longer term, support should be added in the kernel to expose a
1096 // direct link from card->client (or client->card) so that all
1097 // these heuristics will be obsolete. Once that is there, we can
1098 // assume our old heuristics will work on old kernels and the new
1099 // robust code will be used on new. Then we will not need to worry
1100 // about changes to kernel internals breaking our code.
1101 // See the TODO above at kMinimumClientIdForCards.
1102 for (int device = -1;
1103 !snd_ctl_hwdep_next_device(handle, &device) && device >= 0;) {
1104 err = snd_ctl_hwdep_info(handle, hwdep);
1105 if (err != 0) {
1106 VLOG(1) << "snd_ctl_hwdep_info fails: " << snd_strerror(err);
1107 continue;
1108 }
1109 snd_hwdep_iface_t iface = snd_hwdep_info_get_iface(hwdep);
1110 if (iface == SND_HWDEP_IFACE_OPL2 || iface == SND_HWDEP_IFACE_OPL3 ||
1111 iface == SND_HWDEP_IFACE_OPL4)
1112 ++midi_count;
1113 }
1114 snd_ctl_close(handle);
1115
mgiucad9af8452015-06-25 02:11:57 -07001116 if (midi_count > 0) {
1117 scoped_ptr<AlsaCard> card(
1118 new AlsaCard(dev, name, longname, driver, midi_count));
limasdfe59d0392015-11-19 20:28:57 -08001119 alsa_cards_.insert(std::make_pair(number, std::move(card)));
mgiucad9af8452015-06-25 02:11:57 -07001120 alsa_card_midi_count_ += midi_count;
1121 }
agoodeb09423b2015-05-11 11:39:57 -07001122}
1123
1124void MidiManagerAlsa::RemoveCard(int number) {
1125 auto it = alsa_cards_.find(number);
1126 if (it == alsa_cards_.end())
1127 return;
1128
1129 alsa_card_midi_count_ -= it->second->midi_device_count();
agoodeb09423b2015-05-11 11:39:57 -07001130 alsa_cards_.erase(it);
1131}
1132
agoode99d63292015-04-13 08:39:25 -07001133void MidiManagerAlsa::UpdatePortStateAndGenerateEvents() {
agoodeb09423b2015-05-11 11:39:57 -07001134 // Verify that our information from ALSA and udev are in sync. If
1135 // not, we cannot generate events right now.
1136 if (alsa_card_midi_count_ != alsa_seq_state_.card_client_count())
1137 return;
1138
agoode99d63292015-04-13 08:39:25 -07001139 // Generate new port state.
agoodeb09423b2015-05-11 11:39:57 -07001140 auto new_port_state = alsa_seq_state_.ToMidiPortState(alsa_cards_);
agoode99d63292015-04-13 08:39:25 -07001141
1142 // Disconnect any connected old ports that are now missing.
agoode5ebc4932015-12-01 08:25:12 -08001143 for (auto& old_port : port_state_) {
agoode99d63292015-04-13 08:39:25 -07001144 if (old_port->connected() &&
1145 (new_port_state->FindConnected(*old_port) == new_port_state->end())) {
1146 old_port->set_connected(false);
Avi Drissman3528fd02015-12-18 20:11:31 -05001147 uint32_t web_port_index = old_port->web_port_index();
agoode99d63292015-04-13 08:39:25 -07001148 switch (old_port->type()) {
1149 case MidiPort::Type::kInput:
1150 source_map_.erase(
1151 AddrToInt(old_port->client_id(), old_port->port_id()));
1152 SetInputPortState(web_port_index, MIDI_PORT_DISCONNECTED);
1153 break;
1154 case MidiPort::Type::kOutput:
1155 DeleteAlsaOutputPort(web_port_index);
1156 SetOutputPortState(web_port_index, MIDI_PORT_DISCONNECTED);
1157 break;
1158 }
1159 }
1160 }
1161
1162 // Reconnect or add new ports.
1163 auto it = new_port_state->begin();
1164 while (it != new_port_state->end()) {
agoode5ebc4932015-12-01 08:25:12 -08001165 auto& new_port = *it;
agoode99d63292015-04-13 08:39:25 -07001166 auto old_port = port_state_.Find(*new_port);
1167 if (old_port == port_state_.end()) {
1168 // Add new port.
agoode5ebc4932015-12-01 08:25:12 -08001169 const auto& opaque_key = new_port->OpaqueKey();
1170 const auto& manufacturer = new_port->manufacturer();
1171 const auto& port_name = new_port->port_name();
1172 const auto& version = new_port->version();
1173 const auto& type = new_port->type();
1174 const auto& client_id = new_port->client_id();
1175 const auto& port_id = new_port->port_id();
1176
Avi Drissman3528fd02015-12-18 20:11:31 -05001177 uint32_t web_port_index = port_state_.push_back(new_port.Pass());
agoode5ebc4932015-12-01 08:25:12 -08001178 it = new_port_state->erase(it);
1179
1180 MidiPortInfo info(opaque_key, manufacturer, port_name, version,
agoode99d63292015-04-13 08:39:25 -07001181 MIDI_PORT_OPENED);
agoode5ebc4932015-12-01 08:25:12 -08001182 switch (type) {
agoode99d63292015-04-13 08:39:25 -07001183 case MidiPort::Type::kInput:
agoode5ebc4932015-12-01 08:25:12 -08001184 if (Subscribe(web_port_index, client_id, port_id))
agoode99d63292015-04-13 08:39:25 -07001185 AddInputPort(info);
1186 break;
agoode99d63292015-04-13 08:39:25 -07001187 case MidiPort::Type::kOutput:
agoode5ebc4932015-12-01 08:25:12 -08001188 if (CreateAlsaOutputPort(web_port_index, client_id, port_id))
agoode99d63292015-04-13 08:39:25 -07001189 AddOutputPort(info);
1190 break;
1191 }
agoode99d63292015-04-13 08:39:25 -07001192 } else if (!(*old_port)->connected()) {
1193 // Reconnect.
Avi Drissman3528fd02015-12-18 20:11:31 -05001194 uint32_t web_port_index = (*old_port)->web_port_index();
agoode99d63292015-04-13 08:39:25 -07001195 (*old_port)->Update(new_port->path(), new_port->client_id(),
1196 new_port->port_id(), new_port->client_name(),
1197 new_port->port_name(), new_port->manufacturer(),
1198 new_port->version());
1199 switch ((*old_port)->type()) {
1200 case MidiPort::Type::kInput:
1201 if (Subscribe(web_port_index, (*old_port)->client_id(),
1202 (*old_port)->port_id()))
1203 SetInputPortState(web_port_index, MIDI_PORT_OPENED);
1204 break;
agoode99d63292015-04-13 08:39:25 -07001205 case MidiPort::Type::kOutput:
1206 if (CreateAlsaOutputPort(web_port_index, (*old_port)->client_id(),
1207 (*old_port)->port_id()))
1208 SetOutputPortState(web_port_index, MIDI_PORT_OPENED);
1209 break;
1210 }
1211 (*old_port)->set_connected(true);
1212 ++it;
1213 } else {
1214 ++it;
1215 }
1216 }
1217}
1218
agoode975043d2015-05-11 00:46:17 -07001219// TODO(agoode): return false on failure.
agoode99d63292015-04-13 08:39:25 -07001220void MidiManagerAlsa::EnumerateAlsaPorts() {
1221 snd_seq_client_info_t* client_info;
1222 snd_seq_client_info_alloca(&client_info);
1223 snd_seq_port_info_t* port_info;
1224 snd_seq_port_info_alloca(&port_info);
1225
1226 // Enumerate clients.
1227 snd_seq_client_info_set_client(client_info, -1);
agoode5a1aa112015-06-21 20:51:00 -07001228 while (!snd_seq_query_next_client(in_client_.get(), client_info)) {
agoode99d63292015-04-13 08:39:25 -07001229 int client_id = snd_seq_client_info_get_client(client_info);
1230 ProcessClientStartEvent(client_id);
1231
1232 // Enumerate ports.
1233 snd_seq_port_info_set_client(port_info, client_id);
1234 snd_seq_port_info_set_port(port_info, -1);
agoode5a1aa112015-06-21 20:51:00 -07001235 while (!snd_seq_query_next_port(in_client_.get(), port_info)) {
agoode99d63292015-04-13 08:39:25 -07001236 const snd_seq_addr_t* addr = snd_seq_port_info_get_addr(port_info);
1237 ProcessPortStartEvent(*addr);
1238 }
1239 }
1240}
1241
agoode975043d2015-05-11 00:46:17 -07001242bool MidiManagerAlsa::EnumerateUdevCards() {
1243 int err;
1244
1245 device::ScopedUdevEnumeratePtr enumerate(
1246 device::udev_enumerate_new(udev_.get()));
1247 if (!enumerate.get()) {
1248 VLOG(1) << "udev_enumerate_new fails";
1249 return false;
1250 }
1251
1252 err = device::udev_enumerate_add_match_subsystem(enumerate.get(),
1253 kUdevSubsystemSound);
1254 if (err) {
1255 VLOG(1) << "udev_enumerate_add_match_subsystem fails: "
brettwf7f870f2015-06-09 11:05:24 -07001256 << base::safe_strerror(-err);
agoode975043d2015-05-11 00:46:17 -07001257 return false;
1258 }
1259
1260 err = device::udev_enumerate_scan_devices(enumerate.get());
1261 if (err) {
brettwf7f870f2015-06-09 11:05:24 -07001262 VLOG(1) << "udev_enumerate_scan_devices fails: "
1263 << base::safe_strerror(-err);
agoode975043d2015-05-11 00:46:17 -07001264 return false;
1265 }
1266
1267 udev_list_entry* list_entry;
1268 auto* devices = device::udev_enumerate_get_list_entry(enumerate.get());
1269 udev_list_entry_foreach(list_entry, devices) {
1270 const char* path = device::udev_list_entry_get_name(list_entry);
1271 device::ScopedUdevDevicePtr dev(
1272 device::udev_device_new_from_syspath(udev_.get(), path));
1273 if (dev.get())
1274 ProcessUdevEvent(dev.get());
1275 }
1276
1277 return true;
1278}
1279
Avi Drissman3528fd02015-12-18 20:11:31 -05001280bool MidiManagerAlsa::CreateAlsaOutputPort(uint32_t port_index,
agoode99d63292015-04-13 08:39:25 -07001281 int client_id,
1282 int port_id) {
1283 // Create the port.
1284 int out_port = snd_seq_create_simple_port(
agoode5a1aa112015-06-21 20:51:00 -07001285 out_client_.get(), NULL, kCreateOutputPortCaps, kCreatePortType);
agoode99d63292015-04-13 08:39:25 -07001286 if (out_port < 0) {
1287 VLOG(1) << "snd_seq_create_simple_port fails: " << snd_strerror(out_port);
1288 return false;
1289 }
1290 // Activate port subscription.
1291 snd_seq_port_subscribe_t* subs;
1292 snd_seq_port_subscribe_alloca(&subs);
1293 snd_seq_addr_t sender;
1294 sender.client = out_client_id_;
1295 sender.port = out_port;
1296 snd_seq_port_subscribe_set_sender(subs, &sender);
1297 snd_seq_addr_t dest;
1298 dest.client = client_id;
1299 dest.port = port_id;
1300 snd_seq_port_subscribe_set_dest(subs, &dest);
agoode5a1aa112015-06-21 20:51:00 -07001301 int err = snd_seq_subscribe_port(out_client_.get(), subs);
agoode99d63292015-04-13 08:39:25 -07001302 if (err != 0) {
1303 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
agoode5a1aa112015-06-21 20:51:00 -07001304 snd_seq_delete_simple_port(out_client_.get(), out_port);
agoode99d63292015-04-13 08:39:25 -07001305 return false;
1306 }
1307
1308 // Update our map.
1309 base::AutoLock lock(out_ports_lock_);
1310 out_ports_[port_index] = out_port;
1311 return true;
1312}
1313
Avi Drissman3528fd02015-12-18 20:11:31 -05001314void MidiManagerAlsa::DeleteAlsaOutputPort(uint32_t port_index) {
agoode99d63292015-04-13 08:39:25 -07001315 base::AutoLock lock(out_ports_lock_);
1316 auto it = out_ports_.find(port_index);
1317 if (it == out_ports_.end())
1318 return;
1319
1320 int alsa_port = it->second;
agoode5a1aa112015-06-21 20:51:00 -07001321 snd_seq_delete_simple_port(out_client_.get(), alsa_port);
agoode99d63292015-04-13 08:39:25 -07001322 out_ports_.erase(it);
1323}
1324
Avi Drissman3528fd02015-12-18 20:11:31 -05001325bool MidiManagerAlsa::Subscribe(uint32_t port_index,
1326 int client_id,
1327 int port_id) {
agoode99d63292015-04-13 08:39:25 -07001328 // Activate port subscription.
1329 snd_seq_port_subscribe_t* subs;
1330 snd_seq_port_subscribe_alloca(&subs);
1331 snd_seq_addr_t sender;
1332 sender.client = client_id;
1333 sender.port = port_id;
1334 snd_seq_port_subscribe_set_sender(subs, &sender);
1335 snd_seq_addr_t dest;
1336 dest.client = in_client_id_;
1337 dest.port = in_port_id_;
1338 snd_seq_port_subscribe_set_dest(subs, &dest);
agoode5a1aa112015-06-21 20:51:00 -07001339 int err = snd_seq_subscribe_port(in_client_.get(), subs);
agoode99d63292015-04-13 08:39:25 -07001340 if (err != 0) {
1341 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
1342 return false;
1343 }
1344
1345 // Update our map.
1346 source_map_[AddrToInt(client_id, port_id)] = port_index;
1347 return true;
1348}
1349
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +00001350MidiManager* MidiManager::Create() {
1351 return new MidiManagerAlsa();
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +00001352}
1353
toyoshime147c5e2015-05-07 21:58:31 -07001354} // namespace midi
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +00001355} // namespace media