blob: d80aa2b3a894e9b6eb2b92d5bcdf676727cf5553 [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
7#include <alsa/asoundlib.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"
agoodef212b2a2015-03-19 12:53:23 -070015#include "base/memory/scoped_ptr.h"
agoode@chromium.org25227512014-06-08 05:12:05 +000016#include "base/memory/scoped_vector.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000017#include "base/message_loop/message_loop.h"
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000018#include "base/posix/eintr_wrapper.h"
agoodef212b2a2015-03-19 12:53:23 -070019#include "base/strings/string_number_conversions.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000020#include "base/strings/stringprintf.h"
21#include "base/threading/thread.h"
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000022#include "base/time/time.h"
agoodef212b2a2015-03-19 12:53:23 -070023#include "base/values.h"
24#include "crypto/sha2.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000025#include "media/midi/midi_port_info.h"
26
27namespace media {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000028
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000029namespace {
30
agoode@chromium.org25227512014-06-08 05:12:05 +000031// Per-output buffer. This can be smaller, but then large sysex messages
32// will be (harmlessly) split across multiple seq events. This should
33// not have any real practical effect, except perhaps to slightly reorder
34// realtime messages with respect to sysex.
35const size_t kSendBufferSize = 256;
36
agoodebd4be9b2015-03-16 19:17:25 -070037// Minimum client id for which we will have ALSA card devices for. When we
38// are searching for card devices (used to get the path, id, and manufacturer),
39// we don't want to get confused by kernel clients that do not have a card.
40// See seq_clientmgr.c in the ALSA code for this.
41// TODO(agoode): Add proper client -> card export from the kernel to avoid
42// hardcoding.
43const int kMinimumClientIdForCards = 16;
44
agoodef212b2a2015-03-19 12:53:23 -070045// udev key constants.
gunschf2115a02015-03-19 23:32:29 -070046#if defined(USE_UDEV)
agoodef212b2a2015-03-19 12:53:23 -070047const char kSoundClass[] = "sound";
48const char kIdVendor[] = "ID_VENDOR";
49const char kIdVendorEnc[] = "ID_VENDOR_ENC";
50const char kIdVendorFromDatabase[] = "ID_VENDOR_FROM_DATABASE";
51const char kSysattrVendorName[] = "vendor_name";
52const char kIdVendorId[] = "ID_VENDOR_ID";
53const char kSysattrVendor[] = "vendor";
54const char kIdModelId[] = "ID_MODEL_ID";
55const char kSysattrModel[] = "model";
56const char kIdBus[] = "ID_BUS";
57const char kIdPath[] = "ID_PATH";
58const char kUsbInterfaceNum[] = "ID_USB_INTERFACE_NUM";
gunschf2115a02015-03-19 23:32:29 -070059#endif // defined(USE_UDEV)
agoodef212b2a2015-03-19 12:53:23 -070060
61// ALSA constants.
62const char kAlsaHw[] = "hw";
63
agoode@chromium.org25227512014-06-08 05:12:05 +000064// Constants for the capabilities we search for in inputs and outputs.
65// See http://www.alsa-project.org/alsa-doc/alsa-lib/seq.html.
66const unsigned int kRequiredInputPortCaps =
67 SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ;
68const unsigned int kRequiredOutputPortCaps =
69 SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE;
70
71int AddrToInt(const snd_seq_addr_t* addr) {
72 return (addr->client << 8) | addr->port;
73}
74
agoodef212b2a2015-03-19 12:53:23 -070075// TODO(agoode): Move this to device/udev_linux.
76#if defined(USE_UDEV)
77const std::string UdevDeviceGetPropertyOrSysattr(
78 struct udev_device* udev_device,
79 const char* property_key,
80 const char* sysattr_key) {
81 // First try the property.
82 std::string value =
83 device::UdevDeviceGetPropertyValue(udev_device, property_key);
84
85 // If no property, look for sysattrs and walk up the parent devices too.
86 while (value.empty() && udev_device) {
87 value = device::UdevDeviceGetSysattrValue(udev_device, sysattr_key);
88 udev_device = device::udev_device_get_parent(udev_device);
89 }
90 return value;
91}
92#endif // defined(USE_UDEV)
93
agoode8caab0b2015-03-23 18:48:02 -070094void SetStringIfNonEmpty(base::DictionaryValue* value,
95 const std::string& path,
96 const std::string& in_value) {
97 if (!in_value.empty())
98 value->SetString(path, in_value);
99}
100
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000101} // namespace
102
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000103MidiManagerAlsa::MidiManagerAlsa()
agoode@chromium.org25227512014-06-08 05:12:05 +0000104 : in_client_(NULL),
105 out_client_(NULL),
106 out_client_id_(-1),
107 in_port_(-1),
108 decoder_(NULL),
agoode55a8b522015-03-08 12:40:17 -0700109#if defined(USE_UDEV)
110 udev_(device::udev_new()),
111#endif // defined(USE_UDEV)
agoode@chromium.org25227512014-06-08 05:12:05 +0000112 send_thread_("MidiSendThread"),
113 event_thread_("MidiEventThread"),
114 event_thread_shutdown_(false) {
115 // Initialize decoder.
116 snd_midi_event_new(0, &decoder_);
117 snd_midi_event_no_status(decoder_, 1);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000118}
119
agoodebd4be9b2015-03-16 19:17:25 -0700120MidiManagerAlsa::~MidiManagerAlsa() {
121 // Tell the event thread it will soon be time to shut down. This gives
122 // us assurance the thread will stop in case the SND_SEQ_EVENT_CLIENT_EXIT
123 // message is lost.
124 {
125 base::AutoLock lock(shutdown_lock_);
126 event_thread_shutdown_ = true;
127 }
128
129 // Stop the send thread.
130 send_thread_.Stop();
131
132 // Close the out client. This will trigger the event thread to stop,
133 // because of SND_SEQ_EVENT_CLIENT_EXIT.
134 if (out_client_)
135 snd_seq_close(out_client_);
136
137 // Wait for the event thread to stop.
138 event_thread_.Stop();
139
140 // Close the in client.
141 if (in_client_)
142 snd_seq_close(in_client_);
143
144 // Free the decoder.
145 snd_midi_event_free(decoder_);
agoodebd4be9b2015-03-16 19:17:25 -0700146}
147
toyoshim@chromium.org51c7f532014-05-01 17:17:32 +0000148void MidiManagerAlsa::StartInitialization() {
agoode@chromium.org25227512014-06-08 05:12:05 +0000149 // TODO(agoode): Move off I/O thread. See http://crbug.com/374341.
150
151 // Create client handles.
agoodef212b2a2015-03-19 12:53:23 -0700152 int err = snd_seq_open(&in_client_, kAlsaHw, SND_SEQ_OPEN_INPUT, 0);
agoode@chromium.org25227512014-06-08 05:12:05 +0000153 if (err != 0) {
154 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err);
155 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
156 }
157 int in_client_id = snd_seq_client_id(in_client_);
agoodef212b2a2015-03-19 12:53:23 -0700158 err = snd_seq_open(&out_client_, kAlsaHw, SND_SEQ_OPEN_OUTPUT, 0);
agoode@chromium.org25227512014-06-08 05:12:05 +0000159 if (err != 0) {
160 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err);
161 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
162 }
163 out_client_id_ = snd_seq_client_id(out_client_);
164
165 // Name the clients.
166 err = snd_seq_set_client_name(in_client_, "Chrome (input)");
167 if (err != 0) {
168 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err);
169 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
170 }
171 err = snd_seq_set_client_name(out_client_, "Chrome (output)");
172 if (err != 0) {
173 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err);
174 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
175 }
176
177 // Create input port.
178 in_port_ = snd_seq_create_simple_port(in_client_, NULL,
179 SND_SEQ_PORT_CAP_WRITE |
180 SND_SEQ_PORT_CAP_NO_EXPORT,
181 SND_SEQ_PORT_TYPE_MIDI_GENERIC |
182 SND_SEQ_PORT_TYPE_APPLICATION);
183 if (in_port_ < 0) {
184 VLOG(1) << "snd_seq_create_simple_port fails: " << snd_strerror(in_port_);
185 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
186 }
187
188 // Subscribe to the announce port.
189 snd_seq_port_subscribe_t* subs;
190 snd_seq_port_subscribe_alloca(&subs);
191 snd_seq_addr_t announce_sender;
192 snd_seq_addr_t announce_dest;
193 announce_sender.client = SND_SEQ_CLIENT_SYSTEM;
194 announce_sender.port = SND_SEQ_PORT_SYSTEM_ANNOUNCE;
195 announce_dest.client = in_client_id;
196 announce_dest.port = in_port_;
197 snd_seq_port_subscribe_set_sender(subs, &announce_sender);
198 snd_seq_port_subscribe_set_dest(subs, &announce_dest);
199 err = snd_seq_subscribe_port(in_client_, subs);
200 if (err != 0) {
201 VLOG(1) << "snd_seq_subscribe_port on the announce port fails: "
202 << snd_strerror(err);
203 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
204 }
205
agoodebd4be9b2015-03-16 19:17:25 -0700206 EnumeratePorts();
agoode@chromium.org25227512014-06-08 05:12:05 +0000207
208 event_thread_.Start();
209 event_thread_.message_loop()->PostTask(
210 FROM_HERE,
agoodebd4be9b2015-03-16 19:17:25 -0700211 base::Bind(&MidiManagerAlsa::ScheduleEventLoop, base::Unretained(this)));
agoode@chromium.org25227512014-06-08 05:12:05 +0000212
213 CompleteInitialization(MIDI_OK);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000214}
215
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000216void MidiManagerAlsa::DispatchSendMidiData(MidiManagerClient* client,
dnicoara@chromium.org9f2a6f02014-01-03 21:25:00 +0000217 uint32 port_index,
218 const std::vector<uint8>& data,
219 double timestamp) {
agoode@chromium.org25227512014-06-08 05:12:05 +0000220 if (out_ports_.size() <= port_index)
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000221 return;
222
agoode@chromium.org25227512014-06-08 05:12:05 +0000223 // Not correct right now. http://crbug.com/374341.
224 if (!send_thread_.IsRunning())
225 send_thread_.Start();
226
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000227 base::TimeDelta delay;
228 if (timestamp != 0.0) {
229 base::TimeTicks time_to_send =
230 base::TimeTicks() + base::TimeDelta::FromMicroseconds(
agoodebd4be9b2015-03-16 19:17:25 -0700231 timestamp * base::Time::kMicrosecondsPerSecond);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000232 delay = std::max(time_to_send - base::TimeTicks::Now(), base::TimeDelta());
233 }
234
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000235 send_thread_.message_loop()->PostDelayedTask(
agoodebd4be9b2015-03-16 19:17:25 -0700236 FROM_HERE, base::Bind(&MidiManagerAlsa::SendMidiData,
237 base::Unretained(this), port_index, data),
238 delay);
agoode@chromium.org25227512014-06-08 05:12:05 +0000239
240 // Acknowledge send.
241 send_thread_.message_loop()->PostTask(
agoodebd4be9b2015-03-16 19:17:25 -0700242 FROM_HERE, base::Bind(&MidiManagerClient::AccumulateMidiBytesSent,
243 base::Unretained(client), data.size()));
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000244}
245
agoodef212b2a2015-03-19 12:53:23 -0700246MidiManagerAlsa::AlsaRawmidi::AlsaRawmidi(const MidiManagerAlsa* outer,
247 const std::string& alsa_name,
248 const std::string& alsa_longname,
249 const std::string& alsa_driver,
250 int card_index)
251 : alsa_name_(alsa_name),
252 alsa_longname_(alsa_longname),
253 alsa_driver_(alsa_driver) {
agoodebd4be9b2015-03-16 19:17:25 -0700254 // Get udev properties if available.
agoodef212b2a2015-03-19 12:53:23 -0700255 std::string vendor;
256 std::string vendor_from_database;
agoodebd4be9b2015-03-16 19:17:25 -0700257
258#if defined(USE_UDEV)
259 const std::string sysname = base::StringPrintf("card%i", card_index);
260 device::ScopedUdevDevicePtr udev_device(
agoodef212b2a2015-03-19 12:53:23 -0700261 device::udev_device_new_from_subsystem_sysname(
262 outer->udev_.get(), kSoundClass, sysname.c_str()));
agoodebd4be9b2015-03-16 19:17:25 -0700263
agoodef212b2a2015-03-19 12:53:23 -0700264 // TODO(agoode): Move this to a new utility class in device/udev_linux?
265
266 // Try to get the vendor string. Sometimes it is encoded.
267 vendor = device::UdevDecodeString(
268 device::UdevDeviceGetPropertyValue(udev_device.get(), kIdVendorEnc));
269 // Sometimes it is not encoded.
270 if (vendor.empty())
agoodee83758c2015-03-23 22:07:54 -0700271 vendor = UdevDeviceGetPropertyOrSysattr(udev_device.get(), kIdVendor,
272 kSysattrVendorName);
agoodef212b2a2015-03-19 12:53:23 -0700273 // Also get the vendor string from the hardware database.
274 vendor_from_database = device::UdevDeviceGetPropertyValue(
275 udev_device.get(), kIdVendorFromDatabase);
276
277 // Get the device path.
278 path_ = device::UdevDeviceGetPropertyValue(udev_device.get(), kIdPath);
279
280 // Get the bus.
281 bus_ = device::UdevDeviceGetPropertyValue(udev_device.get(), kIdBus);
282
283 // Get the vendor id, by either property or sysattr.
284 vendor_id_ = UdevDeviceGetPropertyOrSysattr(udev_device.get(), kIdVendorId,
285 kSysattrVendor);
286
287 // Get the model id, by either property or sysattr.
288 model_id_ = UdevDeviceGetPropertyOrSysattr(udev_device.get(), kIdModelId,
289 kSysattrModel);
290
291 // Get the usb interface number.
292 usb_interface_num_ =
293 device::UdevDeviceGetPropertyValue(udev_device.get(), kUsbInterfaceNum);
agoodebd4be9b2015-03-16 19:17:25 -0700294#endif // defined(USE_UDEV)
295
agoodef212b2a2015-03-19 12:53:23 -0700296 manufacturer_ = ExtractManufacturerString(
297 vendor, vendor_id_, vendor_from_database, alsa_name, alsa_longname);
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000298}
299
agoodef212b2a2015-03-19 12:53:23 -0700300MidiManagerAlsa::AlsaRawmidi::~AlsaRawmidi() {
agoodebd4be9b2015-03-16 19:17:25 -0700301}
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000302
agoodef212b2a2015-03-19 12:53:23 -0700303const std::string MidiManagerAlsa::AlsaRawmidi::alsa_name() const {
agoodebd4be9b2015-03-16 19:17:25 -0700304 return alsa_name_;
305}
agoode@chromium.org25227512014-06-08 05:12:05 +0000306
agoodef212b2a2015-03-19 12:53:23 -0700307const std::string MidiManagerAlsa::AlsaRawmidi::alsa_longname() const {
308 return alsa_longname_;
309}
310
311const std::string MidiManagerAlsa::AlsaRawmidi::manufacturer() const {
agoodebd4be9b2015-03-16 19:17:25 -0700312 return manufacturer_;
313}
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000314
agoodef212b2a2015-03-19 12:53:23 -0700315const std::string MidiManagerAlsa::AlsaRawmidi::alsa_driver() const {
agoodebd4be9b2015-03-16 19:17:25 -0700316 return alsa_driver_;
317}
318
agoodef212b2a2015-03-19 12:53:23 -0700319const std::string MidiManagerAlsa::AlsaRawmidi::path() const {
320 return path_;
agoodebd4be9b2015-03-16 19:17:25 -0700321}
322
agoodef212b2a2015-03-19 12:53:23 -0700323const std::string MidiManagerAlsa::AlsaRawmidi::bus() const {
324 return bus_;
325}
326
327const std::string MidiManagerAlsa::AlsaRawmidi::vendor_id() const {
328 return vendor_id_;
329}
330
331const std::string MidiManagerAlsa::AlsaRawmidi::id() const {
332 std::string id = vendor_id_;
333 if (!model_id_.empty())
334 id += ":" + model_id_;
335 if (!usb_interface_num_.empty())
336 id += ":" + usb_interface_num_;
337 return id;
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000338}
339
agoode55a8b522015-03-08 12:40:17 -0700340// static
agoodef212b2a2015-03-19 12:53:23 -0700341std::string MidiManagerAlsa::AlsaRawmidi::ExtractManufacturerString(
agoode5e4e9cd2015-03-09 12:34:24 -0700342 const std::string& udev_id_vendor,
agoode55a8b522015-03-08 12:40:17 -0700343 const std::string& udev_id_vendor_id,
344 const std::string& udev_id_vendor_from_database,
345 const std::string& alsa_name,
346 const std::string& alsa_longname) {
347 // Let's try to determine the manufacturer. Here is the ordered preference
348 // in extraction:
agoodef212b2a2015-03-19 12:53:23 -0700349 // 1. Vendor name from the hardware device string, from udev properties
350 // or sysattrs.
agoode5e4e9cd2015-03-09 12:34:24 -0700351 // 2. Vendor name from the udev database (property ID_VENDOR_FROM_DATABASE).
agoode55a8b522015-03-08 12:40:17 -0700352 // 3. Heuristic from ALSA.
353
agoodee83758c2015-03-23 22:07:54 -0700354 // Is the vendor string present and not just the vendor hex id?
355 if (!udev_id_vendor.empty() && (udev_id_vendor != udev_id_vendor_id)) {
agoode55a8b522015-03-08 12:40:17 -0700356 return udev_id_vendor;
357 }
358
359 // Is there a vendor string in the hardware database?
360 if (!udev_id_vendor_from_database.empty()) {
361 return udev_id_vendor_from_database;
362 }
363
364 // Ok, udev gave us nothing useful, or was unavailable. So try a heuristic.
365 // We assume that card longname is in the format of
366 // "<manufacturer> <name> at <bus>". Otherwise, we give up to detect
367 // a manufacturer name here.
368 size_t at_index = alsa_longname.rfind(" at ");
agoodef212b2a2015-03-19 12:53:23 -0700369 if (at_index && at_index != std::string::npos) {
agoode55a8b522015-03-08 12:40:17 -0700370 size_t name_index = alsa_longname.rfind(alsa_name, at_index - 1);
agoodef212b2a2015-03-19 12:53:23 -0700371 if (name_index && name_index != std::string::npos)
agoode55a8b522015-03-08 12:40:17 -0700372 return alsa_longname.substr(0, name_index - 1);
373 }
374
375 // Failure.
376 return "";
377}
378
agoodef212b2a2015-03-19 12:53:23 -0700379MidiManagerAlsa::AlsaPortMetadata::AlsaPortMetadata(
380 const std::string& path,
381 const std::string& bus,
382 const std::string& id,
383 const snd_seq_addr_t* address,
384 const std::string& client_name,
385 const std::string& port_name,
386 const std::string& card_name,
387 const std::string& card_longname,
388 Type type)
389 : path_(path),
390 bus_(bus),
391 id_(id),
392 client_addr_(address->client),
393 port_addr_(address->port),
394 client_name_(client_name),
395 port_name_(port_name),
396 card_name_(card_name),
397 card_longname_(card_longname),
398 type_(type) {
399}
400
401MidiManagerAlsa::AlsaPortMetadata::~AlsaPortMetadata() {
402}
403
404scoped_ptr<base::Value> MidiManagerAlsa::AlsaPortMetadata::Value() const {
agoode8caab0b2015-03-23 18:48:02 -0700405 scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue);
406 SetStringIfNonEmpty(value.get(), "path", path_);
407 SetStringIfNonEmpty(value.get(), "bus", bus_);
408 SetStringIfNonEmpty(value.get(), "id", id_);
agoodef212b2a2015-03-19 12:53:23 -0700409 value->SetInteger("clientAddr", client_addr_);
410 value->SetInteger("portAddr", port_addr_);
agoode8caab0b2015-03-23 18:48:02 -0700411 SetStringIfNonEmpty(value.get(), "clientName", client_name_);
412 SetStringIfNonEmpty(value.get(), "portName", port_name_);
413 SetStringIfNonEmpty(value.get(), "cardName", card_name_);
414 SetStringIfNonEmpty(value.get(), "cardLongname", card_longname_);
agoodef212b2a2015-03-19 12:53:23 -0700415 std::string type;
416 switch (type_) {
417 case Type::kInput:
418 type = "input";
419 break;
420
421 case Type::kOutput:
422 type = "output";
423 break;
424 }
agoode8caab0b2015-03-23 18:48:02 -0700425 SetStringIfNonEmpty(value.get(), "type", type);
agoodef212b2a2015-03-19 12:53:23 -0700426
agoode8caab0b2015-03-23 18:48:02 -0700427 return value.Pass();
agoodef212b2a2015-03-19 12:53:23 -0700428}
429
430std::string MidiManagerAlsa::AlsaPortMetadata::JSONValue() const {
431 std::string json;
432 JSONStringValueSerializer serializer(&json);
433 serializer.Serialize(*Value().get());
434 return json;
435}
436
437// TODO(agoode): Do not use SHA256 here. Instead store a persistent
438// mapping and just use a UUID or other random string.
439// http://crbug.com/465320
440std::string MidiManagerAlsa::AlsaPortMetadata::OpaqueKey() const {
441 uint8 hash[crypto::kSHA256Length];
442 crypto::SHA256HashString(JSONValue(), &hash, sizeof(hash));
443 return base::HexEncode(&hash, sizeof(hash));
444}
445
446// TODO(agoode): Add a client->card/rawmidi mapping to the kernel to avoid
447// needing to probe in this way.
448ScopedVector<MidiManagerAlsa::AlsaRawmidi> MidiManagerAlsa::AllAlsaRawmidis() {
449 ScopedVector<AlsaRawmidi> devices;
agoodebd4be9b2015-03-16 19:17:25 -0700450 snd_ctl_card_info_t* card;
agoodee83758c2015-03-23 22:07:54 -0700451 snd_hwdep_info_t* hwdep;
agoodebd4be9b2015-03-16 19:17:25 -0700452 snd_ctl_card_info_alloca(&card);
agoodee83758c2015-03-23 22:07:54 -0700453 snd_hwdep_info_alloca(&hwdep);
agoodebd4be9b2015-03-16 19:17:25 -0700454 for (int card_index = -1; !snd_card_next(&card_index) && card_index >= 0;) {
455 const std::string id = base::StringPrintf("hw:CARD=%i", card_index);
456 snd_ctl_t* handle;
457 int err = snd_ctl_open(&handle, id.c_str(), 0);
458 if (err != 0) {
459 VLOG(1) << "snd_ctl_open fails: " << snd_strerror(err);
460 continue;
461 }
462 err = snd_ctl_card_info(handle, card);
463 if (err != 0) {
464 VLOG(1) << "snd_ctl_card_info fails: " << snd_strerror(err);
465 snd_ctl_close(handle);
466 continue;
467 }
agoodee83758c2015-03-23 22:07:54 -0700468 std::string name = snd_ctl_card_info_get_name(card);
469 std::string longname = snd_ctl_card_info_get_longname(card);
470 std::string driver = snd_ctl_card_info_get_driver(card);
471
472 // Count rawmidi devices (not subdevices).
agoodebd4be9b2015-03-16 19:17:25 -0700473 for (int device = -1;
474 !snd_ctl_rawmidi_next_device(handle, &device) && device >= 0;) {
agoodee83758c2015-03-23 22:07:54 -0700475 devices.push_back(
476 new AlsaRawmidi(this, name, longname, driver, card_index));
477 }
agoodebd4be9b2015-03-16 19:17:25 -0700478
agoodee83758c2015-03-23 22:07:54 -0700479 // Count any hwdep synths that become MIDI devices.
480 //
481 // Explanation:
482 // Any kernel driver can create an ALSA client (visible to us).
483 // With modern hardware, only rawmidi devices do this. Kernel
484 // drivers create rawmidi devices and the rawmidi subsystem makes
485 // the seq clients. But the OPL3 driver is special, it does not
486 // make a rawmidi device but a seq client directly. (This is the
487 // only one to worry about in the kernel code, as of 2015-03-23.)
488 //
489 // OPL3 is very old (but still possible to get in new
490 // hardware). It is unlikely that new drivers would not use
491 // rawmidi and defeat our heuristic.
492 //
493 // Longer term, support should be added in the kernel to expose a
494 // direct link from card->client (or client->card) so that all
495 // these heuristics will be obsolete. Once that is there, we can
496 // assume our old heuristics will work on old kernels and the new
497 // robust code will be used on new. Then we will not need to worry
498 // about changes to kernel internals breaking our code.
499 // See the TODO above at kMinimumClientIdForCards.
500 for (int device = -1;
501 !snd_ctl_hwdep_next_device(handle, &device) && device >= 0;) {
502 snd_ctl_hwdep_info(handle, hwdep);
503 snd_hwdep_iface_t iface = snd_hwdep_info_get_iface(hwdep);
504 if (iface == SND_HWDEP_IFACE_OPL2 || iface == SND_HWDEP_IFACE_OPL3 ||
505 iface == SND_HWDEP_IFACE_OPL4)
506 devices.push_back(
507 new AlsaRawmidi(this, name, longname, driver, card_index));
agoodebd4be9b2015-03-16 19:17:25 -0700508 }
509 snd_ctl_close(handle);
510 }
511
512 return devices.Pass();
513}
514
515void MidiManagerAlsa::EnumeratePorts() {
agoodef212b2a2015-03-19 12:53:23 -0700516 ScopedVector<AlsaRawmidi> devices = AllAlsaRawmidis();
agoodebd4be9b2015-03-16 19:17:25 -0700517
518 snd_seq_port_subscribe_t* subs;
519 snd_seq_port_subscribe_alloca(&subs);
520
521 int in_client_id = snd_seq_client_id(in_client_);
522
523 // Enumerate all clients.
524 snd_seq_client_info_t* client_info;
525 snd_seq_client_info_alloca(&client_info);
526 snd_seq_port_info_t* port_info;
527 snd_seq_port_info_alloca(&port_info);
528
529 // Enumerate clients.
530 snd_seq_client_info_set_client(client_info, -1);
531 uint32 current_input = 0;
532 unsigned int current_device = 0;
533 while (!snd_seq_query_next_client(in_client_, client_info)) {
534 int client_id = snd_seq_client_info_get_client(client_info);
535 if ((client_id == in_client_id) || (client_id == out_client_id_)) {
536 // Skip our own clients.
537 continue;
538 }
539
540 // Get client metadata.
541 const std::string client_name = snd_seq_client_info_get_name(client_info);
542 snd_seq_port_info_set_client(port_info, client_id);
543 snd_seq_port_info_set_port(port_info, -1);
544
545 std::string manufacturer;
546 std::string driver;
agoodef212b2a2015-03-19 12:53:23 -0700547 std::string path;
548 std::string bus;
549 std::string vendor_id;
550 std::string id;
551 std::string card_name;
552 std::string card_longname;
agoodebd4be9b2015-03-16 19:17:25 -0700553
agoodef212b2a2015-03-19 12:53:23 -0700554 // Join kernel clients against the list of AlsaRawmidis.
agoodebd4be9b2015-03-16 19:17:25 -0700555 // In the current ALSA kernel implementation, kernel clients match the
556 // kernel devices in the same order, for devices with client_id over
557 // kMinimumClientIdForCards.
558 if ((snd_seq_client_info_get_type(client_info) == SND_SEQ_KERNEL_CLIENT) &&
559 (current_device < devices.size()) &&
560 (client_id >= kMinimumClientIdForCards)) {
agoodef212b2a2015-03-19 12:53:23 -0700561 const AlsaRawmidi* device = devices[current_device];
agoodebd4be9b2015-03-16 19:17:25 -0700562 manufacturer = device->manufacturer();
563 driver = device->alsa_driver();
agoodef212b2a2015-03-19 12:53:23 -0700564 path = device->path();
565 bus = device->bus();
566 vendor_id = device->vendor_id();
567 id = device->id();
568 card_name = device->alsa_name();
569 card_longname = device->alsa_longname();
agoodebd4be9b2015-03-16 19:17:25 -0700570 current_device++;
571 }
572 // Enumerate ports.
573 while (!snd_seq_query_next_port(in_client_, port_info)) {
574 unsigned int port_type = snd_seq_port_info_get_type(port_info);
575 if (port_type & SND_SEQ_PORT_TYPE_MIDI_GENERIC) {
576 const snd_seq_addr_t* addr = snd_seq_port_info_get_addr(port_info);
577 const std::string name = snd_seq_port_info_get_name(port_info);
agoodebd4be9b2015-03-16 19:17:25 -0700578 std::string version;
579 if (!driver.empty()) {
580 version = driver + " / ";
581 }
582 version += base::StringPrintf("ALSA library version %d.%d.%d",
583 SND_LIB_MAJOR,
584 SND_LIB_MINOR,
585 SND_LIB_SUBMINOR);
586 unsigned int caps = snd_seq_port_info_get_capability(port_info);
587 if ((caps & kRequiredInputPortCaps) == kRequiredInputPortCaps) {
588 // Subscribe to this port.
589 const snd_seq_addr_t* sender = snd_seq_port_info_get_addr(port_info);
590 snd_seq_addr_t dest;
591 dest.client = snd_seq_client_id(in_client_);
592 dest.port = in_port_;
593 snd_seq_port_subscribe_set_sender(subs, sender);
594 snd_seq_port_subscribe_set_dest(subs, &dest);
595 int err = snd_seq_subscribe_port(in_client_, subs);
596 if (err != 0) {
597 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
598 } else {
599 source_map_[AddrToInt(sender)] = current_input++;
agoodef212b2a2015-03-19 12:53:23 -0700600 const AlsaPortMetadata metadata(path, bus, id, addr, client_name,
601 name, card_name, card_longname,
602 AlsaPortMetadata::Type::kInput);
603 const std::string id = metadata.OpaqueKey();
604 AddInputPort(MidiPortInfo(id.c_str(), manufacturer, name, version,
605 MIDI_PORT_OPENED));
agoodebd4be9b2015-03-16 19:17:25 -0700606 }
607 }
608 if ((caps & kRequiredOutputPortCaps) == kRequiredOutputPortCaps) {
609 // Create a port for us to send on.
610 int out_port =
611 snd_seq_create_simple_port(out_client_, NULL,
612 SND_SEQ_PORT_CAP_READ |
613 SND_SEQ_PORT_CAP_NO_EXPORT,
614 SND_SEQ_PORT_TYPE_MIDI_GENERIC |
615 SND_SEQ_PORT_TYPE_APPLICATION);
616 if (out_port < 0) {
617 VLOG(1) << "snd_seq_create_simple_port fails: "
618 << snd_strerror(out_port);
619 // Skip this output port for now.
620 continue;
621 }
622
623 // Activate port subscription.
624 snd_seq_addr_t sender;
625 const snd_seq_addr_t* dest = snd_seq_port_info_get_addr(port_info);
626 sender.client = snd_seq_client_id(out_client_);
627 sender.port = out_port;
628 snd_seq_port_subscribe_set_sender(subs, &sender);
629 snd_seq_port_subscribe_set_dest(subs, dest);
630 int err = snd_seq_subscribe_port(out_client_, subs);
631 if (err != 0) {
632 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
633 snd_seq_delete_simple_port(out_client_, out_port);
634 } else {
agoodebd4be9b2015-03-16 19:17:25 -0700635 out_ports_.push_back(out_port);
agoodef212b2a2015-03-19 12:53:23 -0700636 const AlsaPortMetadata metadata(path, bus, id, addr, client_name,
637 name, card_name, card_longname,
638 AlsaPortMetadata::Type::kOutput);
639 const std::string id = metadata.OpaqueKey();
640 AddOutputPort(MidiPortInfo(id.c_str(), manufacturer, name, version,
641 MIDI_PORT_OPENED));
agoodebd4be9b2015-03-16 19:17:25 -0700642 }
643 }
644 }
645 }
646 }
647}
648
649void MidiManagerAlsa::SendMidiData(uint32 port_index,
650 const std::vector<uint8>& data) {
651 DCHECK(send_thread_.message_loop_proxy()->BelongsToCurrentThread());
652
agoodef212b2a2015-03-19 12:53:23 -0700653 snd_midi_event_t* encoder;
654 snd_midi_event_new(kSendBufferSize, &encoder);
agoodebd4be9b2015-03-16 19:17:25 -0700655 for (unsigned int i = 0; i < data.size(); i++) {
656 snd_seq_event_t event;
657 int result = snd_midi_event_encode_byte(encoder, data[i], &event);
658 if (result == 1) {
659 // Full event, send it.
660 snd_seq_ev_set_source(&event, out_ports_[port_index]);
661 snd_seq_ev_set_subs(&event);
662 snd_seq_ev_set_direct(&event);
663 snd_seq_event_output_direct(out_client_, &event);
664 }
665 }
agoodef212b2a2015-03-19 12:53:23 -0700666 snd_midi_event_free(encoder);
agoodebd4be9b2015-03-16 19:17:25 -0700667}
668
669void MidiManagerAlsa::ScheduleEventLoop() {
670 event_thread_.message_loop()->PostTask(
671 FROM_HERE,
672 base::Bind(&MidiManagerAlsa::EventLoop, base::Unretained(this)));
673}
674
675void MidiManagerAlsa::EventLoop() {
676 // Read available incoming MIDI data.
677 snd_seq_event_t* event;
678 int err = snd_seq_event_input(in_client_, &event);
679 double timestamp = (base::TimeTicks::Now() - base::TimeTicks()).InSecondsF();
680
681 // Handle errors.
682 if (err == -ENOSPC) {
683 VLOG(1) << "snd_seq_event_input detected buffer overrun";
684 // We've lost events: check another way to see if we need to shut down.
685 base::AutoLock lock(shutdown_lock_);
686 if (!event_thread_shutdown_)
687 ScheduleEventLoop();
688 return;
689 } else if (err < 0) {
690 VLOG(1) << "snd_seq_event_input fails: " << snd_strerror(err);
691 // TODO(agoode): Use RecordAction() or similar to log this.
692 return;
693 }
694
695 // Handle announce events.
696 if (event->source.client == SND_SEQ_CLIENT_SYSTEM &&
697 event->source.port == SND_SEQ_PORT_SYSTEM_ANNOUNCE) {
698 switch (event->type) {
699 case SND_SEQ_EVENT_CLIENT_START:
700 // TODO(agoode): rescan hardware devices.
701 break;
702
703 case SND_SEQ_EVENT_CLIENT_EXIT:
704 // Check for disconnection of our "out" client. This means "shut down".
705 if (event->data.addr.client == out_client_id_)
706 return;
707
708 // TODO(agoode): remove all ports for a client.
709 break;
710
711 case SND_SEQ_EVENT_PORT_START:
712 // TODO(agoode): add port.
713 break;
714
715 case SND_SEQ_EVENT_PORT_EXIT:
716 // TODO(agoode): remove port.
717 break;
718 }
719 }
720
721 ProcessSingleEvent(event, timestamp);
722
723 // Do again.
724 ScheduleEventLoop();
725}
726
727void MidiManagerAlsa::ProcessSingleEvent(snd_seq_event_t* event,
728 double timestamp) {
729 std::map<int, uint32>::iterator source_it =
730 source_map_.find(AddrToInt(&event->source));
731 if (source_it != source_map_.end()) {
732 uint32 source = source_it->second;
733 if (event->type == SND_SEQ_EVENT_SYSEX) {
734 // Special! Variable-length sysex.
735 ReceiveMidiData(source, static_cast<const uint8*>(event->data.ext.ptr),
736 event->data.ext.len, timestamp);
737 } else {
738 // Otherwise, decode this and send that on.
739 unsigned char buf[12];
740 long count = snd_midi_event_decode(decoder_, buf, sizeof(buf), event);
741 if (count <= 0) {
742 if (count != -ENOENT) {
743 // ENOENT means that it's not a MIDI message, which is not an
744 // error, but other negative values are errors for us.
745 VLOG(1) << "snd_midi_event_decoder fails " << snd_strerror(count);
746 // TODO(agoode): Record this failure.
747 }
748 } else {
749 ReceiveMidiData(source, buf, count, timestamp);
750 }
751 }
752 }
753}
754
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000755MidiManager* MidiManager::Create() {
756 return new MidiManagerAlsa();
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000757}
758
759} // namespace media