Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / media / midi / midi_manager_alsa.cc
blobaf618efdb27d99859b84837a6f974504ce57d015
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 <poll.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/message_loop/message_loop.h"
16 #include "base/posix/eintr_wrapper.h"
17 #include "base/posix/safe_strerror.h"
18 #include "base/strings/string_number_conversions.h"
19 #include "base/strings/stringprintf.h"
20 #include "base/time/time.h"
21 #include "crypto/sha2.h"
22 #include "media/midi/midi_port_info.h"
24 namespace media {
25 namespace midi {
27 namespace {
29 // Per-output buffer. This can be smaller, but then large sysex messages
30 // will be (harmlessly) split across multiple seq events. This should
31 // not have any real practical effect, except perhaps to slightly reorder
32 // realtime messages with respect to sysex.
33 const size_t kSendBufferSize = 256;
35 // Minimum client id for which we will have ALSA card devices for. When we
36 // are searching for card devices (used to get the path, id, and manufacturer),
37 // we don't want to get confused by kernel clients that do not have a card.
38 // See seq_clientmgr.c in the ALSA code for this.
39 // TODO(agoode): Add proper client -> card export from the kernel to avoid
40 // hardcoding.
41 const int kMinimumClientIdForCards = 16;
43 // ALSA constants.
44 const char kAlsaHw[] = "hw";
46 // udev constants.
47 const char kUdev[] = "udev";
48 const char kUdevSubsystemSound[] = "sound";
49 const char kUdevPropertySoundInitialized[] = "SOUND_INITIALIZED";
50 const char kUdevActionChange[] = "change";
51 const char kUdevActionRemove[] = "remove";
53 const char kUdevIdVendor[] = "ID_VENDOR";
54 const char kUdevIdVendorEnc[] = "ID_VENDOR_ENC";
55 const char kUdevIdVendorFromDatabase[] = "ID_VENDOR_FROM_DATABASE";
56 const char kUdevIdVendorId[] = "ID_VENDOR_ID";
57 const char kUdevIdModelId[] = "ID_MODEL_ID";
58 const char kUdevIdBus[] = "ID_BUS";
59 const char kUdevIdPath[] = "ID_PATH";
60 const char kUdevIdUsbInterfaceNum[] = "ID_USB_INTERFACE_NUM";
61 const char kUdevIdSerialShort[] = "ID_SERIAL_SHORT";
63 const char kSysattrVendorName[] = "vendor_name";
64 const char kSysattrVendor[] = "vendor";
65 const char kSysattrModel[] = "model";
66 const char kSysattrGuid[] = "guid";
68 const char kCardSyspath[] = "/card";
70 // Constants for the capabilities we search for in inputs and outputs.
71 // See http://www.alsa-project.org/alsa-doc/alsa-lib/seq.html.
72 const unsigned int kRequiredInputPortCaps =
73 SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ;
74 const unsigned int kRequiredOutputPortCaps =
75 SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE;
77 const unsigned int kCreateOutputPortCaps =
78 SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_NO_EXPORT;
79 const unsigned int kCreateInputPortCaps =
80 SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_NO_EXPORT;
81 const unsigned int kCreatePortType =
82 SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION;
84 int AddrToInt(int client, int port) {
85 return (client << 8) | port;
88 // Returns true if this client has an ALSA card associated with it.
89 bool IsCardClient(snd_seq_client_type_t type, int client_id) {
90 return (type == SND_SEQ_KERNEL_CLIENT) &&
91 (client_id >= kMinimumClientIdForCards);
94 // TODO(agoode): Move this to device/udev_linux.
95 const std::string UdevDeviceGetPropertyOrSysattr(
96 struct udev_device* udev_device,
97 const char* property_key,
98 const char* sysattr_key) {
99 // First try the property.
100 std::string value =
101 device::UdevDeviceGetPropertyValue(udev_device, property_key);
103 // If no property, look for sysattrs and walk up the parent devices too.
104 while (value.empty() && udev_device) {
105 value = device::UdevDeviceGetSysattrValue(udev_device, sysattr_key);
106 udev_device = device::udev_device_get_parent(udev_device);
108 return value;
111 int GetCardNumber(udev_device* dev) {
112 const char* syspath = device::udev_device_get_syspath(dev);
113 if (!syspath)
114 return -1;
116 std::string syspath_str(syspath);
117 size_t i = syspath_str.rfind(kCardSyspath);
118 if (i == std::string::npos)
119 return -1;
121 int number;
122 if (!base::StringToInt(syspath_str.substr(i + strlen(kCardSyspath)), &number))
123 return -1;
124 return number;
127 std::string GetVendor(udev_device* dev) {
128 // Try to get the vendor string. Sometimes it is encoded.
129 std::string vendor = device::UdevDecodeString(
130 device::UdevDeviceGetPropertyValue(dev, kUdevIdVendorEnc));
131 // Sometimes it is not encoded.
132 if (vendor.empty())
133 vendor =
134 UdevDeviceGetPropertyOrSysattr(dev, kUdevIdVendor, kSysattrVendorName);
135 return vendor;
138 void SetStringIfNonEmpty(base::DictionaryValue* value,
139 const std::string& path,
140 const std::string& in_value) {
141 if (!in_value.empty())
142 value->SetString(path, in_value);
145 } // namespace
147 MidiManagerAlsa::MidiManagerAlsa()
148 : udev_(device::udev_new()),
149 send_thread_("MidiSendThread"),
150 event_thread_("MidiEventThread") {
151 // Initialize decoder.
152 snd_midi_event_t* decoder;
153 snd_midi_event_new(0, &decoder);
154 decoder_.reset(decoder);
155 snd_midi_event_no_status(decoder_.get(), 1);
158 MidiManagerAlsa::~MidiManagerAlsa() {
159 // Tell the event thread it will soon be time to shut down. This gives
160 // us assurance the thread will stop in case the SND_SEQ_EVENT_CLIENT_EXIT
161 // message is lost.
163 base::AutoLock lock(shutdown_lock_);
164 event_thread_shutdown_ = true;
167 // Stop the send thread.
168 send_thread_.Stop();
170 // Close the out client. This will trigger the event thread to stop,
171 // because of SND_SEQ_EVENT_CLIENT_EXIT.
172 if (out_client_.get())
173 snd_seq_close(out_client_.release());
175 // Wait for the event thread to stop.
176 event_thread_.Stop();
179 void MidiManagerAlsa::StartInitialization() {
180 // TODO(agoode): Move off I/O thread. See http://crbug.com/374341.
182 // Create client handles.
183 snd_seq_t* in_client;
184 int err =
185 snd_seq_open(&in_client, kAlsaHw, SND_SEQ_OPEN_INPUT, SND_SEQ_NONBLOCK);
186 if (err != 0) {
187 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err);
188 return CompleteInitialization(Result::INITIALIZATION_ERROR);
190 in_client_.reset(in_client);
191 in_client_id_ = snd_seq_client_id(in_client_.get());
193 snd_seq_t* out_client;
194 err = snd_seq_open(&out_client, kAlsaHw, SND_SEQ_OPEN_OUTPUT, 0);
195 if (err != 0) {
196 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err);
197 return CompleteInitialization(Result::INITIALIZATION_ERROR);
199 out_client_.reset(out_client);
200 out_client_id_ = snd_seq_client_id(out_client_.get());
202 // Name the clients.
203 err = snd_seq_set_client_name(in_client_.get(), "Chrome (input)");
204 if (err != 0) {
205 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err);
206 return CompleteInitialization(Result::INITIALIZATION_ERROR);
208 err = snd_seq_set_client_name(out_client_.get(), "Chrome (output)");
209 if (err != 0) {
210 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err);
211 return CompleteInitialization(Result::INITIALIZATION_ERROR);
214 // Create input port.
215 in_port_id_ = snd_seq_create_simple_port(
216 in_client_.get(), NULL, kCreateInputPortCaps, kCreatePortType);
217 if (in_port_id_ < 0) {
218 VLOG(1) << "snd_seq_create_simple_port fails: "
219 << snd_strerror(in_port_id_);
220 return CompleteInitialization(Result::INITIALIZATION_ERROR);
223 // Subscribe to the announce port.
224 snd_seq_port_subscribe_t* subs;
225 snd_seq_port_subscribe_alloca(&subs);
226 snd_seq_addr_t announce_sender;
227 snd_seq_addr_t announce_dest;
228 announce_sender.client = SND_SEQ_CLIENT_SYSTEM;
229 announce_sender.port = SND_SEQ_PORT_SYSTEM_ANNOUNCE;
230 announce_dest.client = in_client_id_;
231 announce_dest.port = in_port_id_;
232 snd_seq_port_subscribe_set_sender(subs, &announce_sender);
233 snd_seq_port_subscribe_set_dest(subs, &announce_dest);
234 err = snd_seq_subscribe_port(in_client_.get(), subs);
235 if (err != 0) {
236 VLOG(1) << "snd_seq_subscribe_port on the announce port fails: "
237 << snd_strerror(err);
238 return CompleteInitialization(Result::INITIALIZATION_ERROR);
241 // Generate hotplug events for existing ports.
242 // TODO(agoode): Check the return value for failure.
243 EnumerateAlsaPorts();
245 // Initialize udev monitor.
246 udev_monitor_.reset(
247 device::udev_monitor_new_from_netlink(udev_.get(), kUdev));
248 if (!udev_monitor_.get()) {
249 VLOG(1) << "udev_monitor_new_from_netlink fails";
250 return CompleteInitialization(Result::INITIALIZATION_ERROR);
252 err = device::udev_monitor_filter_add_match_subsystem_devtype(
253 udev_monitor_.get(), kUdevSubsystemSound, nullptr);
254 if (err != 0) {
255 VLOG(1) << "udev_monitor_add_match_subsystem fails: "
256 << base::safe_strerror(-err);
257 return CompleteInitialization(Result::INITIALIZATION_ERROR);
259 err = device::udev_monitor_enable_receiving(udev_monitor_.get());
260 if (err != 0) {
261 VLOG(1) << "udev_monitor_enable_receiving fails: "
262 << base::safe_strerror(-err);
263 return CompleteInitialization(Result::INITIALIZATION_ERROR);
266 // Generate hotplug events for existing udev devices.
267 EnumerateUdevCards();
269 // Start processing events.
270 event_thread_.Start();
271 event_thread_.message_loop()->PostTask(
272 FROM_HERE,
273 base::Bind(&MidiManagerAlsa::ScheduleEventLoop, base::Unretained(this)));
275 CompleteInitialization(Result::OK);
278 void MidiManagerAlsa::DispatchSendMidiData(MidiManagerClient* client,
279 uint32 port_index,
280 const std::vector<uint8>& data,
281 double timestamp) {
282 // Not correct right now. http://crbug.com/374341.
283 if (!send_thread_.IsRunning())
284 send_thread_.Start();
286 base::TimeDelta delay;
287 if (timestamp != 0.0) {
288 base::TimeTicks time_to_send =
289 base::TimeTicks() + base::TimeDelta::FromMicroseconds(
290 timestamp * base::Time::kMicrosecondsPerSecond);
291 delay = std::max(time_to_send - base::TimeTicks::Now(), base::TimeDelta());
294 send_thread_.message_loop()->PostDelayedTask(
295 FROM_HERE, base::Bind(&MidiManagerAlsa::SendMidiData,
296 base::Unretained(this), port_index, data),
297 delay);
299 // Acknowledge send.
300 send_thread_.message_loop()->PostTask(
301 FROM_HERE, base::Bind(&MidiManagerClient::AccumulateMidiBytesSent,
302 base::Unretained(client), data.size()));
305 MidiManagerAlsa::MidiPort::Id::Id() = default;
307 MidiManagerAlsa::MidiPort::Id::Id(const std::string& bus,
308 const std::string& vendor_id,
309 const std::string& model_id,
310 const std::string& usb_interface_num,
311 const std::string& serial)
312 : bus_(bus),
313 vendor_id_(vendor_id),
314 model_id_(model_id),
315 usb_interface_num_(usb_interface_num),
316 serial_(serial) {
319 MidiManagerAlsa::MidiPort::Id::Id(const Id&) = default;
321 MidiManagerAlsa::MidiPort::Id::~Id() = default;
323 bool MidiManagerAlsa::MidiPort::Id::operator==(const Id& rhs) const {
324 return (bus_ == rhs.bus_) && (vendor_id_ == rhs.vendor_id_) &&
325 (model_id_ == rhs.model_id_) &&
326 (usb_interface_num_ == rhs.usb_interface_num_) &&
327 (serial_ == rhs.serial_);
330 bool MidiManagerAlsa::MidiPort::Id::empty() const {
331 return bus_.empty() && vendor_id_.empty() && model_id_.empty() &&
332 usb_interface_num_.empty() && serial_.empty();
335 MidiManagerAlsa::MidiPort::MidiPort(const std::string& path,
336 const Id& id,
337 int client_id,
338 int port_id,
339 int midi_device,
340 const std::string& client_name,
341 const std::string& port_name,
342 const std::string& manufacturer,
343 const std::string& version,
344 Type type)
345 : id_(id),
346 midi_device_(midi_device),
347 type_(type),
348 path_(path),
349 client_id_(client_id),
350 port_id_(port_id),
351 client_name_(client_name),
352 port_name_(port_name),
353 manufacturer_(manufacturer),
354 version_(version) {
357 MidiManagerAlsa::MidiPort::~MidiPort() = default;
359 // Note: keep synchronized with the MidiPort::Match* methods.
360 scoped_ptr<base::Value> MidiManagerAlsa::MidiPort::Value() const {
361 scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue);
363 std::string type;
364 switch (type_) {
365 case Type::kInput:
366 type = "input";
367 break;
368 case Type::kOutput:
369 type = "output";
370 break;
372 value->SetString("type", type);
373 SetStringIfNonEmpty(value.get(), "path", path_);
374 SetStringIfNonEmpty(value.get(), "clientName", client_name_);
375 SetStringIfNonEmpty(value.get(), "portName", port_name_);
376 value->SetInteger("clientId", client_id_);
377 value->SetInteger("portId", port_id_);
378 value->SetInteger("midiDevice", midi_device_);
380 // Flatten id fields.
381 SetStringIfNonEmpty(value.get(), "bus", id_.bus());
382 SetStringIfNonEmpty(value.get(), "vendorId", id_.vendor_id());
383 SetStringIfNonEmpty(value.get(), "modelId", id_.model_id());
384 SetStringIfNonEmpty(value.get(), "usbInterfaceNum", id_.usb_interface_num());
385 SetStringIfNonEmpty(value.get(), "serial", id_.serial());
387 return value.Pass();
390 std::string MidiManagerAlsa::MidiPort::JSONValue() const {
391 std::string json;
392 JSONStringValueSerializer serializer(&json);
393 serializer.Serialize(*Value().get());
394 return json;
397 // TODO(agoode): Do not use SHA256 here. Instead store a persistent
398 // mapping and just use a UUID or other random string.
399 // http://crbug.com/465320
400 std::string MidiManagerAlsa::MidiPort::OpaqueKey() const {
401 uint8 hash[crypto::kSHA256Length];
402 crypto::SHA256HashString(JSONValue(), &hash, sizeof(hash));
403 return base::HexEncode(&hash, sizeof(hash));
406 bool MidiManagerAlsa::MidiPort::MatchConnected(const MidiPort& query) const {
407 // Matches on:
408 // connected == true
409 // type
410 // path
411 // id
412 // client_id
413 // port_id
414 // midi_device
415 // client_name
416 // port_name
417 return connected() && (type() == query.type()) && (path() == query.path()) &&
418 (id() == query.id()) && (client_id() == query.client_id()) &&
419 (port_id() == query.port_id()) &&
420 (midi_device() == query.midi_device()) &&
421 (client_name() == query.client_name()) &&
422 (port_name() == query.port_name());
425 bool MidiManagerAlsa::MidiPort::MatchCardPass1(const MidiPort& query) const {
426 // Matches on:
427 // connected == false
428 // type
429 // path
430 // id
431 // port_id
432 // midi_device
433 return MatchCardPass2(query) && (path() == query.path());
436 bool MidiManagerAlsa::MidiPort::MatchCardPass2(const MidiPort& query) const {
437 // Matches on:
438 // connected == false
439 // type
440 // id
441 // port_id
442 // midi_device
443 return !connected() && (type() == query.type()) && (id() == query.id()) &&
444 (port_id() == query.port_id()) &&
445 (midi_device() == query.midi_device());
448 bool MidiManagerAlsa::MidiPort::MatchNoCardPass1(const MidiPort& query) const {
449 // Matches on:
450 // connected == false
451 // type
452 // path.empty(), for both this and query
453 // id.empty(), for both this and query
454 // client_id
455 // port_id
456 // client_name
457 // port_name
458 // midi_device == -1, for both this and query
459 return MatchNoCardPass2(query) && (client_id() == query.client_id());
462 bool MidiManagerAlsa::MidiPort::MatchNoCardPass2(const MidiPort& query) const {
463 // Matches on:
464 // connected == false
465 // type
466 // path.empty(), for both this and query
467 // id.empty(), for both this and query
468 // port_id
469 // client_name
470 // port_name
471 // midi_device == -1, for both this and query
472 return !connected() && (type() == query.type()) && path().empty() &&
473 query.path().empty() && id().empty() && query.id().empty() &&
474 (port_id() == query.port_id()) &&
475 (client_name() == query.client_name()) &&
476 (port_name() == query.port_name()) && (midi_device() == -1) &&
477 (query.midi_device() == -1);
480 MidiManagerAlsa::MidiPortStateBase::~MidiPortStateBase() = default;
482 MidiManagerAlsa::MidiPortStateBase::iterator
483 MidiManagerAlsa::MidiPortStateBase::Find(
484 const MidiManagerAlsa::MidiPort& port) {
485 auto result = FindConnected(port);
486 if (result == end())
487 result = FindDisconnected(port);
488 return result;
491 MidiManagerAlsa::MidiPortStateBase::iterator
492 MidiManagerAlsa::MidiPortStateBase::FindConnected(
493 const MidiManagerAlsa::MidiPort& port) {
494 // Exact match required for connected ports.
495 auto it = std::find_if(ports_.begin(), ports_.end(), [&port](MidiPort* p) {
496 return p->MatchConnected(port);
498 return it;
501 MidiManagerAlsa::MidiPortStateBase::iterator
502 MidiManagerAlsa::MidiPortStateBase::FindDisconnected(
503 const MidiManagerAlsa::MidiPort& port) {
504 // Always match on:
505 // type
506 // Possible things to match on:
507 // path
508 // id
509 // client_id
510 // port_id
511 // midi_device
512 // client_name
513 // port_name
515 if (!port.path().empty()) {
516 // If path is present, then we have a card-based client.
518 // Pass 1. Match on path, id, midi_device, port_id.
519 // This is the best possible match for hardware card-based clients.
520 // This will also match the empty id correctly for devices without an id.
521 auto it = std::find_if(ports_.begin(), ports_.end(), [&port](MidiPort* p) {
522 return p->MatchCardPass1(port);
524 if (it != ports_.end())
525 return it;
527 if (!port.id().empty()) {
528 // Pass 2. Match on id, midi_device, port_id.
529 // This will give us a high-confidence match when a user moves a device to
530 // another USB/Firewire/Thunderbolt/etc port, but only works if the device
531 // has a hardware id.
532 it = std::find_if(ports_.begin(), ports_.end(), [&port](MidiPort* p) {
533 return p->MatchCardPass2(port);
535 if (it != ports_.end())
536 return it;
538 } else {
539 // Else, we have a non-card-based client.
540 // Pass 1. Match on client_id, port_id, client_name, port_name.
541 // This will give us a reasonably good match.
542 auto it = std::find_if(ports_.begin(), ports_.end(), [&port](MidiPort* p) {
543 return p->MatchNoCardPass1(port);
545 if (it != ports_.end())
546 return it;
548 // Pass 2. Match on port_id, client_name, port_name.
549 // This is weaker but similar to pass 2 in the hardware card-based clients
550 // match.
551 it = std::find_if(ports_.begin(), ports_.end(), [&port](MidiPort* p) {
552 return p->MatchNoCardPass2(port);
554 if (it != ports_.end())
555 return it;
558 // No match.
559 return ports_.end();
562 MidiManagerAlsa::MidiPortStateBase::MidiPortStateBase() = default;
564 void MidiManagerAlsa::TemporaryMidiPortState::Insert(
565 scoped_ptr<MidiPort> port) {
566 ports().push_back(port.release());
569 MidiManagerAlsa::MidiPortState::MidiPortState() = default;
571 uint32 MidiManagerAlsa::MidiPortState::Insert(scoped_ptr<MidiPort> port) {
572 // Add the web midi index.
573 uint32 web_port_index = 0;
574 switch (port->type()) {
575 case MidiPort::Type::kInput:
576 web_port_index = num_input_ports_++;
577 break;
578 case MidiPort::Type::kOutput:
579 web_port_index = num_output_ports_++;
580 break;
582 port->set_web_port_index(web_port_index);
583 ports().push_back(port.release());
584 return web_port_index;
587 MidiManagerAlsa::AlsaSeqState::AlsaSeqState() = default;
589 MidiManagerAlsa::AlsaSeqState::~AlsaSeqState() = default;
591 void MidiManagerAlsa::AlsaSeqState::ClientStart(int client_id,
592 const std::string& client_name,
593 snd_seq_client_type_t type) {
594 ClientExit(client_id);
595 clients_.insert(client_id, make_scoped_ptr(new Client(client_name, type)));
596 if (IsCardClient(type, client_id))
597 ++card_client_count_;
600 bool MidiManagerAlsa::AlsaSeqState::ClientStarted(int client_id) {
601 return clients_.find(client_id) != clients_.end();
604 void MidiManagerAlsa::AlsaSeqState::ClientExit(int client_id) {
605 auto it = clients_.find(client_id);
606 if (it != clients_.end()) {
607 if (IsCardClient(it->second->type(), client_id))
608 --card_client_count_;
609 clients_.erase(it);
613 void MidiManagerAlsa::AlsaSeqState::PortStart(
614 int client_id,
615 int port_id,
616 const std::string& port_name,
617 MidiManagerAlsa::AlsaSeqState::PortDirection direction,
618 bool midi) {
619 auto it = clients_.find(client_id);
620 if (it != clients_.end())
621 it->second->AddPort(port_id,
622 make_scoped_ptr(new Port(port_name, direction, midi)));
625 void MidiManagerAlsa::AlsaSeqState::PortExit(int client_id, int port_id) {
626 auto it = clients_.find(client_id);
627 if (it != clients_.end())
628 it->second->RemovePort(port_id);
631 snd_seq_client_type_t MidiManagerAlsa::AlsaSeqState::ClientType(
632 int client_id) const {
633 auto it = clients_.find(client_id);
634 if (it == clients_.end())
635 return SND_SEQ_USER_CLIENT;
636 return it->second->type();
639 scoped_ptr<MidiManagerAlsa::TemporaryMidiPortState>
640 MidiManagerAlsa::AlsaSeqState::ToMidiPortState(const AlsaCardMap& alsa_cards) {
641 scoped_ptr<MidiManagerAlsa::TemporaryMidiPortState> midi_ports(
642 new TemporaryMidiPortState);
643 auto card_it = alsa_cards.begin();
645 int card_midi_device = -1;
646 for (const auto& client_pair : clients_) {
647 int client_id = client_pair.first;
648 const auto& client = client_pair.second;
650 // Get client metadata.
651 const std::string client_name = client->name();
652 std::string manufacturer;
653 std::string driver;
654 std::string path;
655 MidiPort::Id id;
656 std::string card_name;
657 std::string card_longname;
658 int midi_device = -1;
660 if (IsCardClient(client->type(), client_id)) {
661 auto& card = card_it->second;
662 if (card_midi_device == -1)
663 card_midi_device = 0;
665 manufacturer = card->manufacturer();
666 path = card->path();
667 id = MidiPort::Id(card->bus(), card->vendor_id(), card->model_id(),
668 card->usb_interface_num(), card->serial());
669 card_name = card->name();
670 card_longname = card->longname();
671 midi_device = card_midi_device;
673 ++card_midi_device;
674 if (card_midi_device >= card->midi_device_count()) {
675 card_midi_device = -1;
676 ++card_it;
680 for (const auto& port_pair : *client) {
681 int port_id = port_pair.first;
682 const auto& port = port_pair.second;
684 if (port->midi()) {
685 std::string version;
686 if (!driver.empty()) {
687 version = driver + " / ";
689 version +=
690 base::StringPrintf("ALSA library version %d.%d.%d", SND_LIB_MAJOR,
691 SND_LIB_MINOR, SND_LIB_SUBMINOR);
692 PortDirection direction = port->direction();
693 if (direction == PortDirection::kInput ||
694 direction == PortDirection::kDuplex) {
695 midi_ports->Insert(make_scoped_ptr(new MidiPort(
696 path, id, client_id, port_id, midi_device, client->name(),
697 port->name(), manufacturer, version, MidiPort::Type::kInput)));
699 if (direction == PortDirection::kOutput ||
700 direction == PortDirection::kDuplex) {
701 midi_ports->Insert(make_scoped_ptr(new MidiPort(
702 path, id, client_id, port_id, midi_device, client->name(),
703 port->name(), manufacturer, version, MidiPort::Type::kOutput)));
709 return midi_ports.Pass();
712 MidiManagerAlsa::AlsaSeqState::Port::Port(
713 const std::string& name,
714 MidiManagerAlsa::AlsaSeqState::PortDirection direction,
715 bool midi)
716 : name_(name), direction_(direction), midi_(midi) {
719 MidiManagerAlsa::AlsaSeqState::Port::~Port() = default;
721 MidiManagerAlsa::AlsaSeqState::Client::Client(const std::string& name,
722 snd_seq_client_type_t type)
723 : name_(name), type_(type) {
726 MidiManagerAlsa::AlsaSeqState::Client::~Client() = default;
728 void MidiManagerAlsa::AlsaSeqState::Client::AddPort(int addr,
729 scoped_ptr<Port> port) {
730 ports_.set(addr, port.Pass());
733 void MidiManagerAlsa::AlsaSeqState::Client::RemovePort(int addr) {
734 ports_.erase(addr);
737 MidiManagerAlsa::AlsaSeqState::Client::PortMap::const_iterator
738 MidiManagerAlsa::AlsaSeqState::Client::begin() const {
739 return ports_.begin();
742 MidiManagerAlsa::AlsaSeqState::Client::PortMap::const_iterator
743 MidiManagerAlsa::AlsaSeqState::Client::end() const {
744 return ports_.end();
747 MidiManagerAlsa::AlsaCard::AlsaCard(udev_device* dev,
748 const std::string& name,
749 const std::string& longname,
750 const std::string& driver,
751 int midi_device_count)
752 : name_(name),
753 longname_(longname),
754 driver_(driver),
755 path_(device::UdevDeviceGetPropertyValue(dev, kUdevIdPath)),
756 bus_(device::UdevDeviceGetPropertyValue(dev, kUdevIdBus)),
757 vendor_id_(
758 UdevDeviceGetPropertyOrSysattr(dev, kUdevIdVendorId, kSysattrVendor)),
759 model_id_(
760 UdevDeviceGetPropertyOrSysattr(dev, kUdevIdModelId, kSysattrModel)),
761 usb_interface_num_(
762 device::UdevDeviceGetPropertyValue(dev, kUdevIdUsbInterfaceNum)),
763 serial_(UdevDeviceGetPropertyOrSysattr(dev,
764 kUdevIdSerialShort,
765 kSysattrGuid)),
766 midi_device_count_(midi_device_count),
767 manufacturer_(ExtractManufacturerString(
768 GetVendor(dev),
769 vendor_id_,
770 device::UdevDeviceGetPropertyValue(dev, kUdevIdVendorFromDatabase),
771 name,
772 longname)) {
775 MidiManagerAlsa::AlsaCard::~AlsaCard() = default;
777 // static
778 std::string MidiManagerAlsa::AlsaCard::ExtractManufacturerString(
779 const std::string& udev_id_vendor,
780 const std::string& udev_id_vendor_id,
781 const std::string& udev_id_vendor_from_database,
782 const std::string& alsa_name,
783 const std::string& alsa_longname) {
784 // Let's try to determine the manufacturer. Here is the ordered preference
785 // in extraction:
786 // 1. Vendor name from the hardware device string, from udev properties
787 // or sysattrs.
788 // 2. Vendor name from the udev database (property ID_VENDOR_FROM_DATABASE).
789 // 3. Heuristic from ALSA.
791 // Is the vendor string present and not just the vendor hex id?
792 if (!udev_id_vendor.empty() && (udev_id_vendor != udev_id_vendor_id)) {
793 return udev_id_vendor;
796 // Is there a vendor string in the hardware database?
797 if (!udev_id_vendor_from_database.empty()) {
798 return udev_id_vendor_from_database;
801 // Ok, udev gave us nothing useful, or was unavailable. So try a heuristic.
802 // We assume that card longname is in the format of
803 // "<manufacturer> <name> at <bus>". Otherwise, we give up to detect
804 // a manufacturer name here.
805 size_t at_index = alsa_longname.rfind(" at ");
806 if (at_index && at_index != std::string::npos) {
807 size_t name_index = alsa_longname.rfind(alsa_name, at_index - 1);
808 if (name_index && name_index != std::string::npos)
809 return alsa_longname.substr(0, name_index - 1);
812 // Failure.
813 return "";
816 void MidiManagerAlsa::SendMidiData(uint32 port_index,
817 const std::vector<uint8>& data) {
818 DCHECK(send_thread_.task_runner()->BelongsToCurrentThread());
820 snd_midi_event_t* encoder;
821 snd_midi_event_new(kSendBufferSize, &encoder);
822 for (const auto datum : data) {
823 snd_seq_event_t event;
824 int result = snd_midi_event_encode_byte(encoder, datum, &event);
825 if (result == 1) {
826 // Full event, send it.
827 base::AutoLock lock(out_ports_lock_);
828 auto it = out_ports_.find(port_index);
829 if (it != out_ports_.end()) {
830 snd_seq_ev_set_source(&event, it->second);
831 snd_seq_ev_set_subs(&event);
832 snd_seq_ev_set_direct(&event);
833 snd_seq_event_output_direct(out_client_.get(), &event);
837 snd_midi_event_free(encoder);
840 void MidiManagerAlsa::ScheduleEventLoop() {
841 event_thread_.message_loop()->PostTask(
842 FROM_HERE,
843 base::Bind(&MidiManagerAlsa::EventLoop, base::Unretained(this)));
846 void MidiManagerAlsa::EventLoop() {
847 bool loop_again = true;
849 struct pollfd pfd[2];
850 snd_seq_poll_descriptors(in_client_.get(), &pfd[0], 1, POLLIN);
851 pfd[1].fd = device::udev_monitor_get_fd(udev_monitor_.get());
852 pfd[1].events = POLLIN;
854 int err = HANDLE_EINTR(poll(pfd, arraysize(pfd), -1));
855 if (err < 0) {
856 VLOG(1) << "poll fails: " << base::safe_strerror(errno);
857 loop_again = false;
858 } else {
859 if (pfd[0].revents & POLLIN) {
860 // Read available incoming MIDI data.
861 int remaining;
862 double timestamp =
863 (base::TimeTicks::Now() - base::TimeTicks()).InSecondsF();
864 do {
865 snd_seq_event_t* event;
866 err = snd_seq_event_input(in_client_.get(), &event);
867 remaining = snd_seq_event_input_pending(in_client_.get(), 0);
869 if (err == -ENOSPC) {
870 // Handle out of space error.
871 VLOG(1) << "snd_seq_event_input detected buffer overrun";
872 // We've lost events: check another way to see if we need to shut
873 // down.
874 base::AutoLock lock(shutdown_lock_);
875 if (event_thread_shutdown_)
876 loop_again = false;
877 } else if (err == -EAGAIN) {
878 // We've read all the data.
879 } else if (err < 0) {
880 // Handle other errors.
881 VLOG(1) << "snd_seq_event_input fails: " << snd_strerror(err);
882 // TODO(agoode): Use RecordAction() or similar to log this.
883 loop_again = false;
884 } else if (event->source.client == SND_SEQ_CLIENT_SYSTEM &&
885 event->source.port == SND_SEQ_PORT_SYSTEM_ANNOUNCE) {
886 // Handle announce events.
887 switch (event->type) {
888 case SND_SEQ_EVENT_PORT_START:
889 // Don't use SND_SEQ_EVENT_CLIENT_START because the
890 // client name may not be set by the time we query
891 // it. It should be set by the time ports are made.
892 ProcessClientStartEvent(event->data.addr.client);
893 ProcessPortStartEvent(event->data.addr);
894 break;
895 case SND_SEQ_EVENT_CLIENT_EXIT:
896 // Check for disconnection of our "out" client. This means "shut
897 // down".
898 if (event->data.addr.client == out_client_id_) {
899 loop_again = false;
900 remaining = 0;
901 } else
902 ProcessClientExitEvent(event->data.addr);
903 break;
904 case SND_SEQ_EVENT_PORT_EXIT:
905 ProcessPortExitEvent(event->data.addr);
906 break;
908 } else {
909 // Normal operation.
910 ProcessSingleEvent(event, timestamp);
912 } while (remaining > 0);
914 if (pfd[1].revents & POLLIN) {
915 device::ScopedUdevDevicePtr dev(
916 device::udev_monitor_receive_device(udev_monitor_.get()));
917 if (dev.get())
918 ProcessUdevEvent(dev.get());
919 else
920 VLOG(1) << "udev_monitor_receive_device fails";
924 // Do again.
925 if (loop_again)
926 ScheduleEventLoop();
929 void MidiManagerAlsa::ProcessSingleEvent(snd_seq_event_t* event,
930 double timestamp) {
931 auto source_it =
932 source_map_.find(AddrToInt(event->source.client, event->source.port));
933 if (source_it != source_map_.end()) {
934 uint32 source = source_it->second;
935 if (event->type == SND_SEQ_EVENT_SYSEX) {
936 // Special! Variable-length sysex.
937 ReceiveMidiData(source, static_cast<const uint8*>(event->data.ext.ptr),
938 event->data.ext.len, timestamp);
939 } else {
940 // Otherwise, decode this and send that on.
941 unsigned char buf[12];
942 long count =
943 snd_midi_event_decode(decoder_.get(), buf, sizeof(buf), event);
944 if (count <= 0) {
945 if (count != -ENOENT) {
946 // ENOENT means that it's not a MIDI message, which is not an
947 // error, but other negative values are errors for us.
948 VLOG(1) << "snd_midi_event_decoder fails " << snd_strerror(count);
949 // TODO(agoode): Record this failure.
951 } else {
952 ReceiveMidiData(source, buf, count, timestamp);
958 void MidiManagerAlsa::ProcessClientStartEvent(int client_id) {
959 // Ignore if client is already started.
960 if (alsa_seq_state_.ClientStarted(client_id))
961 return;
963 snd_seq_client_info_t* client_info;
964 snd_seq_client_info_alloca(&client_info);
965 int err =
966 snd_seq_get_any_client_info(in_client_.get(), client_id, client_info);
967 if (err != 0)
968 return;
970 // Skip our own clients.
971 if ((client_id == in_client_id_) || (client_id == out_client_id_))
972 return;
974 // Update our view of ALSA seq state.
975 alsa_seq_state_.ClientStart(client_id,
976 snd_seq_client_info_get_name(client_info),
977 snd_seq_client_info_get_type(client_info));
979 // Generate Web MIDI events.
980 UpdatePortStateAndGenerateEvents();
983 void MidiManagerAlsa::ProcessPortStartEvent(const snd_seq_addr_t& addr) {
984 snd_seq_port_info_t* port_info;
985 snd_seq_port_info_alloca(&port_info);
986 int err = snd_seq_get_any_port_info(in_client_.get(), addr.client, addr.port,
987 port_info);
988 if (err != 0)
989 return;
991 unsigned int caps = snd_seq_port_info_get_capability(port_info);
992 bool input = (caps & kRequiredInputPortCaps) == kRequiredInputPortCaps;
993 bool output = (caps & kRequiredOutputPortCaps) == kRequiredOutputPortCaps;
994 AlsaSeqState::PortDirection direction;
995 if (input && output)
996 direction = AlsaSeqState::PortDirection::kDuplex;
997 else if (input)
998 direction = AlsaSeqState::PortDirection::kInput;
999 else if (output)
1000 direction = AlsaSeqState::PortDirection::kOutput;
1001 else
1002 return;
1004 // Update our view of ALSA seq state.
1005 alsa_seq_state_.PortStart(
1006 addr.client, addr.port, snd_seq_port_info_get_name(port_info), direction,
1007 snd_seq_port_info_get_type(port_info) & SND_SEQ_PORT_TYPE_MIDI_GENERIC);
1008 // Generate Web MIDI events.
1009 UpdatePortStateAndGenerateEvents();
1012 void MidiManagerAlsa::ProcessClientExitEvent(const snd_seq_addr_t& addr) {
1013 // Update our view of ALSA seq state.
1014 alsa_seq_state_.ClientExit(addr.client);
1015 // Generate Web MIDI events.
1016 UpdatePortStateAndGenerateEvents();
1019 void MidiManagerAlsa::ProcessPortExitEvent(const snd_seq_addr_t& addr) {
1020 // Update our view of ALSA seq state.
1021 alsa_seq_state_.PortExit(addr.client, addr.port);
1022 // Generate Web MIDI events.
1023 UpdatePortStateAndGenerateEvents();
1026 void MidiManagerAlsa::ProcessUdevEvent(udev_device* dev) {
1027 // Only card devices have this property set, and only when they are
1028 // fully initialized.
1029 if (!device::udev_device_get_property_value(dev,
1030 kUdevPropertySoundInitialized))
1031 return;
1033 // Get the action. If no action, then we are doing first time enumeration
1034 // and the device is treated as new.
1035 const char* action = device::udev_device_get_action(dev);
1036 if (!action)
1037 action = kUdevActionChange;
1039 if (strcmp(action, kUdevActionChange) == 0) {
1040 AddCard(dev);
1041 // Generate Web MIDI events.
1042 UpdatePortStateAndGenerateEvents();
1043 } else if (strcmp(action, kUdevActionRemove) == 0) {
1044 RemoveCard(GetCardNumber(dev));
1045 // Generate Web MIDI events.
1046 UpdatePortStateAndGenerateEvents();
1050 void MidiManagerAlsa::AddCard(udev_device* dev) {
1051 int number = GetCardNumber(dev);
1052 if (number == -1)
1053 return;
1055 RemoveCard(number);
1057 snd_ctl_card_info_t* card;
1058 snd_hwdep_info_t* hwdep;
1059 snd_ctl_card_info_alloca(&card);
1060 snd_hwdep_info_alloca(&hwdep);
1061 const std::string id = base::StringPrintf("hw:CARD=%i", number);
1062 snd_ctl_t* handle;
1063 int err = snd_ctl_open(&handle, id.c_str(), 0);
1064 if (err != 0) {
1065 VLOG(1) << "snd_ctl_open fails: " << snd_strerror(err);
1066 return;
1068 err = snd_ctl_card_info(handle, card);
1069 if (err != 0) {
1070 VLOG(1) << "snd_ctl_card_info fails: " << snd_strerror(err);
1071 snd_ctl_close(handle);
1072 return;
1074 std::string name = snd_ctl_card_info_get_name(card);
1075 std::string longname = snd_ctl_card_info_get_longname(card);
1076 std::string driver = snd_ctl_card_info_get_driver(card);
1078 // Count rawmidi devices (not subdevices).
1079 int midi_count = 0;
1080 for (int device = -1;
1081 !snd_ctl_rawmidi_next_device(handle, &device) && device >= 0;)
1082 ++midi_count;
1084 // Count any hwdep synths that become MIDI devices outside of rawmidi.
1086 // Explanation:
1087 // Any kernel driver can create an ALSA client (visible to us).
1088 // With modern hardware, only rawmidi devices do this. Kernel
1089 // drivers create rawmidi devices and the rawmidi subsystem makes
1090 // the seq clients. But the OPL3 driver is special, it does not
1091 // make a rawmidi device but a seq client directly. (This is the
1092 // only one to worry about in the kernel code, as of 2015-03-23.)
1094 // OPL3 is very old (but still possible to get in new
1095 // hardware). It is unlikely that new drivers would not use
1096 // rawmidi and defeat our heuristic.
1098 // Longer term, support should be added in the kernel to expose a
1099 // direct link from card->client (or client->card) so that all
1100 // these heuristics will be obsolete. Once that is there, we can
1101 // assume our old heuristics will work on old kernels and the new
1102 // robust code will be used on new. Then we will not need to worry
1103 // about changes to kernel internals breaking our code.
1104 // See the TODO above at kMinimumClientIdForCards.
1105 for (int device = -1;
1106 !snd_ctl_hwdep_next_device(handle, &device) && device >= 0;) {
1107 err = snd_ctl_hwdep_info(handle, hwdep);
1108 if (err != 0) {
1109 VLOG(1) << "snd_ctl_hwdep_info fails: " << snd_strerror(err);
1110 continue;
1112 snd_hwdep_iface_t iface = snd_hwdep_info_get_iface(hwdep);
1113 if (iface == SND_HWDEP_IFACE_OPL2 || iface == SND_HWDEP_IFACE_OPL3 ||
1114 iface == SND_HWDEP_IFACE_OPL4)
1115 ++midi_count;
1117 snd_ctl_close(handle);
1119 if (midi_count > 0) {
1120 scoped_ptr<AlsaCard> card(
1121 new AlsaCard(dev, name, longname, driver, midi_count));
1122 alsa_cards_.insert(number, card.Pass());
1123 alsa_card_midi_count_ += midi_count;
1127 void MidiManagerAlsa::RemoveCard(int number) {
1128 auto it = alsa_cards_.find(number);
1129 if (it == alsa_cards_.end())
1130 return;
1132 alsa_card_midi_count_ -= it->second->midi_device_count();
1133 alsa_cards_.erase(it);
1136 void MidiManagerAlsa::UpdatePortStateAndGenerateEvents() {
1137 // Verify that our information from ALSA and udev are in sync. If
1138 // not, we cannot generate events right now.
1139 if (alsa_card_midi_count_ != alsa_seq_state_.card_client_count())
1140 return;
1142 // Generate new port state.
1143 auto new_port_state = alsa_seq_state_.ToMidiPortState(alsa_cards_);
1145 // Disconnect any connected old ports that are now missing.
1146 for (auto* old_port : port_state_) {
1147 if (old_port->connected() &&
1148 (new_port_state->FindConnected(*old_port) == new_port_state->end())) {
1149 old_port->set_connected(false);
1150 uint32 web_port_index = old_port->web_port_index();
1151 switch (old_port->type()) {
1152 case MidiPort::Type::kInput:
1153 source_map_.erase(
1154 AddrToInt(old_port->client_id(), old_port->port_id()));
1155 SetInputPortState(web_port_index, MIDI_PORT_DISCONNECTED);
1156 break;
1157 case MidiPort::Type::kOutput:
1158 DeleteAlsaOutputPort(web_port_index);
1159 SetOutputPortState(web_port_index, MIDI_PORT_DISCONNECTED);
1160 break;
1165 // Reconnect or add new ports.
1166 auto it = new_port_state->begin();
1167 while (it != new_port_state->end()) {
1168 auto* new_port = *it;
1169 auto old_port = port_state_.Find(*new_port);
1170 if (old_port == port_state_.end()) {
1171 // Add new port.
1172 uint32 web_port_index = port_state_.Insert(make_scoped_ptr(new_port));
1173 MidiPortInfo info(new_port->OpaqueKey(), new_port->manufacturer(),
1174 new_port->port_name(), new_port->version(),
1175 MIDI_PORT_OPENED);
1176 switch (new_port->type()) {
1177 case MidiPort::Type::kInput:
1178 if (Subscribe(web_port_index, new_port->client_id(),
1179 new_port->port_id()))
1180 AddInputPort(info);
1181 break;
1182 case MidiPort::Type::kOutput:
1183 if (CreateAlsaOutputPort(web_port_index, new_port->client_id(),
1184 new_port->port_id()))
1185 AddOutputPort(info);
1186 break;
1188 it = new_port_state->weak_erase(it);
1189 } else if (!(*old_port)->connected()) {
1190 // Reconnect.
1191 uint32 web_port_index = (*old_port)->web_port_index();
1192 (*old_port)->Update(new_port->path(), new_port->client_id(),
1193 new_port->port_id(), new_port->client_name(),
1194 new_port->port_name(), new_port->manufacturer(),
1195 new_port->version());
1196 switch ((*old_port)->type()) {
1197 case MidiPort::Type::kInput:
1198 if (Subscribe(web_port_index, (*old_port)->client_id(),
1199 (*old_port)->port_id()))
1200 SetInputPortState(web_port_index, MIDI_PORT_OPENED);
1201 break;
1202 case MidiPort::Type::kOutput:
1203 if (CreateAlsaOutputPort(web_port_index, (*old_port)->client_id(),
1204 (*old_port)->port_id()))
1205 SetOutputPortState(web_port_index, MIDI_PORT_OPENED);
1206 break;
1208 (*old_port)->set_connected(true);
1209 ++it;
1210 } else {
1211 ++it;
1216 // TODO(agoode): return false on failure.
1217 void MidiManagerAlsa::EnumerateAlsaPorts() {
1218 snd_seq_client_info_t* client_info;
1219 snd_seq_client_info_alloca(&client_info);
1220 snd_seq_port_info_t* port_info;
1221 snd_seq_port_info_alloca(&port_info);
1223 // Enumerate clients.
1224 snd_seq_client_info_set_client(client_info, -1);
1225 while (!snd_seq_query_next_client(in_client_.get(), client_info)) {
1226 int client_id = snd_seq_client_info_get_client(client_info);
1227 ProcessClientStartEvent(client_id);
1229 // Enumerate ports.
1230 snd_seq_port_info_set_client(port_info, client_id);
1231 snd_seq_port_info_set_port(port_info, -1);
1232 while (!snd_seq_query_next_port(in_client_.get(), port_info)) {
1233 const snd_seq_addr_t* addr = snd_seq_port_info_get_addr(port_info);
1234 ProcessPortStartEvent(*addr);
1239 bool MidiManagerAlsa::EnumerateUdevCards() {
1240 int err;
1242 device::ScopedUdevEnumeratePtr enumerate(
1243 device::udev_enumerate_new(udev_.get()));
1244 if (!enumerate.get()) {
1245 VLOG(1) << "udev_enumerate_new fails";
1246 return false;
1249 err = device::udev_enumerate_add_match_subsystem(enumerate.get(),
1250 kUdevSubsystemSound);
1251 if (err) {
1252 VLOG(1) << "udev_enumerate_add_match_subsystem fails: "
1253 << base::safe_strerror(-err);
1254 return false;
1257 err = device::udev_enumerate_scan_devices(enumerate.get());
1258 if (err) {
1259 VLOG(1) << "udev_enumerate_scan_devices fails: "
1260 << base::safe_strerror(-err);
1261 return false;
1264 udev_list_entry* list_entry;
1265 auto* devices = device::udev_enumerate_get_list_entry(enumerate.get());
1266 udev_list_entry_foreach(list_entry, devices) {
1267 const char* path = device::udev_list_entry_get_name(list_entry);
1268 device::ScopedUdevDevicePtr dev(
1269 device::udev_device_new_from_syspath(udev_.get(), path));
1270 if (dev.get())
1271 ProcessUdevEvent(dev.get());
1274 return true;
1277 bool MidiManagerAlsa::CreateAlsaOutputPort(uint32 port_index,
1278 int client_id,
1279 int port_id) {
1280 // Create the port.
1281 int out_port = snd_seq_create_simple_port(
1282 out_client_.get(), NULL, kCreateOutputPortCaps, kCreatePortType);
1283 if (out_port < 0) {
1284 VLOG(1) << "snd_seq_create_simple_port fails: " << snd_strerror(out_port);
1285 return false;
1287 // Activate port subscription.
1288 snd_seq_port_subscribe_t* subs;
1289 snd_seq_port_subscribe_alloca(&subs);
1290 snd_seq_addr_t sender;
1291 sender.client = out_client_id_;
1292 sender.port = out_port;
1293 snd_seq_port_subscribe_set_sender(subs, &sender);
1294 snd_seq_addr_t dest;
1295 dest.client = client_id;
1296 dest.port = port_id;
1297 snd_seq_port_subscribe_set_dest(subs, &dest);
1298 int err = snd_seq_subscribe_port(out_client_.get(), subs);
1299 if (err != 0) {
1300 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
1301 snd_seq_delete_simple_port(out_client_.get(), out_port);
1302 return false;
1305 // Update our map.
1306 base::AutoLock lock(out_ports_lock_);
1307 out_ports_[port_index] = out_port;
1308 return true;
1311 void MidiManagerAlsa::DeleteAlsaOutputPort(uint32 port_index) {
1312 base::AutoLock lock(out_ports_lock_);
1313 auto it = out_ports_.find(port_index);
1314 if (it == out_ports_.end())
1315 return;
1317 int alsa_port = it->second;
1318 snd_seq_delete_simple_port(out_client_.get(), alsa_port);
1319 out_ports_.erase(it);
1322 bool MidiManagerAlsa::Subscribe(uint32 port_index, int client_id, int port_id) {
1323 // Activate port subscription.
1324 snd_seq_port_subscribe_t* subs;
1325 snd_seq_port_subscribe_alloca(&subs);
1326 snd_seq_addr_t sender;
1327 sender.client = client_id;
1328 sender.port = port_id;
1329 snd_seq_port_subscribe_set_sender(subs, &sender);
1330 snd_seq_addr_t dest;
1331 dest.client = in_client_id_;
1332 dest.port = in_port_id_;
1333 snd_seq_port_subscribe_set_dest(subs, &dest);
1334 int err = snd_seq_subscribe_port(in_client_.get(), subs);
1335 if (err != 0) {
1336 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
1337 return false;
1340 // Update our map.
1341 source_map_[AddrToInt(client_id, port_id)] = port_index;
1342 return true;
1345 MidiManager* MidiManager::Create() {
1346 return new MidiManagerAlsa();
1349 } // namespace midi
1350 } // namespace media