Supervised user import: Listen for profile creation/deletion
[chromium-blink-merge.git] / media / midi / midi_manager_alsa.cc
blobcfa6371cd36a88cab7d6bcba74471a0dec0d75f0
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 udev_(device::udev_new()),
74 send_thread_("MidiSendThread"),
75 event_thread_("MidiEventThread"),
76 event_thread_shutdown_(false) {
77 // Initialize decoder.
78 snd_midi_event_new(0, &decoder_);
79 snd_midi_event_no_status(decoder_, 1);
82 MidiManagerAlsa::~MidiManagerAlsa() {
83 // Tell the event thread it will soon be time to shut down. This gives
84 // us assurance the thread will stop in case the SND_SEQ_EVENT_CLIENT_EXIT
85 // message is lost.
87 base::AutoLock lock(shutdown_lock_);
88 event_thread_shutdown_ = true;
91 // Stop the send thread.
92 send_thread_.Stop();
94 // Close the out client. This will trigger the event thread to stop,
95 // because of SND_SEQ_EVENT_CLIENT_EXIT.
96 if (out_client_)
97 snd_seq_close(out_client_);
99 // Wait for the event thread to stop.
100 event_thread_.Stop();
102 // Close the in client.
103 if (in_client_)
104 snd_seq_close(in_client_);
106 // Free the decoder.
107 snd_midi_event_free(decoder_);
110 void MidiManagerAlsa::StartInitialization() {
111 // TODO(agoode): Move off I/O thread. See http://crbug.com/374341.
113 // Create client handles.
114 int err = snd_seq_open(&in_client_, kAlsaHw, SND_SEQ_OPEN_INPUT, 0);
115 if (err != 0) {
116 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err);
117 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
119 in_client_id_ = snd_seq_client_id(in_client_);
120 err = snd_seq_open(&out_client_, kAlsaHw, SND_SEQ_OPEN_OUTPUT, 0);
121 if (err != 0) {
122 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err);
123 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
125 out_client_id_ = snd_seq_client_id(out_client_);
127 // Name the clients.
128 err = snd_seq_set_client_name(in_client_, "Chrome (input)");
129 if (err != 0) {
130 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err);
131 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
133 err = snd_seq_set_client_name(out_client_, "Chrome (output)");
134 if (err != 0) {
135 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err);
136 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
139 // Create input port.
140 in_port_id_ = snd_seq_create_simple_port(
141 in_client_, NULL, kCreateInputPortCaps, kCreatePortType);
142 if (in_port_id_ < 0) {
143 VLOG(1) << "snd_seq_create_simple_port fails: "
144 << snd_strerror(in_port_id_);
145 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
148 // Subscribe to the announce port.
149 snd_seq_port_subscribe_t* subs;
150 snd_seq_port_subscribe_alloca(&subs);
151 snd_seq_addr_t announce_sender;
152 snd_seq_addr_t announce_dest;
153 announce_sender.client = SND_SEQ_CLIENT_SYSTEM;
154 announce_sender.port = SND_SEQ_PORT_SYSTEM_ANNOUNCE;
155 announce_dest.client = in_client_id_;
156 announce_dest.port = in_port_id_;
157 snd_seq_port_subscribe_set_sender(subs, &announce_sender);
158 snd_seq_port_subscribe_set_dest(subs, &announce_dest);
159 err = snd_seq_subscribe_port(in_client_, subs);
160 if (err != 0) {
161 VLOG(1) << "snd_seq_subscribe_port on the announce port fails: "
162 << snd_strerror(err);
163 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
166 // Generate hotplug events for existing ports.
167 EnumerateAlsaPorts();
169 // Start processing events.
170 event_thread_.Start();
171 event_thread_.message_loop()->PostTask(
172 FROM_HERE,
173 base::Bind(&MidiManagerAlsa::ScheduleEventLoop, base::Unretained(this)));
175 CompleteInitialization(MIDI_OK);
178 void MidiManagerAlsa::DispatchSendMidiData(MidiManagerClient* client,
179 uint32 port_index,
180 const std::vector<uint8>& data,
181 double timestamp) {
182 // Not correct right now. http://crbug.com/374341.
183 if (!send_thread_.IsRunning())
184 send_thread_.Start();
186 base::TimeDelta delay;
187 if (timestamp != 0.0) {
188 base::TimeTicks time_to_send =
189 base::TimeTicks() + base::TimeDelta::FromMicroseconds(
190 timestamp * base::Time::kMicrosecondsPerSecond);
191 delay = std::max(time_to_send - base::TimeTicks::Now(), base::TimeDelta());
194 send_thread_.message_loop()->PostDelayedTask(
195 FROM_HERE, base::Bind(&MidiManagerAlsa::SendMidiData,
196 base::Unretained(this), port_index, data),
197 delay);
199 // Acknowledge send.
200 send_thread_.message_loop()->PostTask(
201 FROM_HERE, base::Bind(&MidiManagerClient::AccumulateMidiBytesSent,
202 base::Unretained(client), data.size()));
205 MidiManagerAlsa::MidiPort::MidiPort(const std::string& path,
206 const std::string& id,
207 int client_id,
208 int port_id,
209 int midi_device,
210 const std::string& client_name,
211 const std::string& port_name,
212 const std::string& manufacturer,
213 const std::string& version,
214 Type type)
215 : id_(id),
216 midi_device_(midi_device),
217 type_(type),
218 path_(path),
219 client_id_(client_id),
220 port_id_(port_id),
221 client_name_(client_name),
222 port_name_(port_name),
223 manufacturer_(manufacturer),
224 version_(version),
225 web_port_index_(0),
226 connected_(true) {
229 MidiManagerAlsa::MidiPort::~MidiPort() {
232 // Note: keep synchronized with the MidiPort::Match* methods.
233 scoped_ptr<base::Value> MidiManagerAlsa::MidiPort::Value() const {
234 scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue);
236 std::string type;
237 switch (type_) {
238 case Type::kInput:
239 type = "input";
240 break;
242 case Type::kOutput:
243 type = "output";
244 break;
246 value->SetString("type", type);
247 SetStringIfNonEmpty(value.get(), "path", path_);
248 SetStringIfNonEmpty(value.get(), "id", id_);
249 SetStringIfNonEmpty(value.get(), "clientName", client_name_);
250 SetStringIfNonEmpty(value.get(), "portName", port_name_);
251 value->SetInteger("clientId", client_id_);
252 value->SetInteger("portId", port_id_);
253 value->SetInteger("midiDevice", midi_device_);
255 return value.Pass();
258 std::string MidiManagerAlsa::MidiPort::JSONValue() const {
259 std::string json;
260 JSONStringValueSerializer serializer(&json);
261 serializer.Serialize(*Value().get());
262 return json;
265 // TODO(agoode): Do not use SHA256 here. Instead store a persistent
266 // mapping and just use a UUID or other random string.
267 // http://crbug.com/465320
268 std::string MidiManagerAlsa::MidiPort::OpaqueKey() const {
269 uint8 hash[crypto::kSHA256Length];
270 crypto::SHA256HashString(JSONValue(), &hash, sizeof(hash));
271 return base::HexEncode(&hash, sizeof(hash));
274 bool MidiManagerAlsa::MidiPort::MatchConnected(const MidiPort& query) const {
275 // Matches on:
276 // connected == true
277 // type
278 // path
279 // id
280 // client_id
281 // port_id
282 // midi_device
283 // client_name
284 // port_name
285 return connected() && (type() == query.type()) && (path() == query.path()) &&
286 (id() == query.id()) && (client_id() == query.client_id()) &&
287 (port_id() == query.port_id()) &&
288 (midi_device() == query.midi_device()) &&
289 (client_name() == query.client_name()) &&
290 (port_name() == query.port_name());
293 bool MidiManagerAlsa::MidiPort::MatchCardPass1(const MidiPort& query) const {
294 // Matches on:
295 // connected == false
296 // type
297 // path
298 // id
299 // port_id
300 // midi_device
301 return MatchCardPass2(query) && (path() == query.path());
304 bool MidiManagerAlsa::MidiPort::MatchCardPass2(const MidiPort& query) const {
305 // Matches on:
306 // connected == false
307 // type
308 // id
309 // port_id
310 // midi_device
311 return !connected() && (type() == query.type()) && (id() == query.id()) &&
312 (port_id() == query.port_id()) &&
313 (midi_device() == query.midi_device());
316 bool MidiManagerAlsa::MidiPort::MatchNoCardPass1(const MidiPort& query) const {
317 // Matches on:
318 // connected == false
319 // type
320 // path.empty(), for both this and query
321 // id.empty(), for both this and query
322 // client_id
323 // port_id
324 // client_name
325 // port_name
326 // midi_device == -1, for both this and query
327 return MatchNoCardPass2(query) && (client_id() == query.client_id());
330 bool MidiManagerAlsa::MidiPort::MatchNoCardPass2(const MidiPort& query) const {
331 // Matches on:
332 // connected == false
333 // type
334 // path.empty(), for both this and query
335 // id.empty(), for both this and query
336 // port_id
337 // client_name
338 // port_name
339 // midi_device == -1, for both this and query
340 return !connected() && (type() == query.type()) && path().empty() &&
341 query.path().empty() && id().empty() && query.id().empty() &&
342 (port_id() == query.port_id()) &&
343 (client_name() == query.client_name()) &&
344 (port_name() == query.port_name()) && (midi_device() == -1) &&
345 (query.midi_device() == -1);
348 MidiManagerAlsa::MidiPortStateBase::~MidiPortStateBase() {
351 ScopedVector<MidiManagerAlsa::MidiPort>*
352 MidiManagerAlsa::MidiPortStateBase::ports() {
353 return &ports_;
356 MidiManagerAlsa::MidiPortStateBase::iterator
357 MidiManagerAlsa::MidiPortStateBase::Find(
358 const MidiManagerAlsa::MidiPort& port) {
359 auto result = FindConnected(port);
360 if (result == end())
361 result = FindDisconnected(port);
362 return result;
365 MidiManagerAlsa::MidiPortStateBase::iterator
366 MidiManagerAlsa::MidiPortStateBase::FindConnected(
367 const MidiManagerAlsa::MidiPort& port) {
368 // Exact match required for connected ports.
369 auto it = std::find_if(ports_.begin(), ports_.end(), [&port](MidiPort* p) {
370 return p->MatchConnected(port);
372 return it;
375 MidiManagerAlsa::MidiPortStateBase::iterator
376 MidiManagerAlsa::MidiPortStateBase::FindDisconnected(
377 const MidiManagerAlsa::MidiPort& port) {
378 // Always match on:
379 // type
380 // Possible things to match on:
381 // path
382 // id
383 // client_id
384 // port_id
385 // midi_device
386 // client_name
387 // port_name
389 if (!port.path().empty()) {
390 // If path is present, then we have a card-based client.
392 // Pass 1. Match on path, id, midi_device, port_id.
393 // This is the best possible match for hardware card-based clients.
394 // This will also match the empty id correctly for devices without an id.
395 auto it = std::find_if(ports_.begin(), ports_.end(), [&port](MidiPort* p) {
396 return p->MatchCardPass1(port);
398 if (it != ports_.end())
399 return it;
401 if (!port.id().empty()) {
402 // Pass 2. Match on id, midi_device, port_id.
403 // This will give us a high-confidence match when a user moves a device to
404 // another USB/Firewire/Thunderbolt/etc port, but only works if the device
405 // has a hardware id.
406 it = std::find_if(ports_.begin(), ports_.end(), [&port](MidiPort* p) {
407 return p->MatchCardPass2(port);
409 if (it != ports_.end())
410 return it;
412 } else {
413 // Else, we have a non-card-based client.
414 // Pass 1. Match on client_id, port_id, client_name, port_name.
415 // This will give us a reasonably good match.
416 auto it = std::find_if(ports_.begin(), ports_.end(), [&port](MidiPort* p) {
417 return p->MatchNoCardPass1(port);
419 if (it != ports_.end())
420 return it;
422 // Pass 2. Match on port_id, client_name, port_name.
423 // This is weaker but similar to pass 2 in the hardware card-based clients
424 // match.
425 it = std::find_if(ports_.begin(), ports_.end(), [&port](MidiPort* p) {
426 return p->MatchNoCardPass2(port);
428 if (it != ports_.end())
429 return it;
432 // No match.
433 return ports_.end();
436 MidiManagerAlsa::MidiPortStateBase::MidiPortStateBase() {
439 void MidiManagerAlsa::TemporaryMidiPortState::Insert(
440 scoped_ptr<MidiPort> port) {
441 ports()->push_back(port.Pass());
444 MidiManagerAlsa::MidiPortState::MidiPortState()
445 : num_input_ports_(0), num_output_ports_(0) {
448 uint32 MidiManagerAlsa::MidiPortState::Insert(scoped_ptr<MidiPort> port) {
449 // Add the web midi index.
450 uint32 web_port_index = 0;
451 switch (port->type()) {
452 case MidiPort::Type::kInput:
453 web_port_index = num_input_ports_++;
454 break;
455 case MidiPort::Type::kOutput:
456 web_port_index = num_output_ports_++;
457 break;
459 port->set_web_port_index(web_port_index);
460 ports()->push_back(port.Pass());
461 return web_port_index;
464 MidiManagerAlsa::AlsaSeqState::AlsaSeqState() : clients_deleter_(&clients_) {
467 MidiManagerAlsa::AlsaSeqState::~AlsaSeqState() {
470 void MidiManagerAlsa::AlsaSeqState::ClientStart(int client_id,
471 const std::string& client_name,
472 snd_seq_client_type_t type) {
473 ClientExit(client_id);
474 clients_[client_id] = new Client(client_name, type);
477 bool MidiManagerAlsa::AlsaSeqState::ClientStarted(int client_id) {
478 return clients_.find(client_id) != clients_.end();
481 void MidiManagerAlsa::AlsaSeqState::ClientExit(int client_id) {
482 auto it = clients_.find(client_id);
483 if (it != clients_.end()) {
484 delete it->second;
485 clients_.erase(it);
489 void MidiManagerAlsa::AlsaSeqState::PortStart(
490 int client_id,
491 int port_id,
492 const std::string& port_name,
493 MidiManagerAlsa::AlsaSeqState::PortDirection direction,
494 bool midi) {
495 auto it = clients_.find(client_id);
496 if (it != clients_.end())
497 it->second->AddPort(port_id,
498 scoped_ptr<Port>(new Port(port_name, direction, midi)));
501 void MidiManagerAlsa::AlsaSeqState::PortExit(int client_id, int port_id) {
502 auto it = clients_.find(client_id);
503 if (it != clients_.end())
504 it->second->RemovePort(port_id);
507 snd_seq_client_type_t MidiManagerAlsa::AlsaSeqState::ClientType(
508 int client_id) const {
509 auto it = clients_.find(client_id);
510 if (it == clients_.end())
511 return SND_SEQ_USER_CLIENT;
512 return it->second->type();
515 scoped_ptr<MidiManagerAlsa::TemporaryMidiPortState>
516 MidiManagerAlsa::AlsaSeqState::ToMidiPortState() {
517 scoped_ptr<MidiManagerAlsa::TemporaryMidiPortState> midi_ports(
518 new TemporaryMidiPortState);
519 // TODO(agoode): Use information from udev as well.
521 for (const auto& client_pair : clients_) {
522 int client_id = client_pair.first;
523 const auto& client = client_pair.second;
525 // Get client metadata.
526 const std::string client_name = client->name();
527 std::string manufacturer;
528 std::string driver;
529 std::string path;
530 std::string id;
531 std::string serial;
532 std::string card_name;
533 std::string card_longname;
534 int midi_device = -1;
536 for (const auto& port_pair : *client) {
537 int port_id = port_pair.first;
538 const auto& port = port_pair.second;
540 if (port->midi()) {
541 std::string version;
542 if (!driver.empty()) {
543 version = driver + " / ";
545 version +=
546 base::StringPrintf("ALSA library version %d.%d.%d", SND_LIB_MAJOR,
547 SND_LIB_MINOR, SND_LIB_SUBMINOR);
548 PortDirection direction = port->direction();
549 if (direction == PortDirection::kInput ||
550 direction == PortDirection::kDuplex) {
551 midi_ports->Insert(scoped_ptr<MidiPort>(new MidiPort(
552 path, id, client_id, port_id, midi_device, client->name(),
553 port->name(), manufacturer, version, MidiPort::Type::kInput)));
555 if (direction == PortDirection::kOutput ||
556 direction == PortDirection::kDuplex) {
557 midi_ports->Insert(scoped_ptr<MidiPort>(new MidiPort(
558 path, id, client_id, port_id, midi_device, client->name(),
559 port->name(), manufacturer, version, MidiPort::Type::kOutput)));
565 return midi_ports.Pass();
568 MidiManagerAlsa::AlsaSeqState::Port::Port(
569 const std::string& name,
570 MidiManagerAlsa::AlsaSeqState::PortDirection direction,
571 bool midi)
572 : name_(name), direction_(direction), midi_(midi) {
575 MidiManagerAlsa::AlsaSeqState::Port::~Port() {
578 std::string MidiManagerAlsa::AlsaSeqState::Port::name() const {
579 return name_;
582 MidiManagerAlsa::AlsaSeqState::PortDirection
583 MidiManagerAlsa::AlsaSeqState::Port::direction() const {
584 return direction_;
587 bool MidiManagerAlsa::AlsaSeqState::Port::midi() const {
588 return midi_;
591 MidiManagerAlsa::AlsaSeqState::Client::Client(const std::string& name,
592 snd_seq_client_type_t type)
593 : name_(name), type_(type), ports_deleter_(&ports_) {
596 MidiManagerAlsa::AlsaSeqState::Client::~Client() {
599 std::string MidiManagerAlsa::AlsaSeqState::Client::name() const {
600 return name_;
603 snd_seq_client_type_t MidiManagerAlsa::AlsaSeqState::Client::type() const {
604 return type_;
607 void MidiManagerAlsa::AlsaSeqState::Client::AddPort(int addr,
608 scoped_ptr<Port> port) {
609 RemovePort(addr);
610 ports_[addr] = port.release();
613 void MidiManagerAlsa::AlsaSeqState::Client::RemovePort(int addr) {
614 auto it = ports_.find(addr);
615 if (it != ports_.end()) {
616 delete it->second;
617 ports_.erase(it);
621 MidiManagerAlsa::AlsaSeqState::Client::PortMap::const_iterator
622 MidiManagerAlsa::AlsaSeqState::Client::begin() const {
623 return ports_.begin();
626 MidiManagerAlsa::AlsaSeqState::Client::PortMap::const_iterator
627 MidiManagerAlsa::AlsaSeqState::Client::end() const {
628 return ports_.end();
631 // static
632 std::string MidiManagerAlsa::ExtractManufacturerString(
633 const std::string& udev_id_vendor,
634 const std::string& udev_id_vendor_id,
635 const std::string& udev_id_vendor_from_database,
636 const std::string& alsa_name,
637 const std::string& alsa_longname) {
638 // Let's try to determine the manufacturer. Here is the ordered preference
639 // in extraction:
640 // 1. Vendor name from the hardware device string, from udev properties
641 // or sysattrs.
642 // 2. Vendor name from the udev database (property ID_VENDOR_FROM_DATABASE).
643 // 3. Heuristic from ALSA.
645 // Is the vendor string present and not just the vendor hex id?
646 if (!udev_id_vendor.empty() && (udev_id_vendor != udev_id_vendor_id)) {
647 return udev_id_vendor;
650 // Is there a vendor string in the hardware database?
651 if (!udev_id_vendor_from_database.empty()) {
652 return udev_id_vendor_from_database;
655 // Ok, udev gave us nothing useful, or was unavailable. So try a heuristic.
656 // We assume that card longname is in the format of
657 // "<manufacturer> <name> at <bus>". Otherwise, we give up to detect
658 // a manufacturer name here.
659 size_t at_index = alsa_longname.rfind(" at ");
660 if (at_index && at_index != std::string::npos) {
661 size_t name_index = alsa_longname.rfind(alsa_name, at_index - 1);
662 if (name_index && name_index != std::string::npos)
663 return alsa_longname.substr(0, name_index - 1);
666 // Failure.
667 return "";
670 void MidiManagerAlsa::SendMidiData(uint32 port_index,
671 const std::vector<uint8>& data) {
672 DCHECK(send_thread_.message_loop_proxy()->BelongsToCurrentThread());
674 snd_midi_event_t* encoder;
675 snd_midi_event_new(kSendBufferSize, &encoder);
676 for (unsigned int i = 0; i < data.size(); i++) {
677 snd_seq_event_t event;
678 int result = snd_midi_event_encode_byte(encoder, data[i], &event);
679 if (result == 1) {
680 // Full event, send it.
681 base::AutoLock lock(out_ports_lock_);
682 auto it = out_ports_.find(port_index);
683 if (it != out_ports_.end()) {
684 snd_seq_ev_set_source(&event, it->second);
685 snd_seq_ev_set_subs(&event);
686 snd_seq_ev_set_direct(&event);
687 snd_seq_event_output_direct(out_client_, &event);
691 snd_midi_event_free(encoder);
694 void MidiManagerAlsa::ScheduleEventLoop() {
695 event_thread_.message_loop()->PostTask(
696 FROM_HERE,
697 base::Bind(&MidiManagerAlsa::EventLoop, base::Unretained(this)));
700 void MidiManagerAlsa::EventLoop() {
701 // Read available incoming MIDI data.
702 snd_seq_event_t* event;
703 int err = snd_seq_event_input(in_client_, &event);
704 double timestamp = (base::TimeTicks::Now() - base::TimeTicks()).InSecondsF();
706 // Handle errors.
707 if (err == -ENOSPC) {
708 VLOG(1) << "snd_seq_event_input detected buffer overrun";
709 // We've lost events: check another way to see if we need to shut down.
710 base::AutoLock lock(shutdown_lock_);
711 if (!event_thread_shutdown_)
712 ScheduleEventLoop();
713 return;
714 } else if (err < 0) {
715 VLOG(1) << "snd_seq_event_input fails: " << snd_strerror(err);
716 // TODO(agoode): Use RecordAction() or similar to log this.
717 return;
720 // Handle announce events.
721 if (event->source.client == SND_SEQ_CLIENT_SYSTEM &&
722 event->source.port == SND_SEQ_PORT_SYSTEM_ANNOUNCE) {
723 switch (event->type) {
724 case SND_SEQ_EVENT_PORT_START:
725 // Don't use SND_SEQ_EVENT_CLIENT_START because the client name may not
726 // be set by the time we query it. It should be set by the time ports
727 // are made.
728 ProcessClientStartEvent(event->data.addr.client);
729 ProcessPortStartEvent(event->data.addr);
730 break;
732 case SND_SEQ_EVENT_CLIENT_EXIT:
733 // Check for disconnection of our "out" client. This means "shut down".
734 if (event->data.addr.client == out_client_id_)
735 return;
737 ProcessClientExitEvent(event->data.addr);
738 break;
740 case SND_SEQ_EVENT_PORT_EXIT:
741 ProcessPortExitEvent(event->data.addr);
742 break;
744 } else {
745 ProcessSingleEvent(event, timestamp);
748 // Do again.
749 ScheduleEventLoop();
752 void MidiManagerAlsa::ProcessSingleEvent(snd_seq_event_t* event,
753 double timestamp) {
754 auto source_it =
755 source_map_.find(AddrToInt(event->source.client, event->source.port));
756 if (source_it != source_map_.end()) {
757 uint32 source = source_it->second;
758 if (event->type == SND_SEQ_EVENT_SYSEX) {
759 // Special! Variable-length sysex.
760 ReceiveMidiData(source, static_cast<const uint8*>(event->data.ext.ptr),
761 event->data.ext.len, timestamp);
762 } else {
763 // Otherwise, decode this and send that on.
764 unsigned char buf[12];
765 long count = snd_midi_event_decode(decoder_, buf, sizeof(buf), event);
766 if (count <= 0) {
767 if (count != -ENOENT) {
768 // ENOENT means that it's not a MIDI message, which is not an
769 // error, but other negative values are errors for us.
770 VLOG(1) << "snd_midi_event_decoder fails " << snd_strerror(count);
771 // TODO(agoode): Record this failure.
773 } else {
774 ReceiveMidiData(source, buf, count, timestamp);
780 void MidiManagerAlsa::ProcessClientStartEvent(int client_id) {
781 // Ignore if client is already started.
782 if (alsa_seq_state_.ClientStarted(client_id))
783 return;
785 snd_seq_client_info_t* client_info;
786 snd_seq_client_info_alloca(&client_info);
787 int err = snd_seq_get_any_client_info(in_client_, client_id, client_info);
788 if (err != 0)
789 return;
791 // Skip our own clients.
792 if ((client_id == in_client_id_) || (client_id == out_client_id_))
793 return;
795 // Update our view of ALSA seq state.
796 alsa_seq_state_.ClientStart(client_id,
797 snd_seq_client_info_get_name(client_info),
798 snd_seq_client_info_get_type(client_info));
800 // Generate Web MIDI events.
801 UpdatePortStateAndGenerateEvents();
804 void MidiManagerAlsa::ProcessPortStartEvent(const snd_seq_addr_t& addr) {
805 snd_seq_port_info_t* port_info;
806 snd_seq_port_info_alloca(&port_info);
807 int err =
808 snd_seq_get_any_port_info(in_client_, addr.client, addr.port, port_info);
809 if (err != 0)
810 return;
812 unsigned int caps = snd_seq_port_info_get_capability(port_info);
813 bool input = (caps & kRequiredInputPortCaps) == kRequiredInputPortCaps;
814 bool output = (caps & kRequiredOutputPortCaps) == kRequiredOutputPortCaps;
815 AlsaSeqState::PortDirection direction;
816 if (input && output)
817 direction = AlsaSeqState::PortDirection::kDuplex;
818 else if (input)
819 direction = AlsaSeqState::PortDirection::kInput;
820 else if (output)
821 direction = AlsaSeqState::PortDirection::kOutput;
822 else
823 return;
825 // Update our view of ALSA seq state.
826 alsa_seq_state_.PortStart(
827 addr.client, addr.port, snd_seq_port_info_get_name(port_info), direction,
828 snd_seq_port_info_get_type(port_info) & SND_SEQ_PORT_TYPE_MIDI_GENERIC);
829 // Generate Web MIDI events.
830 UpdatePortStateAndGenerateEvents();
833 void MidiManagerAlsa::ProcessClientExitEvent(const snd_seq_addr_t& addr) {
834 // Update our view of ALSA seq state.
835 alsa_seq_state_.ClientExit(addr.client);
836 // Generate Web MIDI events.
837 UpdatePortStateAndGenerateEvents();
840 void MidiManagerAlsa::ProcessPortExitEvent(const snd_seq_addr_t& addr) {
841 // Update our view of ALSA seq state.
842 alsa_seq_state_.PortExit(addr.client, addr.port);
843 // Generate Web MIDI events.
844 UpdatePortStateAndGenerateEvents();
847 void MidiManagerAlsa::UpdatePortStateAndGenerateEvents() {
848 // Generate new port state.
849 auto new_port_state = alsa_seq_state_.ToMidiPortState();
851 // Disconnect any connected old ports that are now missing.
852 for (auto* old_port : port_state_) {
853 if (old_port->connected() &&
854 (new_port_state->FindConnected(*old_port) == new_port_state->end())) {
855 old_port->set_connected(false);
856 uint32 web_port_index = old_port->web_port_index();
857 switch (old_port->type()) {
858 case MidiPort::Type::kInput:
859 source_map_.erase(
860 AddrToInt(old_port->client_id(), old_port->port_id()));
861 SetInputPortState(web_port_index, MIDI_PORT_DISCONNECTED);
862 break;
863 case MidiPort::Type::kOutput:
864 DeleteAlsaOutputPort(web_port_index);
865 SetOutputPortState(web_port_index, MIDI_PORT_DISCONNECTED);
866 break;
871 // Reconnect or add new ports.
872 auto it = new_port_state->begin();
873 while (it != new_port_state->end()) {
874 auto* new_port = *it;
875 auto old_port = port_state_.Find(*new_port);
876 if (old_port == port_state_.end()) {
877 // Add new port.
878 uint32 web_port_index =
879 port_state_.Insert(scoped_ptr<MidiPort>(new_port));
880 MidiPortInfo info(new_port->OpaqueKey(), new_port->manufacturer(),
881 new_port->port_name(), new_port->version(),
882 MIDI_PORT_OPENED);
883 switch (new_port->type()) {
884 case MidiPort::Type::kInput:
885 if (Subscribe(web_port_index, new_port->client_id(),
886 new_port->port_id()))
887 AddInputPort(info);
888 break;
890 case MidiPort::Type::kOutput:
891 if (CreateAlsaOutputPort(web_port_index, new_port->client_id(),
892 new_port->port_id()))
893 AddOutputPort(info);
894 break;
896 it = new_port_state->weak_erase(it);
897 } else if (!(*old_port)->connected()) {
898 // Reconnect.
899 uint32 web_port_index = (*old_port)->web_port_index();
900 (*old_port)->Update(new_port->path(), new_port->client_id(),
901 new_port->port_id(), new_port->client_name(),
902 new_port->port_name(), new_port->manufacturer(),
903 new_port->version());
904 switch ((*old_port)->type()) {
905 case MidiPort::Type::kInput:
906 if (Subscribe(web_port_index, (*old_port)->client_id(),
907 (*old_port)->port_id()))
908 SetInputPortState(web_port_index, MIDI_PORT_OPENED);
909 break;
911 case MidiPort::Type::kOutput:
912 if (CreateAlsaOutputPort(web_port_index, (*old_port)->client_id(),
913 (*old_port)->port_id()))
914 SetOutputPortState(web_port_index, MIDI_PORT_OPENED);
915 break;
917 (*old_port)->set_connected(true);
918 ++it;
919 } else {
920 ++it;
925 void MidiManagerAlsa::EnumerateAlsaPorts() {
926 snd_seq_client_info_t* client_info;
927 snd_seq_client_info_alloca(&client_info);
928 snd_seq_port_info_t* port_info;
929 snd_seq_port_info_alloca(&port_info);
931 // Enumerate clients.
932 snd_seq_client_info_set_client(client_info, -1);
933 while (!snd_seq_query_next_client(in_client_, client_info)) {
934 int client_id = snd_seq_client_info_get_client(client_info);
935 ProcessClientStartEvent(client_id);
937 // Enumerate ports.
938 snd_seq_port_info_set_client(port_info, client_id);
939 snd_seq_port_info_set_port(port_info, -1);
940 while (!snd_seq_query_next_port(in_client_, port_info)) {
941 const snd_seq_addr_t* addr = snd_seq_port_info_get_addr(port_info);
942 ProcessPortStartEvent(*addr);
947 bool MidiManagerAlsa::CreateAlsaOutputPort(uint32 port_index,
948 int client_id,
949 int port_id) {
950 // Create the port.
951 int out_port = snd_seq_create_simple_port(
952 out_client_, NULL, kCreateOutputPortCaps, kCreatePortType);
953 if (out_port < 0) {
954 VLOG(1) << "snd_seq_create_simple_port fails: " << snd_strerror(out_port);
955 return false;
957 // Activate port subscription.
958 snd_seq_port_subscribe_t* subs;
959 snd_seq_port_subscribe_alloca(&subs);
960 snd_seq_addr_t sender;
961 sender.client = out_client_id_;
962 sender.port = out_port;
963 snd_seq_port_subscribe_set_sender(subs, &sender);
964 snd_seq_addr_t dest;
965 dest.client = client_id;
966 dest.port = port_id;
967 snd_seq_port_subscribe_set_dest(subs, &dest);
968 int err = snd_seq_subscribe_port(out_client_, subs);
969 if (err != 0) {
970 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
971 snd_seq_delete_simple_port(out_client_, out_port);
972 return false;
975 // Update our map.
976 base::AutoLock lock(out_ports_lock_);
977 out_ports_[port_index] = out_port;
978 return true;
981 void MidiManagerAlsa::DeleteAlsaOutputPort(uint32 port_index) {
982 base::AutoLock lock(out_ports_lock_);
983 auto it = out_ports_.find(port_index);
984 if (it == out_ports_.end())
985 return;
987 int alsa_port = it->second;
988 snd_seq_delete_simple_port(out_client_, alsa_port);
989 out_ports_.erase(it);
992 bool MidiManagerAlsa::Subscribe(uint32 port_index, int client_id, int port_id) {
993 // Activate port subscription.
994 snd_seq_port_subscribe_t* subs;
995 snd_seq_port_subscribe_alloca(&subs);
996 snd_seq_addr_t sender;
997 sender.client = client_id;
998 sender.port = port_id;
999 snd_seq_port_subscribe_set_sender(subs, &sender);
1000 snd_seq_addr_t dest;
1001 dest.client = in_client_id_;
1002 dest.port = in_port_id_;
1003 snd_seq_port_subscribe_set_dest(subs, &dest);
1004 int err = snd_seq_subscribe_port(in_client_, subs);
1005 if (err != 0) {
1006 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
1007 return false;
1010 // Update our map.
1011 source_map_[AddrToInt(client_id, port_id)] = port_index;
1012 return true;
1015 MidiManager* MidiManager::Create() {
1016 return new MidiManagerAlsa();
1019 } // namespace media