blob: 990b50a1643db9c5ee9d0bbbb1b16720bdf6dbed [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
agoode975043d2015-05-11 00:46:17 -07007#include <poll.h>
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +00008#include <stdlib.h>
yhirano@chromium.orgcfa642c2014-05-01 08:54:41 +00009#include <algorithm>
10#include <string>
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000011
12#include "base/bind.h"
agoodef212b2a2015-03-19 12:53:23 -070013#include "base/json/json_string_value_serializer.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000014#include "base/logging.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000015#include "base/message_loop/message_loop.h"
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000016#include "base/posix/eintr_wrapper.h"
brettwf7f870f2015-06-09 11:05:24 -070017#include "base/posix/safe_strerror.h"
agoodef212b2a2015-03-19 12:53:23 -070018#include "base/strings/string_number_conversions.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000019#include "base/strings/stringprintf.h"
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000020#include "base/time/time.h"
agoodef212b2a2015-03-19 12:53:23 -070021#include "crypto/sha2.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000022#include "media/midi/midi_port_info.h"
23
24namespace media {
toyoshime147c5e2015-05-07 21:58:31 -070025namespace midi {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000026
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000027namespace {
28
agoode@chromium.org25227512014-06-08 05:12:05 +000029// Per-output buffer. This can be smaller, but then large sysex messages
30// will be (harmlessly) split across multiple seq events. This should
31// not have any real practical effect, except perhaps to slightly reorder
32// realtime messages with respect to sysex.
33const size_t kSendBufferSize = 256;
34
agoodeb09423b2015-05-11 11:39:57 -070035// Minimum client id for which we will have ALSA card devices for. When we
36// are searching for card devices (used to get the path, id, and manufacturer),
37// we don't want to get confused by kernel clients that do not have a card.
38// See seq_clientmgr.c in the ALSA code for this.
39// TODO(agoode): Add proper client -> card export from the kernel to avoid
40// hardcoding.
41const int kMinimumClientIdForCards = 16;
42
agoodef212b2a2015-03-19 12:53:23 -070043// ALSA constants.
44const char kAlsaHw[] = "hw";
45
agoode975043d2015-05-11 00:46:17 -070046// udev constants.
47const char kUdev[] = "udev";
48const char kUdevSubsystemSound[] = "sound";
49const char kUdevPropertySoundInitialized[] = "SOUND_INITIALIZED";
50const char kUdevActionChange[] = "change";
51const char kUdevActionRemove[] = "remove";
52
agoodeb09423b2015-05-11 11:39:57 -070053const char kUdevIdVendor[] = "ID_VENDOR";
54const char kUdevIdVendorEnc[] = "ID_VENDOR_ENC";
55const char kUdevIdVendorFromDatabase[] = "ID_VENDOR_FROM_DATABASE";
56const char kUdevIdVendorId[] = "ID_VENDOR_ID";
57const char kUdevIdModelId[] = "ID_MODEL_ID";
58const char kUdevIdBus[] = "ID_BUS";
59const char kUdevIdPath[] = "ID_PATH";
60const char kUdevIdUsbInterfaceNum[] = "ID_USB_INTERFACE_NUM";
61const char kUdevIdSerialShort[] = "ID_SERIAL_SHORT";
62
63const char kSysattrVendorName[] = "vendor_name";
64const char kSysattrVendor[] = "vendor";
65const char kSysattrModel[] = "model";
66const char kSysattrGuid[] = "guid";
67
68const char kCardSyspath[] = "/card";
69
agoode@chromium.org25227512014-06-08 05:12:05 +000070// Constants for the capabilities we search for in inputs and outputs.
71// See http://www.alsa-project.org/alsa-doc/alsa-lib/seq.html.
72const unsigned int kRequiredInputPortCaps =
73 SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ;
74const unsigned int kRequiredOutputPortCaps =
75 SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE;
76
agoode99d63292015-04-13 08:39:25 -070077const unsigned int kCreateOutputPortCaps =
78 SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_NO_EXPORT;
79const unsigned int kCreateInputPortCaps =
80 SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_NO_EXPORT;
81const unsigned int kCreatePortType =
82 SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION;
agoode@chromium.org25227512014-06-08 05:12:05 +000083
agoode99d63292015-04-13 08:39:25 -070084int AddrToInt(int client, int port) {
85 return (client << 8) | port;
agoodef212b2a2015-03-19 12:53:23 -070086}
agoodef212b2a2015-03-19 12:53:23 -070087
agoodeb09423b2015-05-11 11:39:57 -070088// Returns true if this client has an ALSA card associated with it.
89bool IsCardClient(snd_seq_client_type_t type, int client_id) {
90 return (type == SND_SEQ_KERNEL_CLIENT) &&
91 (client_id >= kMinimumClientIdForCards);
92}
93
94// TODO(agoode): Move this to device/udev_linux.
95const std::string UdevDeviceGetPropertyOrSysattr(
96 struct udev_device* udev_device,
97 const char* property_key,
98 const char* sysattr_key) {
99 // First try the property.
100 std::string value =
101 device::UdevDeviceGetPropertyValue(udev_device, property_key);
102
103 // If no property, look for sysattrs and walk up the parent devices too.
104 while (value.empty() && udev_device) {
105 value = device::UdevDeviceGetSysattrValue(udev_device, sysattr_key);
106 udev_device = device::udev_device_get_parent(udev_device);
107 }
108 return value;
109}
110
111int GetCardNumber(udev_device* dev) {
112 const char* syspath = device::udev_device_get_syspath(dev);
113 if (!syspath)
114 return -1;
115
116 std::string syspath_str(syspath);
117 size_t i = syspath_str.rfind(kCardSyspath);
118 if (i == std::string::npos)
119 return -1;
120
121 int number;
122 if (!base::StringToInt(syspath_str.substr(i + strlen(kCardSyspath)), &number))
123 return -1;
124 return number;
125}
126
agooded87fc0f2015-05-21 08:29:31 -0700127std::string GetVendor(udev_device* dev) {
128 // Try to get the vendor string. Sometimes it is encoded.
129 std::string vendor = device::UdevDecodeString(
130 device::UdevDeviceGetPropertyValue(dev, kUdevIdVendorEnc));
131 // Sometimes it is not encoded.
132 if (vendor.empty())
133 vendor =
134 UdevDeviceGetPropertyOrSysattr(dev, kUdevIdVendor, kSysattrVendorName);
135 return vendor;
136}
137
agoode8caab0b2015-03-23 18:48:02 -0700138void SetStringIfNonEmpty(base::DictionaryValue* value,
139 const std::string& path,
140 const std::string& in_value) {
141 if (!in_value.empty())
142 value->SetString(path, in_value);
143}
144
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000145} // namespace
146
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000147MidiManagerAlsa::MidiManagerAlsa()
agoode5a1aa112015-06-21 20:51:00 -0700148 : alsa_cards_deleter_(&alsa_cards_),
agoode7de413f2015-04-24 00:13:39 -0700149 udev_(device::udev_new()),
agoode@chromium.org25227512014-06-08 05:12:05 +0000150 send_thread_("MidiSendThread"),
agoode5a1aa112015-06-21 20:51:00 -0700151 event_thread_("MidiEventThread") {
agoode@chromium.org25227512014-06-08 05:12:05 +0000152 // Initialize decoder.
agoode5a1aa112015-06-21 20:51:00 -0700153 snd_midi_event_t* decoder;
154 snd_midi_event_new(0, &decoder);
155 decoder_.reset(decoder);
156 snd_midi_event_no_status(decoder_.get(), 1);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000157}
158
agoodebd4be9b2015-03-16 19:17:25 -0700159MidiManagerAlsa::~MidiManagerAlsa() {
160 // Tell the event thread it will soon be time to shut down. This gives
161 // us assurance the thread will stop in case the SND_SEQ_EVENT_CLIENT_EXIT
162 // message is lost.
163 {
164 base::AutoLock lock(shutdown_lock_);
165 event_thread_shutdown_ = true;
166 }
167
168 // Stop the send thread.
169 send_thread_.Stop();
170
171 // Close the out client. This will trigger the event thread to stop,
172 // because of SND_SEQ_EVENT_CLIENT_EXIT.
agoode5a1aa112015-06-21 20:51:00 -0700173 if (out_client_.get())
174 snd_seq_close(out_client_.release());
agoodebd4be9b2015-03-16 19:17:25 -0700175
176 // Wait for the event thread to stop.
177 event_thread_.Stop();
agoodebd4be9b2015-03-16 19:17:25 -0700178}
179
toyoshim@chromium.org51c7f532014-05-01 17:17:32 +0000180void MidiManagerAlsa::StartInitialization() {
agoode@chromium.org25227512014-06-08 05:12:05 +0000181 // TODO(agoode): Move off I/O thread. See http://crbug.com/374341.
182
183 // Create client handles.
agoode5a1aa112015-06-21 20:51:00 -0700184 snd_seq_t* in_client;
agoode975043d2015-05-11 00:46:17 -0700185 int err =
agoode5a1aa112015-06-21 20:51:00 -0700186 snd_seq_open(&in_client, kAlsaHw, SND_SEQ_OPEN_INPUT, SND_SEQ_NONBLOCK);
agoode@chromium.org25227512014-06-08 05:12:05 +0000187 if (err != 0) {
188 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err);
189 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
190 }
agoode5a1aa112015-06-21 20:51:00 -0700191 in_client_.reset(in_client);
192 in_client_id_ = snd_seq_client_id(in_client_.get());
193
194 snd_seq_t* out_client;
195 err = snd_seq_open(&out_client, kAlsaHw, SND_SEQ_OPEN_OUTPUT, 0);
agoode@chromium.org25227512014-06-08 05:12:05 +0000196 if (err != 0) {
197 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err);
198 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
199 }
agoode5a1aa112015-06-21 20:51:00 -0700200 out_client_.reset(out_client);
201 out_client_id_ = snd_seq_client_id(out_client_.get());
agoode@chromium.org25227512014-06-08 05:12:05 +0000202
203 // Name the clients.
agoode5a1aa112015-06-21 20:51:00 -0700204 err = snd_seq_set_client_name(in_client_.get(), "Chrome (input)");
agoode@chromium.org25227512014-06-08 05:12:05 +0000205 if (err != 0) {
206 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err);
207 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
208 }
agoode5a1aa112015-06-21 20:51:00 -0700209 err = snd_seq_set_client_name(out_client_.get(), "Chrome (output)");
agoode@chromium.org25227512014-06-08 05:12:05 +0000210 if (err != 0) {
211 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err);
212 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
213 }
214
215 // Create input port.
agoode99d63292015-04-13 08:39:25 -0700216 in_port_id_ = snd_seq_create_simple_port(
agoode5a1aa112015-06-21 20:51:00 -0700217 in_client_.get(), NULL, kCreateInputPortCaps, kCreatePortType);
agoode99d63292015-04-13 08:39:25 -0700218 if (in_port_id_ < 0) {
219 VLOG(1) << "snd_seq_create_simple_port fails: "
220 << snd_strerror(in_port_id_);
agoode@chromium.org25227512014-06-08 05:12:05 +0000221 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
222 }
223
224 // Subscribe to the announce port.
225 snd_seq_port_subscribe_t* subs;
226 snd_seq_port_subscribe_alloca(&subs);
227 snd_seq_addr_t announce_sender;
228 snd_seq_addr_t announce_dest;
229 announce_sender.client = SND_SEQ_CLIENT_SYSTEM;
230 announce_sender.port = SND_SEQ_PORT_SYSTEM_ANNOUNCE;
agoode99d63292015-04-13 08:39:25 -0700231 announce_dest.client = in_client_id_;
232 announce_dest.port = in_port_id_;
agoode@chromium.org25227512014-06-08 05:12:05 +0000233 snd_seq_port_subscribe_set_sender(subs, &announce_sender);
234 snd_seq_port_subscribe_set_dest(subs, &announce_dest);
agoode5a1aa112015-06-21 20:51:00 -0700235 err = snd_seq_subscribe_port(in_client_.get(), subs);
agoode@chromium.org25227512014-06-08 05:12:05 +0000236 if (err != 0) {
237 VLOG(1) << "snd_seq_subscribe_port on the announce port fails: "
238 << snd_strerror(err);
239 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
240 }
241
agoode99d63292015-04-13 08:39:25 -0700242 // Generate hotplug events for existing ports.
agoode975043d2015-05-11 00:46:17 -0700243 // TODO(agoode): Check the return value for failure.
agoode99d63292015-04-13 08:39:25 -0700244 EnumerateAlsaPorts();
agoode@chromium.org25227512014-06-08 05:12:05 +0000245
agoode975043d2015-05-11 00:46:17 -0700246 // Initialize udev monitor.
247 udev_monitor_.reset(
248 device::udev_monitor_new_from_netlink(udev_.get(), kUdev));
249 if (!udev_monitor_.get()) {
250 VLOG(1) << "udev_monitor_new_from_netlink fails";
251 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
252 }
253 err = device::udev_monitor_filter_add_match_subsystem_devtype(
254 udev_monitor_.get(), kUdevSubsystemSound, nullptr);
255 if (err != 0) {
256 VLOG(1) << "udev_monitor_add_match_subsystem fails: "
brettwf7f870f2015-06-09 11:05:24 -0700257 << base::safe_strerror(-err);
agoode975043d2015-05-11 00:46:17 -0700258 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
259 }
260 err = device::udev_monitor_enable_receiving(udev_monitor_.get());
261 if (err != 0) {
brettwf7f870f2015-06-09 11:05:24 -0700262 VLOG(1) << "udev_monitor_enable_receiving fails: "
263 << base::safe_strerror(-err);
agoode975043d2015-05-11 00:46:17 -0700264 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
265 }
266
267 // Generate hotplug events for existing udev devices.
268 EnumerateUdevCards();
269
agoode99d63292015-04-13 08:39:25 -0700270 // Start processing events.
agoode@chromium.org25227512014-06-08 05:12:05 +0000271 event_thread_.Start();
272 event_thread_.message_loop()->PostTask(
273 FROM_HERE,
agoodebd4be9b2015-03-16 19:17:25 -0700274 base::Bind(&MidiManagerAlsa::ScheduleEventLoop, base::Unretained(this)));
agoode@chromium.org25227512014-06-08 05:12:05 +0000275
276 CompleteInitialization(MIDI_OK);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000277}
278
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000279void MidiManagerAlsa::DispatchSendMidiData(MidiManagerClient* client,
dnicoara@chromium.org9f2a6f02014-01-03 21:25:00 +0000280 uint32 port_index,
281 const std::vector<uint8>& data,
282 double timestamp) {
agoode@chromium.org25227512014-06-08 05:12:05 +0000283 // Not correct right now. http://crbug.com/374341.
284 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(
agoodebd4be9b2015-03-16 19:17:25 -0700302 FROM_HERE, base::Bind(&MidiManagerClient::AccumulateMidiBytesSent,
303 base::Unretained(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 {
402 uint8 hash[crypto::kSHA256Length];
403 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.
496 auto it = std::find_if(ports_.begin(), ports_.end(), [&port](MidiPort* p) {
497 return p->MatchConnected(port);
498 });
499 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.
522 auto it = std::find_if(ports_.begin(), ports_.end(), [&port](MidiPort* p) {
523 return p->MatchCardPass1(port);
524 });
525 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.
533 it = std::find_if(ports_.begin(), ports_.end(), [&port](MidiPort* p) {
534 return p->MatchCardPass2(port);
535 });
536 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.
543 auto it = std::find_if(ports_.begin(), ports_.end(), [&port](MidiPort* p) {
544 return p->MatchNoCardPass1(port);
545 });
546 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.
552 it = std::find_if(ports_.begin(), ports_.end(), [&port](MidiPort* p) {
553 return p->MatchNoCardPass2(port);
554 });
555 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
565void MidiManagerAlsa::TemporaryMidiPortState::Insert(
566 scoped_ptr<MidiPort> port) {
agoode5a1aa112015-06-21 20:51:00 -0700567 ports().push_back(port.release());
agoode99d63292015-04-13 08:39:25 -0700568}
569
570MidiManagerAlsa::MidiPortState::MidiPortState()
571 : num_input_ports_(0), num_output_ports_(0) {
572}
573
574uint32 MidiManagerAlsa::MidiPortState::Insert(scoped_ptr<MidiPort> port) {
575 // Add the web midi index.
vivek.vg0365a022015-04-14 23:11:23 -0700576 uint32 web_port_index = 0;
agoode99d63292015-04-13 08:39:25 -0700577 switch (port->type()) {
578 case MidiPort::Type::kInput:
579 web_port_index = num_input_ports_++;
580 break;
581 case MidiPort::Type::kOutput:
582 web_port_index = num_output_ports_++;
583 break;
584 }
585 port->set_web_port_index(web_port_index);
agoode5a1aa112015-06-21 20:51:00 -0700586 ports().push_back(port.release());
agoode99d63292015-04-13 08:39:25 -0700587 return web_port_index;
588}
589
agoodeb09423b2015-05-11 11:39:57 -0700590MidiManagerAlsa::AlsaSeqState::AlsaSeqState()
591 : clients_deleter_(&clients_), card_client_count_(0) {
agoode99d63292015-04-13 08:39:25 -0700592}
593
agoodeb0582872015-05-20 05:22:24 -0700594MidiManagerAlsa::AlsaSeqState::~AlsaSeqState() = default;
agoode99d63292015-04-13 08:39:25 -0700595
596void MidiManagerAlsa::AlsaSeqState::ClientStart(int client_id,
597 const std::string& client_name,
598 snd_seq_client_type_t type) {
599 ClientExit(client_id);
600 clients_[client_id] = new Client(client_name, type);
agoodeb09423b2015-05-11 11:39:57 -0700601 if (IsCardClient(type, client_id))
602 ++card_client_count_;
agoode99d63292015-04-13 08:39:25 -0700603}
604
605bool MidiManagerAlsa::AlsaSeqState::ClientStarted(int client_id) {
606 return clients_.find(client_id) != clients_.end();
607}
608
609void MidiManagerAlsa::AlsaSeqState::ClientExit(int client_id) {
610 auto it = clients_.find(client_id);
611 if (it != clients_.end()) {
agoodeb09423b2015-05-11 11:39:57 -0700612 if (IsCardClient(it->second->type(), client_id))
613 --card_client_count_;
agoode99d63292015-04-13 08:39:25 -0700614 delete it->second;
615 clients_.erase(it);
616 }
617}
618
619void MidiManagerAlsa::AlsaSeqState::PortStart(
620 int client_id,
621 int port_id,
622 const std::string& port_name,
623 MidiManagerAlsa::AlsaSeqState::PortDirection direction,
624 bool midi) {
625 auto it = clients_.find(client_id);
626 if (it != clients_.end())
627 it->second->AddPort(port_id,
agoode5a1aa112015-06-21 20:51:00 -0700628 make_scoped_ptr(new Port(port_name, direction, midi)));
agoode99d63292015-04-13 08:39:25 -0700629}
630
631void MidiManagerAlsa::AlsaSeqState::PortExit(int client_id, int port_id) {
632 auto it = clients_.find(client_id);
633 if (it != clients_.end())
634 it->second->RemovePort(port_id);
635}
636
637snd_seq_client_type_t MidiManagerAlsa::AlsaSeqState::ClientType(
638 int client_id) const {
639 auto it = clients_.find(client_id);
640 if (it == clients_.end())
641 return SND_SEQ_USER_CLIENT;
642 return it->second->type();
643}
644
645scoped_ptr<MidiManagerAlsa::TemporaryMidiPortState>
agoodeb09423b2015-05-11 11:39:57 -0700646MidiManagerAlsa::AlsaSeqState::ToMidiPortState(const AlsaCardMap& alsa_cards) {
agoode99d63292015-04-13 08:39:25 -0700647 scoped_ptr<MidiManagerAlsa::TemporaryMidiPortState> midi_ports(
648 new TemporaryMidiPortState);
agoodeb09423b2015-05-11 11:39:57 -0700649 // TODO(agoode): Use more information from udev, to allow hardware matching.
650 // See http://crbug.com/486471.
651 auto card_it = alsa_cards.begin();
agoode99d63292015-04-13 08:39:25 -0700652
agoodeb09423b2015-05-11 11:39:57 -0700653 int card_midi_device = -1;
agoode99d63292015-04-13 08:39:25 -0700654 for (const auto& client_pair : clients_) {
655 int client_id = client_pair.first;
656 const auto& client = client_pair.second;
657
658 // Get client metadata.
659 const std::string client_name = client->name();
660 std::string manufacturer;
661 std::string driver;
662 std::string path;
agooded87fc0f2015-05-21 08:29:31 -0700663 MidiPort::Id id;
agoode99d63292015-04-13 08:39:25 -0700664 std::string card_name;
665 std::string card_longname;
666 int midi_device = -1;
667
agoodeb09423b2015-05-11 11:39:57 -0700668 if (IsCardClient(client->type(), client_id)) {
669 auto& card = card_it->second;
670 if (card_midi_device == -1)
671 card_midi_device = 0;
672
673 manufacturer = card->manufacturer();
agooded87fc0f2015-05-21 08:29:31 -0700674 path = card->path();
675 id = MidiPort::Id(card->bus(), card->vendor_id(), card->model_id(),
676 card->usb_interface_num(), card->serial());
677 card_name = card->name();
678 card_longname = card->longname();
agoodeb09423b2015-05-11 11:39:57 -0700679 midi_device = card_midi_device;
680
681 ++card_midi_device;
682 if (card_midi_device >= card->midi_device_count()) {
683 card_midi_device = -1;
684 ++card_it;
685 }
686 }
687
agoode99d63292015-04-13 08:39:25 -0700688 for (const auto& port_pair : *client) {
689 int port_id = port_pair.first;
690 const auto& port = port_pair.second;
691
692 if (port->midi()) {
693 std::string version;
694 if (!driver.empty()) {
695 version = driver + " / ";
696 }
697 version +=
698 base::StringPrintf("ALSA library version %d.%d.%d", SND_LIB_MAJOR,
699 SND_LIB_MINOR, SND_LIB_SUBMINOR);
700 PortDirection direction = port->direction();
701 if (direction == PortDirection::kInput ||
702 direction == PortDirection::kDuplex) {
agoode5a1aa112015-06-21 20:51:00 -0700703 midi_ports->Insert(make_scoped_ptr(new MidiPort(
agoode99d63292015-04-13 08:39:25 -0700704 path, id, client_id, port_id, midi_device, client->name(),
705 port->name(), manufacturer, version, MidiPort::Type::kInput)));
706 }
707 if (direction == PortDirection::kOutput ||
708 direction == PortDirection::kDuplex) {
agoode5a1aa112015-06-21 20:51:00 -0700709 midi_ports->Insert(make_scoped_ptr(new MidiPort(
agoode99d63292015-04-13 08:39:25 -0700710 path, id, client_id, port_id, midi_device, client->name(),
711 port->name(), manufacturer, version, MidiPort::Type::kOutput)));
712 }
713 }
714 }
715 }
716
717 return midi_ports.Pass();
718}
719
720MidiManagerAlsa::AlsaSeqState::Port::Port(
721 const std::string& name,
722 MidiManagerAlsa::AlsaSeqState::PortDirection direction,
723 bool midi)
724 : name_(name), direction_(direction), midi_(midi) {
725}
726
agoodeb0582872015-05-20 05:22:24 -0700727MidiManagerAlsa::AlsaSeqState::Port::~Port() = default;
agoode99d63292015-04-13 08:39:25 -0700728
729MidiManagerAlsa::AlsaSeqState::Client::Client(const std::string& name,
730 snd_seq_client_type_t type)
731 : name_(name), type_(type), ports_deleter_(&ports_) {
732}
733
agoodeb0582872015-05-20 05:22:24 -0700734MidiManagerAlsa::AlsaSeqState::Client::~Client() = default;
agoode99d63292015-04-13 08:39:25 -0700735
736void MidiManagerAlsa::AlsaSeqState::Client::AddPort(int addr,
737 scoped_ptr<Port> port) {
738 RemovePort(addr);
739 ports_[addr] = port.release();
740}
741
742void MidiManagerAlsa::AlsaSeqState::Client::RemovePort(int addr) {
743 auto it = ports_.find(addr);
744 if (it != ports_.end()) {
745 delete it->second;
746 ports_.erase(it);
747 }
748}
749
750MidiManagerAlsa::AlsaSeqState::Client::PortMap::const_iterator
751MidiManagerAlsa::AlsaSeqState::Client::begin() const {
752 return ports_.begin();
753}
754
755MidiManagerAlsa::AlsaSeqState::Client::PortMap::const_iterator
756MidiManagerAlsa::AlsaSeqState::Client::end() const {
757 return ports_.end();
agoodeaf6e9f52015-03-24 10:23:49 -0700758}
759
agoodeb09423b2015-05-11 11:39:57 -0700760MidiManagerAlsa::AlsaCard::AlsaCard(udev_device* dev,
agooded87fc0f2015-05-21 08:29:31 -0700761 const std::string& name,
762 const std::string& longname,
763 const std::string& driver,
agoodeb09423b2015-05-11 11:39:57 -0700764 int midi_device_count)
agooded87fc0f2015-05-21 08:29:31 -0700765 : name_(name),
766 longname_(longname),
767 driver_(driver),
768 path_(device::UdevDeviceGetPropertyValue(dev, kUdevIdPath)),
769 bus_(device::UdevDeviceGetPropertyValue(dev, kUdevIdBus)),
770 vendor_id_(
771 UdevDeviceGetPropertyOrSysattr(dev, kUdevIdVendorId, kSysattrVendor)),
772 model_id_(
773 UdevDeviceGetPropertyOrSysattr(dev, kUdevIdModelId, kSysattrModel)),
774 usb_interface_num_(
775 device::UdevDeviceGetPropertyValue(dev, kUdevIdUsbInterfaceNum)),
776 serial_(UdevDeviceGetPropertyOrSysattr(dev,
777 kUdevIdSerialShort,
778 kSysattrGuid)),
779 midi_device_count_(midi_device_count),
780 manufacturer_(ExtractManufacturerString(
781 GetVendor(dev),
782 vendor_id_,
783 device::UdevDeviceGetPropertyValue(dev, kUdevIdVendorFromDatabase),
784 name,
785 longname)) {
agoodeb09423b2015-05-11 11:39:57 -0700786}
787
agoodeb0582872015-05-20 05:22:24 -0700788MidiManagerAlsa::AlsaCard::~AlsaCard() = default;
agoodeb09423b2015-05-11 11:39:57 -0700789
agoode55a8b522015-03-08 12:40:17 -0700790// static
agoodeb09423b2015-05-11 11:39:57 -0700791std::string MidiManagerAlsa::AlsaCard::ExtractManufacturerString(
agoode5e4e9cd2015-03-09 12:34:24 -0700792 const std::string& udev_id_vendor,
agoode55a8b522015-03-08 12:40:17 -0700793 const std::string& udev_id_vendor_id,
794 const std::string& udev_id_vendor_from_database,
795 const std::string& alsa_name,
796 const std::string& alsa_longname) {
797 // Let's try to determine the manufacturer. Here is the ordered preference
798 // in extraction:
agoodef212b2a2015-03-19 12:53:23 -0700799 // 1. Vendor name from the hardware device string, from udev properties
800 // or sysattrs.
agoode5e4e9cd2015-03-09 12:34:24 -0700801 // 2. Vendor name from the udev database (property ID_VENDOR_FROM_DATABASE).
agoode55a8b522015-03-08 12:40:17 -0700802 // 3. Heuristic from ALSA.
803
agoodee83758c2015-03-23 22:07:54 -0700804 // Is the vendor string present and not just the vendor hex id?
805 if (!udev_id_vendor.empty() && (udev_id_vendor != udev_id_vendor_id)) {
agoode55a8b522015-03-08 12:40:17 -0700806 return udev_id_vendor;
807 }
808
809 // Is there a vendor string in the hardware database?
810 if (!udev_id_vendor_from_database.empty()) {
811 return udev_id_vendor_from_database;
812 }
813
814 // Ok, udev gave us nothing useful, or was unavailable. So try a heuristic.
815 // We assume that card longname is in the format of
816 // "<manufacturer> <name> at <bus>". Otherwise, we give up to detect
817 // a manufacturer name here.
818 size_t at_index = alsa_longname.rfind(" at ");
agoodef212b2a2015-03-19 12:53:23 -0700819 if (at_index && at_index != std::string::npos) {
agoode55a8b522015-03-08 12:40:17 -0700820 size_t name_index = alsa_longname.rfind(alsa_name, at_index - 1);
agoodef212b2a2015-03-19 12:53:23 -0700821 if (name_index && name_index != std::string::npos)
agoode55a8b522015-03-08 12:40:17 -0700822 return alsa_longname.substr(0, name_index - 1);
823 }
824
825 // Failure.
826 return "";
827}
828
agoodebd4be9b2015-03-16 19:17:25 -0700829void MidiManagerAlsa::SendMidiData(uint32 port_index,
830 const std::vector<uint8>& data) {
skyostil93e2ec22015-06-17 08:49:09 -0700831 DCHECK(send_thread_.task_runner()->BelongsToCurrentThread());
agoodebd4be9b2015-03-16 19:17:25 -0700832
agoodef212b2a2015-03-19 12:53:23 -0700833 snd_midi_event_t* encoder;
834 snd_midi_event_new(kSendBufferSize, &encoder);
agoode5a1aa112015-06-21 20:51:00 -0700835 for (const auto datum : data) {
agoodebd4be9b2015-03-16 19:17:25 -0700836 snd_seq_event_t event;
agoode5a1aa112015-06-21 20:51:00 -0700837 int result = snd_midi_event_encode_byte(encoder, datum, &event);
agoodebd4be9b2015-03-16 19:17:25 -0700838 if (result == 1) {
839 // Full event, send it.
agoode99d63292015-04-13 08:39:25 -0700840 base::AutoLock lock(out_ports_lock_);
841 auto it = out_ports_.find(port_index);
842 if (it != out_ports_.end()) {
843 snd_seq_ev_set_source(&event, it->second);
844 snd_seq_ev_set_subs(&event);
845 snd_seq_ev_set_direct(&event);
agoode5a1aa112015-06-21 20:51:00 -0700846 snd_seq_event_output_direct(out_client_.get(), &event);
agoode99d63292015-04-13 08:39:25 -0700847 }
agoodebd4be9b2015-03-16 19:17:25 -0700848 }
849 }
agoodef212b2a2015-03-19 12:53:23 -0700850 snd_midi_event_free(encoder);
agoodebd4be9b2015-03-16 19:17:25 -0700851}
852
853void MidiManagerAlsa::ScheduleEventLoop() {
854 event_thread_.message_loop()->PostTask(
855 FROM_HERE,
856 base::Bind(&MidiManagerAlsa::EventLoop, base::Unretained(this)));
857}
858
859void MidiManagerAlsa::EventLoop() {
agoode975043d2015-05-11 00:46:17 -0700860 bool loop_again = true;
agoodebd4be9b2015-03-16 19:17:25 -0700861
agoode975043d2015-05-11 00:46:17 -0700862 struct pollfd pfd[2];
agoode5a1aa112015-06-21 20:51:00 -0700863 snd_seq_poll_descriptors(in_client_.get(), &pfd[0], 1, POLLIN);
agoode975043d2015-05-11 00:46:17 -0700864 pfd[1].fd = device::udev_monitor_get_fd(udev_monitor_.get());
865 pfd[1].events = POLLIN;
agoodebd4be9b2015-03-16 19:17:25 -0700866
agoode975043d2015-05-11 00:46:17 -0700867 int err = HANDLE_EINTR(poll(pfd, arraysize(pfd), -1));
868 if (err < 0) {
brettwf7f870f2015-06-09 11:05:24 -0700869 VLOG(1) << "poll fails: " << base::safe_strerror(errno);
agoode975043d2015-05-11 00:46:17 -0700870 loop_again = false;
agoodeaf6e9f52015-03-24 10:23:49 -0700871 } else {
agoode975043d2015-05-11 00:46:17 -0700872 if (pfd[0].revents & POLLIN) {
873 // Read available incoming MIDI data.
874 int remaining;
875 double timestamp =
876 (base::TimeTicks::Now() - base::TimeTicks()).InSecondsF();
877 do {
878 snd_seq_event_t* event;
agoode5a1aa112015-06-21 20:51:00 -0700879 err = snd_seq_event_input(in_client_.get(), &event);
880 remaining = snd_seq_event_input_pending(in_client_.get(), 0);
agoode975043d2015-05-11 00:46:17 -0700881
882 if (err == -ENOSPC) {
883 // Handle out of space error.
884 VLOG(1) << "snd_seq_event_input detected buffer overrun";
885 // We've lost events: check another way to see if we need to shut
886 // down.
887 base::AutoLock lock(shutdown_lock_);
888 if (event_thread_shutdown_)
889 loop_again = false;
890 } else if (err == -EAGAIN) {
891 // We've read all the data.
892 } else if (err < 0) {
893 // Handle other errors.
894 VLOG(1) << "snd_seq_event_input fails: " << snd_strerror(err);
895 // TODO(agoode): Use RecordAction() or similar to log this.
896 loop_again = false;
897 } else if (event->source.client == SND_SEQ_CLIENT_SYSTEM &&
898 event->source.port == SND_SEQ_PORT_SYSTEM_ANNOUNCE) {
899 // Handle announce events.
900 switch (event->type) {
901 case SND_SEQ_EVENT_PORT_START:
902 // Don't use SND_SEQ_EVENT_CLIENT_START because the
903 // client name may not be set by the time we query
904 // it. It should be set by the time ports are made.
905 ProcessClientStartEvent(event->data.addr.client);
906 ProcessPortStartEvent(event->data.addr);
907 break;
908 case SND_SEQ_EVENT_CLIENT_EXIT:
909 // Check for disconnection of our "out" client. This means "shut
910 // down".
911 if (event->data.addr.client == out_client_id_) {
912 loop_again = false;
913 remaining = 0;
914 } else
915 ProcessClientExitEvent(event->data.addr);
916 break;
917 case SND_SEQ_EVENT_PORT_EXIT:
918 ProcessPortExitEvent(event->data.addr);
919 break;
920 }
921 } else {
922 // Normal operation.
923 ProcessSingleEvent(event, timestamp);
924 }
925 } while (remaining > 0);
926 }
927 if (pfd[1].revents & POLLIN) {
928 device::ScopedUdevDevicePtr dev(
929 device::udev_monitor_receive_device(udev_monitor_.get()));
930 if (dev.get())
931 ProcessUdevEvent(dev.get());
932 else
933 VLOG(1) << "udev_monitor_receive_device fails";
934 }
agoodebd4be9b2015-03-16 19:17:25 -0700935 }
936
agoodebd4be9b2015-03-16 19:17:25 -0700937 // Do again.
agoode975043d2015-05-11 00:46:17 -0700938 if (loop_again)
939 ScheduleEventLoop();
agoodebd4be9b2015-03-16 19:17:25 -0700940}
941
942void MidiManagerAlsa::ProcessSingleEvent(snd_seq_event_t* event,
943 double timestamp) {
agoode99d63292015-04-13 08:39:25 -0700944 auto source_it =
945 source_map_.find(AddrToInt(event->source.client, event->source.port));
agoodebd4be9b2015-03-16 19:17:25 -0700946 if (source_it != source_map_.end()) {
947 uint32 source = source_it->second;
948 if (event->type == SND_SEQ_EVENT_SYSEX) {
949 // Special! Variable-length sysex.
950 ReceiveMidiData(source, static_cast<const uint8*>(event->data.ext.ptr),
951 event->data.ext.len, timestamp);
952 } else {
953 // Otherwise, decode this and send that on.
954 unsigned char buf[12];
agoode5a1aa112015-06-21 20:51:00 -0700955 long count =
956 snd_midi_event_decode(decoder_.get(), buf, sizeof(buf), event);
agoodebd4be9b2015-03-16 19:17:25 -0700957 if (count <= 0) {
958 if (count != -ENOENT) {
959 // ENOENT means that it's not a MIDI message, which is not an
960 // error, but other negative values are errors for us.
961 VLOG(1) << "snd_midi_event_decoder fails " << snd_strerror(count);
962 // TODO(agoode): Record this failure.
963 }
964 } else {
965 ReceiveMidiData(source, buf, count, timestamp);
966 }
967 }
968 }
969}
970
agoode99d63292015-04-13 08:39:25 -0700971void MidiManagerAlsa::ProcessClientStartEvent(int client_id) {
972 // Ignore if client is already started.
973 if (alsa_seq_state_.ClientStarted(client_id))
974 return;
975
976 snd_seq_client_info_t* client_info;
977 snd_seq_client_info_alloca(&client_info);
agoode5a1aa112015-06-21 20:51:00 -0700978 int err =
979 snd_seq_get_any_client_info(in_client_.get(), client_id, client_info);
agoode99d63292015-04-13 08:39:25 -0700980 if (err != 0)
981 return;
982
983 // Skip our own clients.
984 if ((client_id == in_client_id_) || (client_id == out_client_id_))
985 return;
986
987 // Update our view of ALSA seq state.
988 alsa_seq_state_.ClientStart(client_id,
989 snd_seq_client_info_get_name(client_info),
990 snd_seq_client_info_get_type(client_info));
991
992 // Generate Web MIDI events.
993 UpdatePortStateAndGenerateEvents();
994}
995
996void MidiManagerAlsa::ProcessPortStartEvent(const snd_seq_addr_t& addr) {
997 snd_seq_port_info_t* port_info;
998 snd_seq_port_info_alloca(&port_info);
agoode5a1aa112015-06-21 20:51:00 -0700999 int err = snd_seq_get_any_port_info(in_client_.get(), addr.client, addr.port,
1000 port_info);
agoode99d63292015-04-13 08:39:25 -07001001 if (err != 0)
1002 return;
1003
1004 unsigned int caps = snd_seq_port_info_get_capability(port_info);
1005 bool input = (caps & kRequiredInputPortCaps) == kRequiredInputPortCaps;
1006 bool output = (caps & kRequiredOutputPortCaps) == kRequiredOutputPortCaps;
1007 AlsaSeqState::PortDirection direction;
1008 if (input && output)
1009 direction = AlsaSeqState::PortDirection::kDuplex;
1010 else if (input)
1011 direction = AlsaSeqState::PortDirection::kInput;
1012 else if (output)
1013 direction = AlsaSeqState::PortDirection::kOutput;
1014 else
1015 return;
1016
1017 // Update our view of ALSA seq state.
1018 alsa_seq_state_.PortStart(
1019 addr.client, addr.port, snd_seq_port_info_get_name(port_info), direction,
1020 snd_seq_port_info_get_type(port_info) & SND_SEQ_PORT_TYPE_MIDI_GENERIC);
1021 // Generate Web MIDI events.
1022 UpdatePortStateAndGenerateEvents();
1023}
1024
1025void MidiManagerAlsa::ProcessClientExitEvent(const snd_seq_addr_t& addr) {
1026 // Update our view of ALSA seq state.
1027 alsa_seq_state_.ClientExit(addr.client);
1028 // Generate Web MIDI events.
1029 UpdatePortStateAndGenerateEvents();
1030}
1031
1032void MidiManagerAlsa::ProcessPortExitEvent(const snd_seq_addr_t& addr) {
1033 // Update our view of ALSA seq state.
1034 alsa_seq_state_.PortExit(addr.client, addr.port);
1035 // Generate Web MIDI events.
1036 UpdatePortStateAndGenerateEvents();
1037}
1038
agoode975043d2015-05-11 00:46:17 -07001039void MidiManagerAlsa::ProcessUdevEvent(udev_device* dev) {
1040 // Only card devices have this property set, and only when they are
1041 // fully initialized.
1042 if (!device::udev_device_get_property_value(dev,
1043 kUdevPropertySoundInitialized))
1044 return;
1045
1046 // Get the action. If no action, then we are doing first time enumeration
1047 // and the device is treated as new.
1048 const char* action = device::udev_device_get_action(dev);
1049 if (!action)
1050 action = kUdevActionChange;
1051
1052 if (strcmp(action, kUdevActionChange) == 0) {
agoodeb09423b2015-05-11 11:39:57 -07001053 AddCard(dev);
1054 // Generate Web MIDI events.
1055 UpdatePortStateAndGenerateEvents();
agoode975043d2015-05-11 00:46:17 -07001056 } else if (strcmp(action, kUdevActionRemove) == 0) {
agoodeb09423b2015-05-11 11:39:57 -07001057 RemoveCard(GetCardNumber(dev));
1058 // Generate Web MIDI events.
1059 UpdatePortStateAndGenerateEvents();
agoode975043d2015-05-11 00:46:17 -07001060 }
1061}
1062
agoodeb09423b2015-05-11 11:39:57 -07001063void MidiManagerAlsa::AddCard(udev_device* dev) {
1064 int number = GetCardNumber(dev);
1065 if (number == -1)
1066 return;
1067
1068 RemoveCard(number);
1069
1070 snd_ctl_card_info_t* card;
1071 snd_hwdep_info_t* hwdep;
1072 snd_ctl_card_info_alloca(&card);
1073 snd_hwdep_info_alloca(&hwdep);
1074 const std::string id = base::StringPrintf("hw:CARD=%i", number);
1075 snd_ctl_t* handle;
1076 int err = snd_ctl_open(&handle, id.c_str(), 0);
1077 if (err != 0) {
1078 VLOG(1) << "snd_ctl_open fails: " << snd_strerror(err);
1079 return;
1080 }
1081 err = snd_ctl_card_info(handle, card);
1082 if (err != 0) {
1083 VLOG(1) << "snd_ctl_card_info fails: " << snd_strerror(err);
1084 snd_ctl_close(handle);
1085 return;
1086 }
1087 std::string name = snd_ctl_card_info_get_name(card);
1088 std::string longname = snd_ctl_card_info_get_longname(card);
1089 std::string driver = snd_ctl_card_info_get_driver(card);
1090
1091 // Count rawmidi devices (not subdevices).
1092 int midi_count = 0;
1093 for (int device = -1;
1094 !snd_ctl_rawmidi_next_device(handle, &device) && device >= 0;)
1095 ++midi_count;
1096
1097 // Count any hwdep synths that become MIDI devices outside of rawmidi.
1098 //
1099 // Explanation:
1100 // Any kernel driver can create an ALSA client (visible to us).
1101 // With modern hardware, only rawmidi devices do this. Kernel
1102 // drivers create rawmidi devices and the rawmidi subsystem makes
1103 // the seq clients. But the OPL3 driver is special, it does not
1104 // make a rawmidi device but a seq client directly. (This is the
1105 // only one to worry about in the kernel code, as of 2015-03-23.)
1106 //
1107 // OPL3 is very old (but still possible to get in new
1108 // hardware). It is unlikely that new drivers would not use
1109 // rawmidi and defeat our heuristic.
1110 //
1111 // Longer term, support should be added in the kernel to expose a
1112 // direct link from card->client (or client->card) so that all
1113 // these heuristics will be obsolete. Once that is there, we can
1114 // assume our old heuristics will work on old kernels and the new
1115 // robust code will be used on new. Then we will not need to worry
1116 // about changes to kernel internals breaking our code.
1117 // See the TODO above at kMinimumClientIdForCards.
1118 for (int device = -1;
1119 !snd_ctl_hwdep_next_device(handle, &device) && device >= 0;) {
1120 err = snd_ctl_hwdep_info(handle, hwdep);
1121 if (err != 0) {
1122 VLOG(1) << "snd_ctl_hwdep_info fails: " << snd_strerror(err);
1123 continue;
1124 }
1125 snd_hwdep_iface_t iface = snd_hwdep_info_get_iface(hwdep);
1126 if (iface == SND_HWDEP_IFACE_OPL2 || iface == SND_HWDEP_IFACE_OPL3 ||
1127 iface == SND_HWDEP_IFACE_OPL4)
1128 ++midi_count;
1129 }
1130 snd_ctl_close(handle);
1131
1132 if (midi_count > 0)
1133 alsa_cards_[number] = new AlsaCard(dev, name, longname, driver, midi_count);
1134 alsa_card_midi_count_ += midi_count;
1135}
1136
1137void MidiManagerAlsa::RemoveCard(int number) {
1138 auto it = alsa_cards_.find(number);
1139 if (it == alsa_cards_.end())
1140 return;
1141
1142 alsa_card_midi_count_ -= it->second->midi_device_count();
1143 delete it->second;
1144 alsa_cards_.erase(it);
1145}
1146
agoode99d63292015-04-13 08:39:25 -07001147void MidiManagerAlsa::UpdatePortStateAndGenerateEvents() {
agoodeb09423b2015-05-11 11:39:57 -07001148 // Verify that our information from ALSA and udev are in sync. If
1149 // not, we cannot generate events right now.
1150 if (alsa_card_midi_count_ != alsa_seq_state_.card_client_count())
1151 return;
1152
agoode99d63292015-04-13 08:39:25 -07001153 // Generate new port state.
agoodeb09423b2015-05-11 11:39:57 -07001154 auto new_port_state = alsa_seq_state_.ToMidiPortState(alsa_cards_);
agoode99d63292015-04-13 08:39:25 -07001155
1156 // Disconnect any connected old ports that are now missing.
1157 for (auto* old_port : port_state_) {
1158 if (old_port->connected() &&
1159 (new_port_state->FindConnected(*old_port) == new_port_state->end())) {
1160 old_port->set_connected(false);
1161 uint32 web_port_index = old_port->web_port_index();
1162 switch (old_port->type()) {
1163 case MidiPort::Type::kInput:
1164 source_map_.erase(
1165 AddrToInt(old_port->client_id(), old_port->port_id()));
1166 SetInputPortState(web_port_index, MIDI_PORT_DISCONNECTED);
1167 break;
1168 case MidiPort::Type::kOutput:
1169 DeleteAlsaOutputPort(web_port_index);
1170 SetOutputPortState(web_port_index, MIDI_PORT_DISCONNECTED);
1171 break;
1172 }
1173 }
1174 }
1175
1176 // Reconnect or add new ports.
1177 auto it = new_port_state->begin();
1178 while (it != new_port_state->end()) {
1179 auto* new_port = *it;
1180 auto old_port = port_state_.Find(*new_port);
1181 if (old_port == port_state_.end()) {
1182 // Add new port.
agoode5a1aa112015-06-21 20:51:00 -07001183 uint32 web_port_index = port_state_.Insert(make_scoped_ptr(new_port));
agoode99d63292015-04-13 08:39:25 -07001184 MidiPortInfo info(new_port->OpaqueKey(), new_port->manufacturer(),
1185 new_port->port_name(), new_port->version(),
1186 MIDI_PORT_OPENED);
1187 switch (new_port->type()) {
1188 case MidiPort::Type::kInput:
1189 if (Subscribe(web_port_index, new_port->client_id(),
1190 new_port->port_id()))
1191 AddInputPort(info);
1192 break;
agoode99d63292015-04-13 08:39:25 -07001193 case MidiPort::Type::kOutput:
1194 if (CreateAlsaOutputPort(web_port_index, new_port->client_id(),
1195 new_port->port_id()))
1196 AddOutputPort(info);
1197 break;
1198 }
1199 it = new_port_state->weak_erase(it);
1200 } else if (!(*old_port)->connected()) {
1201 // Reconnect.
1202 uint32 web_port_index = (*old_port)->web_port_index();
1203 (*old_port)->Update(new_port->path(), new_port->client_id(),
1204 new_port->port_id(), new_port->client_name(),
1205 new_port->port_name(), new_port->manufacturer(),
1206 new_port->version());
1207 switch ((*old_port)->type()) {
1208 case MidiPort::Type::kInput:
1209 if (Subscribe(web_port_index, (*old_port)->client_id(),
1210 (*old_port)->port_id()))
1211 SetInputPortState(web_port_index, MIDI_PORT_OPENED);
1212 break;
agoode99d63292015-04-13 08:39:25 -07001213 case MidiPort::Type::kOutput:
1214 if (CreateAlsaOutputPort(web_port_index, (*old_port)->client_id(),
1215 (*old_port)->port_id()))
1216 SetOutputPortState(web_port_index, MIDI_PORT_OPENED);
1217 break;
1218 }
1219 (*old_port)->set_connected(true);
1220 ++it;
1221 } else {
1222 ++it;
1223 }
1224 }
1225}
1226
agoode975043d2015-05-11 00:46:17 -07001227// TODO(agoode): return false on failure.
agoode99d63292015-04-13 08:39:25 -07001228void MidiManagerAlsa::EnumerateAlsaPorts() {
1229 snd_seq_client_info_t* client_info;
1230 snd_seq_client_info_alloca(&client_info);
1231 snd_seq_port_info_t* port_info;
1232 snd_seq_port_info_alloca(&port_info);
1233
1234 // Enumerate clients.
1235 snd_seq_client_info_set_client(client_info, -1);
agoode5a1aa112015-06-21 20:51:00 -07001236 while (!snd_seq_query_next_client(in_client_.get(), client_info)) {
agoode99d63292015-04-13 08:39:25 -07001237 int client_id = snd_seq_client_info_get_client(client_info);
1238 ProcessClientStartEvent(client_id);
1239
1240 // Enumerate ports.
1241 snd_seq_port_info_set_client(port_info, client_id);
1242 snd_seq_port_info_set_port(port_info, -1);
agoode5a1aa112015-06-21 20:51:00 -07001243 while (!snd_seq_query_next_port(in_client_.get(), port_info)) {
agoode99d63292015-04-13 08:39:25 -07001244 const snd_seq_addr_t* addr = snd_seq_port_info_get_addr(port_info);
1245 ProcessPortStartEvent(*addr);
1246 }
1247 }
1248}
1249
agoode975043d2015-05-11 00:46:17 -07001250bool MidiManagerAlsa::EnumerateUdevCards() {
1251 int err;
1252
1253 device::ScopedUdevEnumeratePtr enumerate(
1254 device::udev_enumerate_new(udev_.get()));
1255 if (!enumerate.get()) {
1256 VLOG(1) << "udev_enumerate_new fails";
1257 return false;
1258 }
1259
1260 err = device::udev_enumerate_add_match_subsystem(enumerate.get(),
1261 kUdevSubsystemSound);
1262 if (err) {
1263 VLOG(1) << "udev_enumerate_add_match_subsystem fails: "
brettwf7f870f2015-06-09 11:05:24 -07001264 << base::safe_strerror(-err);
agoode975043d2015-05-11 00:46:17 -07001265 return false;
1266 }
1267
1268 err = device::udev_enumerate_scan_devices(enumerate.get());
1269 if (err) {
brettwf7f870f2015-06-09 11:05:24 -07001270 VLOG(1) << "udev_enumerate_scan_devices fails: "
1271 << base::safe_strerror(-err);
agoode975043d2015-05-11 00:46:17 -07001272 return false;
1273 }
1274
1275 udev_list_entry* list_entry;
1276 auto* devices = device::udev_enumerate_get_list_entry(enumerate.get());
1277 udev_list_entry_foreach(list_entry, devices) {
1278 const char* path = device::udev_list_entry_get_name(list_entry);
1279 device::ScopedUdevDevicePtr dev(
1280 device::udev_device_new_from_syspath(udev_.get(), path));
1281 if (dev.get())
1282 ProcessUdevEvent(dev.get());
1283 }
1284
1285 return true;
1286}
1287
agoode99d63292015-04-13 08:39:25 -07001288bool MidiManagerAlsa::CreateAlsaOutputPort(uint32 port_index,
1289 int client_id,
1290 int port_id) {
1291 // Create the port.
1292 int out_port = snd_seq_create_simple_port(
agoode5a1aa112015-06-21 20:51:00 -07001293 out_client_.get(), NULL, kCreateOutputPortCaps, kCreatePortType);
agoode99d63292015-04-13 08:39:25 -07001294 if (out_port < 0) {
1295 VLOG(1) << "snd_seq_create_simple_port fails: " << snd_strerror(out_port);
1296 return false;
1297 }
1298 // Activate port subscription.
1299 snd_seq_port_subscribe_t* subs;
1300 snd_seq_port_subscribe_alloca(&subs);
1301 snd_seq_addr_t sender;
1302 sender.client = out_client_id_;
1303 sender.port = out_port;
1304 snd_seq_port_subscribe_set_sender(subs, &sender);
1305 snd_seq_addr_t dest;
1306 dest.client = client_id;
1307 dest.port = port_id;
1308 snd_seq_port_subscribe_set_dest(subs, &dest);
agoode5a1aa112015-06-21 20:51:00 -07001309 int err = snd_seq_subscribe_port(out_client_.get(), subs);
agoode99d63292015-04-13 08:39:25 -07001310 if (err != 0) {
1311 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
agoode5a1aa112015-06-21 20:51:00 -07001312 snd_seq_delete_simple_port(out_client_.get(), out_port);
agoode99d63292015-04-13 08:39:25 -07001313 return false;
1314 }
1315
1316 // Update our map.
1317 base::AutoLock lock(out_ports_lock_);
1318 out_ports_[port_index] = out_port;
1319 return true;
1320}
1321
1322void MidiManagerAlsa::DeleteAlsaOutputPort(uint32 port_index) {
1323 base::AutoLock lock(out_ports_lock_);
1324 auto it = out_ports_.find(port_index);
1325 if (it == out_ports_.end())
1326 return;
1327
1328 int alsa_port = it->second;
agoode5a1aa112015-06-21 20:51:00 -07001329 snd_seq_delete_simple_port(out_client_.get(), alsa_port);
agoode99d63292015-04-13 08:39:25 -07001330 out_ports_.erase(it);
1331}
1332
1333bool MidiManagerAlsa::Subscribe(uint32 port_index, int client_id, int port_id) {
1334 // Activate port subscription.
1335 snd_seq_port_subscribe_t* subs;
1336 snd_seq_port_subscribe_alloca(&subs);
1337 snd_seq_addr_t sender;
1338 sender.client = client_id;
1339 sender.port = port_id;
1340 snd_seq_port_subscribe_set_sender(subs, &sender);
1341 snd_seq_addr_t dest;
1342 dest.client = in_client_id_;
1343 dest.port = in_port_id_;
1344 snd_seq_port_subscribe_set_dest(subs, &dest);
agoode5a1aa112015-06-21 20:51:00 -07001345 int err = snd_seq_subscribe_port(in_client_.get(), subs);
agoode99d63292015-04-13 08:39:25 -07001346 if (err != 0) {
1347 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
1348 return false;
1349 }
1350
1351 // Update our map.
1352 source_map_[AddrToInt(client_id, port_id)] = port_index;
1353 return true;
1354}
1355
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +00001356MidiManager* MidiManager::Create() {
1357 return new MidiManagerAlsa();
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +00001358}
1359
toyoshime147c5e2015-05-07 21:58:31 -07001360} // namespace midi
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +00001361} // namespace media