blob: f141692e8a02f3912e6cdcd9452acf79913505b0 [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()
agoode@chromium.org25227512014-06-08 05:12:05 +0000148 : in_client_(NULL),
149 out_client_(NULL),
150 out_client_id_(-1),
agoode99d63292015-04-13 08:39:25 -0700151 in_port_id_(-1),
agoodeb09423b2015-05-11 11:39:57 -0700152 alsa_cards_deleter_(&alsa_cards_),
153 alsa_card_midi_count_(0),
agoode@chromium.org25227512014-06-08 05:12:05 +0000154 decoder_(NULL),
agoode7de413f2015-04-24 00:13:39 -0700155 udev_(device::udev_new()),
agoode@chromium.org25227512014-06-08 05:12:05 +0000156 send_thread_("MidiSendThread"),
157 event_thread_("MidiEventThread"),
158 event_thread_shutdown_(false) {
159 // Initialize decoder.
160 snd_midi_event_new(0, &decoder_);
161 snd_midi_event_no_status(decoder_, 1);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000162}
163
agoodebd4be9b2015-03-16 19:17:25 -0700164MidiManagerAlsa::~MidiManagerAlsa() {
165 // Tell the event thread it will soon be time to shut down. This gives
166 // us assurance the thread will stop in case the SND_SEQ_EVENT_CLIENT_EXIT
167 // message is lost.
168 {
169 base::AutoLock lock(shutdown_lock_);
170 event_thread_shutdown_ = true;
171 }
172
173 // Stop the send thread.
174 send_thread_.Stop();
175
176 // Close the out client. This will trigger the event thread to stop,
177 // because of SND_SEQ_EVENT_CLIENT_EXIT.
178 if (out_client_)
179 snd_seq_close(out_client_);
180
181 // Wait for the event thread to stop.
182 event_thread_.Stop();
183
184 // Close the in client.
185 if (in_client_)
186 snd_seq_close(in_client_);
187
188 // Free the decoder.
189 snd_midi_event_free(decoder_);
agoodebd4be9b2015-03-16 19:17:25 -0700190}
191
toyoshim@chromium.org51c7f532014-05-01 17:17:32 +0000192void MidiManagerAlsa::StartInitialization() {
agoode@chromium.org25227512014-06-08 05:12:05 +0000193 // TODO(agoode): Move off I/O thread. See http://crbug.com/374341.
194
195 // Create client handles.
agoode975043d2015-05-11 00:46:17 -0700196 int err =
197 snd_seq_open(&in_client_, kAlsaHw, SND_SEQ_OPEN_INPUT, SND_SEQ_NONBLOCK);
agoode@chromium.org25227512014-06-08 05:12:05 +0000198 if (err != 0) {
199 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err);
200 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
201 }
agoode99d63292015-04-13 08:39:25 -0700202 in_client_id_ = snd_seq_client_id(in_client_);
agoodef212b2a2015-03-19 12:53:23 -0700203 err = snd_seq_open(&out_client_, kAlsaHw, SND_SEQ_OPEN_OUTPUT, 0);
agoode@chromium.org25227512014-06-08 05:12:05 +0000204 if (err != 0) {
205 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err);
206 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
207 }
208 out_client_id_ = snd_seq_client_id(out_client_);
209
210 // Name the clients.
211 err = snd_seq_set_client_name(in_client_, "Chrome (input)");
212 if (err != 0) {
213 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err);
214 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
215 }
216 err = snd_seq_set_client_name(out_client_, "Chrome (output)");
217 if (err != 0) {
218 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err);
219 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
220 }
221
222 // Create input port.
agoode99d63292015-04-13 08:39:25 -0700223 in_port_id_ = snd_seq_create_simple_port(
224 in_client_, NULL, kCreateInputPortCaps, kCreatePortType);
225 if (in_port_id_ < 0) {
226 VLOG(1) << "snd_seq_create_simple_port fails: "
227 << snd_strerror(in_port_id_);
agoode@chromium.org25227512014-06-08 05:12:05 +0000228 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
229 }
230
231 // Subscribe to the announce port.
232 snd_seq_port_subscribe_t* subs;
233 snd_seq_port_subscribe_alloca(&subs);
234 snd_seq_addr_t announce_sender;
235 snd_seq_addr_t announce_dest;
236 announce_sender.client = SND_SEQ_CLIENT_SYSTEM;
237 announce_sender.port = SND_SEQ_PORT_SYSTEM_ANNOUNCE;
agoode99d63292015-04-13 08:39:25 -0700238 announce_dest.client = in_client_id_;
239 announce_dest.port = in_port_id_;
agoode@chromium.org25227512014-06-08 05:12:05 +0000240 snd_seq_port_subscribe_set_sender(subs, &announce_sender);
241 snd_seq_port_subscribe_set_dest(subs, &announce_dest);
242 err = snd_seq_subscribe_port(in_client_, subs);
243 if (err != 0) {
244 VLOG(1) << "snd_seq_subscribe_port on the announce port fails: "
245 << snd_strerror(err);
246 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
247 }
248
agoode99d63292015-04-13 08:39:25 -0700249 // Generate hotplug events for existing ports.
agoode975043d2015-05-11 00:46:17 -0700250 // TODO(agoode): Check the return value for failure.
agoode99d63292015-04-13 08:39:25 -0700251 EnumerateAlsaPorts();
agoode@chromium.org25227512014-06-08 05:12:05 +0000252
agoode975043d2015-05-11 00:46:17 -0700253 // Initialize udev monitor.
254 udev_monitor_.reset(
255 device::udev_monitor_new_from_netlink(udev_.get(), kUdev));
256 if (!udev_monitor_.get()) {
257 VLOG(1) << "udev_monitor_new_from_netlink fails";
258 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
259 }
260 err = device::udev_monitor_filter_add_match_subsystem_devtype(
261 udev_monitor_.get(), kUdevSubsystemSound, nullptr);
262 if (err != 0) {
263 VLOG(1) << "udev_monitor_add_match_subsystem fails: "
brettwf7f870f2015-06-09 11:05:24 -0700264 << base::safe_strerror(-err);
agoode975043d2015-05-11 00:46:17 -0700265 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
266 }
267 err = device::udev_monitor_enable_receiving(udev_monitor_.get());
268 if (err != 0) {
brettwf7f870f2015-06-09 11:05:24 -0700269 VLOG(1) << "udev_monitor_enable_receiving fails: "
270 << base::safe_strerror(-err);
agoode975043d2015-05-11 00:46:17 -0700271 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
272 }
273
274 // Generate hotplug events for existing udev devices.
275 EnumerateUdevCards();
276
agoode99d63292015-04-13 08:39:25 -0700277 // Start processing events.
agoode@chromium.org25227512014-06-08 05:12:05 +0000278 event_thread_.Start();
279 event_thread_.message_loop()->PostTask(
280 FROM_HERE,
agoodebd4be9b2015-03-16 19:17:25 -0700281 base::Bind(&MidiManagerAlsa::ScheduleEventLoop, base::Unretained(this)));
agoode@chromium.org25227512014-06-08 05:12:05 +0000282
283 CompleteInitialization(MIDI_OK);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000284}
285
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000286void MidiManagerAlsa::DispatchSendMidiData(MidiManagerClient* client,
dnicoara@chromium.org9f2a6f02014-01-03 21:25:00 +0000287 uint32 port_index,
288 const std::vector<uint8>& data,
289 double timestamp) {
agoode@chromium.org25227512014-06-08 05:12:05 +0000290 // Not correct right now. http://crbug.com/374341.
291 if (!send_thread_.IsRunning())
292 send_thread_.Start();
293
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000294 base::TimeDelta delay;
295 if (timestamp != 0.0) {
296 base::TimeTicks time_to_send =
297 base::TimeTicks() + base::TimeDelta::FromMicroseconds(
agoodebd4be9b2015-03-16 19:17:25 -0700298 timestamp * base::Time::kMicrosecondsPerSecond);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000299 delay = std::max(time_to_send - base::TimeTicks::Now(), base::TimeDelta());
300 }
301
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000302 send_thread_.message_loop()->PostDelayedTask(
agoodebd4be9b2015-03-16 19:17:25 -0700303 FROM_HERE, base::Bind(&MidiManagerAlsa::SendMidiData,
304 base::Unretained(this), port_index, data),
305 delay);
agoode@chromium.org25227512014-06-08 05:12:05 +0000306
307 // Acknowledge send.
308 send_thread_.message_loop()->PostTask(
agoodebd4be9b2015-03-16 19:17:25 -0700309 FROM_HERE, base::Bind(&MidiManagerClient::AccumulateMidiBytesSent,
310 base::Unretained(client), data.size()));
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000311}
312
agooded87fc0f2015-05-21 08:29:31 -0700313MidiManagerAlsa::MidiPort::Id::Id() = default;
314
315MidiManagerAlsa::MidiPort::Id::Id(const std::string& bus,
316 const std::string& vendor_id,
317 const std::string& model_id,
318 const std::string& usb_interface_num,
319 const std::string& serial)
320 : bus_(bus),
321 vendor_id_(vendor_id),
322 model_id_(model_id),
323 usb_interface_num_(usb_interface_num),
324 serial_(serial) {
325}
326
327MidiManagerAlsa::MidiPort::Id::Id(const Id&) = default;
328
329MidiManagerAlsa::MidiPort::Id::~Id() = default;
330
331bool MidiManagerAlsa::MidiPort::Id::operator==(const Id& rhs) const {
332 return (bus_ == rhs.bus_) && (vendor_id_ == rhs.vendor_id_) &&
333 (model_id_ == rhs.model_id_) &&
334 (usb_interface_num_ == rhs.usb_interface_num_) &&
335 (serial_ == rhs.serial_);
336}
337
338bool MidiManagerAlsa::MidiPort::Id::empty() const {
339 return bus_.empty() && vendor_id_.empty() && model_id_.empty() &&
340 usb_interface_num_.empty() && serial_.empty();
341}
342
agoode99d63292015-04-13 08:39:25 -0700343MidiManagerAlsa::MidiPort::MidiPort(const std::string& path,
agooded87fc0f2015-05-21 08:29:31 -0700344 const Id& id,
agoode99d63292015-04-13 08:39:25 -0700345 int client_id,
346 int port_id,
347 int midi_device,
348 const std::string& client_name,
349 const std::string& port_name,
350 const std::string& manufacturer,
351 const std::string& version,
352 Type type)
353 : id_(id),
354 midi_device_(midi_device),
355 type_(type),
356 path_(path),
357 client_id_(client_id),
358 port_id_(port_id),
359 client_name_(client_name),
360 port_name_(port_name),
361 manufacturer_(manufacturer),
362 version_(version),
363 web_port_index_(0),
364 connected_(true) {
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000365}
366
agoodeb0582872015-05-20 05:22:24 -0700367MidiManagerAlsa::MidiPort::~MidiPort() = default;
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000368
agoode99d63292015-04-13 08:39:25 -0700369// Note: keep synchronized with the MidiPort::Match* methods.
370scoped_ptr<base::Value> MidiManagerAlsa::MidiPort::Value() const {
371 scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue);
372
373 std::string type;
374 switch (type_) {
375 case Type::kInput:
376 type = "input";
377 break;
agoode99d63292015-04-13 08:39:25 -0700378 case Type::kOutput:
379 type = "output";
380 break;
381 }
382 value->SetString("type", type);
383 SetStringIfNonEmpty(value.get(), "path", path_);
agoode99d63292015-04-13 08:39:25 -0700384 SetStringIfNonEmpty(value.get(), "clientName", client_name_);
385 SetStringIfNonEmpty(value.get(), "portName", port_name_);
386 value->SetInteger("clientId", client_id_);
387 value->SetInteger("portId", port_id_);
388 value->SetInteger("midiDevice", midi_device_);
389
agooded87fc0f2015-05-21 08:29:31 -0700390 // Flatten id fields.
391 SetStringIfNonEmpty(value.get(), "bus", id_.bus());
392 SetStringIfNonEmpty(value.get(), "vendorId", id_.vendor_id());
393 SetStringIfNonEmpty(value.get(), "modelId", id_.model_id());
394 SetStringIfNonEmpty(value.get(), "usbInterfaceNum", id_.usb_interface_num());
395 SetStringIfNonEmpty(value.get(), "serial", id_.serial());
396
agoode99d63292015-04-13 08:39:25 -0700397 return value.Pass();
agoodebd4be9b2015-03-16 19:17:25 -0700398}
agoode@chromium.org25227512014-06-08 05:12:05 +0000399
agoode99d63292015-04-13 08:39:25 -0700400std::string MidiManagerAlsa::MidiPort::JSONValue() const {
401 std::string json;
402 JSONStringValueSerializer serializer(&json);
403 serializer.Serialize(*Value().get());
404 return json;
agoodef212b2a2015-03-19 12:53:23 -0700405}
406
agoode99d63292015-04-13 08:39:25 -0700407// TODO(agoode): Do not use SHA256 here. Instead store a persistent
408// mapping and just use a UUID or other random string.
409// http://crbug.com/465320
410std::string MidiManagerAlsa::MidiPort::OpaqueKey() const {
411 uint8 hash[crypto::kSHA256Length];
412 crypto::SHA256HashString(JSONValue(), &hash, sizeof(hash));
413 return base::HexEncode(&hash, sizeof(hash));
agoodebd4be9b2015-03-16 19:17:25 -0700414}
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000415
agoode99d63292015-04-13 08:39:25 -0700416bool MidiManagerAlsa::MidiPort::MatchConnected(const MidiPort& query) const {
417 // Matches on:
418 // connected == true
419 // type
420 // path
421 // id
422 // client_id
423 // port_id
424 // midi_device
425 // client_name
426 // port_name
427 return connected() && (type() == query.type()) && (path() == query.path()) &&
428 (id() == query.id()) && (client_id() == query.client_id()) &&
429 (port_id() == query.port_id()) &&
430 (midi_device() == query.midi_device()) &&
431 (client_name() == query.client_name()) &&
432 (port_name() == query.port_name());
agoodebd4be9b2015-03-16 19:17:25 -0700433}
434
agoode99d63292015-04-13 08:39:25 -0700435bool MidiManagerAlsa::MidiPort::MatchCardPass1(const MidiPort& query) const {
436 // Matches on:
437 // connected == false
438 // type
439 // path
440 // id
441 // port_id
442 // midi_device
443 return MatchCardPass2(query) && (path() == query.path());
agoodebd4be9b2015-03-16 19:17:25 -0700444}
445
agoode99d63292015-04-13 08:39:25 -0700446bool MidiManagerAlsa::MidiPort::MatchCardPass2(const MidiPort& query) const {
447 // Matches on:
448 // connected == false
449 // type
450 // id
451 // port_id
452 // midi_device
453 return !connected() && (type() == query.type()) && (id() == query.id()) &&
454 (port_id() == query.port_id()) &&
455 (midi_device() == query.midi_device());
agoodef212b2a2015-03-19 12:53:23 -0700456}
457
agoode99d63292015-04-13 08:39:25 -0700458bool MidiManagerAlsa::MidiPort::MatchNoCardPass1(const MidiPort& query) const {
459 // Matches on:
460 // connected == false
461 // type
462 // path.empty(), for both this and query
463 // id.empty(), for both this and query
464 // client_id
465 // port_id
466 // client_name
467 // port_name
468 // midi_device == -1, for both this and query
469 return MatchNoCardPass2(query) && (client_id() == query.client_id());
agoodef212b2a2015-03-19 12:53:23 -0700470}
471
agoode99d63292015-04-13 08:39:25 -0700472bool MidiManagerAlsa::MidiPort::MatchNoCardPass2(const MidiPort& query) const {
473 // Matches on:
474 // connected == false
475 // type
476 // path.empty(), for both this and query
477 // id.empty(), for both this and query
478 // port_id
479 // client_name
480 // port_name
481 // midi_device == -1, for both this and query
482 return !connected() && (type() == query.type()) && path().empty() &&
483 query.path().empty() && id().empty() && query.id().empty() &&
484 (port_id() == query.port_id()) &&
485 (client_name() == query.client_name()) &&
486 (port_name() == query.port_name()) && (midi_device() == -1) &&
487 (query.midi_device() == -1);
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000488}
489
agoodeb0582872015-05-20 05:22:24 -0700490MidiManagerAlsa::MidiPortStateBase::~MidiPortStateBase() = default;
agoode99d63292015-04-13 08:39:25 -0700491
492MidiManagerAlsa::MidiPortStateBase::iterator
493MidiManagerAlsa::MidiPortStateBase::Find(
494 const MidiManagerAlsa::MidiPort& port) {
495 auto result = FindConnected(port);
496 if (result == end())
497 result = FindDisconnected(port);
498 return result;
499}
500
501MidiManagerAlsa::MidiPortStateBase::iterator
502MidiManagerAlsa::MidiPortStateBase::FindConnected(
503 const MidiManagerAlsa::MidiPort& port) {
504 // Exact match required for connected ports.
505 auto it = std::find_if(ports_.begin(), ports_.end(), [&port](MidiPort* p) {
506 return p->MatchConnected(port);
507 });
508 return it;
509}
510
511MidiManagerAlsa::MidiPortStateBase::iterator
512MidiManagerAlsa::MidiPortStateBase::FindDisconnected(
513 const MidiManagerAlsa::MidiPort& port) {
514 // Always match on:
515 // type
516 // Possible things to match on:
517 // path
518 // id
519 // client_id
520 // port_id
521 // midi_device
522 // client_name
523 // port_name
524
525 if (!port.path().empty()) {
526 // If path is present, then we have a card-based client.
527
528 // Pass 1. Match on path, id, midi_device, port_id.
529 // This is the best possible match for hardware card-based clients.
530 // This will also match the empty id correctly for devices without an id.
531 auto it = std::find_if(ports_.begin(), ports_.end(), [&port](MidiPort* p) {
532 return p->MatchCardPass1(port);
533 });
534 if (it != ports_.end())
535 return it;
536
537 if (!port.id().empty()) {
538 // Pass 2. Match on id, midi_device, port_id.
539 // This will give us a high-confidence match when a user moves a device to
540 // another USB/Firewire/Thunderbolt/etc port, but only works if the device
541 // has a hardware id.
542 it = std::find_if(ports_.begin(), ports_.end(), [&port](MidiPort* p) {
543 return p->MatchCardPass2(port);
544 });
545 if (it != ports_.end())
546 return it;
547 }
548 } else {
549 // Else, we have a non-card-based client.
550 // Pass 1. Match on client_id, port_id, client_name, port_name.
551 // This will give us a reasonably good match.
552 auto it = std::find_if(ports_.begin(), ports_.end(), [&port](MidiPort* p) {
553 return p->MatchNoCardPass1(port);
554 });
555 if (it != ports_.end())
556 return it;
557
558 // Pass 2. Match on port_id, client_name, port_name.
559 // This is weaker but similar to pass 2 in the hardware card-based clients
560 // match.
561 it = std::find_if(ports_.begin(), ports_.end(), [&port](MidiPort* p) {
562 return p->MatchNoCardPass2(port);
563 });
564 if (it != ports_.end())
565 return it;
566 }
567
568 // No match.
569 return ports_.end();
570}
571
agoodeb0582872015-05-20 05:22:24 -0700572MidiManagerAlsa::MidiPortStateBase::MidiPortStateBase() = default;
agoode99d63292015-04-13 08:39:25 -0700573
574void MidiManagerAlsa::TemporaryMidiPortState::Insert(
575 scoped_ptr<MidiPort> port) {
576 ports()->push_back(port.Pass());
577}
578
579MidiManagerAlsa::MidiPortState::MidiPortState()
580 : num_input_ports_(0), num_output_ports_(0) {
581}
582
583uint32 MidiManagerAlsa::MidiPortState::Insert(scoped_ptr<MidiPort> port) {
584 // Add the web midi index.
vivek.vg0365a022015-04-14 23:11:23 -0700585 uint32 web_port_index = 0;
agoode99d63292015-04-13 08:39:25 -0700586 switch (port->type()) {
587 case MidiPort::Type::kInput:
588 web_port_index = num_input_ports_++;
589 break;
590 case MidiPort::Type::kOutput:
591 web_port_index = num_output_ports_++;
592 break;
593 }
594 port->set_web_port_index(web_port_index);
595 ports()->push_back(port.Pass());
596 return web_port_index;
597}
598
agoodeb09423b2015-05-11 11:39:57 -0700599MidiManagerAlsa::AlsaSeqState::AlsaSeqState()
600 : clients_deleter_(&clients_), card_client_count_(0) {
agoode99d63292015-04-13 08:39:25 -0700601}
602
agoodeb0582872015-05-20 05:22:24 -0700603MidiManagerAlsa::AlsaSeqState::~AlsaSeqState() = default;
agoode99d63292015-04-13 08:39:25 -0700604
605void MidiManagerAlsa::AlsaSeqState::ClientStart(int client_id,
606 const std::string& client_name,
607 snd_seq_client_type_t type) {
608 ClientExit(client_id);
609 clients_[client_id] = new Client(client_name, type);
agoodeb09423b2015-05-11 11:39:57 -0700610 if (IsCardClient(type, client_id))
611 ++card_client_count_;
agoode99d63292015-04-13 08:39:25 -0700612}
613
614bool MidiManagerAlsa::AlsaSeqState::ClientStarted(int client_id) {
615 return clients_.find(client_id) != clients_.end();
616}
617
618void MidiManagerAlsa::AlsaSeqState::ClientExit(int client_id) {
619 auto it = clients_.find(client_id);
620 if (it != clients_.end()) {
agoodeb09423b2015-05-11 11:39:57 -0700621 if (IsCardClient(it->second->type(), client_id))
622 --card_client_count_;
agoode99d63292015-04-13 08:39:25 -0700623 delete it->second;
624 clients_.erase(it);
625 }
626}
627
628void MidiManagerAlsa::AlsaSeqState::PortStart(
629 int client_id,
630 int port_id,
631 const std::string& port_name,
632 MidiManagerAlsa::AlsaSeqState::PortDirection direction,
633 bool midi) {
634 auto it = clients_.find(client_id);
635 if (it != clients_.end())
636 it->second->AddPort(port_id,
637 scoped_ptr<Port>(new Port(port_name, direction, midi)));
638}
639
640void MidiManagerAlsa::AlsaSeqState::PortExit(int client_id, int port_id) {
641 auto it = clients_.find(client_id);
642 if (it != clients_.end())
643 it->second->RemovePort(port_id);
644}
645
646snd_seq_client_type_t MidiManagerAlsa::AlsaSeqState::ClientType(
647 int client_id) const {
648 auto it = clients_.find(client_id);
649 if (it == clients_.end())
650 return SND_SEQ_USER_CLIENT;
651 return it->second->type();
652}
653
654scoped_ptr<MidiManagerAlsa::TemporaryMidiPortState>
agoodeb09423b2015-05-11 11:39:57 -0700655MidiManagerAlsa::AlsaSeqState::ToMidiPortState(const AlsaCardMap& alsa_cards) {
agoode99d63292015-04-13 08:39:25 -0700656 scoped_ptr<MidiManagerAlsa::TemporaryMidiPortState> midi_ports(
657 new TemporaryMidiPortState);
agoodeb09423b2015-05-11 11:39:57 -0700658 // TODO(agoode): Use more information from udev, to allow hardware matching.
659 // See http://crbug.com/486471.
660 auto card_it = alsa_cards.begin();
agoode99d63292015-04-13 08:39:25 -0700661
agoodeb09423b2015-05-11 11:39:57 -0700662 int card_midi_device = -1;
agoode99d63292015-04-13 08:39:25 -0700663 for (const auto& client_pair : clients_) {
664 int client_id = client_pair.first;
665 const auto& client = client_pair.second;
666
667 // Get client metadata.
668 const std::string client_name = client->name();
669 std::string manufacturer;
670 std::string driver;
671 std::string path;
agooded87fc0f2015-05-21 08:29:31 -0700672 MidiPort::Id id;
agoode99d63292015-04-13 08:39:25 -0700673 std::string card_name;
674 std::string card_longname;
675 int midi_device = -1;
676
agoodeb09423b2015-05-11 11:39:57 -0700677 if (IsCardClient(client->type(), client_id)) {
678 auto& card = card_it->second;
679 if (card_midi_device == -1)
680 card_midi_device = 0;
681
682 manufacturer = card->manufacturer();
agooded87fc0f2015-05-21 08:29:31 -0700683 path = card->path();
684 id = MidiPort::Id(card->bus(), card->vendor_id(), card->model_id(),
685 card->usb_interface_num(), card->serial());
686 card_name = card->name();
687 card_longname = card->longname();
agoodeb09423b2015-05-11 11:39:57 -0700688 midi_device = card_midi_device;
689
690 ++card_midi_device;
691 if (card_midi_device >= card->midi_device_count()) {
692 card_midi_device = -1;
693 ++card_it;
694 }
695 }
696
agoode99d63292015-04-13 08:39:25 -0700697 for (const auto& port_pair : *client) {
698 int port_id = port_pair.first;
699 const auto& port = port_pair.second;
700
701 if (port->midi()) {
702 std::string version;
703 if (!driver.empty()) {
704 version = driver + " / ";
705 }
706 version +=
707 base::StringPrintf("ALSA library version %d.%d.%d", SND_LIB_MAJOR,
708 SND_LIB_MINOR, SND_LIB_SUBMINOR);
709 PortDirection direction = port->direction();
710 if (direction == PortDirection::kInput ||
711 direction == PortDirection::kDuplex) {
712 midi_ports->Insert(scoped_ptr<MidiPort>(new MidiPort(
713 path, id, client_id, port_id, midi_device, client->name(),
714 port->name(), manufacturer, version, MidiPort::Type::kInput)));
715 }
716 if (direction == PortDirection::kOutput ||
717 direction == PortDirection::kDuplex) {
718 midi_ports->Insert(scoped_ptr<MidiPort>(new MidiPort(
719 path, id, client_id, port_id, midi_device, client->name(),
720 port->name(), manufacturer, version, MidiPort::Type::kOutput)));
721 }
722 }
723 }
724 }
725
726 return midi_ports.Pass();
727}
728
729MidiManagerAlsa::AlsaSeqState::Port::Port(
730 const std::string& name,
731 MidiManagerAlsa::AlsaSeqState::PortDirection direction,
732 bool midi)
733 : name_(name), direction_(direction), midi_(midi) {
734}
735
agoodeb0582872015-05-20 05:22:24 -0700736MidiManagerAlsa::AlsaSeqState::Port::~Port() = default;
agoode99d63292015-04-13 08:39:25 -0700737
738MidiManagerAlsa::AlsaSeqState::Client::Client(const std::string& name,
739 snd_seq_client_type_t type)
740 : name_(name), type_(type), ports_deleter_(&ports_) {
741}
742
agoodeb0582872015-05-20 05:22:24 -0700743MidiManagerAlsa::AlsaSeqState::Client::~Client() = default;
agoode99d63292015-04-13 08:39:25 -0700744
745void MidiManagerAlsa::AlsaSeqState::Client::AddPort(int addr,
746 scoped_ptr<Port> port) {
747 RemovePort(addr);
748 ports_[addr] = port.release();
749}
750
751void MidiManagerAlsa::AlsaSeqState::Client::RemovePort(int addr) {
752 auto it = ports_.find(addr);
753 if (it != ports_.end()) {
754 delete it->second;
755 ports_.erase(it);
756 }
757}
758
759MidiManagerAlsa::AlsaSeqState::Client::PortMap::const_iterator
760MidiManagerAlsa::AlsaSeqState::Client::begin() const {
761 return ports_.begin();
762}
763
764MidiManagerAlsa::AlsaSeqState::Client::PortMap::const_iterator
765MidiManagerAlsa::AlsaSeqState::Client::end() const {
766 return ports_.end();
agoodeaf6e9f52015-03-24 10:23:49 -0700767}
768
agoodeb09423b2015-05-11 11:39:57 -0700769MidiManagerAlsa::AlsaCard::AlsaCard(udev_device* dev,
agooded87fc0f2015-05-21 08:29:31 -0700770 const std::string& name,
771 const std::string& longname,
772 const std::string& driver,
agoodeb09423b2015-05-11 11:39:57 -0700773 int midi_device_count)
agooded87fc0f2015-05-21 08:29:31 -0700774 : name_(name),
775 longname_(longname),
776 driver_(driver),
777 path_(device::UdevDeviceGetPropertyValue(dev, kUdevIdPath)),
778 bus_(device::UdevDeviceGetPropertyValue(dev, kUdevIdBus)),
779 vendor_id_(
780 UdevDeviceGetPropertyOrSysattr(dev, kUdevIdVendorId, kSysattrVendor)),
781 model_id_(
782 UdevDeviceGetPropertyOrSysattr(dev, kUdevIdModelId, kSysattrModel)),
783 usb_interface_num_(
784 device::UdevDeviceGetPropertyValue(dev, kUdevIdUsbInterfaceNum)),
785 serial_(UdevDeviceGetPropertyOrSysattr(dev,
786 kUdevIdSerialShort,
787 kSysattrGuid)),
788 midi_device_count_(midi_device_count),
789 manufacturer_(ExtractManufacturerString(
790 GetVendor(dev),
791 vendor_id_,
792 device::UdevDeviceGetPropertyValue(dev, kUdevIdVendorFromDatabase),
793 name,
794 longname)) {
agoodeb09423b2015-05-11 11:39:57 -0700795}
796
agoodeb0582872015-05-20 05:22:24 -0700797MidiManagerAlsa::AlsaCard::~AlsaCard() = default;
agoodeb09423b2015-05-11 11:39:57 -0700798
agoode55a8b522015-03-08 12:40:17 -0700799// static
agoodeb09423b2015-05-11 11:39:57 -0700800std::string MidiManagerAlsa::AlsaCard::ExtractManufacturerString(
agoode5e4e9cd2015-03-09 12:34:24 -0700801 const std::string& udev_id_vendor,
agoode55a8b522015-03-08 12:40:17 -0700802 const std::string& udev_id_vendor_id,
803 const std::string& udev_id_vendor_from_database,
804 const std::string& alsa_name,
805 const std::string& alsa_longname) {
806 // Let's try to determine the manufacturer. Here is the ordered preference
807 // in extraction:
agoodef212b2a2015-03-19 12:53:23 -0700808 // 1. Vendor name from the hardware device string, from udev properties
809 // or sysattrs.
agoode5e4e9cd2015-03-09 12:34:24 -0700810 // 2. Vendor name from the udev database (property ID_VENDOR_FROM_DATABASE).
agoode55a8b522015-03-08 12:40:17 -0700811 // 3. Heuristic from ALSA.
812
agoodee83758c2015-03-23 22:07:54 -0700813 // Is the vendor string present and not just the vendor hex id?
814 if (!udev_id_vendor.empty() && (udev_id_vendor != udev_id_vendor_id)) {
agoode55a8b522015-03-08 12:40:17 -0700815 return udev_id_vendor;
816 }
817
818 // Is there a vendor string in the hardware database?
819 if (!udev_id_vendor_from_database.empty()) {
820 return udev_id_vendor_from_database;
821 }
822
823 // Ok, udev gave us nothing useful, or was unavailable. So try a heuristic.
824 // We assume that card longname is in the format of
825 // "<manufacturer> <name> at <bus>". Otherwise, we give up to detect
826 // a manufacturer name here.
827 size_t at_index = alsa_longname.rfind(" at ");
agoodef212b2a2015-03-19 12:53:23 -0700828 if (at_index && at_index != std::string::npos) {
agoode55a8b522015-03-08 12:40:17 -0700829 size_t name_index = alsa_longname.rfind(alsa_name, at_index - 1);
agoodef212b2a2015-03-19 12:53:23 -0700830 if (name_index && name_index != std::string::npos)
agoode55a8b522015-03-08 12:40:17 -0700831 return alsa_longname.substr(0, name_index - 1);
832 }
833
834 // Failure.
835 return "";
836}
837
agoodebd4be9b2015-03-16 19:17:25 -0700838void MidiManagerAlsa::SendMidiData(uint32 port_index,
839 const std::vector<uint8>& data) {
skyostil93e2ec22015-06-17 08:49:09 -0700840 DCHECK(send_thread_.task_runner()->BelongsToCurrentThread());
agoodebd4be9b2015-03-16 19:17:25 -0700841
agoodef212b2a2015-03-19 12:53:23 -0700842 snd_midi_event_t* encoder;
843 snd_midi_event_new(kSendBufferSize, &encoder);
agoodebd4be9b2015-03-16 19:17:25 -0700844 for (unsigned int i = 0; i < data.size(); i++) {
845 snd_seq_event_t event;
846 int result = snd_midi_event_encode_byte(encoder, data[i], &event);
847 if (result == 1) {
848 // Full event, send it.
agoode99d63292015-04-13 08:39:25 -0700849 base::AutoLock lock(out_ports_lock_);
850 auto it = out_ports_.find(port_index);
851 if (it != out_ports_.end()) {
852 snd_seq_ev_set_source(&event, it->second);
853 snd_seq_ev_set_subs(&event);
854 snd_seq_ev_set_direct(&event);
855 snd_seq_event_output_direct(out_client_, &event);
856 }
agoodebd4be9b2015-03-16 19:17:25 -0700857 }
858 }
agoodef212b2a2015-03-19 12:53:23 -0700859 snd_midi_event_free(encoder);
agoodebd4be9b2015-03-16 19:17:25 -0700860}
861
862void MidiManagerAlsa::ScheduleEventLoop() {
863 event_thread_.message_loop()->PostTask(
864 FROM_HERE,
865 base::Bind(&MidiManagerAlsa::EventLoop, base::Unretained(this)));
866}
867
868void MidiManagerAlsa::EventLoop() {
agoode975043d2015-05-11 00:46:17 -0700869 bool loop_again = true;
agoodebd4be9b2015-03-16 19:17:25 -0700870
agoode975043d2015-05-11 00:46:17 -0700871 struct pollfd pfd[2];
872 snd_seq_poll_descriptors(in_client_, &pfd[0], 1, POLLIN);
873 pfd[1].fd = device::udev_monitor_get_fd(udev_monitor_.get());
874 pfd[1].events = POLLIN;
agoodebd4be9b2015-03-16 19:17:25 -0700875
agoode975043d2015-05-11 00:46:17 -0700876 int err = HANDLE_EINTR(poll(pfd, arraysize(pfd), -1));
877 if (err < 0) {
brettwf7f870f2015-06-09 11:05:24 -0700878 VLOG(1) << "poll fails: " << base::safe_strerror(errno);
agoode975043d2015-05-11 00:46:17 -0700879 loop_again = false;
agoodeaf6e9f52015-03-24 10:23:49 -0700880 } else {
agoode975043d2015-05-11 00:46:17 -0700881 if (pfd[0].revents & POLLIN) {
882 // Read available incoming MIDI data.
883 int remaining;
884 double timestamp =
885 (base::TimeTicks::Now() - base::TimeTicks()).InSecondsF();
886 do {
887 snd_seq_event_t* event;
888 err = snd_seq_event_input(in_client_, &event);
889 remaining = snd_seq_event_input_pending(in_client_, 0);
890
891 if (err == -ENOSPC) {
892 // Handle out of space error.
893 VLOG(1) << "snd_seq_event_input detected buffer overrun";
894 // We've lost events: check another way to see if we need to shut
895 // down.
896 base::AutoLock lock(shutdown_lock_);
897 if (event_thread_shutdown_)
898 loop_again = false;
899 } else if (err == -EAGAIN) {
900 // We've read all the data.
901 } else if (err < 0) {
902 // Handle other errors.
903 VLOG(1) << "snd_seq_event_input fails: " << snd_strerror(err);
904 // TODO(agoode): Use RecordAction() or similar to log this.
905 loop_again = false;
906 } else if (event->source.client == SND_SEQ_CLIENT_SYSTEM &&
907 event->source.port == SND_SEQ_PORT_SYSTEM_ANNOUNCE) {
908 // Handle announce events.
909 switch (event->type) {
910 case SND_SEQ_EVENT_PORT_START:
911 // Don't use SND_SEQ_EVENT_CLIENT_START because the
912 // client name may not be set by the time we query
913 // it. It should be set by the time ports are made.
914 ProcessClientStartEvent(event->data.addr.client);
915 ProcessPortStartEvent(event->data.addr);
916 break;
917 case SND_SEQ_EVENT_CLIENT_EXIT:
918 // Check for disconnection of our "out" client. This means "shut
919 // down".
920 if (event->data.addr.client == out_client_id_) {
921 loop_again = false;
922 remaining = 0;
923 } else
924 ProcessClientExitEvent(event->data.addr);
925 break;
926 case SND_SEQ_EVENT_PORT_EXIT:
927 ProcessPortExitEvent(event->data.addr);
928 break;
929 }
930 } else {
931 // Normal operation.
932 ProcessSingleEvent(event, timestamp);
933 }
934 } while (remaining > 0);
935 }
936 if (pfd[1].revents & POLLIN) {
937 device::ScopedUdevDevicePtr dev(
938 device::udev_monitor_receive_device(udev_monitor_.get()));
939 if (dev.get())
940 ProcessUdevEvent(dev.get());
941 else
942 VLOG(1) << "udev_monitor_receive_device fails";
943 }
agoodebd4be9b2015-03-16 19:17:25 -0700944 }
945
agoodebd4be9b2015-03-16 19:17:25 -0700946 // Do again.
agoode975043d2015-05-11 00:46:17 -0700947 if (loop_again)
948 ScheduleEventLoop();
agoodebd4be9b2015-03-16 19:17:25 -0700949}
950
951void MidiManagerAlsa::ProcessSingleEvent(snd_seq_event_t* event,
952 double timestamp) {
agoode99d63292015-04-13 08:39:25 -0700953 auto source_it =
954 source_map_.find(AddrToInt(event->source.client, event->source.port));
agoodebd4be9b2015-03-16 19:17:25 -0700955 if (source_it != source_map_.end()) {
956 uint32 source = source_it->second;
957 if (event->type == SND_SEQ_EVENT_SYSEX) {
958 // Special! Variable-length sysex.
959 ReceiveMidiData(source, static_cast<const uint8*>(event->data.ext.ptr),
960 event->data.ext.len, timestamp);
961 } else {
962 // Otherwise, decode this and send that on.
963 unsigned char buf[12];
964 long count = snd_midi_event_decode(decoder_, buf, sizeof(buf), event);
965 if (count <= 0) {
966 if (count != -ENOENT) {
967 // ENOENT means that it's not a MIDI message, which is not an
968 // error, but other negative values are errors for us.
969 VLOG(1) << "snd_midi_event_decoder fails " << snd_strerror(count);
970 // TODO(agoode): Record this failure.
971 }
972 } else {
973 ReceiveMidiData(source, buf, count, timestamp);
974 }
975 }
976 }
977}
978
agoode99d63292015-04-13 08:39:25 -0700979void MidiManagerAlsa::ProcessClientStartEvent(int client_id) {
980 // Ignore if client is already started.
981 if (alsa_seq_state_.ClientStarted(client_id))
982 return;
983
984 snd_seq_client_info_t* client_info;
985 snd_seq_client_info_alloca(&client_info);
986 int err = snd_seq_get_any_client_info(in_client_, client_id, client_info);
987 if (err != 0)
988 return;
989
990 // Skip our own clients.
991 if ((client_id == in_client_id_) || (client_id == out_client_id_))
992 return;
993
994 // Update our view of ALSA seq state.
995 alsa_seq_state_.ClientStart(client_id,
996 snd_seq_client_info_get_name(client_info),
997 snd_seq_client_info_get_type(client_info));
998
999 // Generate Web MIDI events.
1000 UpdatePortStateAndGenerateEvents();
1001}
1002
1003void MidiManagerAlsa::ProcessPortStartEvent(const snd_seq_addr_t& addr) {
1004 snd_seq_port_info_t* port_info;
1005 snd_seq_port_info_alloca(&port_info);
1006 int err =
1007 snd_seq_get_any_port_info(in_client_, addr.client, addr.port, port_info);
1008 if (err != 0)
1009 return;
1010
1011 unsigned int caps = snd_seq_port_info_get_capability(port_info);
1012 bool input = (caps & kRequiredInputPortCaps) == kRequiredInputPortCaps;
1013 bool output = (caps & kRequiredOutputPortCaps) == kRequiredOutputPortCaps;
1014 AlsaSeqState::PortDirection direction;
1015 if (input && output)
1016 direction = AlsaSeqState::PortDirection::kDuplex;
1017 else if (input)
1018 direction = AlsaSeqState::PortDirection::kInput;
1019 else if (output)
1020 direction = AlsaSeqState::PortDirection::kOutput;
1021 else
1022 return;
1023
1024 // Update our view of ALSA seq state.
1025 alsa_seq_state_.PortStart(
1026 addr.client, addr.port, snd_seq_port_info_get_name(port_info), direction,
1027 snd_seq_port_info_get_type(port_info) & SND_SEQ_PORT_TYPE_MIDI_GENERIC);
1028 // Generate Web MIDI events.
1029 UpdatePortStateAndGenerateEvents();
1030}
1031
1032void MidiManagerAlsa::ProcessClientExitEvent(const snd_seq_addr_t& addr) {
1033 // Update our view of ALSA seq state.
1034 alsa_seq_state_.ClientExit(addr.client);
1035 // Generate Web MIDI events.
1036 UpdatePortStateAndGenerateEvents();
1037}
1038
1039void MidiManagerAlsa::ProcessPortExitEvent(const snd_seq_addr_t& addr) {
1040 // Update our view of ALSA seq state.
1041 alsa_seq_state_.PortExit(addr.client, addr.port);
1042 // Generate Web MIDI events.
1043 UpdatePortStateAndGenerateEvents();
1044}
1045
agoode975043d2015-05-11 00:46:17 -07001046void MidiManagerAlsa::ProcessUdevEvent(udev_device* dev) {
1047 // Only card devices have this property set, and only when they are
1048 // fully initialized.
1049 if (!device::udev_device_get_property_value(dev,
1050 kUdevPropertySoundInitialized))
1051 return;
1052
1053 // Get the action. If no action, then we are doing first time enumeration
1054 // and the device is treated as new.
1055 const char* action = device::udev_device_get_action(dev);
1056 if (!action)
1057 action = kUdevActionChange;
1058
1059 if (strcmp(action, kUdevActionChange) == 0) {
agoodeb09423b2015-05-11 11:39:57 -07001060 AddCard(dev);
1061 // Generate Web MIDI events.
1062 UpdatePortStateAndGenerateEvents();
agoode975043d2015-05-11 00:46:17 -07001063 } else if (strcmp(action, kUdevActionRemove) == 0) {
agoodeb09423b2015-05-11 11:39:57 -07001064 RemoveCard(GetCardNumber(dev));
1065 // Generate Web MIDI events.
1066 UpdatePortStateAndGenerateEvents();
agoode975043d2015-05-11 00:46:17 -07001067 }
1068}
1069
agoodeb09423b2015-05-11 11:39:57 -07001070void MidiManagerAlsa::AddCard(udev_device* dev) {
1071 int number = GetCardNumber(dev);
1072 if (number == -1)
1073 return;
1074
1075 RemoveCard(number);
1076
1077 snd_ctl_card_info_t* card;
1078 snd_hwdep_info_t* hwdep;
1079 snd_ctl_card_info_alloca(&card);
1080 snd_hwdep_info_alloca(&hwdep);
1081 const std::string id = base::StringPrintf("hw:CARD=%i", number);
1082 snd_ctl_t* handle;
1083 int err = snd_ctl_open(&handle, id.c_str(), 0);
1084 if (err != 0) {
1085 VLOG(1) << "snd_ctl_open fails: " << snd_strerror(err);
1086 return;
1087 }
1088 err = snd_ctl_card_info(handle, card);
1089 if (err != 0) {
1090 VLOG(1) << "snd_ctl_card_info fails: " << snd_strerror(err);
1091 snd_ctl_close(handle);
1092 return;
1093 }
1094 std::string name = snd_ctl_card_info_get_name(card);
1095 std::string longname = snd_ctl_card_info_get_longname(card);
1096 std::string driver = snd_ctl_card_info_get_driver(card);
1097
1098 // Count rawmidi devices (not subdevices).
1099 int midi_count = 0;
1100 for (int device = -1;
1101 !snd_ctl_rawmidi_next_device(handle, &device) && device >= 0;)
1102 ++midi_count;
1103
1104 // Count any hwdep synths that become MIDI devices outside of rawmidi.
1105 //
1106 // Explanation:
1107 // Any kernel driver can create an ALSA client (visible to us).
1108 // With modern hardware, only rawmidi devices do this. Kernel
1109 // drivers create rawmidi devices and the rawmidi subsystem makes
1110 // the seq clients. But the OPL3 driver is special, it does not
1111 // make a rawmidi device but a seq client directly. (This is the
1112 // only one to worry about in the kernel code, as of 2015-03-23.)
1113 //
1114 // OPL3 is very old (but still possible to get in new
1115 // hardware). It is unlikely that new drivers would not use
1116 // rawmidi and defeat our heuristic.
1117 //
1118 // Longer term, support should be added in the kernel to expose a
1119 // direct link from card->client (or client->card) so that all
1120 // these heuristics will be obsolete. Once that is there, we can
1121 // assume our old heuristics will work on old kernels and the new
1122 // robust code will be used on new. Then we will not need to worry
1123 // about changes to kernel internals breaking our code.
1124 // See the TODO above at kMinimumClientIdForCards.
1125 for (int device = -1;
1126 !snd_ctl_hwdep_next_device(handle, &device) && device >= 0;) {
1127 err = snd_ctl_hwdep_info(handle, hwdep);
1128 if (err != 0) {
1129 VLOG(1) << "snd_ctl_hwdep_info fails: " << snd_strerror(err);
1130 continue;
1131 }
1132 snd_hwdep_iface_t iface = snd_hwdep_info_get_iface(hwdep);
1133 if (iface == SND_HWDEP_IFACE_OPL2 || iface == SND_HWDEP_IFACE_OPL3 ||
1134 iface == SND_HWDEP_IFACE_OPL4)
1135 ++midi_count;
1136 }
1137 snd_ctl_close(handle);
1138
1139 if (midi_count > 0)
1140 alsa_cards_[number] = new AlsaCard(dev, name, longname, driver, midi_count);
1141 alsa_card_midi_count_ += midi_count;
1142}
1143
1144void MidiManagerAlsa::RemoveCard(int number) {
1145 auto it = alsa_cards_.find(number);
1146 if (it == alsa_cards_.end())
1147 return;
1148
1149 alsa_card_midi_count_ -= it->second->midi_device_count();
1150 delete it->second;
1151 alsa_cards_.erase(it);
1152}
1153
agoode99d63292015-04-13 08:39:25 -07001154void MidiManagerAlsa::UpdatePortStateAndGenerateEvents() {
agoodeb09423b2015-05-11 11:39:57 -07001155 // Verify that our information from ALSA and udev are in sync. If
1156 // not, we cannot generate events right now.
1157 if (alsa_card_midi_count_ != alsa_seq_state_.card_client_count())
1158 return;
1159
agoode99d63292015-04-13 08:39:25 -07001160 // Generate new port state.
agoodeb09423b2015-05-11 11:39:57 -07001161 auto new_port_state = alsa_seq_state_.ToMidiPortState(alsa_cards_);
agoode99d63292015-04-13 08:39:25 -07001162
1163 // Disconnect any connected old ports that are now missing.
1164 for (auto* old_port : port_state_) {
1165 if (old_port->connected() &&
1166 (new_port_state->FindConnected(*old_port) == new_port_state->end())) {
1167 old_port->set_connected(false);
1168 uint32 web_port_index = old_port->web_port_index();
1169 switch (old_port->type()) {
1170 case MidiPort::Type::kInput:
1171 source_map_.erase(
1172 AddrToInt(old_port->client_id(), old_port->port_id()));
1173 SetInputPortState(web_port_index, MIDI_PORT_DISCONNECTED);
1174 break;
1175 case MidiPort::Type::kOutput:
1176 DeleteAlsaOutputPort(web_port_index);
1177 SetOutputPortState(web_port_index, MIDI_PORT_DISCONNECTED);
1178 break;
1179 }
1180 }
1181 }
1182
1183 // Reconnect or add new ports.
1184 auto it = new_port_state->begin();
1185 while (it != new_port_state->end()) {
1186 auto* new_port = *it;
1187 auto old_port = port_state_.Find(*new_port);
1188 if (old_port == port_state_.end()) {
1189 // Add new port.
1190 uint32 web_port_index =
1191 port_state_.Insert(scoped_ptr<MidiPort>(new_port));
1192 MidiPortInfo info(new_port->OpaqueKey(), new_port->manufacturer(),
1193 new_port->port_name(), new_port->version(),
1194 MIDI_PORT_OPENED);
1195 switch (new_port->type()) {
1196 case MidiPort::Type::kInput:
1197 if (Subscribe(web_port_index, new_port->client_id(),
1198 new_port->port_id()))
1199 AddInputPort(info);
1200 break;
agoode99d63292015-04-13 08:39:25 -07001201 case MidiPort::Type::kOutput:
1202 if (CreateAlsaOutputPort(web_port_index, new_port->client_id(),
1203 new_port->port_id()))
1204 AddOutputPort(info);
1205 break;
1206 }
1207 it = new_port_state->weak_erase(it);
1208 } else if (!(*old_port)->connected()) {
1209 // Reconnect.
1210 uint32 web_port_index = (*old_port)->web_port_index();
1211 (*old_port)->Update(new_port->path(), new_port->client_id(),
1212 new_port->port_id(), new_port->client_name(),
1213 new_port->port_name(), new_port->manufacturer(),
1214 new_port->version());
1215 switch ((*old_port)->type()) {
1216 case MidiPort::Type::kInput:
1217 if (Subscribe(web_port_index, (*old_port)->client_id(),
1218 (*old_port)->port_id()))
1219 SetInputPortState(web_port_index, MIDI_PORT_OPENED);
1220 break;
agoode99d63292015-04-13 08:39:25 -07001221 case MidiPort::Type::kOutput:
1222 if (CreateAlsaOutputPort(web_port_index, (*old_port)->client_id(),
1223 (*old_port)->port_id()))
1224 SetOutputPortState(web_port_index, MIDI_PORT_OPENED);
1225 break;
1226 }
1227 (*old_port)->set_connected(true);
1228 ++it;
1229 } else {
1230 ++it;
1231 }
1232 }
1233}
1234
agoode975043d2015-05-11 00:46:17 -07001235// TODO(agoode): return false on failure.
agoode99d63292015-04-13 08:39:25 -07001236void MidiManagerAlsa::EnumerateAlsaPorts() {
1237 snd_seq_client_info_t* client_info;
1238 snd_seq_client_info_alloca(&client_info);
1239 snd_seq_port_info_t* port_info;
1240 snd_seq_port_info_alloca(&port_info);
1241
1242 // Enumerate clients.
1243 snd_seq_client_info_set_client(client_info, -1);
1244 while (!snd_seq_query_next_client(in_client_, client_info)) {
1245 int client_id = snd_seq_client_info_get_client(client_info);
1246 ProcessClientStartEvent(client_id);
1247
1248 // Enumerate ports.
1249 snd_seq_port_info_set_client(port_info, client_id);
1250 snd_seq_port_info_set_port(port_info, -1);
1251 while (!snd_seq_query_next_port(in_client_, port_info)) {
1252 const snd_seq_addr_t* addr = snd_seq_port_info_get_addr(port_info);
1253 ProcessPortStartEvent(*addr);
1254 }
1255 }
1256}
1257
agoode975043d2015-05-11 00:46:17 -07001258bool MidiManagerAlsa::EnumerateUdevCards() {
1259 int err;
1260
1261 device::ScopedUdevEnumeratePtr enumerate(
1262 device::udev_enumerate_new(udev_.get()));
1263 if (!enumerate.get()) {
1264 VLOG(1) << "udev_enumerate_new fails";
1265 return false;
1266 }
1267
1268 err = device::udev_enumerate_add_match_subsystem(enumerate.get(),
1269 kUdevSubsystemSound);
1270 if (err) {
1271 VLOG(1) << "udev_enumerate_add_match_subsystem fails: "
brettwf7f870f2015-06-09 11:05:24 -07001272 << base::safe_strerror(-err);
agoode975043d2015-05-11 00:46:17 -07001273 return false;
1274 }
1275
1276 err = device::udev_enumerate_scan_devices(enumerate.get());
1277 if (err) {
brettwf7f870f2015-06-09 11:05:24 -07001278 VLOG(1) << "udev_enumerate_scan_devices fails: "
1279 << base::safe_strerror(-err);
agoode975043d2015-05-11 00:46:17 -07001280 return false;
1281 }
1282
1283 udev_list_entry* list_entry;
1284 auto* devices = device::udev_enumerate_get_list_entry(enumerate.get());
1285 udev_list_entry_foreach(list_entry, devices) {
1286 const char* path = device::udev_list_entry_get_name(list_entry);
1287 device::ScopedUdevDevicePtr dev(
1288 device::udev_device_new_from_syspath(udev_.get(), path));
1289 if (dev.get())
1290 ProcessUdevEvent(dev.get());
1291 }
1292
1293 return true;
1294}
1295
agoode99d63292015-04-13 08:39:25 -07001296bool MidiManagerAlsa::CreateAlsaOutputPort(uint32 port_index,
1297 int client_id,
1298 int port_id) {
1299 // Create the port.
1300 int out_port = snd_seq_create_simple_port(
1301 out_client_, NULL, kCreateOutputPortCaps, kCreatePortType);
1302 if (out_port < 0) {
1303 VLOG(1) << "snd_seq_create_simple_port fails: " << snd_strerror(out_port);
1304 return false;
1305 }
1306 // Activate port subscription.
1307 snd_seq_port_subscribe_t* subs;
1308 snd_seq_port_subscribe_alloca(&subs);
1309 snd_seq_addr_t sender;
1310 sender.client = out_client_id_;
1311 sender.port = out_port;
1312 snd_seq_port_subscribe_set_sender(subs, &sender);
1313 snd_seq_addr_t dest;
1314 dest.client = client_id;
1315 dest.port = port_id;
1316 snd_seq_port_subscribe_set_dest(subs, &dest);
1317 int err = snd_seq_subscribe_port(out_client_, subs);
1318 if (err != 0) {
1319 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
1320 snd_seq_delete_simple_port(out_client_, out_port);
1321 return false;
1322 }
1323
1324 // Update our map.
1325 base::AutoLock lock(out_ports_lock_);
1326 out_ports_[port_index] = out_port;
1327 return true;
1328}
1329
1330void MidiManagerAlsa::DeleteAlsaOutputPort(uint32 port_index) {
1331 base::AutoLock lock(out_ports_lock_);
1332 auto it = out_ports_.find(port_index);
1333 if (it == out_ports_.end())
1334 return;
1335
1336 int alsa_port = it->second;
1337 snd_seq_delete_simple_port(out_client_, alsa_port);
1338 out_ports_.erase(it);
1339}
1340
1341bool MidiManagerAlsa::Subscribe(uint32 port_index, int client_id, int port_id) {
1342 // Activate port subscription.
1343 snd_seq_port_subscribe_t* subs;
1344 snd_seq_port_subscribe_alloca(&subs);
1345 snd_seq_addr_t sender;
1346 sender.client = client_id;
1347 sender.port = port_id;
1348 snd_seq_port_subscribe_set_sender(subs, &sender);
1349 snd_seq_addr_t dest;
1350 dest.client = in_client_id_;
1351 dest.port = in_port_id_;
1352 snd_seq_port_subscribe_set_dest(subs, &dest);
1353 int err = snd_seq_subscribe_port(in_client_, subs);
1354 if (err != 0) {
1355 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
1356 return false;
1357 }
1358
1359 // Update our map.
1360 source_map_[AddrToInt(client_id, port_id)] = port_index;
1361 return true;
1362}
1363
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +00001364MidiManager* MidiManager::Create() {
1365 return new MidiManagerAlsa();
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +00001366}
1367
toyoshime147c5e2015-05-07 21:58:31 -07001368} // namespace midi
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +00001369} // namespace media