blob: 477f22036ceb7e8d9ec34883aecacb6b47d5d434 [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"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000013#include "base/logging.h"
14#include "base/memory/ref_counted.h"
agoode@chromium.org25227512014-06-08 05:12:05 +000015#include "base/memory/scoped_vector.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000016#include "base/message_loop/message_loop.h"
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000017#include "base/posix/eintr_wrapper.h"
agoode55a8b522015-03-08 12:40:17 -070018#include "base/strings/string_util.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000019#include "base/strings/stringprintf.h"
20#include "base/threading/thread.h"
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000021#include "base/time/time.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000022#include "media/midi/midi_port_info.h"
23
24namespace media {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000025
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000026namespace {
27
agoode@chromium.org25227512014-06-08 05:12:05 +000028// Per-output buffer. This can be smaller, but then large sysex messages
29// will be (harmlessly) split across multiple seq events. This should
30// not have any real practical effect, except perhaps to slightly reorder
31// realtime messages with respect to sysex.
32const size_t kSendBufferSize = 256;
33
34// Constants for the capabilities we search for in inputs and outputs.
35// See http://www.alsa-project.org/alsa-doc/alsa-lib/seq.html.
36const unsigned int kRequiredInputPortCaps =
37 SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ;
38const unsigned int kRequiredOutputPortCaps =
39 SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE;
40
41int AddrToInt(const snd_seq_addr_t* addr) {
42 return (addr->client << 8) | addr->port;
43}
44
agoode55a8b522015-03-08 12:40:17 -070045#if defined(USE_UDEV)
46// Copied from components/storage_monitor/udev_util_linux.cc.
47// TODO(agoode): Move this into a common place. Maybe device/udev_linux?
48std::string GetUdevDevicePropertyValue(udev_device* udev_device,
49 const char* key) {
50 const char* value = device::udev_device_get_property_value(udev_device, key);
51 return value ? value : std::string();
52}
53#endif // defined(USE_UDEV)
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000054
55} // namespace
56
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +000057MidiManagerAlsa::MidiManagerAlsa()
agoode@chromium.org25227512014-06-08 05:12:05 +000058 : in_client_(NULL),
59 out_client_(NULL),
60 out_client_id_(-1),
61 in_port_(-1),
62 decoder_(NULL),
agoode55a8b522015-03-08 12:40:17 -070063#if defined(USE_UDEV)
64 udev_(device::udev_new()),
65#endif // defined(USE_UDEV)
agoode@chromium.org25227512014-06-08 05:12:05 +000066 send_thread_("MidiSendThread"),
67 event_thread_("MidiEventThread"),
68 event_thread_shutdown_(false) {
69 // Initialize decoder.
70 snd_midi_event_new(0, &decoder_);
71 snd_midi_event_no_status(decoder_, 1);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000072}
73
toyoshim@chromium.org51c7f532014-05-01 17:17:32 +000074void MidiManagerAlsa::StartInitialization() {
agoode@chromium.org25227512014-06-08 05:12:05 +000075 // TODO(agoode): Move off I/O thread. See http://crbug.com/374341.
76
77 // Create client handles.
78 int err = snd_seq_open(&in_client_, "hw", SND_SEQ_OPEN_INPUT, 0);
79 if (err != 0) {
80 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err);
81 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
82 }
83 int in_client_id = snd_seq_client_id(in_client_);
84 err = snd_seq_open(&out_client_, "hw", SND_SEQ_OPEN_OUTPUT, 0);
85 if (err != 0) {
86 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err);
87 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
88 }
89 out_client_id_ = snd_seq_client_id(out_client_);
90
91 // Name the clients.
92 err = snd_seq_set_client_name(in_client_, "Chrome (input)");
93 if (err != 0) {
94 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err);
95 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
96 }
97 err = snd_seq_set_client_name(out_client_, "Chrome (output)");
98 if (err != 0) {
99 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err);
100 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
101 }
102
103 // Create input port.
104 in_port_ = snd_seq_create_simple_port(in_client_, NULL,
105 SND_SEQ_PORT_CAP_WRITE |
106 SND_SEQ_PORT_CAP_NO_EXPORT,
107 SND_SEQ_PORT_TYPE_MIDI_GENERIC |
108 SND_SEQ_PORT_TYPE_APPLICATION);
109 if (in_port_ < 0) {
110 VLOG(1) << "snd_seq_create_simple_port fails: " << snd_strerror(in_port_);
111 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
112 }
113
114 // Subscribe to the announce port.
115 snd_seq_port_subscribe_t* subs;
116 snd_seq_port_subscribe_alloca(&subs);
117 snd_seq_addr_t announce_sender;
118 snd_seq_addr_t announce_dest;
119 announce_sender.client = SND_SEQ_CLIENT_SYSTEM;
120 announce_sender.port = SND_SEQ_PORT_SYSTEM_ANNOUNCE;
121 announce_dest.client = in_client_id;
122 announce_dest.port = in_port_;
123 snd_seq_port_subscribe_set_sender(subs, &announce_sender);
124 snd_seq_port_subscribe_set_dest(subs, &announce_dest);
125 err = snd_seq_subscribe_port(in_client_, subs);
126 if (err != 0) {
127 VLOG(1) << "snd_seq_subscribe_port on the announce port fails: "
128 << snd_strerror(err);
129 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
130 }
131
agoode55a8b522015-03-08 12:40:17 -0700132 // Extract the list of manufacturers for the hardware MIDI
agoode@chromium.org25227512014-06-08 05:12:05 +0000133 // devices. This won't work for all devices. It is also brittle until
agoode55a8b522015-03-08 12:40:17 -0700134 // hotplug is implemented. (See http://crbug.com/431489.)
agoode@chromium.org25227512014-06-08 05:12:05 +0000135 ScopedVector<CardInfo> cards;
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000136 snd_ctl_card_info_t* card;
137 snd_rawmidi_info_t* midi_out;
138 snd_rawmidi_info_t* midi_in;
139 snd_ctl_card_info_alloca(&card);
140 snd_rawmidi_info_alloca(&midi_out);
141 snd_rawmidi_info_alloca(&midi_in);
agoode55a8b522015-03-08 12:40:17 -0700142 for (int card_index = -1; !snd_card_next(&card_index) && card_index >= 0; ) {
143 const std::string id = base::StringPrintf("hw:CARD=%i", card_index);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000144 snd_ctl_t* handle;
145 int err = snd_ctl_open(&handle, id.c_str(), 0);
146 if (err != 0) {
toyoshim@chromium.org2b058e82014-02-26 06:10:46 +0000147 VLOG(1) << "snd_ctl_open fails: " << snd_strerror(err);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000148 continue;
149 }
150 err = snd_ctl_card_info(handle, card);
151 if (err != 0) {
toyoshim@chromium.org2b058e82014-02-26 06:10:46 +0000152 VLOG(1) << "snd_ctl_card_info fails: " << snd_strerror(err);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000153 snd_ctl_close(handle);
154 continue;
155 }
agoode@chromium.org25227512014-06-08 05:12:05 +0000156 // Enumerate any rawmidi devices (not subdevices) and extract CardInfo.
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000157 for (int device = -1;
agoode@chromium.org25227512014-06-08 05:12:05 +0000158 !snd_ctl_rawmidi_next_device(handle, &device) && device >= 0; ) {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000159 bool output;
160 bool input;
161 snd_rawmidi_info_set_device(midi_out, device);
162 snd_rawmidi_info_set_subdevice(midi_out, 0);
163 snd_rawmidi_info_set_stream(midi_out, SND_RAWMIDI_STREAM_OUTPUT);
164 output = snd_ctl_rawmidi_info(handle, midi_out) == 0;
165 snd_rawmidi_info_set_device(midi_in, device);
166 snd_rawmidi_info_set_subdevice(midi_in, 0);
167 snd_rawmidi_info_set_stream(midi_in, SND_RAWMIDI_STREAM_INPUT);
168 input = snd_ctl_rawmidi_info(handle, midi_in) == 0;
169 if (!output && !input)
170 continue;
agoode@chromium.org25227512014-06-08 05:12:05 +0000171
agoode55a8b522015-03-08 12:40:17 -0700172 // Compute and save Alsa and udev properties.
agoode@chromium.org25227512014-06-08 05:12:05 +0000173 snd_rawmidi_info_t* midi = midi_out ? midi_out : midi_in;
agoode55a8b522015-03-08 12:40:17 -0700174 cards.push_back(new CardInfo(
175 this,
176 snd_rawmidi_info_get_name(midi),
177 snd_ctl_card_info_get_longname(card),
178 snd_ctl_card_info_get_driver(card),
179 card_index));
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000180 }
sudarsana.nagineni3f6e7e62014-09-01 07:52:42 -0700181 snd_ctl_close(handle);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000182 }
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000183
agoode@chromium.org25227512014-06-08 05:12:05 +0000184 // Enumerate all ports in all clients.
185 snd_seq_client_info_t* client_info;
186 snd_seq_client_info_alloca(&client_info);
187 snd_seq_port_info_t* port_info;
188 snd_seq_port_info_alloca(&port_info);
189
190 snd_seq_client_info_set_client(client_info, -1);
191 // Enumerate clients.
192 uint32 current_input = 0;
193 unsigned int current_card = 0;
194 while (!snd_seq_query_next_client(in_client_, client_info)) {
195 int client_id = snd_seq_client_info_get_client(client_info);
196 if ((client_id == in_client_id) || (client_id == out_client_id_)) {
197 // Skip our own clients.
198 continue;
199 }
200 const std::string client_name = snd_seq_client_info_get_name(client_info);
201 snd_seq_port_info_set_client(port_info, client_id);
202 snd_seq_port_info_set_port(port_info, -1);
203
204 std::string manufacturer;
205 std::string driver;
206 // In the current Alsa kernel implementation, hardware clients match the
207 // cards in the same order.
208 if ((snd_seq_client_info_get_type(client_info) == SND_SEQ_KERNEL_CLIENT) &&
209 (current_card < cards.size())) {
210 const CardInfo* info = cards[current_card];
agoode55a8b522015-03-08 12:40:17 -0700211 if (info->alsa_name() == client_name) {
212 manufacturer = info->manufacturer();
213 driver = info->alsa_driver();
agoode@chromium.org25227512014-06-08 05:12:05 +0000214 current_card++;
215 }
216 }
217 // Enumerate ports.
218 while (!snd_seq_query_next_port(in_client_, port_info)) {
219 unsigned int port_type = snd_seq_port_info_get_type(port_info);
220 if (port_type & SND_SEQ_PORT_TYPE_MIDI_GENERIC) {
221 const snd_seq_addr_t* addr = snd_seq_port_info_get_addr(port_info);
222 const std::string name = snd_seq_port_info_get_name(port_info);
223 const std::string id = base::StringPrintf("%d:%d %s",
224 addr->client,
225 addr->port,
226 name.c_str());
227 std::string version;
thestig31a7f142015-01-09 22:04:33 -0800228 if (!driver.empty()) {
agoode@chromium.org25227512014-06-08 05:12:05 +0000229 version = driver + " / ";
230 }
231 version += base::StringPrintf("ALSA library version %d.%d.%d",
232 SND_LIB_MAJOR,
233 SND_LIB_MINOR,
234 SND_LIB_SUBMINOR);
235 unsigned int caps = snd_seq_port_info_get_capability(port_info);
236 if ((caps & kRequiredInputPortCaps) == kRequiredInputPortCaps) {
237 // Subscribe to this port.
238 const snd_seq_addr_t* sender = snd_seq_port_info_get_addr(port_info);
239 snd_seq_addr_t dest;
240 dest.client = snd_seq_client_id(in_client_);
241 dest.port = in_port_;
242 snd_seq_port_subscribe_set_sender(subs, sender);
243 snd_seq_port_subscribe_set_dest(subs, &dest);
244 err = snd_seq_subscribe_port(in_client_, subs);
245 if (err != 0) {
246 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
247 } else {
248 source_map_[AddrToInt(sender)] = current_input++;
toyoshim5c6fe4b2015-02-18 23:28:09 -0800249 AddInputPort(MidiPortInfo(
250 id, manufacturer, name, version, MIDI_PORT_OPENED));
agoode@chromium.org25227512014-06-08 05:12:05 +0000251 }
252 }
253 if ((caps & kRequiredOutputPortCaps) == kRequiredOutputPortCaps) {
254 // Create a port for us to send on.
255 int out_port =
256 snd_seq_create_simple_port(out_client_, NULL,
257 SND_SEQ_PORT_CAP_READ |
258 SND_SEQ_PORT_CAP_NO_EXPORT,
259 SND_SEQ_PORT_TYPE_MIDI_GENERIC |
260 SND_SEQ_PORT_TYPE_APPLICATION);
261 if (out_port < 0) {
262 VLOG(1) << "snd_seq_create_simple_port fails: "
263 << snd_strerror(out_port);
264 // Skip this output port for now.
265 continue;
266 }
267
268 // Activate port subscription.
269 snd_seq_addr_t sender;
270 const snd_seq_addr_t* dest = snd_seq_port_info_get_addr(port_info);
271 sender.client = snd_seq_client_id(out_client_);
272 sender.port = out_port;
273 snd_seq_port_subscribe_set_sender(subs, &sender);
274 snd_seq_port_subscribe_set_dest(subs, dest);
275 err = snd_seq_subscribe_port(out_client_, subs);
276 if (err != 0) {
277 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
278 snd_seq_delete_simple_port(out_client_, out_port);
279 } else {
280 snd_midi_event_t* encoder;
281 snd_midi_event_new(kSendBufferSize, &encoder);
282 encoders_.push_back(encoder);
283 out_ports_.push_back(out_port);
toyoshim5c6fe4b2015-02-18 23:28:09 -0800284 AddOutputPort(MidiPortInfo(
285 id, manufacturer, name, version, MIDI_PORT_OPENED));
agoode@chromium.org25227512014-06-08 05:12:05 +0000286 }
287 }
288 }
289 }
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000290 }
agoode@chromium.org25227512014-06-08 05:12:05 +0000291
292 event_thread_.Start();
293 event_thread_.message_loop()->PostTask(
294 FROM_HERE,
295 base::Bind(&MidiManagerAlsa::EventReset, base::Unretained(this)));
296
297 CompleteInitialization(MIDI_OK);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000298}
299
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000300MidiManagerAlsa::~MidiManagerAlsa() {
agoode@chromium.org25227512014-06-08 05:12:05 +0000301 // Tell the event thread it will soon be time to shut down. This gives
302 // us assurance the thread will stop in case the SND_SEQ_EVENT_CLIENT_EXIT
303 // message is lost.
304 {
305 base::AutoLock lock(shutdown_lock_);
306 event_thread_shutdown_ = true;
307 }
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000308
agoode@chromium.org25227512014-06-08 05:12:05 +0000309 // Stop the send thread.
310 send_thread_.Stop();
311
312 // Close the out client. This will trigger the event thread to stop,
313 // because of SND_SEQ_EVENT_CLIENT_EXIT.
314 if (out_client_)
315 snd_seq_close(out_client_);
316
317 // Wait for the event thread to stop.
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000318 event_thread_.Stop();
319
agoode@chromium.org25227512014-06-08 05:12:05 +0000320 // Close the in client.
321 if (in_client_)
322 snd_seq_close(in_client_);
323
324 // Free the decoder.
325 snd_midi_event_free(decoder_);
326
327 // Free the encoders.
328 for (EncoderList::iterator i = encoders_.begin(); i != encoders_.end(); ++i)
329 snd_midi_event_free(*i);
330}
331
agoode55a8b522015-03-08 12:40:17 -0700332MidiManagerAlsa::CardInfo::CardInfo(
333 const MidiManagerAlsa* outer,
334 const std::string& alsa_name, const std::string& alsa_longname,
335 const std::string& alsa_driver, int card_index)
336 : alsa_name_(alsa_name), alsa_driver_(alsa_driver) {
337 // Get udev properties if available.
338 std::string udev_id_vendor_enc;
339 std::string udev_id_vendor_id;
340 std::string udev_id_vendor_from_database;
341
342#if defined(USE_UDEV)
343 const std::string sysname = base::StringPrintf("card%i", card_index);
344 device::ScopedUdevDevicePtr udev_device(
345 device::udev_device_new_from_subsystem_sysname(
346 outer->udev_.get(), "sound", sysname.c_str()));
347 udev_id_vendor_enc = GetUdevDevicePropertyValue(
348 udev_device.get(), "ID_VENDOR_ENC");
349 udev_id_vendor_id = GetUdevDevicePropertyValue(
350 udev_device.get(), "ID_VENDOR_ID");
351 udev_id_vendor_from_database = GetUdevDevicePropertyValue(
352 udev_device.get(), "ID_VENDOR_FROM_DATABASE");
353
354 udev_id_path_ = GetUdevDevicePropertyValue(
355 udev_device.get(), "ID_PATH");
356 udev_id_id_ = GetUdevDevicePropertyValue(
357 udev_device.get(), "ID_ID");
358#endif // defined(USE_UDEV)
359
360 manufacturer_ = ExtractManufacturerString(
361 udev_id_vendor_enc, udev_id_vendor_id, udev_id_vendor_from_database,
362 alsa_name, alsa_longname);
363}
364
365MidiManagerAlsa::CardInfo::~CardInfo() {
366}
367
368const std::string MidiManagerAlsa::CardInfo::alsa_name() const {
369 return alsa_name_;
370}
371
372const std::string MidiManagerAlsa::CardInfo::manufacturer() const {
373 return manufacturer_;
374}
375
376const std::string MidiManagerAlsa::CardInfo::alsa_driver() const {
377 return alsa_driver_;
378}
379
380const std::string MidiManagerAlsa::CardInfo::udev_id_path() const {
381 return udev_id_path_;
382}
383
384const std::string MidiManagerAlsa::CardInfo::udev_id_id() const {
385 return udev_id_id_;
386}
387
agoode@chromium.org25227512014-06-08 05:12:05 +0000388void MidiManagerAlsa::SendMidiData(uint32 port_index,
389 const std::vector<uint8>& data) {
390 DCHECK(send_thread_.message_loop_proxy()->BelongsToCurrentThread());
391
392 snd_midi_event_t* encoder = encoders_[port_index];
393 for (unsigned int i = 0; i < data.size(); i++) {
394 snd_seq_event_t event;
395 int result = snd_midi_event_encode_byte(encoder, data[i], &event);
396 if (result == 1) {
397 // Full event, send it.
398 snd_seq_ev_set_source(&event, out_ports_[port_index]);
399 snd_seq_ev_set_subs(&event);
400 snd_seq_ev_set_direct(&event);
401 snd_seq_event_output_direct(out_client_, &event);
402 }
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000403 }
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000404}
405
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000406void MidiManagerAlsa::DispatchSendMidiData(MidiManagerClient* client,
dnicoara@chromium.org9f2a6f02014-01-03 21:25:00 +0000407 uint32 port_index,
408 const std::vector<uint8>& data,
409 double timestamp) {
agoode@chromium.org25227512014-06-08 05:12:05 +0000410 if (out_ports_.size() <= port_index)
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000411 return;
412
agoode@chromium.org25227512014-06-08 05:12:05 +0000413 // Not correct right now. http://crbug.com/374341.
414 if (!send_thread_.IsRunning())
415 send_thread_.Start();
416
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000417 base::TimeDelta delay;
418 if (timestamp != 0.0) {
419 base::TimeTicks time_to_send =
420 base::TimeTicks() + base::TimeDelta::FromMicroseconds(
421 timestamp * base::Time::kMicrosecondsPerSecond);
422 delay = std::max(time_to_send - base::TimeTicks::Now(), base::TimeDelta());
423 }
424
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000425 send_thread_.message_loop()->PostDelayedTask(
426 FROM_HERE,
agoode@chromium.org25227512014-06-08 05:12:05 +0000427 base::Bind(&MidiManagerAlsa::SendMidiData, base::Unretained(this),
428 port_index, data), delay);
429
430 // Acknowledge send.
431 send_thread_.message_loop()->PostTask(
432 FROM_HERE,
433 base::Bind(&MidiManagerClient::AccumulateMidiBytesSent,
434 base::Unretained(client), data.size()));
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000435}
436
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000437void MidiManagerAlsa::EventReset() {
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000438 event_thread_.message_loop()->PostTask(
439 FROM_HERE,
440 base::Bind(&MidiManagerAlsa::EventLoop, base::Unretained(this)));
441}
442
443void MidiManagerAlsa::EventLoop() {
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000444 // Read available incoming MIDI data.
agoode@chromium.org25227512014-06-08 05:12:05 +0000445 snd_seq_event_t* event;
446 int err = snd_seq_event_input(in_client_, &event);
charlieaaf3320a2015-01-26 09:35:41 -0800447 double timestamp = (base::TimeTicks::Now() - base::TimeTicks()).InSecondsF();
agoode@chromium.org25227512014-06-08 05:12:05 +0000448 if (err == -ENOSPC) {
449 VLOG(1) << "snd_seq_event_input detected buffer overrun";
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000450
agoode@chromium.org25227512014-06-08 05:12:05 +0000451 // We've lost events: check another way to see if we need to shut down.
452 base::AutoLock lock(shutdown_lock_);
453 if (event_thread_shutdown_) {
454 return;
455 }
456 } else if (err < 0) {
457 VLOG(1) << "snd_seq_event_input fails: " << snd_strerror(err);
458 return;
459 } else {
460 // Check for disconnection of out client. This means "shut down".
461 if (event->source.client == SND_SEQ_CLIENT_SYSTEM &&
462 event->source.port == SND_SEQ_PORT_SYSTEM_ANNOUNCE &&
463 event->type == SND_SEQ_EVENT_CLIENT_EXIT &&
464 event->data.addr.client == out_client_id_) {
465 return;
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000466 }
agoode@chromium.org25227512014-06-08 05:12:05 +0000467
468 std::map<int, uint32>::iterator source_it =
469 source_map_.find(AddrToInt(&event->source));
470 if (source_it != source_map_.end()) {
471 uint32 source = source_it->second;
472 if (event->type == SND_SEQ_EVENT_SYSEX) {
473 // Special! Variable-length sysex.
474 ReceiveMidiData(source, static_cast<const uint8*>(event->data.ext.ptr),
475 event->data.ext.len,
476 timestamp);
477 } else {
478 // Otherwise, decode this and send that on.
479 unsigned char buf[12];
480 long count = snd_midi_event_decode(decoder_, buf, sizeof(buf), event);
481 if (count <= 0) {
482 if (count != -ENOENT) {
483 // ENOENT means that it's not a MIDI message, which is not an
484 // error, but other negative values are errors for us.
485 VLOG(1) << "snd_midi_event_decoder fails " << snd_strerror(count);
486 }
487 } else {
488 ReceiveMidiData(source, buf, count, timestamp);
489 }
490 }
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000491 }
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000492 }
493
494 // Do again.
495 event_thread_.message_loop()->PostTask(
496 FROM_HERE,
497 base::Bind(&MidiManagerAlsa::EventLoop, base::Unretained(this)));
498}
499
agoode55a8b522015-03-08 12:40:17 -0700500// static
501std::string MidiManagerAlsa::CardInfo::ExtractManufacturerString(
502 const std::string& udev_id_vendor_enc,
503 const std::string& udev_id_vendor_id,
504 const std::string& udev_id_vendor_from_database,
505 const std::string& alsa_name,
506 const std::string& alsa_longname) {
507 // Let's try to determine the manufacturer. Here is the ordered preference
508 // in extraction:
509 // 1. Vendor name from the USB device iManufacturer string, stored in
510 // udev_id_vendor_enc.
511 // 2. Vendor name from the udev hwid database.
512 // 3. Heuristic from ALSA.
513
514 // Is the vendor string not just the USB vendor hex id?
515 std::string udev_id_vendor = UnescapeUdev(udev_id_vendor_enc);
516 if (udev_id_vendor != udev_id_vendor_id) {
517 return udev_id_vendor;
518 }
519
520 // Is there a vendor string in the hardware database?
521 if (!udev_id_vendor_from_database.empty()) {
522 return udev_id_vendor_from_database;
523 }
524
525 // Ok, udev gave us nothing useful, or was unavailable. So try a heuristic.
526 // We assume that card longname is in the format of
527 // "<manufacturer> <name> at <bus>". Otherwise, we give up to detect
528 // a manufacturer name here.
529 size_t at_index = alsa_longname.rfind(" at ");
530 if (std::string::npos != at_index) {
531 size_t name_index = alsa_longname.rfind(alsa_name, at_index - 1);
532 if (std::string::npos != name_index)
533 return alsa_longname.substr(0, name_index - 1);
534 }
535
536 // Failure.
537 return "";
538}
539
540// static
541std::string MidiManagerAlsa::CardInfo::UnescapeUdev(const std::string& s) {
542 std::string unescaped;
543 const size_t size = s.size();
544 for (size_t i = 0; i < size; ++i) {
545 char c = s[i];
546 if ((i + 3 < size) && c == '\\' && s[i + 1] == 'x') {
547 c = (HexDigitToInt(s[i + 2]) << 4) +
548 HexDigitToInt(s[i + 3]);
549 i += 3;
550 }
551 unescaped.push_back(c);
552 }
553 return unescaped;
554}
555
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000556MidiManager* MidiManager::Create() {
557 return new MidiManagerAlsa();
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000558}
559
560} // namespace media