Add some instrumentation for jank in URLRequest::Start.
[chromium-blink-merge.git] / media / midi / midi_manager_alsa.cc
blob3db6955c1ffcb60f510529bf0433399359da6ab2
1 // 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.
5 #include "media/midi/midi_manager_alsa.h"
7 #include <alsa/asoundlib.h>
8 #include <stdlib.h>
9 #include <algorithm>
10 #include <string>
12 #include "base/bind.h"
13 #include "base/json/json_string_value_serializer.h"
14 #include "base/logging.h"
15 #include "base/memory/scoped_ptr.h"
16 #include "base/memory/scoped_vector.h"
17 #include "base/message_loop/message_loop.h"
18 #include "base/posix/eintr_wrapper.h"
19 #include "base/strings/string_number_conversions.h"
20 #include "base/strings/stringprintf.h"
21 #include "base/threading/thread.h"
22 #include "base/time/time.h"
23 #include "base/values.h"
24 #include "crypto/sha2.h"
25 #include "media/midi/midi_port_info.h"
27 namespace media {
29 namespace {
31 // 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.
35 const size_t kSendBufferSize = 256;
37 // ALSA constants.
38 const char kAlsaHw[] = "hw";
40 // Constants for the capabilities we search for in inputs and outputs.
41 // See http://www.alsa-project.org/alsa-doc/alsa-lib/seq.html.
42 const unsigned int kRequiredInputPortCaps =
43 SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ;
44 const unsigned int kRequiredOutputPortCaps =
45 SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE;
47 const unsigned int kCreateOutputPortCaps =
48 SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_NO_EXPORT;
49 const unsigned int kCreateInputPortCaps =
50 SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_NO_EXPORT;
51 const unsigned int kCreatePortType =
52 SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION;
54 int AddrToInt(int client, int port) {
55 return (client << 8) | port;
58 void SetStringIfNonEmpty(base::DictionaryValue* value,
59 const std::string& path,
60 const std::string& in_value) {
61 if (!in_value.empty())
62 value->SetString(path, in_value);
65 } // namespace
67 MidiManagerAlsa::MidiManagerAlsa()
68 : in_client_(NULL),
69 out_client_(NULL),
70 out_client_id_(-1),
71 in_port_id_(-1),
72 decoder_(NULL),
73 send_thread_("MidiSendThread"),
74 event_thread_("MidiEventThread"),
75 event_thread_shutdown_(false) {
76 // Initialize decoder.
77 snd_midi_event_new(0, &decoder_);
78 snd_midi_event_no_status(decoder_, 1);
81 MidiManagerAlsa::~MidiManagerAlsa() {
82 // Tell the event thread it will soon be time to shut down. This gives
83 // us assurance the thread will stop in case the SND_SEQ_EVENT_CLIENT_EXIT
84 // message is lost.
86 base::AutoLock lock(shutdown_lock_);
87 event_thread_shutdown_ = true;
90 // Stop the send thread.
91 send_thread_.Stop();
93 // Close the out client. This will trigger the event thread to stop,
94 // because of SND_SEQ_EVENT_CLIENT_EXIT.
95 if (out_client_)
96 snd_seq_close(out_client_);
98 // Wait for the event thread to stop.
99 event_thread_.Stop();
101 // Close the in client.
102 if (in_client_)
103 snd_seq_close(in_client_);
105 // Free the decoder.
106 snd_midi_event_free(decoder_);
109 void MidiManagerAlsa::StartInitialization() {
110 // TODO(agoode): Move off I/O thread. See http://crbug.com/374341.
112 // Create client handles.
113 int err = snd_seq_open(&in_client_, kAlsaHw, SND_SEQ_OPEN_INPUT, 0);
114 if (err != 0) {
115 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err);
116 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
118 in_client_id_ = snd_seq_client_id(in_client_);
119 err = snd_seq_open(&out_client_, kAlsaHw, SND_SEQ_OPEN_OUTPUT, 0);
120 if (err != 0) {
121 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err);
122 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
124 out_client_id_ = snd_seq_client_id(out_client_);
126 // Name the clients.
127 err = snd_seq_set_client_name(in_client_, "Chrome (input)");
128 if (err != 0) {
129 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err);
130 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
132 err = snd_seq_set_client_name(out_client_, "Chrome (output)");
133 if (err != 0) {
134 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err);
135 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
138 // Create input port.
139 in_port_id_ = snd_seq_create_simple_port(
140 in_client_, NULL, kCreateInputPortCaps, kCreatePortType);
141 if (in_port_id_ < 0) {
142 VLOG(1) << "snd_seq_create_simple_port fails: "
143 << snd_strerror(in_port_id_);
144 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
147 // Subscribe to the announce port.
148 snd_seq_port_subscribe_t* subs;
149 snd_seq_port_subscribe_alloca(&subs);
150 snd_seq_addr_t announce_sender;
151 snd_seq_addr_t announce_dest;
152 announce_sender.client = SND_SEQ_CLIENT_SYSTEM;
153 announce_sender.port = SND_SEQ_PORT_SYSTEM_ANNOUNCE;
154 announce_dest.client = in_client_id_;
155 announce_dest.port = in_port_id_;
156 snd_seq_port_subscribe_set_sender(subs, &announce_sender);
157 snd_seq_port_subscribe_set_dest(subs, &announce_dest);
158 err = snd_seq_subscribe_port(in_client_, subs);
159 if (err != 0) {
160 VLOG(1) << "snd_seq_subscribe_port on the announce port fails: "
161 << snd_strerror(err);
162 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
165 // Generate hotplug events for existing ports.
166 EnumerateAlsaPorts();
168 // Start processing events.
169 event_thread_.Start();
170 event_thread_.message_loop()->PostTask(
171 FROM_HERE,
172 base::Bind(&MidiManagerAlsa::ScheduleEventLoop, base::Unretained(this)));
174 CompleteInitialization(MIDI_OK);
177 void MidiManagerAlsa::DispatchSendMidiData(MidiManagerClient* client,
178 uint32 port_index,
179 const std::vector<uint8>& data,
180 double timestamp) {
181 // Not correct right now. http://crbug.com/374341.
182 if (!send_thread_.IsRunning())
183 send_thread_.Start();
185 base::TimeDelta delay;
186 if (timestamp != 0.0) {
187 base::TimeTicks time_to_send =
188 base::TimeTicks() + base::TimeDelta::FromMicroseconds(
189 timestamp * base::Time::kMicrosecondsPerSecond);
190 delay = std::max(time_to_send - base::TimeTicks::Now(), base::TimeDelta());
193 send_thread_.message_loop()->PostDelayedTask(
194 FROM_HERE, base::Bind(&MidiManagerAlsa::SendMidiData,
195 base::Unretained(this), port_index, data),
196 delay);
198 // Acknowledge send.
199 send_thread_.message_loop()->PostTask(
200 FROM_HERE, base::Bind(&MidiManagerClient::AccumulateMidiBytesSent,
201 base::Unretained(client), data.size()));
204 MidiManagerAlsa::MidiPort::MidiPort(const std::string& path,
205 const std::string& id,
206 int client_id,
207 int port_id,
208 int midi_device,
209 const std::string& client_name,
210 const std::string& port_name,
211 const std::string& manufacturer,
212 const std::string& version,
213 Type type)
214 : id_(id),
215 midi_device_(midi_device),
216 type_(type),
217 path_(path),
218 client_id_(client_id),
219 port_id_(port_id),
220 client_name_(client_name),
221 port_name_(port_name),
222 manufacturer_(manufacturer),
223 version_(version),
224 web_port_index_(0),
225 connected_(true) {
228 MidiManagerAlsa::MidiPort::~MidiPort() {
231 // Note: keep synchronized with the MidiPort::Match* methods.
232 scoped_ptr<base::Value> MidiManagerAlsa::MidiPort::Value() const {
233 scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue);
235 std::string type;
236 switch (type_) {
237 case Type::kInput:
238 type = "input";
239 break;
241 case Type::kOutput:
242 type = "output";
243 break;
245 value->SetString("type", type);
246 SetStringIfNonEmpty(value.get(), "path", path_);
247 SetStringIfNonEmpty(value.get(), "id", id_);
248 SetStringIfNonEmpty(value.get(), "clientName", client_name_);
249 SetStringIfNonEmpty(value.get(), "portName", port_name_);
250 value->SetInteger("clientId", client_id_);
251 value->SetInteger("portId", port_id_);
252 value->SetInteger("midiDevice", midi_device_);
254 return value.Pass();
257 std::string MidiManagerAlsa::MidiPort::JSONValue() const {
258 std::string json;
259 JSONStringValueSerializer serializer(&json);
260 serializer.Serialize(*Value().get());
261 return json;
264 // TODO(agoode): Do not use SHA256 here. Instead store a persistent
265 // mapping and just use a UUID or other random string.
266 // http://crbug.com/465320
267 std::string MidiManagerAlsa::MidiPort::OpaqueKey() const {
268 uint8 hash[crypto::kSHA256Length];
269 crypto::SHA256HashString(JSONValue(), &hash, sizeof(hash));
270 return base::HexEncode(&hash, sizeof(hash));
273 bool MidiManagerAlsa::MidiPort::MatchConnected(const MidiPort& query) const {
274 // Matches on:
275 // connected == true
276 // type
277 // path
278 // id
279 // client_id
280 // port_id
281 // midi_device
282 // client_name
283 // port_name
284 return connected() && (type() == query.type()) && (path() == query.path()) &&
285 (id() == query.id()) && (client_id() == query.client_id()) &&
286 (port_id() == query.port_id()) &&
287 (midi_device() == query.midi_device()) &&
288 (client_name() == query.client_name()) &&
289 (port_name() == query.port_name());
292 bool MidiManagerAlsa::MidiPort::MatchCardPass1(const MidiPort& query) const {
293 // Matches on:
294 // connected == false
295 // type
296 // path
297 // id
298 // port_id
299 // midi_device
300 return MatchCardPass2(query) && (path() == query.path());
303 bool MidiManagerAlsa::MidiPort::MatchCardPass2(const MidiPort& query) const {
304 // Matches on:
305 // connected == false
306 // type
307 // id
308 // port_id
309 // midi_device
310 return !connected() && (type() == query.type()) && (id() == query.id()) &&
311 (port_id() == query.port_id()) &&
312 (midi_device() == query.midi_device());
315 bool MidiManagerAlsa::MidiPort::MatchNoCardPass1(const MidiPort& query) const {
316 // Matches on:
317 // connected == false
318 // type
319 // path.empty(), for both this and query
320 // id.empty(), for both this and query
321 // client_id
322 // port_id
323 // client_name
324 // port_name
325 // midi_device == -1, for both this and query
326 return MatchNoCardPass2(query) && (client_id() == query.client_id());
329 bool MidiManagerAlsa::MidiPort::MatchNoCardPass2(const MidiPort& query) const {
330 // Matches on:
331 // connected == false
332 // type
333 // path.empty(), for both this and query
334 // id.empty(), for both this and query
335 // port_id
336 // client_name
337 // port_name
338 // midi_device == -1, for both this and query
339 return !connected() && (type() == query.type()) && path().empty() &&
340 query.path().empty() && id().empty() && query.id().empty() &&
341 (port_id() == query.port_id()) &&
342 (client_name() == query.client_name()) &&
343 (port_name() == query.port_name()) && (midi_device() == -1) &&
344 (query.midi_device() == -1);
347 MidiManagerAlsa::MidiPortStateBase::~MidiPortStateBase() {
350 ScopedVector<MidiManagerAlsa::MidiPort>*
351 MidiManagerAlsa::MidiPortStateBase::ports() {
352 return &ports_;
355 MidiManagerAlsa::MidiPortStateBase::iterator
356 MidiManagerAlsa::MidiPortStateBase::Find(
357 const MidiManagerAlsa::MidiPort& port) {
358 auto result = FindConnected(port);
359 if (result == end())
360 result = FindDisconnected(port);
361 return result;
364 MidiManagerAlsa::MidiPortStateBase::iterator
365 MidiManagerAlsa::MidiPortStateBase::FindConnected(
366 const MidiManagerAlsa::MidiPort& port) {
367 // Exact match required for connected ports.
368 auto it = std::find_if(ports_.begin(), ports_.end(), [&port](MidiPort* p) {
369 return p->MatchConnected(port);
371 return it;
374 MidiManagerAlsa::MidiPortStateBase::iterator
375 MidiManagerAlsa::MidiPortStateBase::FindDisconnected(
376 const MidiManagerAlsa::MidiPort& port) {
377 // Always match on:
378 // type
379 // Possible things to match on:
380 // path
381 // id
382 // client_id
383 // port_id
384 // midi_device
385 // client_name
386 // port_name
388 if (!port.path().empty()) {
389 // If path is present, then we have a card-based client.
391 // Pass 1. Match on path, id, midi_device, port_id.
392 // This is the best possible match for hardware card-based clients.
393 // This will also match the empty id correctly for devices without an id.
394 auto it = std::find_if(ports_.begin(), ports_.end(), [&port](MidiPort* p) {
395 return p->MatchCardPass1(port);
397 if (it != ports_.end())
398 return it;
400 if (!port.id().empty()) {
401 // Pass 2. Match on id, midi_device, port_id.
402 // This will give us a high-confidence match when a user moves a device to
403 // another USB/Firewire/Thunderbolt/etc port, but only works if the device
404 // has a hardware id.
405 it = std::find_if(ports_.begin(), ports_.end(), [&port](MidiPort* p) {
406 return p->MatchCardPass2(port);
408 if (it != ports_.end())
409 return it;
411 } else {
412 // Else, we have a non-card-based client.
413 // Pass 1. Match on client_id, port_id, client_name, port_name.
414 // This will give us a reasonably good match.
415 auto it = std::find_if(ports_.begin(), ports_.end(), [&port](MidiPort* p) {
416 return p->MatchNoCardPass1(port);
418 if (it != ports_.end())
419 return it;
421 // Pass 2. Match on port_id, client_name, port_name.
422 // This is weaker but similar to pass 2 in the hardware card-based clients
423 // match.
424 it = std::find_if(ports_.begin(), ports_.end(), [&port](MidiPort* p) {
425 return p->MatchNoCardPass2(port);
427 if (it != ports_.end())
428 return it;
431 // No match.
432 return ports_.end();
435 MidiManagerAlsa::MidiPortStateBase::MidiPortStateBase() {
438 void MidiManagerAlsa::TemporaryMidiPortState::Insert(
439 scoped_ptr<MidiPort> port) {
440 ports()->push_back(port.Pass());
443 MidiManagerAlsa::MidiPortState::MidiPortState()
444 : num_input_ports_(0), num_output_ports_(0) {
447 uint32 MidiManagerAlsa::MidiPortState::Insert(scoped_ptr<MidiPort> port) {
448 // Add the web midi index.
449 uint32 web_port_index = 0;
450 switch (port->type()) {
451 case MidiPort::Type::kInput:
452 web_port_index = num_input_ports_++;
453 break;
454 case MidiPort::Type::kOutput:
455 web_port_index = num_output_ports_++;
456 break;
458 port->set_web_port_index(web_port_index);
459 ports()->push_back(port.Pass());
460 return web_port_index;
463 MidiManagerAlsa::AlsaSeqState::AlsaSeqState() : clients_deleter_(&clients_) {
466 MidiManagerAlsa::AlsaSeqState::~AlsaSeqState() {
469 void MidiManagerAlsa::AlsaSeqState::ClientStart(int client_id,
470 const std::string& client_name,
471 snd_seq_client_type_t type) {
472 ClientExit(client_id);
473 clients_[client_id] = new Client(client_name, type);
476 bool MidiManagerAlsa::AlsaSeqState::ClientStarted(int client_id) {
477 return clients_.find(client_id) != clients_.end();
480 void MidiManagerAlsa::AlsaSeqState::ClientExit(int client_id) {
481 auto it = clients_.find(client_id);
482 if (it != clients_.end()) {
483 delete it->second;
484 clients_.erase(it);
488 void MidiManagerAlsa::AlsaSeqState::PortStart(
489 int client_id,
490 int port_id,
491 const std::string& port_name,
492 MidiManagerAlsa::AlsaSeqState::PortDirection direction,
493 bool midi) {
494 auto it = clients_.find(client_id);
495 if (it != clients_.end())
496 it->second->AddPort(port_id,
497 scoped_ptr<Port>(new Port(port_name, direction, midi)));
500 void MidiManagerAlsa::AlsaSeqState::PortExit(int client_id, int port_id) {
501 auto it = clients_.find(client_id);
502 if (it != clients_.end())
503 it->second->RemovePort(port_id);
506 snd_seq_client_type_t MidiManagerAlsa::AlsaSeqState::ClientType(
507 int client_id) const {
508 auto it = clients_.find(client_id);
509 if (it == clients_.end())
510 return SND_SEQ_USER_CLIENT;
511 return it->second->type();
514 scoped_ptr<MidiManagerAlsa::TemporaryMidiPortState>
515 MidiManagerAlsa::AlsaSeqState::ToMidiPortState() {
516 scoped_ptr<MidiManagerAlsa::TemporaryMidiPortState> midi_ports(
517 new TemporaryMidiPortState);
518 // TODO(agoode): Use information from udev as well.
520 for (const auto& client_pair : clients_) {
521 int client_id = client_pair.first;
522 const auto& client = client_pair.second;
524 // Get client metadata.
525 const std::string client_name = client->name();
526 std::string manufacturer;
527 std::string driver;
528 std::string path;
529 std::string id;
530 std::string serial;
531 std::string card_name;
532 std::string card_longname;
533 int midi_device = -1;
535 for (const auto& port_pair : *client) {
536 int port_id = port_pair.first;
537 const auto& port = port_pair.second;
539 if (port->midi()) {
540 std::string version;
541 if (!driver.empty()) {
542 version = driver + " / ";
544 version +=
545 base::StringPrintf("ALSA library version %d.%d.%d", SND_LIB_MAJOR,
546 SND_LIB_MINOR, SND_LIB_SUBMINOR);
547 PortDirection direction = port->direction();
548 if (direction == PortDirection::kInput ||
549 direction == PortDirection::kDuplex) {
550 midi_ports->Insert(scoped_ptr<MidiPort>(new MidiPort(
551 path, id, client_id, port_id, midi_device, client->name(),
552 port->name(), manufacturer, version, MidiPort::Type::kInput)));
554 if (direction == PortDirection::kOutput ||
555 direction == PortDirection::kDuplex) {
556 midi_ports->Insert(scoped_ptr<MidiPort>(new MidiPort(
557 path, id, client_id, port_id, midi_device, client->name(),
558 port->name(), manufacturer, version, MidiPort::Type::kOutput)));
564 return midi_ports.Pass();
567 MidiManagerAlsa::AlsaSeqState::Port::Port(
568 const std::string& name,
569 MidiManagerAlsa::AlsaSeqState::PortDirection direction,
570 bool midi)
571 : name_(name), direction_(direction), midi_(midi) {
574 MidiManagerAlsa::AlsaSeqState::Port::~Port() {
577 std::string MidiManagerAlsa::AlsaSeqState::Port::name() const {
578 return name_;
581 MidiManagerAlsa::AlsaSeqState::PortDirection
582 MidiManagerAlsa::AlsaSeqState::Port::direction() const {
583 return direction_;
586 bool MidiManagerAlsa::AlsaSeqState::Port::midi() const {
587 return midi_;
590 MidiManagerAlsa::AlsaSeqState::Client::Client(const std::string& name,
591 snd_seq_client_type_t type)
592 : name_(name), type_(type), ports_deleter_(&ports_) {
595 MidiManagerAlsa::AlsaSeqState::Client::~Client() {
598 std::string MidiManagerAlsa::AlsaSeqState::Client::name() const {
599 return name_;
602 snd_seq_client_type_t MidiManagerAlsa::AlsaSeqState::Client::type() const {
603 return type_;
606 void MidiManagerAlsa::AlsaSeqState::Client::AddPort(int addr,
607 scoped_ptr<Port> port) {
608 RemovePort(addr);
609 ports_[addr] = port.release();
612 void MidiManagerAlsa::AlsaSeqState::Client::RemovePort(int addr) {
613 auto it = ports_.find(addr);
614 if (it != ports_.end()) {
615 delete it->second;
616 ports_.erase(it);
620 MidiManagerAlsa::AlsaSeqState::Client::PortMap::const_iterator
621 MidiManagerAlsa::AlsaSeqState::Client::begin() const {
622 return ports_.begin();
625 MidiManagerAlsa::AlsaSeqState::Client::PortMap::const_iterator
626 MidiManagerAlsa::AlsaSeqState::Client::end() const {
627 return ports_.end();
630 // static
631 std::string MidiManagerAlsa::ExtractManufacturerString(
632 const std::string& udev_id_vendor,
633 const std::string& udev_id_vendor_id,
634 const std::string& udev_id_vendor_from_database,
635 const std::string& alsa_name,
636 const std::string& alsa_longname) {
637 // Let's try to determine the manufacturer. Here is the ordered preference
638 // in extraction:
639 // 1. Vendor name from the hardware device string, from udev properties
640 // or sysattrs.
641 // 2. Vendor name from the udev database (property ID_VENDOR_FROM_DATABASE).
642 // 3. Heuristic from ALSA.
644 // Is the vendor string present and not just the vendor hex id?
645 if (!udev_id_vendor.empty() && (udev_id_vendor != udev_id_vendor_id)) {
646 return udev_id_vendor;
649 // Is there a vendor string in the hardware database?
650 if (!udev_id_vendor_from_database.empty()) {
651 return udev_id_vendor_from_database;
654 // Ok, udev gave us nothing useful, or was unavailable. So try a heuristic.
655 // We assume that card longname is in the format of
656 // "<manufacturer> <name> at <bus>". Otherwise, we give up to detect
657 // a manufacturer name here.
658 size_t at_index = alsa_longname.rfind(" at ");
659 if (at_index && at_index != std::string::npos) {
660 size_t name_index = alsa_longname.rfind(alsa_name, at_index - 1);
661 if (name_index && name_index != std::string::npos)
662 return alsa_longname.substr(0, name_index - 1);
665 // Failure.
666 return "";
669 void MidiManagerAlsa::SendMidiData(uint32 port_index,
670 const std::vector<uint8>& data) {
671 DCHECK(send_thread_.message_loop_proxy()->BelongsToCurrentThread());
673 snd_midi_event_t* encoder;
674 snd_midi_event_new(kSendBufferSize, &encoder);
675 for (unsigned int i = 0; i < data.size(); i++) {
676 snd_seq_event_t event;
677 int result = snd_midi_event_encode_byte(encoder, data[i], &event);
678 if (result == 1) {
679 // Full event, send it.
680 base::AutoLock lock(out_ports_lock_);
681 auto it = out_ports_.find(port_index);
682 if (it != out_ports_.end()) {
683 snd_seq_ev_set_source(&event, it->second);
684 snd_seq_ev_set_subs(&event);
685 snd_seq_ev_set_direct(&event);
686 snd_seq_event_output_direct(out_client_, &event);
690 snd_midi_event_free(encoder);
693 void MidiManagerAlsa::ScheduleEventLoop() {
694 event_thread_.message_loop()->PostTask(
695 FROM_HERE,
696 base::Bind(&MidiManagerAlsa::EventLoop, base::Unretained(this)));
699 void MidiManagerAlsa::EventLoop() {
700 // Read available incoming MIDI data.
701 snd_seq_event_t* event;
702 int err = snd_seq_event_input(in_client_, &event);
703 double timestamp = (base::TimeTicks::Now() - base::TimeTicks()).InSecondsF();
705 // Handle errors.
706 if (err == -ENOSPC) {
707 VLOG(1) << "snd_seq_event_input detected buffer overrun";
708 // We've lost events: check another way to see if we need to shut down.
709 base::AutoLock lock(shutdown_lock_);
710 if (!event_thread_shutdown_)
711 ScheduleEventLoop();
712 return;
713 } else if (err < 0) {
714 VLOG(1) << "snd_seq_event_input fails: " << snd_strerror(err);
715 // TODO(agoode): Use RecordAction() or similar to log this.
716 return;
719 // Handle announce events.
720 if (event->source.client == SND_SEQ_CLIENT_SYSTEM &&
721 event->source.port == SND_SEQ_PORT_SYSTEM_ANNOUNCE) {
722 switch (event->type) {
723 case SND_SEQ_EVENT_PORT_START:
724 // Don't use SND_SEQ_EVENT_CLIENT_START because the client name may not
725 // be set by the time we query it. It should be set by the time ports
726 // are made.
727 ProcessClientStartEvent(event->data.addr.client);
728 ProcessPortStartEvent(event->data.addr);
729 break;
731 case SND_SEQ_EVENT_CLIENT_EXIT:
732 // Check for disconnection of our "out" client. This means "shut down".
733 if (event->data.addr.client == out_client_id_)
734 return;
736 ProcessClientExitEvent(event->data.addr);
737 break;
739 case SND_SEQ_EVENT_PORT_EXIT:
740 ProcessPortExitEvent(event->data.addr);
741 break;
743 } else {
744 ProcessSingleEvent(event, timestamp);
747 // Do again.
748 ScheduleEventLoop();
751 void MidiManagerAlsa::ProcessSingleEvent(snd_seq_event_t* event,
752 double timestamp) {
753 auto source_it =
754 source_map_.find(AddrToInt(event->source.client, event->source.port));
755 if (source_it != source_map_.end()) {
756 uint32 source = source_it->second;
757 if (event->type == SND_SEQ_EVENT_SYSEX) {
758 // Special! Variable-length sysex.
759 ReceiveMidiData(source, static_cast<const uint8*>(event->data.ext.ptr),
760 event->data.ext.len, timestamp);
761 } else {
762 // Otherwise, decode this and send that on.
763 unsigned char buf[12];
764 long count = snd_midi_event_decode(decoder_, buf, sizeof(buf), event);
765 if (count <= 0) {
766 if (count != -ENOENT) {
767 // ENOENT means that it's not a MIDI message, which is not an
768 // error, but other negative values are errors for us.
769 VLOG(1) << "snd_midi_event_decoder fails " << snd_strerror(count);
770 // TODO(agoode): Record this failure.
772 } else {
773 ReceiveMidiData(source, buf, count, timestamp);
779 void MidiManagerAlsa::ProcessClientStartEvent(int client_id) {
780 // Ignore if client is already started.
781 if (alsa_seq_state_.ClientStarted(client_id))
782 return;
784 snd_seq_client_info_t* client_info;
785 snd_seq_client_info_alloca(&client_info);
786 int err = snd_seq_get_any_client_info(in_client_, client_id, client_info);
787 if (err != 0)
788 return;
790 // Skip our own clients.
791 if ((client_id == in_client_id_) || (client_id == out_client_id_))
792 return;
794 // Update our view of ALSA seq state.
795 alsa_seq_state_.ClientStart(client_id,
796 snd_seq_client_info_get_name(client_info),
797 snd_seq_client_info_get_type(client_info));
799 // Generate Web MIDI events.
800 UpdatePortStateAndGenerateEvents();
803 void MidiManagerAlsa::ProcessPortStartEvent(const snd_seq_addr_t& addr) {
804 snd_seq_port_info_t* port_info;
805 snd_seq_port_info_alloca(&port_info);
806 int err =
807 snd_seq_get_any_port_info(in_client_, addr.client, addr.port, port_info);
808 if (err != 0)
809 return;
811 unsigned int caps = snd_seq_port_info_get_capability(port_info);
812 bool input = (caps & kRequiredInputPortCaps) == kRequiredInputPortCaps;
813 bool output = (caps & kRequiredOutputPortCaps) == kRequiredOutputPortCaps;
814 AlsaSeqState::PortDirection direction;
815 if (input && output)
816 direction = AlsaSeqState::PortDirection::kDuplex;
817 else if (input)
818 direction = AlsaSeqState::PortDirection::kInput;
819 else if (output)
820 direction = AlsaSeqState::PortDirection::kOutput;
821 else
822 return;
824 // Update our view of ALSA seq state.
825 alsa_seq_state_.PortStart(
826 addr.client, addr.port, snd_seq_port_info_get_name(port_info), direction,
827 snd_seq_port_info_get_type(port_info) & SND_SEQ_PORT_TYPE_MIDI_GENERIC);
828 // Generate Web MIDI events.
829 UpdatePortStateAndGenerateEvents();
832 void MidiManagerAlsa::ProcessClientExitEvent(const snd_seq_addr_t& addr) {
833 // Update our view of ALSA seq state.
834 alsa_seq_state_.ClientExit(addr.client);
835 // Generate Web MIDI events.
836 UpdatePortStateAndGenerateEvents();
839 void MidiManagerAlsa::ProcessPortExitEvent(const snd_seq_addr_t& addr) {
840 // Update our view of ALSA seq state.
841 alsa_seq_state_.PortExit(addr.client, addr.port);
842 // Generate Web MIDI events.
843 UpdatePortStateAndGenerateEvents();
846 void MidiManagerAlsa::UpdatePortStateAndGenerateEvents() {
847 // Generate new port state.
848 auto new_port_state = alsa_seq_state_.ToMidiPortState();
850 // Disconnect any connected old ports that are now missing.
851 for (auto* old_port : port_state_) {
852 if (old_port->connected() &&
853 (new_port_state->FindConnected(*old_port) == new_port_state->end())) {
854 old_port->set_connected(false);
855 uint32 web_port_index = old_port->web_port_index();
856 switch (old_port->type()) {
857 case MidiPort::Type::kInput:
858 source_map_.erase(
859 AddrToInt(old_port->client_id(), old_port->port_id()));
860 SetInputPortState(web_port_index, MIDI_PORT_DISCONNECTED);
861 break;
862 case MidiPort::Type::kOutput:
863 DeleteAlsaOutputPort(web_port_index);
864 SetOutputPortState(web_port_index, MIDI_PORT_DISCONNECTED);
865 break;
870 // Reconnect or add new ports.
871 auto it = new_port_state->begin();
872 while (it != new_port_state->end()) {
873 auto* new_port = *it;
874 auto old_port = port_state_.Find(*new_port);
875 if (old_port == port_state_.end()) {
876 // Add new port.
877 uint32 web_port_index =
878 port_state_.Insert(scoped_ptr<MidiPort>(new_port));
879 MidiPortInfo info(new_port->OpaqueKey(), new_port->manufacturer(),
880 new_port->port_name(), new_port->version(),
881 MIDI_PORT_OPENED);
882 switch (new_port->type()) {
883 case MidiPort::Type::kInput:
884 if (Subscribe(web_port_index, new_port->client_id(),
885 new_port->port_id()))
886 AddInputPort(info);
887 break;
889 case MidiPort::Type::kOutput:
890 if (CreateAlsaOutputPort(web_port_index, new_port->client_id(),
891 new_port->port_id()))
892 AddOutputPort(info);
893 break;
895 it = new_port_state->weak_erase(it);
896 } else if (!(*old_port)->connected()) {
897 // Reconnect.
898 uint32 web_port_index = (*old_port)->web_port_index();
899 (*old_port)->Update(new_port->path(), new_port->client_id(),
900 new_port->port_id(), new_port->client_name(),
901 new_port->port_name(), new_port->manufacturer(),
902 new_port->version());
903 switch ((*old_port)->type()) {
904 case MidiPort::Type::kInput:
905 if (Subscribe(web_port_index, (*old_port)->client_id(),
906 (*old_port)->port_id()))
907 SetInputPortState(web_port_index, MIDI_PORT_OPENED);
908 break;
910 case MidiPort::Type::kOutput:
911 if (CreateAlsaOutputPort(web_port_index, (*old_port)->client_id(),
912 (*old_port)->port_id()))
913 SetOutputPortState(web_port_index, MIDI_PORT_OPENED);
914 break;
916 (*old_port)->set_connected(true);
917 ++it;
918 } else {
919 ++it;
924 void MidiManagerAlsa::EnumerateAlsaPorts() {
925 snd_seq_client_info_t* client_info;
926 snd_seq_client_info_alloca(&client_info);
927 snd_seq_port_info_t* port_info;
928 snd_seq_port_info_alloca(&port_info);
930 // Enumerate clients.
931 snd_seq_client_info_set_client(client_info, -1);
932 while (!snd_seq_query_next_client(in_client_, client_info)) {
933 int client_id = snd_seq_client_info_get_client(client_info);
934 ProcessClientStartEvent(client_id);
936 // Enumerate ports.
937 snd_seq_port_info_set_client(port_info, client_id);
938 snd_seq_port_info_set_port(port_info, -1);
939 while (!snd_seq_query_next_port(in_client_, port_info)) {
940 const snd_seq_addr_t* addr = snd_seq_port_info_get_addr(port_info);
941 ProcessPortStartEvent(*addr);
946 bool MidiManagerAlsa::CreateAlsaOutputPort(uint32 port_index,
947 int client_id,
948 int port_id) {
949 // Create the port.
950 int out_port = snd_seq_create_simple_port(
951 out_client_, NULL, kCreateOutputPortCaps, kCreatePortType);
952 if (out_port < 0) {
953 VLOG(1) << "snd_seq_create_simple_port fails: " << snd_strerror(out_port);
954 return false;
956 // Activate port subscription.
957 snd_seq_port_subscribe_t* subs;
958 snd_seq_port_subscribe_alloca(&subs);
959 snd_seq_addr_t sender;
960 sender.client = out_client_id_;
961 sender.port = out_port;
962 snd_seq_port_subscribe_set_sender(subs, &sender);
963 snd_seq_addr_t dest;
964 dest.client = client_id;
965 dest.port = port_id;
966 snd_seq_port_subscribe_set_dest(subs, &dest);
967 int err = snd_seq_subscribe_port(out_client_, subs);
968 if (err != 0) {
969 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
970 snd_seq_delete_simple_port(out_client_, out_port);
971 return false;
974 // Update our map.
975 base::AutoLock lock(out_ports_lock_);
976 out_ports_[port_index] = out_port;
977 return true;
980 void MidiManagerAlsa::DeleteAlsaOutputPort(uint32 port_index) {
981 base::AutoLock lock(out_ports_lock_);
982 auto it = out_ports_.find(port_index);
983 if (it == out_ports_.end())
984 return;
986 int alsa_port = it->second;
987 snd_seq_delete_simple_port(out_client_, alsa_port);
988 out_ports_.erase(it);
991 bool MidiManagerAlsa::Subscribe(uint32 port_index, int client_id, int port_id) {
992 // Activate port subscription.
993 snd_seq_port_subscribe_t* subs;
994 snd_seq_port_subscribe_alloca(&subs);
995 snd_seq_addr_t sender;
996 sender.client = client_id;
997 sender.port = port_id;
998 snd_seq_port_subscribe_set_sender(subs, &sender);
999 snd_seq_addr_t dest;
1000 dest.client = in_client_id_;
1001 dest.port = in_port_id_;
1002 snd_seq_port_subscribe_set_dest(subs, &dest);
1003 int err = snd_seq_subscribe_port(in_client_, subs);
1004 if (err != 0) {
1005 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
1006 return false;
1009 // Update our map.
1010 source_map_[AddrToInt(client_id, port_id)] = port_index;
1011 return true;
1014 MidiManager* MidiManager::Create() {
1015 return new MidiManagerAlsa();
1018 } // namespace media