blob: e42543078261ee411162ff6d3108e85d1095632f [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()
mgiucad9af8452015-06-25 02:11:57 -0700148 : udev_(device::udev_new()),
agoode@chromium.org25227512014-06-08 05:12:05 +0000149 send_thread_("MidiSendThread"),
agoode5a1aa112015-06-21 20:51:00 -0700150 event_thread_("MidiEventThread") {
agoode@chromium.org25227512014-06-08 05:12:05 +0000151 // Initialize decoder.
agoode5a1aa112015-06-21 20:51:00 -0700152 snd_midi_event_t* decoder;
153 snd_midi_event_new(0, &decoder);
154 decoder_.reset(decoder);
155 snd_midi_event_no_status(decoder_.get(), 1);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000156}
157
agoodebd4be9b2015-03-16 19:17:25 -0700158MidiManagerAlsa::~MidiManagerAlsa() {
159 // Tell the event thread it will soon be time to shut down. This gives
160 // us assurance the thread will stop in case the SND_SEQ_EVENT_CLIENT_EXIT
161 // message is lost.
162 {
163 base::AutoLock lock(shutdown_lock_);
164 event_thread_shutdown_ = true;
165 }
166
167 // Stop the send thread.
168 send_thread_.Stop();
169
170 // Close the out client. This will trigger the event thread to stop,
171 // because of SND_SEQ_EVENT_CLIENT_EXIT.
agoode5a1aa112015-06-21 20:51:00 -0700172 if (out_client_.get())
173 snd_seq_close(out_client_.release());
agoodebd4be9b2015-03-16 19:17:25 -0700174
175 // Wait for the event thread to stop.
176 event_thread_.Stop();
agoodebd4be9b2015-03-16 19:17:25 -0700177}
178
toyoshim@chromium.org51c7f532014-05-01 17:17:32 +0000179void MidiManagerAlsa::StartInitialization() {
agoode@chromium.org25227512014-06-08 05:12:05 +0000180 // TODO(agoode): Move off I/O thread. See http://crbug.com/374341.
181
182 // Create client handles.
agoode5a1aa112015-06-21 20:51:00 -0700183 snd_seq_t* in_client;
agoode975043d2015-05-11 00:46:17 -0700184 int err =
agoode5a1aa112015-06-21 20:51:00 -0700185 snd_seq_open(&in_client, kAlsaHw, SND_SEQ_OPEN_INPUT, SND_SEQ_NONBLOCK);
agoode@chromium.org25227512014-06-08 05:12:05 +0000186 if (err != 0) {
187 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err);
188 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
189 }
agoode5a1aa112015-06-21 20:51:00 -0700190 in_client_.reset(in_client);
191 in_client_id_ = snd_seq_client_id(in_client_.get());
192
193 snd_seq_t* out_client;
194 err = snd_seq_open(&out_client, kAlsaHw, SND_SEQ_OPEN_OUTPUT, 0);
agoode@chromium.org25227512014-06-08 05:12:05 +0000195 if (err != 0) {
196 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err);
197 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
198 }
agoode5a1aa112015-06-21 20:51:00 -0700199 out_client_.reset(out_client);
200 out_client_id_ = snd_seq_client_id(out_client_.get());
agoode@chromium.org25227512014-06-08 05:12:05 +0000201
202 // Name the clients.
agoode5a1aa112015-06-21 20:51:00 -0700203 err = snd_seq_set_client_name(in_client_.get(), "Chrome (input)");
agoode@chromium.org25227512014-06-08 05:12:05 +0000204 if (err != 0) {
205 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err);
206 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
207 }
agoode5a1aa112015-06-21 20:51:00 -0700208 err = snd_seq_set_client_name(out_client_.get(), "Chrome (output)");
agoode@chromium.org25227512014-06-08 05:12:05 +0000209 if (err != 0) {
210 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err);
211 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
212 }
213
214 // Create input port.
agoode99d63292015-04-13 08:39:25 -0700215 in_port_id_ = snd_seq_create_simple_port(
agoode5a1aa112015-06-21 20:51:00 -0700216 in_client_.get(), NULL, kCreateInputPortCaps, kCreatePortType);
agoode99d63292015-04-13 08:39:25 -0700217 if (in_port_id_ < 0) {
218 VLOG(1) << "snd_seq_create_simple_port fails: "
219 << snd_strerror(in_port_id_);
agoode@chromium.org25227512014-06-08 05:12:05 +0000220 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
221 }
222
223 // Subscribe to the announce port.
224 snd_seq_port_subscribe_t* subs;
225 snd_seq_port_subscribe_alloca(&subs);
226 snd_seq_addr_t announce_sender;
227 snd_seq_addr_t announce_dest;
228 announce_sender.client = SND_SEQ_CLIENT_SYSTEM;
229 announce_sender.port = SND_SEQ_PORT_SYSTEM_ANNOUNCE;
agoode99d63292015-04-13 08:39:25 -0700230 announce_dest.client = in_client_id_;
231 announce_dest.port = in_port_id_;
agoode@chromium.org25227512014-06-08 05:12:05 +0000232 snd_seq_port_subscribe_set_sender(subs, &announce_sender);
233 snd_seq_port_subscribe_set_dest(subs, &announce_dest);
agoode5a1aa112015-06-21 20:51:00 -0700234 err = snd_seq_subscribe_port(in_client_.get(), subs);
agoode@chromium.org25227512014-06-08 05:12:05 +0000235 if (err != 0) {
236 VLOG(1) << "snd_seq_subscribe_port on the announce port fails: "
237 << snd_strerror(err);
238 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
239 }
240
agoode99d63292015-04-13 08:39:25 -0700241 // Generate hotplug events for existing ports.
agoode975043d2015-05-11 00:46:17 -0700242 // TODO(agoode): Check the return value for failure.
agoode99d63292015-04-13 08:39:25 -0700243 EnumerateAlsaPorts();
agoode@chromium.org25227512014-06-08 05:12:05 +0000244
agoode975043d2015-05-11 00:46:17 -0700245 // Initialize udev monitor.
246 udev_monitor_.reset(
247 device::udev_monitor_new_from_netlink(udev_.get(), kUdev));
248 if (!udev_monitor_.get()) {
249 VLOG(1) << "udev_monitor_new_from_netlink fails";
250 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
251 }
252 err = device::udev_monitor_filter_add_match_subsystem_devtype(
253 udev_monitor_.get(), kUdevSubsystemSound, nullptr);
254 if (err != 0) {
255 VLOG(1) << "udev_monitor_add_match_subsystem fails: "
brettwf7f870f2015-06-09 11:05:24 -0700256 << base::safe_strerror(-err);
agoode975043d2015-05-11 00:46:17 -0700257 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
258 }
259 err = device::udev_monitor_enable_receiving(udev_monitor_.get());
260 if (err != 0) {
brettwf7f870f2015-06-09 11:05:24 -0700261 VLOG(1) << "udev_monitor_enable_receiving fails: "
262 << base::safe_strerror(-err);
agoode975043d2015-05-11 00:46:17 -0700263 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
264 }
265
266 // Generate hotplug events for existing udev devices.
267 EnumerateUdevCards();
268
agoode99d63292015-04-13 08:39:25 -0700269 // Start processing events.
agoode@chromium.org25227512014-06-08 05:12:05 +0000270 event_thread_.Start();
271 event_thread_.message_loop()->PostTask(
272 FROM_HERE,
agoodebd4be9b2015-03-16 19:17:25 -0700273 base::Bind(&MidiManagerAlsa::ScheduleEventLoop, base::Unretained(this)));
agoode@chromium.org25227512014-06-08 05:12:05 +0000274
275 CompleteInitialization(MIDI_OK);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000276}
277
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000278void MidiManagerAlsa::DispatchSendMidiData(MidiManagerClient* client,
dnicoara@chromium.org9f2a6f02014-01-03 21:25:00 +0000279 uint32 port_index,
280 const std::vector<uint8>& data,
281 double timestamp) {
agoode@chromium.org25227512014-06-08 05:12:05 +0000282 // Not correct right now. http://crbug.com/374341.
283 if (!send_thread_.IsRunning())
284 send_thread_.Start();
285
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000286 base::TimeDelta delay;
287 if (timestamp != 0.0) {
288 base::TimeTicks time_to_send =
289 base::TimeTicks() + base::TimeDelta::FromMicroseconds(
agoodebd4be9b2015-03-16 19:17:25 -0700290 timestamp * base::Time::kMicrosecondsPerSecond);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000291 delay = std::max(time_to_send - base::TimeTicks::Now(), base::TimeDelta());
292 }
293
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000294 send_thread_.message_loop()->PostDelayedTask(
agoodebd4be9b2015-03-16 19:17:25 -0700295 FROM_HERE, base::Bind(&MidiManagerAlsa::SendMidiData,
296 base::Unretained(this), port_index, data),
297 delay);
agoode@chromium.org25227512014-06-08 05:12:05 +0000298
299 // Acknowledge send.
300 send_thread_.message_loop()->PostTask(
agoodebd4be9b2015-03-16 19:17:25 -0700301 FROM_HERE, base::Bind(&MidiManagerClient::AccumulateMidiBytesSent,
302 base::Unretained(client), data.size()));
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000303}
304
agooded87fc0f2015-05-21 08:29:31 -0700305MidiManagerAlsa::MidiPort::Id::Id() = default;
306
307MidiManagerAlsa::MidiPort::Id::Id(const std::string& bus,
308 const std::string& vendor_id,
309 const std::string& model_id,
310 const std::string& usb_interface_num,
311 const std::string& serial)
312 : bus_(bus),
313 vendor_id_(vendor_id),
314 model_id_(model_id),
315 usb_interface_num_(usb_interface_num),
316 serial_(serial) {
317}
318
319MidiManagerAlsa::MidiPort::Id::Id(const Id&) = default;
320
321MidiManagerAlsa::MidiPort::Id::~Id() = default;
322
323bool MidiManagerAlsa::MidiPort::Id::operator==(const Id& rhs) const {
324 return (bus_ == rhs.bus_) && (vendor_id_ == rhs.vendor_id_) &&
325 (model_id_ == rhs.model_id_) &&
326 (usb_interface_num_ == rhs.usb_interface_num_) &&
327 (serial_ == rhs.serial_);
328}
329
330bool MidiManagerAlsa::MidiPort::Id::empty() const {
331 return bus_.empty() && vendor_id_.empty() && model_id_.empty() &&
332 usb_interface_num_.empty() && serial_.empty();
333}
334
agoode99d63292015-04-13 08:39:25 -0700335MidiManagerAlsa::MidiPort::MidiPort(const std::string& path,
agooded87fc0f2015-05-21 08:29:31 -0700336 const Id& id,
agoode99d63292015-04-13 08:39:25 -0700337 int client_id,
338 int port_id,
339 int midi_device,
340 const std::string& client_name,
341 const std::string& port_name,
342 const std::string& manufacturer,
343 const std::string& version,
344 Type type)
345 : id_(id),
346 midi_device_(midi_device),
347 type_(type),
348 path_(path),
349 client_id_(client_id),
350 port_id_(port_id),
351 client_name_(client_name),
352 port_name_(port_name),
353 manufacturer_(manufacturer),
agoode5a1aa112015-06-21 20:51:00 -0700354 version_(version) {
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000355}
356
agoodeb0582872015-05-20 05:22:24 -0700357MidiManagerAlsa::MidiPort::~MidiPort() = default;
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000358
agoode99d63292015-04-13 08:39:25 -0700359// Note: keep synchronized with the MidiPort::Match* methods.
360scoped_ptr<base::Value> MidiManagerAlsa::MidiPort::Value() const {
361 scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue);
362
363 std::string type;
364 switch (type_) {
365 case Type::kInput:
366 type = "input";
367 break;
agoode99d63292015-04-13 08:39:25 -0700368 case Type::kOutput:
369 type = "output";
370 break;
371 }
372 value->SetString("type", type);
373 SetStringIfNonEmpty(value.get(), "path", path_);
agoode99d63292015-04-13 08:39:25 -0700374 SetStringIfNonEmpty(value.get(), "clientName", client_name_);
375 SetStringIfNonEmpty(value.get(), "portName", port_name_);
376 value->SetInteger("clientId", client_id_);
377 value->SetInteger("portId", port_id_);
378 value->SetInteger("midiDevice", midi_device_);
379
agooded87fc0f2015-05-21 08:29:31 -0700380 // Flatten id fields.
381 SetStringIfNonEmpty(value.get(), "bus", id_.bus());
382 SetStringIfNonEmpty(value.get(), "vendorId", id_.vendor_id());
383 SetStringIfNonEmpty(value.get(), "modelId", id_.model_id());
384 SetStringIfNonEmpty(value.get(), "usbInterfaceNum", id_.usb_interface_num());
385 SetStringIfNonEmpty(value.get(), "serial", id_.serial());
386
agoode99d63292015-04-13 08:39:25 -0700387 return value.Pass();
agoodebd4be9b2015-03-16 19:17:25 -0700388}
agoode@chromium.org25227512014-06-08 05:12:05 +0000389
agoode99d63292015-04-13 08:39:25 -0700390std::string MidiManagerAlsa::MidiPort::JSONValue() const {
391 std::string json;
392 JSONStringValueSerializer serializer(&json);
393 serializer.Serialize(*Value().get());
394 return json;
agoodef212b2a2015-03-19 12:53:23 -0700395}
396
agoode99d63292015-04-13 08:39:25 -0700397// TODO(agoode): Do not use SHA256 here. Instead store a persistent
398// mapping and just use a UUID or other random string.
399// http://crbug.com/465320
400std::string MidiManagerAlsa::MidiPort::OpaqueKey() const {
401 uint8 hash[crypto::kSHA256Length];
402 crypto::SHA256HashString(JSONValue(), &hash, sizeof(hash));
403 return base::HexEncode(&hash, sizeof(hash));
agoodebd4be9b2015-03-16 19:17:25 -0700404}
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000405
agoode99d63292015-04-13 08:39:25 -0700406bool MidiManagerAlsa::MidiPort::MatchConnected(const MidiPort& query) const {
407 // Matches on:
408 // connected == true
409 // type
410 // path
411 // id
412 // client_id
413 // port_id
414 // midi_device
415 // client_name
416 // port_name
417 return connected() && (type() == query.type()) && (path() == query.path()) &&
418 (id() == query.id()) && (client_id() == query.client_id()) &&
419 (port_id() == query.port_id()) &&
420 (midi_device() == query.midi_device()) &&
421 (client_name() == query.client_name()) &&
422 (port_name() == query.port_name());
agoodebd4be9b2015-03-16 19:17:25 -0700423}
424
agoode99d63292015-04-13 08:39:25 -0700425bool MidiManagerAlsa::MidiPort::MatchCardPass1(const MidiPort& query) const {
426 // Matches on:
427 // connected == false
428 // type
429 // path
430 // id
431 // port_id
432 // midi_device
433 return MatchCardPass2(query) && (path() == query.path());
agoodebd4be9b2015-03-16 19:17:25 -0700434}
435
agoode99d63292015-04-13 08:39:25 -0700436bool MidiManagerAlsa::MidiPort::MatchCardPass2(const MidiPort& query) const {
437 // Matches on:
438 // connected == false
439 // type
440 // id
441 // port_id
442 // midi_device
443 return !connected() && (type() == query.type()) && (id() == query.id()) &&
444 (port_id() == query.port_id()) &&
445 (midi_device() == query.midi_device());
agoodef212b2a2015-03-19 12:53:23 -0700446}
447
agoode99d63292015-04-13 08:39:25 -0700448bool MidiManagerAlsa::MidiPort::MatchNoCardPass1(const MidiPort& query) const {
449 // Matches on:
450 // connected == false
451 // type
452 // path.empty(), for both this and query
453 // id.empty(), for both this and query
454 // client_id
455 // port_id
456 // client_name
457 // port_name
458 // midi_device == -1, for both this and query
459 return MatchNoCardPass2(query) && (client_id() == query.client_id());
agoodef212b2a2015-03-19 12:53:23 -0700460}
461
agoode99d63292015-04-13 08:39:25 -0700462bool MidiManagerAlsa::MidiPort::MatchNoCardPass2(const MidiPort& query) const {
463 // Matches on:
464 // connected == false
465 // type
466 // path.empty(), for both this and query
467 // id.empty(), for both this and query
468 // port_id
469 // client_name
470 // port_name
471 // midi_device == -1, for both this and query
472 return !connected() && (type() == query.type()) && path().empty() &&
473 query.path().empty() && id().empty() && query.id().empty() &&
474 (port_id() == query.port_id()) &&
475 (client_name() == query.client_name()) &&
476 (port_name() == query.port_name()) && (midi_device() == -1) &&
477 (query.midi_device() == -1);
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000478}
479
agoodeb0582872015-05-20 05:22:24 -0700480MidiManagerAlsa::MidiPortStateBase::~MidiPortStateBase() = default;
agoode99d63292015-04-13 08:39:25 -0700481
482MidiManagerAlsa::MidiPortStateBase::iterator
483MidiManagerAlsa::MidiPortStateBase::Find(
484 const MidiManagerAlsa::MidiPort& port) {
485 auto result = FindConnected(port);
486 if (result == end())
487 result = FindDisconnected(port);
488 return result;
489}
490
491MidiManagerAlsa::MidiPortStateBase::iterator
492MidiManagerAlsa::MidiPortStateBase::FindConnected(
493 const MidiManagerAlsa::MidiPort& port) {
494 // Exact match required for connected ports.
495 auto it = std::find_if(ports_.begin(), ports_.end(), [&port](MidiPort* p) {
496 return p->MatchConnected(port);
497 });
498 return it;
499}
500
501MidiManagerAlsa::MidiPortStateBase::iterator
502MidiManagerAlsa::MidiPortStateBase::FindDisconnected(
503 const MidiManagerAlsa::MidiPort& port) {
504 // Always match on:
505 // type
506 // Possible things to match on:
507 // path
508 // id
509 // client_id
510 // port_id
511 // midi_device
512 // client_name
513 // port_name
514
515 if (!port.path().empty()) {
516 // If path is present, then we have a card-based client.
517
518 // Pass 1. Match on path, id, midi_device, port_id.
519 // This is the best possible match for hardware card-based clients.
520 // This will also match the empty id correctly for devices without an id.
521 auto it = std::find_if(ports_.begin(), ports_.end(), [&port](MidiPort* p) {
522 return p->MatchCardPass1(port);
523 });
524 if (it != ports_.end())
525 return it;
526
527 if (!port.id().empty()) {
528 // Pass 2. Match on id, midi_device, port_id.
529 // This will give us a high-confidence match when a user moves a device to
530 // another USB/Firewire/Thunderbolt/etc port, but only works if the device
531 // has a hardware id.
532 it = std::find_if(ports_.begin(), ports_.end(), [&port](MidiPort* p) {
533 return p->MatchCardPass2(port);
534 });
535 if (it != ports_.end())
536 return it;
537 }
538 } else {
539 // Else, we have a non-card-based client.
540 // Pass 1. Match on client_id, port_id, client_name, port_name.
541 // This will give us a reasonably good match.
542 auto it = std::find_if(ports_.begin(), ports_.end(), [&port](MidiPort* p) {
543 return p->MatchNoCardPass1(port);
544 });
545 if (it != ports_.end())
546 return it;
547
548 // Pass 2. Match on port_id, client_name, port_name.
549 // This is weaker but similar to pass 2 in the hardware card-based clients
550 // match.
551 it = std::find_if(ports_.begin(), ports_.end(), [&port](MidiPort* p) {
552 return p->MatchNoCardPass2(port);
553 });
554 if (it != ports_.end())
555 return it;
556 }
557
558 // No match.
559 return ports_.end();
560}
561
agoodeb0582872015-05-20 05:22:24 -0700562MidiManagerAlsa::MidiPortStateBase::MidiPortStateBase() = default;
agoode99d63292015-04-13 08:39:25 -0700563
564void MidiManagerAlsa::TemporaryMidiPortState::Insert(
565 scoped_ptr<MidiPort> port) {
agoode5a1aa112015-06-21 20:51:00 -0700566 ports().push_back(port.release());
agoode99d63292015-04-13 08:39:25 -0700567}
568
569MidiManagerAlsa::MidiPortState::MidiPortState()
570 : num_input_ports_(0), num_output_ports_(0) {
571}
572
573uint32 MidiManagerAlsa::MidiPortState::Insert(scoped_ptr<MidiPort> port) {
574 // Add the web midi index.
vivek.vg0365a022015-04-14 23:11:23 -0700575 uint32 web_port_index = 0;
agoode99d63292015-04-13 08:39:25 -0700576 switch (port->type()) {
577 case MidiPort::Type::kInput:
578 web_port_index = num_input_ports_++;
579 break;
580 case MidiPort::Type::kOutput:
581 web_port_index = num_output_ports_++;
582 break;
583 }
584 port->set_web_port_index(web_port_index);
agoode5a1aa112015-06-21 20:51:00 -0700585 ports().push_back(port.release());
agoode99d63292015-04-13 08:39:25 -0700586 return web_port_index;
587}
588
mgiucad9af8452015-06-25 02:11:57 -0700589MidiManagerAlsa::AlsaSeqState::AlsaSeqState() : card_client_count_(0) {
agoode99d63292015-04-13 08:39:25 -0700590}
591
agoodeb0582872015-05-20 05:22:24 -0700592MidiManagerAlsa::AlsaSeqState::~AlsaSeqState() = default;
agoode99d63292015-04-13 08:39:25 -0700593
594void MidiManagerAlsa::AlsaSeqState::ClientStart(int client_id,
595 const std::string& client_name,
596 snd_seq_client_type_t type) {
597 ClientExit(client_id);
mgiucad9af8452015-06-25 02:11:57 -0700598 clients_.insert(client_id, make_scoped_ptr(new Client(client_name, type)));
agoodeb09423b2015-05-11 11:39:57 -0700599 if (IsCardClient(type, client_id))
600 ++card_client_count_;
agoode99d63292015-04-13 08:39:25 -0700601}
602
603bool MidiManagerAlsa::AlsaSeqState::ClientStarted(int client_id) {
604 return clients_.find(client_id) != clients_.end();
605}
606
607void MidiManagerAlsa::AlsaSeqState::ClientExit(int client_id) {
608 auto it = clients_.find(client_id);
609 if (it != clients_.end()) {
agoodeb09423b2015-05-11 11:39:57 -0700610 if (IsCardClient(it->second->type(), client_id))
611 --card_client_count_;
agoode99d63292015-04-13 08:39:25 -0700612 clients_.erase(it);
613 }
614}
615
616void MidiManagerAlsa::AlsaSeqState::PortStart(
617 int client_id,
618 int port_id,
619 const std::string& port_name,
620 MidiManagerAlsa::AlsaSeqState::PortDirection direction,
621 bool midi) {
622 auto it = clients_.find(client_id);
623 if (it != clients_.end())
624 it->second->AddPort(port_id,
agoode5a1aa112015-06-21 20:51:00 -0700625 make_scoped_ptr(new Port(port_name, direction, midi)));
agoode99d63292015-04-13 08:39:25 -0700626}
627
628void MidiManagerAlsa::AlsaSeqState::PortExit(int client_id, int port_id) {
629 auto it = clients_.find(client_id);
630 if (it != clients_.end())
631 it->second->RemovePort(port_id);
632}
633
634snd_seq_client_type_t MidiManagerAlsa::AlsaSeqState::ClientType(
635 int client_id) const {
636 auto it = clients_.find(client_id);
637 if (it == clients_.end())
638 return SND_SEQ_USER_CLIENT;
639 return it->second->type();
640}
641
642scoped_ptr<MidiManagerAlsa::TemporaryMidiPortState>
agoodeb09423b2015-05-11 11:39:57 -0700643MidiManagerAlsa::AlsaSeqState::ToMidiPortState(const AlsaCardMap& alsa_cards) {
agoode99d63292015-04-13 08:39:25 -0700644 scoped_ptr<MidiManagerAlsa::TemporaryMidiPortState> midi_ports(
645 new TemporaryMidiPortState);
agoodeb09423b2015-05-11 11:39:57 -0700646 // TODO(agoode): Use more information from udev, to allow hardware matching.
647 // See http://crbug.com/486471.
648 auto card_it = alsa_cards.begin();
agoode99d63292015-04-13 08:39:25 -0700649
agoodeb09423b2015-05-11 11:39:57 -0700650 int card_midi_device = -1;
agoode99d63292015-04-13 08:39:25 -0700651 for (const auto& client_pair : clients_) {
652 int client_id = client_pair.first;
653 const auto& client = client_pair.second;
654
655 // Get client metadata.
656 const std::string client_name = client->name();
657 std::string manufacturer;
658 std::string driver;
659 std::string path;
agooded87fc0f2015-05-21 08:29:31 -0700660 MidiPort::Id id;
agoode99d63292015-04-13 08:39:25 -0700661 std::string card_name;
662 std::string card_longname;
663 int midi_device = -1;
664
agoodeb09423b2015-05-11 11:39:57 -0700665 if (IsCardClient(client->type(), client_id)) {
666 auto& card = card_it->second;
667 if (card_midi_device == -1)
668 card_midi_device = 0;
669
670 manufacturer = card->manufacturer();
agooded87fc0f2015-05-21 08:29:31 -0700671 path = card->path();
672 id = MidiPort::Id(card->bus(), card->vendor_id(), card->model_id(),
673 card->usb_interface_num(), card->serial());
674 card_name = card->name();
675 card_longname = card->longname();
agoodeb09423b2015-05-11 11:39:57 -0700676 midi_device = card_midi_device;
677
678 ++card_midi_device;
679 if (card_midi_device >= card->midi_device_count()) {
680 card_midi_device = -1;
681 ++card_it;
682 }
683 }
684
agoode99d63292015-04-13 08:39:25 -0700685 for (const auto& port_pair : *client) {
686 int port_id = port_pair.first;
687 const auto& port = port_pair.second;
688
689 if (port->midi()) {
690 std::string version;
691 if (!driver.empty()) {
692 version = driver + " / ";
693 }
694 version +=
695 base::StringPrintf("ALSA library version %d.%d.%d", SND_LIB_MAJOR,
696 SND_LIB_MINOR, SND_LIB_SUBMINOR);
697 PortDirection direction = port->direction();
698 if (direction == PortDirection::kInput ||
699 direction == PortDirection::kDuplex) {
agoode5a1aa112015-06-21 20:51:00 -0700700 midi_ports->Insert(make_scoped_ptr(new MidiPort(
agoode99d63292015-04-13 08:39:25 -0700701 path, id, client_id, port_id, midi_device, client->name(),
702 port->name(), manufacturer, version, MidiPort::Type::kInput)));
703 }
704 if (direction == PortDirection::kOutput ||
705 direction == PortDirection::kDuplex) {
agoode5a1aa112015-06-21 20:51:00 -0700706 midi_ports->Insert(make_scoped_ptr(new MidiPort(
agoode99d63292015-04-13 08:39:25 -0700707 path, id, client_id, port_id, midi_device, client->name(),
708 port->name(), manufacturer, version, MidiPort::Type::kOutput)));
709 }
710 }
711 }
712 }
713
714 return midi_ports.Pass();
715}
716
717MidiManagerAlsa::AlsaSeqState::Port::Port(
718 const std::string& name,
719 MidiManagerAlsa::AlsaSeqState::PortDirection direction,
720 bool midi)
721 : name_(name), direction_(direction), midi_(midi) {
722}
723
agoodeb0582872015-05-20 05:22:24 -0700724MidiManagerAlsa::AlsaSeqState::Port::~Port() = default;
agoode99d63292015-04-13 08:39:25 -0700725
726MidiManagerAlsa::AlsaSeqState::Client::Client(const std::string& name,
727 snd_seq_client_type_t type)
mgiucad9af8452015-06-25 02:11:57 -0700728 : name_(name), type_(type) {
agoode99d63292015-04-13 08:39:25 -0700729}
730
agoodeb0582872015-05-20 05:22:24 -0700731MidiManagerAlsa::AlsaSeqState::Client::~Client() = default;
agoode99d63292015-04-13 08:39:25 -0700732
733void MidiManagerAlsa::AlsaSeqState::Client::AddPort(int addr,
734 scoped_ptr<Port> port) {
mgiucad9af8452015-06-25 02:11:57 -0700735 ports_.set(addr, port.Pass());
agoode99d63292015-04-13 08:39:25 -0700736}
737
738void MidiManagerAlsa::AlsaSeqState::Client::RemovePort(int addr) {
mgiucad9af8452015-06-25 02:11:57 -0700739 ports_.erase(addr);
agoode99d63292015-04-13 08:39:25 -0700740}
741
742MidiManagerAlsa::AlsaSeqState::Client::PortMap::const_iterator
743MidiManagerAlsa::AlsaSeqState::Client::begin() const {
744 return ports_.begin();
745}
746
747MidiManagerAlsa::AlsaSeqState::Client::PortMap::const_iterator
748MidiManagerAlsa::AlsaSeqState::Client::end() const {
749 return ports_.end();
agoodeaf6e9f52015-03-24 10:23:49 -0700750}
751
agoodeb09423b2015-05-11 11:39:57 -0700752MidiManagerAlsa::AlsaCard::AlsaCard(udev_device* dev,
agooded87fc0f2015-05-21 08:29:31 -0700753 const std::string& name,
754 const std::string& longname,
755 const std::string& driver,
agoodeb09423b2015-05-11 11:39:57 -0700756 int midi_device_count)
agooded87fc0f2015-05-21 08:29:31 -0700757 : name_(name),
758 longname_(longname),
759 driver_(driver),
760 path_(device::UdevDeviceGetPropertyValue(dev, kUdevIdPath)),
761 bus_(device::UdevDeviceGetPropertyValue(dev, kUdevIdBus)),
762 vendor_id_(
763 UdevDeviceGetPropertyOrSysattr(dev, kUdevIdVendorId, kSysattrVendor)),
764 model_id_(
765 UdevDeviceGetPropertyOrSysattr(dev, kUdevIdModelId, kSysattrModel)),
766 usb_interface_num_(
767 device::UdevDeviceGetPropertyValue(dev, kUdevIdUsbInterfaceNum)),
768 serial_(UdevDeviceGetPropertyOrSysattr(dev,
769 kUdevIdSerialShort,
770 kSysattrGuid)),
771 midi_device_count_(midi_device_count),
772 manufacturer_(ExtractManufacturerString(
773 GetVendor(dev),
774 vendor_id_,
775 device::UdevDeviceGetPropertyValue(dev, kUdevIdVendorFromDatabase),
776 name,
777 longname)) {
agoodeb09423b2015-05-11 11:39:57 -0700778}
779
agoodeb0582872015-05-20 05:22:24 -0700780MidiManagerAlsa::AlsaCard::~AlsaCard() = default;
agoodeb09423b2015-05-11 11:39:57 -0700781
agoode55a8b522015-03-08 12:40:17 -0700782// static
agoodeb09423b2015-05-11 11:39:57 -0700783std::string MidiManagerAlsa::AlsaCard::ExtractManufacturerString(
agoode5e4e9cd2015-03-09 12:34:24 -0700784 const std::string& udev_id_vendor,
agoode55a8b522015-03-08 12:40:17 -0700785 const std::string& udev_id_vendor_id,
786 const std::string& udev_id_vendor_from_database,
787 const std::string& alsa_name,
788 const std::string& alsa_longname) {
789 // Let's try to determine the manufacturer. Here is the ordered preference
790 // in extraction:
agoodef212b2a2015-03-19 12:53:23 -0700791 // 1. Vendor name from the hardware device string, from udev properties
792 // or sysattrs.
agoode5e4e9cd2015-03-09 12:34:24 -0700793 // 2. Vendor name from the udev database (property ID_VENDOR_FROM_DATABASE).
agoode55a8b522015-03-08 12:40:17 -0700794 // 3. Heuristic from ALSA.
795
agoodee83758c2015-03-23 22:07:54 -0700796 // Is the vendor string present and not just the vendor hex id?
797 if (!udev_id_vendor.empty() && (udev_id_vendor != udev_id_vendor_id)) {
agoode55a8b522015-03-08 12:40:17 -0700798 return udev_id_vendor;
799 }
800
801 // Is there a vendor string in the hardware database?
802 if (!udev_id_vendor_from_database.empty()) {
803 return udev_id_vendor_from_database;
804 }
805
806 // Ok, udev gave us nothing useful, or was unavailable. So try a heuristic.
807 // We assume that card longname is in the format of
808 // "<manufacturer> <name> at <bus>". Otherwise, we give up to detect
809 // a manufacturer name here.
810 size_t at_index = alsa_longname.rfind(" at ");
agoodef212b2a2015-03-19 12:53:23 -0700811 if (at_index && at_index != std::string::npos) {
agoode55a8b522015-03-08 12:40:17 -0700812 size_t name_index = alsa_longname.rfind(alsa_name, at_index - 1);
agoodef212b2a2015-03-19 12:53:23 -0700813 if (name_index && name_index != std::string::npos)
agoode55a8b522015-03-08 12:40:17 -0700814 return alsa_longname.substr(0, name_index - 1);
815 }
816
817 // Failure.
818 return "";
819}
820
agoodebd4be9b2015-03-16 19:17:25 -0700821void MidiManagerAlsa::SendMidiData(uint32 port_index,
822 const std::vector<uint8>& data) {
skyostil93e2ec22015-06-17 08:49:09 -0700823 DCHECK(send_thread_.task_runner()->BelongsToCurrentThread());
agoodebd4be9b2015-03-16 19:17:25 -0700824
agoodef212b2a2015-03-19 12:53:23 -0700825 snd_midi_event_t* encoder;
826 snd_midi_event_new(kSendBufferSize, &encoder);
agoode5a1aa112015-06-21 20:51:00 -0700827 for (const auto datum : data) {
agoodebd4be9b2015-03-16 19:17:25 -0700828 snd_seq_event_t event;
agoode5a1aa112015-06-21 20:51:00 -0700829 int result = snd_midi_event_encode_byte(encoder, datum, &event);
agoodebd4be9b2015-03-16 19:17:25 -0700830 if (result == 1) {
831 // Full event, send it.
agoode99d63292015-04-13 08:39:25 -0700832 base::AutoLock lock(out_ports_lock_);
833 auto it = out_ports_.find(port_index);
834 if (it != out_ports_.end()) {
835 snd_seq_ev_set_source(&event, it->second);
836 snd_seq_ev_set_subs(&event);
837 snd_seq_ev_set_direct(&event);
agoode5a1aa112015-06-21 20:51:00 -0700838 snd_seq_event_output_direct(out_client_.get(), &event);
agoode99d63292015-04-13 08:39:25 -0700839 }
agoodebd4be9b2015-03-16 19:17:25 -0700840 }
841 }
agoodef212b2a2015-03-19 12:53:23 -0700842 snd_midi_event_free(encoder);
agoodebd4be9b2015-03-16 19:17:25 -0700843}
844
845void MidiManagerAlsa::ScheduleEventLoop() {
846 event_thread_.message_loop()->PostTask(
847 FROM_HERE,
848 base::Bind(&MidiManagerAlsa::EventLoop, base::Unretained(this)));
849}
850
851void MidiManagerAlsa::EventLoop() {
agoode975043d2015-05-11 00:46:17 -0700852 bool loop_again = true;
agoodebd4be9b2015-03-16 19:17:25 -0700853
agoode975043d2015-05-11 00:46:17 -0700854 struct pollfd pfd[2];
agoode5a1aa112015-06-21 20:51:00 -0700855 snd_seq_poll_descriptors(in_client_.get(), &pfd[0], 1, POLLIN);
agoode975043d2015-05-11 00:46:17 -0700856 pfd[1].fd = device::udev_monitor_get_fd(udev_monitor_.get());
857 pfd[1].events = POLLIN;
agoodebd4be9b2015-03-16 19:17:25 -0700858
agoode975043d2015-05-11 00:46:17 -0700859 int err = HANDLE_EINTR(poll(pfd, arraysize(pfd), -1));
860 if (err < 0) {
brettwf7f870f2015-06-09 11:05:24 -0700861 VLOG(1) << "poll fails: " << base::safe_strerror(errno);
agoode975043d2015-05-11 00:46:17 -0700862 loop_again = false;
agoodeaf6e9f52015-03-24 10:23:49 -0700863 } else {
agoode975043d2015-05-11 00:46:17 -0700864 if (pfd[0].revents & POLLIN) {
865 // Read available incoming MIDI data.
866 int remaining;
867 double timestamp =
868 (base::TimeTicks::Now() - base::TimeTicks()).InSecondsF();
869 do {
870 snd_seq_event_t* event;
agoode5a1aa112015-06-21 20:51:00 -0700871 err = snd_seq_event_input(in_client_.get(), &event);
872 remaining = snd_seq_event_input_pending(in_client_.get(), 0);
agoode975043d2015-05-11 00:46:17 -0700873
874 if (err == -ENOSPC) {
875 // Handle out of space error.
876 VLOG(1) << "snd_seq_event_input detected buffer overrun";
877 // We've lost events: check another way to see if we need to shut
878 // down.
879 base::AutoLock lock(shutdown_lock_);
880 if (event_thread_shutdown_)
881 loop_again = false;
882 } else if (err == -EAGAIN) {
883 // We've read all the data.
884 } else if (err < 0) {
885 // Handle other errors.
886 VLOG(1) << "snd_seq_event_input fails: " << snd_strerror(err);
887 // TODO(agoode): Use RecordAction() or similar to log this.
888 loop_again = false;
889 } else if (event->source.client == SND_SEQ_CLIENT_SYSTEM &&
890 event->source.port == SND_SEQ_PORT_SYSTEM_ANNOUNCE) {
891 // Handle announce events.
892 switch (event->type) {
893 case SND_SEQ_EVENT_PORT_START:
894 // Don't use SND_SEQ_EVENT_CLIENT_START because the
895 // client name may not be set by the time we query
896 // it. It should be set by the time ports are made.
897 ProcessClientStartEvent(event->data.addr.client);
898 ProcessPortStartEvent(event->data.addr);
899 break;
900 case SND_SEQ_EVENT_CLIENT_EXIT:
901 // Check for disconnection of our "out" client. This means "shut
902 // down".
903 if (event->data.addr.client == out_client_id_) {
904 loop_again = false;
905 remaining = 0;
906 } else
907 ProcessClientExitEvent(event->data.addr);
908 break;
909 case SND_SEQ_EVENT_PORT_EXIT:
910 ProcessPortExitEvent(event->data.addr);
911 break;
912 }
913 } else {
914 // Normal operation.
915 ProcessSingleEvent(event, timestamp);
916 }
917 } while (remaining > 0);
918 }
919 if (pfd[1].revents & POLLIN) {
920 device::ScopedUdevDevicePtr dev(
921 device::udev_monitor_receive_device(udev_monitor_.get()));
922 if (dev.get())
923 ProcessUdevEvent(dev.get());
924 else
925 VLOG(1) << "udev_monitor_receive_device fails";
926 }
agoodebd4be9b2015-03-16 19:17:25 -0700927 }
928
agoodebd4be9b2015-03-16 19:17:25 -0700929 // Do again.
agoode975043d2015-05-11 00:46:17 -0700930 if (loop_again)
931 ScheduleEventLoop();
agoodebd4be9b2015-03-16 19:17:25 -0700932}
933
934void MidiManagerAlsa::ProcessSingleEvent(snd_seq_event_t* event,
935 double timestamp) {
agoode99d63292015-04-13 08:39:25 -0700936 auto source_it =
937 source_map_.find(AddrToInt(event->source.client, event->source.port));
agoodebd4be9b2015-03-16 19:17:25 -0700938 if (source_it != source_map_.end()) {
939 uint32 source = source_it->second;
940 if (event->type == SND_SEQ_EVENT_SYSEX) {
941 // Special! Variable-length sysex.
942 ReceiveMidiData(source, static_cast<const uint8*>(event->data.ext.ptr),
943 event->data.ext.len, timestamp);
944 } else {
945 // Otherwise, decode this and send that on.
946 unsigned char buf[12];
agoode5a1aa112015-06-21 20:51:00 -0700947 long count =
948 snd_midi_event_decode(decoder_.get(), buf, sizeof(buf), event);
agoodebd4be9b2015-03-16 19:17:25 -0700949 if (count <= 0) {
950 if (count != -ENOENT) {
951 // ENOENT means that it's not a MIDI message, which is not an
952 // error, but other negative values are errors for us.
953 VLOG(1) << "snd_midi_event_decoder fails " << snd_strerror(count);
954 // TODO(agoode): Record this failure.
955 }
956 } else {
957 ReceiveMidiData(source, buf, count, timestamp);
958 }
959 }
960 }
961}
962
agoode99d63292015-04-13 08:39:25 -0700963void MidiManagerAlsa::ProcessClientStartEvent(int client_id) {
964 // Ignore if client is already started.
965 if (alsa_seq_state_.ClientStarted(client_id))
966 return;
967
968 snd_seq_client_info_t* client_info;
969 snd_seq_client_info_alloca(&client_info);
agoode5a1aa112015-06-21 20:51:00 -0700970 int err =
971 snd_seq_get_any_client_info(in_client_.get(), client_id, client_info);
agoode99d63292015-04-13 08:39:25 -0700972 if (err != 0)
973 return;
974
975 // Skip our own clients.
976 if ((client_id == in_client_id_) || (client_id == out_client_id_))
977 return;
978
979 // Update our view of ALSA seq state.
980 alsa_seq_state_.ClientStart(client_id,
981 snd_seq_client_info_get_name(client_info),
982 snd_seq_client_info_get_type(client_info));
983
984 // Generate Web MIDI events.
985 UpdatePortStateAndGenerateEvents();
986}
987
988void MidiManagerAlsa::ProcessPortStartEvent(const snd_seq_addr_t& addr) {
989 snd_seq_port_info_t* port_info;
990 snd_seq_port_info_alloca(&port_info);
agoode5a1aa112015-06-21 20:51:00 -0700991 int err = snd_seq_get_any_port_info(in_client_.get(), addr.client, addr.port,
992 port_info);
agoode99d63292015-04-13 08:39:25 -0700993 if (err != 0)
994 return;
995
996 unsigned int caps = snd_seq_port_info_get_capability(port_info);
997 bool input = (caps & kRequiredInputPortCaps) == kRequiredInputPortCaps;
998 bool output = (caps & kRequiredOutputPortCaps) == kRequiredOutputPortCaps;
999 AlsaSeqState::PortDirection direction;
1000 if (input && output)
1001 direction = AlsaSeqState::PortDirection::kDuplex;
1002 else if (input)
1003 direction = AlsaSeqState::PortDirection::kInput;
1004 else if (output)
1005 direction = AlsaSeqState::PortDirection::kOutput;
1006 else
1007 return;
1008
1009 // Update our view of ALSA seq state.
1010 alsa_seq_state_.PortStart(
1011 addr.client, addr.port, snd_seq_port_info_get_name(port_info), direction,
1012 snd_seq_port_info_get_type(port_info) & SND_SEQ_PORT_TYPE_MIDI_GENERIC);
1013 // Generate Web MIDI events.
1014 UpdatePortStateAndGenerateEvents();
1015}
1016
1017void MidiManagerAlsa::ProcessClientExitEvent(const snd_seq_addr_t& addr) {
1018 // Update our view of ALSA seq state.
1019 alsa_seq_state_.ClientExit(addr.client);
1020 // Generate Web MIDI events.
1021 UpdatePortStateAndGenerateEvents();
1022}
1023
1024void MidiManagerAlsa::ProcessPortExitEvent(const snd_seq_addr_t& addr) {
1025 // Update our view of ALSA seq state.
1026 alsa_seq_state_.PortExit(addr.client, addr.port);
1027 // Generate Web MIDI events.
1028 UpdatePortStateAndGenerateEvents();
1029}
1030
agoode975043d2015-05-11 00:46:17 -07001031void MidiManagerAlsa::ProcessUdevEvent(udev_device* dev) {
1032 // Only card devices have this property set, and only when they are
1033 // fully initialized.
1034 if (!device::udev_device_get_property_value(dev,
1035 kUdevPropertySoundInitialized))
1036 return;
1037
1038 // Get the action. If no action, then we are doing first time enumeration
1039 // and the device is treated as new.
1040 const char* action = device::udev_device_get_action(dev);
1041 if (!action)
1042 action = kUdevActionChange;
1043
1044 if (strcmp(action, kUdevActionChange) == 0) {
agoodeb09423b2015-05-11 11:39:57 -07001045 AddCard(dev);
1046 // Generate Web MIDI events.
1047 UpdatePortStateAndGenerateEvents();
agoode975043d2015-05-11 00:46:17 -07001048 } else if (strcmp(action, kUdevActionRemove) == 0) {
agoodeb09423b2015-05-11 11:39:57 -07001049 RemoveCard(GetCardNumber(dev));
1050 // Generate Web MIDI events.
1051 UpdatePortStateAndGenerateEvents();
agoode975043d2015-05-11 00:46:17 -07001052 }
1053}
1054
agoodeb09423b2015-05-11 11:39:57 -07001055void MidiManagerAlsa::AddCard(udev_device* dev) {
1056 int number = GetCardNumber(dev);
1057 if (number == -1)
1058 return;
1059
1060 RemoveCard(number);
1061
1062 snd_ctl_card_info_t* card;
1063 snd_hwdep_info_t* hwdep;
1064 snd_ctl_card_info_alloca(&card);
1065 snd_hwdep_info_alloca(&hwdep);
1066 const std::string id = base::StringPrintf("hw:CARD=%i", number);
1067 snd_ctl_t* handle;
1068 int err = snd_ctl_open(&handle, id.c_str(), 0);
1069 if (err != 0) {
1070 VLOG(1) << "snd_ctl_open fails: " << snd_strerror(err);
1071 return;
1072 }
1073 err = snd_ctl_card_info(handle, card);
1074 if (err != 0) {
1075 VLOG(1) << "snd_ctl_card_info fails: " << snd_strerror(err);
1076 snd_ctl_close(handle);
1077 return;
1078 }
1079 std::string name = snd_ctl_card_info_get_name(card);
1080 std::string longname = snd_ctl_card_info_get_longname(card);
1081 std::string driver = snd_ctl_card_info_get_driver(card);
1082
1083 // Count rawmidi devices (not subdevices).
1084 int midi_count = 0;
1085 for (int device = -1;
1086 !snd_ctl_rawmidi_next_device(handle, &device) && device >= 0;)
1087 ++midi_count;
1088
1089 // Count any hwdep synths that become MIDI devices outside of rawmidi.
1090 //
1091 // Explanation:
1092 // Any kernel driver can create an ALSA client (visible to us).
1093 // With modern hardware, only rawmidi devices do this. Kernel
1094 // drivers create rawmidi devices and the rawmidi subsystem makes
1095 // the seq clients. But the OPL3 driver is special, it does not
1096 // make a rawmidi device but a seq client directly. (This is the
1097 // only one to worry about in the kernel code, as of 2015-03-23.)
1098 //
1099 // OPL3 is very old (but still possible to get in new
1100 // hardware). It is unlikely that new drivers would not use
1101 // rawmidi and defeat our heuristic.
1102 //
1103 // Longer term, support should be added in the kernel to expose a
1104 // direct link from card->client (or client->card) so that all
1105 // these heuristics will be obsolete. Once that is there, we can
1106 // assume our old heuristics will work on old kernels and the new
1107 // robust code will be used on new. Then we will not need to worry
1108 // about changes to kernel internals breaking our code.
1109 // See the TODO above at kMinimumClientIdForCards.
1110 for (int device = -1;
1111 !snd_ctl_hwdep_next_device(handle, &device) && device >= 0;) {
1112 err = snd_ctl_hwdep_info(handle, hwdep);
1113 if (err != 0) {
1114 VLOG(1) << "snd_ctl_hwdep_info fails: " << snd_strerror(err);
1115 continue;
1116 }
1117 snd_hwdep_iface_t iface = snd_hwdep_info_get_iface(hwdep);
1118 if (iface == SND_HWDEP_IFACE_OPL2 || iface == SND_HWDEP_IFACE_OPL3 ||
1119 iface == SND_HWDEP_IFACE_OPL4)
1120 ++midi_count;
1121 }
1122 snd_ctl_close(handle);
1123
mgiucad9af8452015-06-25 02:11:57 -07001124 if (midi_count > 0) {
1125 scoped_ptr<AlsaCard> card(
1126 new AlsaCard(dev, name, longname, driver, midi_count));
1127 alsa_cards_.insert(number, card.Pass());
1128 alsa_card_midi_count_ += midi_count;
1129 }
agoodeb09423b2015-05-11 11:39:57 -07001130}
1131
1132void MidiManagerAlsa::RemoveCard(int number) {
1133 auto it = alsa_cards_.find(number);
1134 if (it == alsa_cards_.end())
1135 return;
1136
1137 alsa_card_midi_count_ -= it->second->midi_device_count();
agoodeb09423b2015-05-11 11:39:57 -07001138 alsa_cards_.erase(it);
1139}
1140
agoode99d63292015-04-13 08:39:25 -07001141void MidiManagerAlsa::UpdatePortStateAndGenerateEvents() {
agoodeb09423b2015-05-11 11:39:57 -07001142 // Verify that our information from ALSA and udev are in sync. If
1143 // not, we cannot generate events right now.
1144 if (alsa_card_midi_count_ != alsa_seq_state_.card_client_count())
1145 return;
1146
agoode99d63292015-04-13 08:39:25 -07001147 // Generate new port state.
agoodeb09423b2015-05-11 11:39:57 -07001148 auto new_port_state = alsa_seq_state_.ToMidiPortState(alsa_cards_);
agoode99d63292015-04-13 08:39:25 -07001149
1150 // Disconnect any connected old ports that are now missing.
1151 for (auto* old_port : port_state_) {
1152 if (old_port->connected() &&
1153 (new_port_state->FindConnected(*old_port) == new_port_state->end())) {
1154 old_port->set_connected(false);
1155 uint32 web_port_index = old_port->web_port_index();
1156 switch (old_port->type()) {
1157 case MidiPort::Type::kInput:
1158 source_map_.erase(
1159 AddrToInt(old_port->client_id(), old_port->port_id()));
1160 SetInputPortState(web_port_index, MIDI_PORT_DISCONNECTED);
1161 break;
1162 case MidiPort::Type::kOutput:
1163 DeleteAlsaOutputPort(web_port_index);
1164 SetOutputPortState(web_port_index, MIDI_PORT_DISCONNECTED);
1165 break;
1166 }
1167 }
1168 }
1169
1170 // Reconnect or add new ports.
1171 auto it = new_port_state->begin();
1172 while (it != new_port_state->end()) {
1173 auto* new_port = *it;
1174 auto old_port = port_state_.Find(*new_port);
1175 if (old_port == port_state_.end()) {
1176 // Add new port.
agoode5a1aa112015-06-21 20:51:00 -07001177 uint32 web_port_index = port_state_.Insert(make_scoped_ptr(new_port));
agoode99d63292015-04-13 08:39:25 -07001178 MidiPortInfo info(new_port->OpaqueKey(), new_port->manufacturer(),
1179 new_port->port_name(), new_port->version(),
1180 MIDI_PORT_OPENED);
1181 switch (new_port->type()) {
1182 case MidiPort::Type::kInput:
1183 if (Subscribe(web_port_index, new_port->client_id(),
1184 new_port->port_id()))
1185 AddInputPort(info);
1186 break;
agoode99d63292015-04-13 08:39:25 -07001187 case MidiPort::Type::kOutput:
1188 if (CreateAlsaOutputPort(web_port_index, new_port->client_id(),
1189 new_port->port_id()))
1190 AddOutputPort(info);
1191 break;
1192 }
1193 it = new_port_state->weak_erase(it);
1194 } else if (!(*old_port)->connected()) {
1195 // Reconnect.
1196 uint32 web_port_index = (*old_port)->web_port_index();
1197 (*old_port)->Update(new_port->path(), new_port->client_id(),
1198 new_port->port_id(), new_port->client_name(),
1199 new_port->port_name(), new_port->manufacturer(),
1200 new_port->version());
1201 switch ((*old_port)->type()) {
1202 case MidiPort::Type::kInput:
1203 if (Subscribe(web_port_index, (*old_port)->client_id(),
1204 (*old_port)->port_id()))
1205 SetInputPortState(web_port_index, MIDI_PORT_OPENED);
1206 break;
agoode99d63292015-04-13 08:39:25 -07001207 case MidiPort::Type::kOutput:
1208 if (CreateAlsaOutputPort(web_port_index, (*old_port)->client_id(),
1209 (*old_port)->port_id()))
1210 SetOutputPortState(web_port_index, MIDI_PORT_OPENED);
1211 break;
1212 }
1213 (*old_port)->set_connected(true);
1214 ++it;
1215 } else {
1216 ++it;
1217 }
1218 }
1219}
1220
agoode975043d2015-05-11 00:46:17 -07001221// TODO(agoode): return false on failure.
agoode99d63292015-04-13 08:39:25 -07001222void MidiManagerAlsa::EnumerateAlsaPorts() {
1223 snd_seq_client_info_t* client_info;
1224 snd_seq_client_info_alloca(&client_info);
1225 snd_seq_port_info_t* port_info;
1226 snd_seq_port_info_alloca(&port_info);
1227
1228 // Enumerate clients.
1229 snd_seq_client_info_set_client(client_info, -1);
agoode5a1aa112015-06-21 20:51:00 -07001230 while (!snd_seq_query_next_client(in_client_.get(), client_info)) {
agoode99d63292015-04-13 08:39:25 -07001231 int client_id = snd_seq_client_info_get_client(client_info);
1232 ProcessClientStartEvent(client_id);
1233
1234 // Enumerate ports.
1235 snd_seq_port_info_set_client(port_info, client_id);
1236 snd_seq_port_info_set_port(port_info, -1);
agoode5a1aa112015-06-21 20:51:00 -07001237 while (!snd_seq_query_next_port(in_client_.get(), port_info)) {
agoode99d63292015-04-13 08:39:25 -07001238 const snd_seq_addr_t* addr = snd_seq_port_info_get_addr(port_info);
1239 ProcessPortStartEvent(*addr);
1240 }
1241 }
1242}
1243
agoode975043d2015-05-11 00:46:17 -07001244bool MidiManagerAlsa::EnumerateUdevCards() {
1245 int err;
1246
1247 device::ScopedUdevEnumeratePtr enumerate(
1248 device::udev_enumerate_new(udev_.get()));
1249 if (!enumerate.get()) {
1250 VLOG(1) << "udev_enumerate_new fails";
1251 return false;
1252 }
1253
1254 err = device::udev_enumerate_add_match_subsystem(enumerate.get(),
1255 kUdevSubsystemSound);
1256 if (err) {
1257 VLOG(1) << "udev_enumerate_add_match_subsystem fails: "
brettwf7f870f2015-06-09 11:05:24 -07001258 << base::safe_strerror(-err);
agoode975043d2015-05-11 00:46:17 -07001259 return false;
1260 }
1261
1262 err = device::udev_enumerate_scan_devices(enumerate.get());
1263 if (err) {
brettwf7f870f2015-06-09 11:05:24 -07001264 VLOG(1) << "udev_enumerate_scan_devices fails: "
1265 << base::safe_strerror(-err);
agoode975043d2015-05-11 00:46:17 -07001266 return false;
1267 }
1268
1269 udev_list_entry* list_entry;
1270 auto* devices = device::udev_enumerate_get_list_entry(enumerate.get());
1271 udev_list_entry_foreach(list_entry, devices) {
1272 const char* path = device::udev_list_entry_get_name(list_entry);
1273 device::ScopedUdevDevicePtr dev(
1274 device::udev_device_new_from_syspath(udev_.get(), path));
1275 if (dev.get())
1276 ProcessUdevEvent(dev.get());
1277 }
1278
1279 return true;
1280}
1281
agoode99d63292015-04-13 08:39:25 -07001282bool MidiManagerAlsa::CreateAlsaOutputPort(uint32 port_index,
1283 int client_id,
1284 int port_id) {
1285 // Create the port.
1286 int out_port = snd_seq_create_simple_port(
agoode5a1aa112015-06-21 20:51:00 -07001287 out_client_.get(), NULL, kCreateOutputPortCaps, kCreatePortType);
agoode99d63292015-04-13 08:39:25 -07001288 if (out_port < 0) {
1289 VLOG(1) << "snd_seq_create_simple_port fails: " << snd_strerror(out_port);
1290 return false;
1291 }
1292 // Activate port subscription.
1293 snd_seq_port_subscribe_t* subs;
1294 snd_seq_port_subscribe_alloca(&subs);
1295 snd_seq_addr_t sender;
1296 sender.client = out_client_id_;
1297 sender.port = out_port;
1298 snd_seq_port_subscribe_set_sender(subs, &sender);
1299 snd_seq_addr_t dest;
1300 dest.client = client_id;
1301 dest.port = port_id;
1302 snd_seq_port_subscribe_set_dest(subs, &dest);
agoode5a1aa112015-06-21 20:51:00 -07001303 int err = snd_seq_subscribe_port(out_client_.get(), subs);
agoode99d63292015-04-13 08:39:25 -07001304 if (err != 0) {
1305 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
agoode5a1aa112015-06-21 20:51:00 -07001306 snd_seq_delete_simple_port(out_client_.get(), out_port);
agoode99d63292015-04-13 08:39:25 -07001307 return false;
1308 }
1309
1310 // Update our map.
1311 base::AutoLock lock(out_ports_lock_);
1312 out_ports_[port_index] = out_port;
1313 return true;
1314}
1315
1316void MidiManagerAlsa::DeleteAlsaOutputPort(uint32 port_index) {
1317 base::AutoLock lock(out_ports_lock_);
1318 auto it = out_ports_.find(port_index);
1319 if (it == out_ports_.end())
1320 return;
1321
1322 int alsa_port = it->second;
agoode5a1aa112015-06-21 20:51:00 -07001323 snd_seq_delete_simple_port(out_client_.get(), alsa_port);
agoode99d63292015-04-13 08:39:25 -07001324 out_ports_.erase(it);
1325}
1326
1327bool MidiManagerAlsa::Subscribe(uint32 port_index, int client_id, int port_id) {
1328 // 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