// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "media/midi/midi_manager_alsa.h" #include #include #include #include #include "base/bind.h" #include "base/json/json_string_value_serializer.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/memory/scoped_vector.h" #include "base/message_loop/message_loop.h" #include "base/posix/eintr_wrapper.h" #include "base/strings/string_number_conversions.h" #include "base/strings/stringprintf.h" #include "base/threading/thread.h" #include "base/time/time.h" #include "base/values.h" #include "crypto/sha2.h" #include "media/midi/midi_port_info.h" namespace media { namespace { // Per-output buffer. This can be smaller, but then large sysex messages // will be (harmlessly) split across multiple seq events. This should // not have any real practical effect, except perhaps to slightly reorder // realtime messages with respect to sysex. const size_t kSendBufferSize = 256; // ALSA constants. const char kAlsaHw[] = "hw"; // Constants for the capabilities we search for in inputs and outputs. // See http://www.alsa-project.org/alsa-doc/alsa-lib/seq.html. const unsigned int kRequiredInputPortCaps = SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ; const unsigned int kRequiredOutputPortCaps = SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE; const unsigned int kCreateOutputPortCaps = SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_NO_EXPORT; const unsigned int kCreateInputPortCaps = SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_NO_EXPORT; const unsigned int kCreatePortType = SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION; int AddrToInt(int client, int port) { return (client << 8) | port; } void SetStringIfNonEmpty(base::DictionaryValue* value, const std::string& path, const std::string& in_value) { if (!in_value.empty()) value->SetString(path, in_value); } } // namespace MidiManagerAlsa::MidiManagerAlsa() : in_client_(NULL), out_client_(NULL), out_client_id_(-1), in_port_id_(-1), decoder_(NULL), udev_(device::udev_new()), send_thread_("MidiSendThread"), event_thread_("MidiEventThread"), event_thread_shutdown_(false) { // Initialize decoder. snd_midi_event_new(0, &decoder_); snd_midi_event_no_status(decoder_, 1); } MidiManagerAlsa::~MidiManagerAlsa() { // Tell the event thread it will soon be time to shut down. This gives // us assurance the thread will stop in case the SND_SEQ_EVENT_CLIENT_EXIT // message is lost. { base::AutoLock lock(shutdown_lock_); event_thread_shutdown_ = true; } // Stop the send thread. send_thread_.Stop(); // Close the out client. This will trigger the event thread to stop, // because of SND_SEQ_EVENT_CLIENT_EXIT. if (out_client_) snd_seq_close(out_client_); // Wait for the event thread to stop. event_thread_.Stop(); // Close the in client. if (in_client_) snd_seq_close(in_client_); // Free the decoder. snd_midi_event_free(decoder_); } void MidiManagerAlsa::StartInitialization() { // TODO(agoode): Move off I/O thread. See http://crbug.com/374341. // Create client handles. int err = snd_seq_open(&in_client_, kAlsaHw, SND_SEQ_OPEN_INPUT, 0); if (err != 0) { VLOG(1) << "snd_seq_open fails: " << snd_strerror(err); return CompleteInitialization(MIDI_INITIALIZATION_ERROR); } in_client_id_ = snd_seq_client_id(in_client_); err = snd_seq_open(&out_client_, kAlsaHw, SND_SEQ_OPEN_OUTPUT, 0); if (err != 0) { VLOG(1) << "snd_seq_open fails: " << snd_strerror(err); return CompleteInitialization(MIDI_INITIALIZATION_ERROR); } out_client_id_ = snd_seq_client_id(out_client_); // Name the clients. err = snd_seq_set_client_name(in_client_, "Chrome (input)"); if (err != 0) { VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err); return CompleteInitialization(MIDI_INITIALIZATION_ERROR); } err = snd_seq_set_client_name(out_client_, "Chrome (output)"); if (err != 0) { VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err); return CompleteInitialization(MIDI_INITIALIZATION_ERROR); } // Create input port. in_port_id_ = snd_seq_create_simple_port( in_client_, NULL, kCreateInputPortCaps, kCreatePortType); if (in_port_id_ < 0) { VLOG(1) << "snd_seq_create_simple_port fails: " << snd_strerror(in_port_id_); return CompleteInitialization(MIDI_INITIALIZATION_ERROR); } // Subscribe to the announce port. snd_seq_port_subscribe_t* subs; snd_seq_port_subscribe_alloca(&subs); snd_seq_addr_t announce_sender; snd_seq_addr_t announce_dest; announce_sender.client = SND_SEQ_CLIENT_SYSTEM; announce_sender.port = SND_SEQ_PORT_SYSTEM_ANNOUNCE; announce_dest.client = in_client_id_; announce_dest.port = in_port_id_; snd_seq_port_subscribe_set_sender(subs, &announce_sender); snd_seq_port_subscribe_set_dest(subs, &announce_dest); err = snd_seq_subscribe_port(in_client_, subs); if (err != 0) { VLOG(1) << "snd_seq_subscribe_port on the announce port fails: " << snd_strerror(err); return CompleteInitialization(MIDI_INITIALIZATION_ERROR); } // Generate hotplug events for existing ports. EnumerateAlsaPorts(); // Start processing events. event_thread_.Start(); event_thread_.message_loop()->PostTask( FROM_HERE, base::Bind(&MidiManagerAlsa::ScheduleEventLoop, base::Unretained(this))); CompleteInitialization(MIDI_OK); } void MidiManagerAlsa::DispatchSendMidiData(MidiManagerClient* client, uint32 port_index, const std::vector& data, double timestamp) { // Not correct right now. http://crbug.com/374341. if (!send_thread_.IsRunning()) send_thread_.Start(); base::TimeDelta delay; if (timestamp != 0.0) { base::TimeTicks time_to_send = base::TimeTicks() + base::TimeDelta::FromMicroseconds( timestamp * base::Time::kMicrosecondsPerSecond); delay = std::max(time_to_send - base::TimeTicks::Now(), base::TimeDelta()); } send_thread_.message_loop()->PostDelayedTask( FROM_HERE, base::Bind(&MidiManagerAlsa::SendMidiData, base::Unretained(this), port_index, data), delay); // Acknowledge send. send_thread_.message_loop()->PostTask( FROM_HERE, base::Bind(&MidiManagerClient::AccumulateMidiBytesSent, base::Unretained(client), data.size())); } MidiManagerAlsa::MidiPort::MidiPort(const std::string& path, const std::string& id, int client_id, int port_id, int midi_device, const std::string& client_name, const std::string& port_name, const std::string& manufacturer, const std::string& version, Type type) : id_(id), midi_device_(midi_device), type_(type), path_(path), client_id_(client_id), port_id_(port_id), client_name_(client_name), port_name_(port_name), manufacturer_(manufacturer), version_(version), web_port_index_(0), connected_(true) { } MidiManagerAlsa::MidiPort::~MidiPort() { } // Note: keep synchronized with the MidiPort::Match* methods. scoped_ptr MidiManagerAlsa::MidiPort::Value() const { scoped_ptr value(new base::DictionaryValue); std::string type; switch (type_) { case Type::kInput: type = "input"; break; case Type::kOutput: type = "output"; break; } value->SetString("type", type); SetStringIfNonEmpty(value.get(), "path", path_); SetStringIfNonEmpty(value.get(), "id", id_); SetStringIfNonEmpty(value.get(), "clientName", client_name_); SetStringIfNonEmpty(value.get(), "portName", port_name_); value->SetInteger("clientId", client_id_); value->SetInteger("portId", port_id_); value->SetInteger("midiDevice", midi_device_); return value.Pass(); } std::string MidiManagerAlsa::MidiPort::JSONValue() const { std::string json; JSONStringValueSerializer serializer(&json); serializer.Serialize(*Value().get()); return json; } // TODO(agoode): Do not use SHA256 here. Instead store a persistent // mapping and just use a UUID or other random string. // http://crbug.com/465320 std::string MidiManagerAlsa::MidiPort::OpaqueKey() const { uint8 hash[crypto::kSHA256Length]; crypto::SHA256HashString(JSONValue(), &hash, sizeof(hash)); return base::HexEncode(&hash, sizeof(hash)); } bool MidiManagerAlsa::MidiPort::MatchConnected(const MidiPort& query) const { // Matches on: // connected == true // type // path // id // client_id // port_id // midi_device // client_name // port_name return connected() && (type() == query.type()) && (path() == query.path()) && (id() == query.id()) && (client_id() == query.client_id()) && (port_id() == query.port_id()) && (midi_device() == query.midi_device()) && (client_name() == query.client_name()) && (port_name() == query.port_name()); } bool MidiManagerAlsa::MidiPort::MatchCardPass1(const MidiPort& query) const { // Matches on: // connected == false // type // path // id // port_id // midi_device return MatchCardPass2(query) && (path() == query.path()); } bool MidiManagerAlsa::MidiPort::MatchCardPass2(const MidiPort& query) const { // Matches on: // connected == false // type // id // port_id // midi_device return !connected() && (type() == query.type()) && (id() == query.id()) && (port_id() == query.port_id()) && (midi_device() == query.midi_device()); } bool MidiManagerAlsa::MidiPort::MatchNoCardPass1(const MidiPort& query) const { // Matches on: // connected == false // type // path.empty(), for both this and query // id.empty(), for both this and query // client_id // port_id // client_name // port_name // midi_device == -1, for both this and query return MatchNoCardPass2(query) && (client_id() == query.client_id()); } bool MidiManagerAlsa::MidiPort::MatchNoCardPass2(const MidiPort& query) const { // Matches on: // connected == false // type // path.empty(), for both this and query // id.empty(), for both this and query // port_id // client_name // port_name // midi_device == -1, for both this and query return !connected() && (type() == query.type()) && path().empty() && query.path().empty() && id().empty() && query.id().empty() && (port_id() == query.port_id()) && (client_name() == query.client_name()) && (port_name() == query.port_name()) && (midi_device() == -1) && (query.midi_device() == -1); } MidiManagerAlsa::MidiPortStateBase::~MidiPortStateBase() { } ScopedVector* MidiManagerAlsa::MidiPortStateBase::ports() { return &ports_; } MidiManagerAlsa::MidiPortStateBase::iterator MidiManagerAlsa::MidiPortStateBase::Find( const MidiManagerAlsa::MidiPort& port) { auto result = FindConnected(port); if (result == end()) result = FindDisconnected(port); return result; } MidiManagerAlsa::MidiPortStateBase::iterator MidiManagerAlsa::MidiPortStateBase::FindConnected( const MidiManagerAlsa::MidiPort& port) { // Exact match required for connected ports. auto it = std::find_if(ports_.begin(), ports_.end(), [&port](MidiPort* p) { return p->MatchConnected(port); }); return it; } MidiManagerAlsa::MidiPortStateBase::iterator MidiManagerAlsa::MidiPortStateBase::FindDisconnected( const MidiManagerAlsa::MidiPort& port) { // Always match on: // type // Possible things to match on: // path // id // client_id // port_id // midi_device // client_name // port_name if (!port.path().empty()) { // If path is present, then we have a card-based client. // Pass 1. Match on path, id, midi_device, port_id. // This is the best possible match for hardware card-based clients. // This will also match the empty id correctly for devices without an id. auto it = std::find_if(ports_.begin(), ports_.end(), [&port](MidiPort* p) { return p->MatchCardPass1(port); }); if (it != ports_.end()) return it; if (!port.id().empty()) { // Pass 2. Match on id, midi_device, port_id. // This will give us a high-confidence match when a user moves a device to // another USB/Firewire/Thunderbolt/etc port, but only works if the device // has a hardware id. it = std::find_if(ports_.begin(), ports_.end(), [&port](MidiPort* p) { return p->MatchCardPass2(port); }); if (it != ports_.end()) return it; } } else { // Else, we have a non-card-based client. // Pass 1. Match on client_id, port_id, client_name, port_name. // This will give us a reasonably good match. auto it = std::find_if(ports_.begin(), ports_.end(), [&port](MidiPort* p) { return p->MatchNoCardPass1(port); }); if (it != ports_.end()) return it; // Pass 2. Match on port_id, client_name, port_name. // This is weaker but similar to pass 2 in the hardware card-based clients // match. it = std::find_if(ports_.begin(), ports_.end(), [&port](MidiPort* p) { return p->MatchNoCardPass2(port); }); if (it != ports_.end()) return it; } // No match. return ports_.end(); } MidiManagerAlsa::MidiPortStateBase::MidiPortStateBase() { } void MidiManagerAlsa::TemporaryMidiPortState::Insert( scoped_ptr port) { ports()->push_back(port.Pass()); } MidiManagerAlsa::MidiPortState::MidiPortState() : num_input_ports_(0), num_output_ports_(0) { } uint32 MidiManagerAlsa::MidiPortState::Insert(scoped_ptr port) { // Add the web midi index. uint32 web_port_index = 0; switch (port->type()) { case MidiPort::Type::kInput: web_port_index = num_input_ports_++; break; case MidiPort::Type::kOutput: web_port_index = num_output_ports_++; break; } port->set_web_port_index(web_port_index); ports()->push_back(port.Pass()); return web_port_index; } MidiManagerAlsa::AlsaSeqState::AlsaSeqState() : clients_deleter_(&clients_) { } MidiManagerAlsa::AlsaSeqState::~AlsaSeqState() { } void MidiManagerAlsa::AlsaSeqState::ClientStart(int client_id, const std::string& client_name, snd_seq_client_type_t type) { ClientExit(client_id); clients_[client_id] = new Client(client_name, type); } bool MidiManagerAlsa::AlsaSeqState::ClientStarted(int client_id) { return clients_.find(client_id) != clients_.end(); } void MidiManagerAlsa::AlsaSeqState::ClientExit(int client_id) { auto it = clients_.find(client_id); if (it != clients_.end()) { delete it->second; clients_.erase(it); } } void MidiManagerAlsa::AlsaSeqState::PortStart( int client_id, int port_id, const std::string& port_name, MidiManagerAlsa::AlsaSeqState::PortDirection direction, bool midi) { auto it = clients_.find(client_id); if (it != clients_.end()) it->second->AddPort(port_id, scoped_ptr(new Port(port_name, direction, midi))); } void MidiManagerAlsa::AlsaSeqState::PortExit(int client_id, int port_id) { auto it = clients_.find(client_id); if (it != clients_.end()) it->second->RemovePort(port_id); } snd_seq_client_type_t MidiManagerAlsa::AlsaSeqState::ClientType( int client_id) const { auto it = clients_.find(client_id); if (it == clients_.end()) return SND_SEQ_USER_CLIENT; return it->second->type(); } scoped_ptr MidiManagerAlsa::AlsaSeqState::ToMidiPortState() { scoped_ptr midi_ports( new TemporaryMidiPortState); // TODO(agoode): Use information from udev as well. for (const auto& client_pair : clients_) { int client_id = client_pair.first; const auto& client = client_pair.second; // Get client metadata. const std::string client_name = client->name(); std::string manufacturer; std::string driver; std::string path; std::string id; std::string serial; std::string card_name; std::string card_longname; int midi_device = -1; for (const auto& port_pair : *client) { int port_id = port_pair.first; const auto& port = port_pair.second; if (port->midi()) { std::string version; if (!driver.empty()) { version = driver + " / "; } version += base::StringPrintf("ALSA library version %d.%d.%d", SND_LIB_MAJOR, SND_LIB_MINOR, SND_LIB_SUBMINOR); PortDirection direction = port->direction(); if (direction == PortDirection::kInput || direction == PortDirection::kDuplex) { midi_ports->Insert(scoped_ptr(new MidiPort( path, id, client_id, port_id, midi_device, client->name(), port->name(), manufacturer, version, MidiPort::Type::kInput))); } if (direction == PortDirection::kOutput || direction == PortDirection::kDuplex) { midi_ports->Insert(scoped_ptr(new MidiPort( path, id, client_id, port_id, midi_device, client->name(), port->name(), manufacturer, version, MidiPort::Type::kOutput))); } } } } return midi_ports.Pass(); } MidiManagerAlsa::AlsaSeqState::Port::Port( const std::string& name, MidiManagerAlsa::AlsaSeqState::PortDirection direction, bool midi) : name_(name), direction_(direction), midi_(midi) { } MidiManagerAlsa::AlsaSeqState::Port::~Port() { } std::string MidiManagerAlsa::AlsaSeqState::Port::name() const { return name_; } MidiManagerAlsa::AlsaSeqState::PortDirection MidiManagerAlsa::AlsaSeqState::Port::direction() const { return direction_; } bool MidiManagerAlsa::AlsaSeqState::Port::midi() const { return midi_; } MidiManagerAlsa::AlsaSeqState::Client::Client(const std::string& name, snd_seq_client_type_t type) : name_(name), type_(type), ports_deleter_(&ports_) { } MidiManagerAlsa::AlsaSeqState::Client::~Client() { } std::string MidiManagerAlsa::AlsaSeqState::Client::name() const { return name_; } snd_seq_client_type_t MidiManagerAlsa::AlsaSeqState::Client::type() const { return type_; } void MidiManagerAlsa::AlsaSeqState::Client::AddPort(int addr, scoped_ptr port) { RemovePort(addr); ports_[addr] = port.release(); } void MidiManagerAlsa::AlsaSeqState::Client::RemovePort(int addr) { auto it = ports_.find(addr); if (it != ports_.end()) { delete it->second; ports_.erase(it); } } MidiManagerAlsa::AlsaSeqState::Client::PortMap::const_iterator MidiManagerAlsa::AlsaSeqState::Client::begin() const { return ports_.begin(); } MidiManagerAlsa::AlsaSeqState::Client::PortMap::const_iterator MidiManagerAlsa::AlsaSeqState::Client::end() const { return ports_.end(); } // static std::string MidiManagerAlsa::ExtractManufacturerString( const std::string& udev_id_vendor, const std::string& udev_id_vendor_id, const std::string& udev_id_vendor_from_database, const std::string& alsa_name, const std::string& alsa_longname) { // Let's try to determine the manufacturer. Here is the ordered preference // in extraction: // 1. Vendor name from the hardware device string, from udev properties // or sysattrs. // 2. Vendor name from the udev database (property ID_VENDOR_FROM_DATABASE). // 3. Heuristic from ALSA. // Is the vendor string present and not just the vendor hex id? if (!udev_id_vendor.empty() && (udev_id_vendor != udev_id_vendor_id)) { return udev_id_vendor; } // Is there a vendor string in the hardware database? if (!udev_id_vendor_from_database.empty()) { return udev_id_vendor_from_database; } // Ok, udev gave us nothing useful, or was unavailable. So try a heuristic. // We assume that card longname is in the format of // " at ". Otherwise, we give up to detect // a manufacturer name here. size_t at_index = alsa_longname.rfind(" at "); if (at_index && at_index != std::string::npos) { size_t name_index = alsa_longname.rfind(alsa_name, at_index - 1); if (name_index && name_index != std::string::npos) return alsa_longname.substr(0, name_index - 1); } // Failure. return ""; } void MidiManagerAlsa::SendMidiData(uint32 port_index, const std::vector& data) { DCHECK(send_thread_.message_loop_proxy()->BelongsToCurrentThread()); snd_midi_event_t* encoder; snd_midi_event_new(kSendBufferSize, &encoder); for (unsigned int i = 0; i < data.size(); i++) { snd_seq_event_t event; int result = snd_midi_event_encode_byte(encoder, data[i], &event); if (result == 1) { // Full event, send it. base::AutoLock lock(out_ports_lock_); auto it = out_ports_.find(port_index); if (it != out_ports_.end()) { snd_seq_ev_set_source(&event, it->second); snd_seq_ev_set_subs(&event); snd_seq_ev_set_direct(&event); snd_seq_event_output_direct(out_client_, &event); } } } snd_midi_event_free(encoder); } void MidiManagerAlsa::ScheduleEventLoop() { event_thread_.message_loop()->PostTask( FROM_HERE, base::Bind(&MidiManagerAlsa::EventLoop, base::Unretained(this))); } void MidiManagerAlsa::EventLoop() { // Read available incoming MIDI data. snd_seq_event_t* event; int err = snd_seq_event_input(in_client_, &event); double timestamp = (base::TimeTicks::Now() - base::TimeTicks()).InSecondsF(); // Handle errors. if (err == -ENOSPC) { VLOG(1) << "snd_seq_event_input detected buffer overrun"; // We've lost events: check another way to see if we need to shut down. base::AutoLock lock(shutdown_lock_); if (!event_thread_shutdown_) ScheduleEventLoop(); return; } else if (err < 0) { VLOG(1) << "snd_seq_event_input fails: " << snd_strerror(err); // TODO(agoode): Use RecordAction() or similar to log this. return; } // Handle announce events. if (event->source.client == SND_SEQ_CLIENT_SYSTEM && event->source.port == SND_SEQ_PORT_SYSTEM_ANNOUNCE) { switch (event->type) { case SND_SEQ_EVENT_PORT_START: // Don't use SND_SEQ_EVENT_CLIENT_START because the client name may not // be set by the time we query it. It should be set by the time ports // are made. ProcessClientStartEvent(event->data.addr.client); ProcessPortStartEvent(event->data.addr); break; case SND_SEQ_EVENT_CLIENT_EXIT: // Check for disconnection of our "out" client. This means "shut down". if (event->data.addr.client == out_client_id_) return; ProcessClientExitEvent(event->data.addr); break; case SND_SEQ_EVENT_PORT_EXIT: ProcessPortExitEvent(event->data.addr); break; } } else { ProcessSingleEvent(event, timestamp); } // Do again. ScheduleEventLoop(); } void MidiManagerAlsa::ProcessSingleEvent(snd_seq_event_t* event, double timestamp) { auto source_it = source_map_.find(AddrToInt(event->source.client, event->source.port)); if (source_it != source_map_.end()) { uint32 source = source_it->second; if (event->type == SND_SEQ_EVENT_SYSEX) { // Special! Variable-length sysex. ReceiveMidiData(source, static_cast(event->data.ext.ptr), event->data.ext.len, timestamp); } else { // Otherwise, decode this and send that on. unsigned char buf[12]; long count = snd_midi_event_decode(decoder_, buf, sizeof(buf), event); if (count <= 0) { if (count != -ENOENT) { // ENOENT means that it's not a MIDI message, which is not an // error, but other negative values are errors for us. VLOG(1) << "snd_midi_event_decoder fails " << snd_strerror(count); // TODO(agoode): Record this failure. } } else { ReceiveMidiData(source, buf, count, timestamp); } } } } void MidiManagerAlsa::ProcessClientStartEvent(int client_id) { // Ignore if client is already started. if (alsa_seq_state_.ClientStarted(client_id)) return; snd_seq_client_info_t* client_info; snd_seq_client_info_alloca(&client_info); int err = snd_seq_get_any_client_info(in_client_, client_id, client_info); if (err != 0) return; // Skip our own clients. if ((client_id == in_client_id_) || (client_id == out_client_id_)) return; // Update our view of ALSA seq state. alsa_seq_state_.ClientStart(client_id, snd_seq_client_info_get_name(client_info), snd_seq_client_info_get_type(client_info)); // Generate Web MIDI events. UpdatePortStateAndGenerateEvents(); } void MidiManagerAlsa::ProcessPortStartEvent(const snd_seq_addr_t& addr) { snd_seq_port_info_t* port_info; snd_seq_port_info_alloca(&port_info); int err = snd_seq_get_any_port_info(in_client_, addr.client, addr.port, port_info); if (err != 0) return; unsigned int caps = snd_seq_port_info_get_capability(port_info); bool input = (caps & kRequiredInputPortCaps) == kRequiredInputPortCaps; bool output = (caps & kRequiredOutputPortCaps) == kRequiredOutputPortCaps; AlsaSeqState::PortDirection direction; if (input && output) direction = AlsaSeqState::PortDirection::kDuplex; else if (input) direction = AlsaSeqState::PortDirection::kInput; else if (output) direction = AlsaSeqState::PortDirection::kOutput; else return; // Update our view of ALSA seq state. alsa_seq_state_.PortStart( addr.client, addr.port, snd_seq_port_info_get_name(port_info), direction, snd_seq_port_info_get_type(port_info) & SND_SEQ_PORT_TYPE_MIDI_GENERIC); // Generate Web MIDI events. UpdatePortStateAndGenerateEvents(); } void MidiManagerAlsa::ProcessClientExitEvent(const snd_seq_addr_t& addr) { // Update our view of ALSA seq state. alsa_seq_state_.ClientExit(addr.client); // Generate Web MIDI events. UpdatePortStateAndGenerateEvents(); } void MidiManagerAlsa::ProcessPortExitEvent(const snd_seq_addr_t& addr) { // Update our view of ALSA seq state. alsa_seq_state_.PortExit(addr.client, addr.port); // Generate Web MIDI events. UpdatePortStateAndGenerateEvents(); } void MidiManagerAlsa::UpdatePortStateAndGenerateEvents() { // Generate new port state. auto new_port_state = alsa_seq_state_.ToMidiPortState(); // Disconnect any connected old ports that are now missing. for (auto* old_port : port_state_) { if (old_port->connected() && (new_port_state->FindConnected(*old_port) == new_port_state->end())) { old_port->set_connected(false); uint32 web_port_index = old_port->web_port_index(); switch (old_port->type()) { case MidiPort::Type::kInput: source_map_.erase( AddrToInt(old_port->client_id(), old_port->port_id())); SetInputPortState(web_port_index, MIDI_PORT_DISCONNECTED); break; case MidiPort::Type::kOutput: DeleteAlsaOutputPort(web_port_index); SetOutputPortState(web_port_index, MIDI_PORT_DISCONNECTED); break; } } } // Reconnect or add new ports. auto it = new_port_state->begin(); while (it != new_port_state->end()) { auto* new_port = *it; auto old_port = port_state_.Find(*new_port); if (old_port == port_state_.end()) { // Add new port. uint32 web_port_index = port_state_.Insert(scoped_ptr(new_port)); MidiPortInfo info(new_port->OpaqueKey(), new_port->manufacturer(), new_port->port_name(), new_port->version(), MIDI_PORT_OPENED); switch (new_port->type()) { case MidiPort::Type::kInput: if (Subscribe(web_port_index, new_port->client_id(), new_port->port_id())) AddInputPort(info); break; case MidiPort::Type::kOutput: if (CreateAlsaOutputPort(web_port_index, new_port->client_id(), new_port->port_id())) AddOutputPort(info); break; } it = new_port_state->weak_erase(it); } else if (!(*old_port)->connected()) { // Reconnect. uint32 web_port_index = (*old_port)->web_port_index(); (*old_port)->Update(new_port->path(), new_port->client_id(), new_port->port_id(), new_port->client_name(), new_port->port_name(), new_port->manufacturer(), new_port->version()); switch ((*old_port)->type()) { case MidiPort::Type::kInput: if (Subscribe(web_port_index, (*old_port)->client_id(), (*old_port)->port_id())) SetInputPortState(web_port_index, MIDI_PORT_OPENED); break; case MidiPort::Type::kOutput: if (CreateAlsaOutputPort(web_port_index, (*old_port)->client_id(), (*old_port)->port_id())) SetOutputPortState(web_port_index, MIDI_PORT_OPENED); break; } (*old_port)->set_connected(true); ++it; } else { ++it; } } } void MidiManagerAlsa::EnumerateAlsaPorts() { snd_seq_client_info_t* client_info; snd_seq_client_info_alloca(&client_info); snd_seq_port_info_t* port_info; snd_seq_port_info_alloca(&port_info); // Enumerate clients. snd_seq_client_info_set_client(client_info, -1); while (!snd_seq_query_next_client(in_client_, client_info)) { int client_id = snd_seq_client_info_get_client(client_info); ProcessClientStartEvent(client_id); // Enumerate ports. snd_seq_port_info_set_client(port_info, client_id); snd_seq_port_info_set_port(port_info, -1); while (!snd_seq_query_next_port(in_client_, port_info)) { const snd_seq_addr_t* addr = snd_seq_port_info_get_addr(port_info); ProcessPortStartEvent(*addr); } } } bool MidiManagerAlsa::CreateAlsaOutputPort(uint32 port_index, int client_id, int port_id) { // Create the port. int out_port = snd_seq_create_simple_port( out_client_, NULL, kCreateOutputPortCaps, kCreatePortType); if (out_port < 0) { VLOG(1) << "snd_seq_create_simple_port fails: " << snd_strerror(out_port); return false; } // Activate port subscription. snd_seq_port_subscribe_t* subs; snd_seq_port_subscribe_alloca(&subs); snd_seq_addr_t sender; sender.client = out_client_id_; sender.port = out_port; snd_seq_port_subscribe_set_sender(subs, &sender); snd_seq_addr_t dest; dest.client = client_id; dest.port = port_id; snd_seq_port_subscribe_set_dest(subs, &dest); int err = snd_seq_subscribe_port(out_client_, subs); if (err != 0) { VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err); snd_seq_delete_simple_port(out_client_, out_port); return false; } // Update our map. base::AutoLock lock(out_ports_lock_); out_ports_[port_index] = out_port; return true; } void MidiManagerAlsa::DeleteAlsaOutputPort(uint32 port_index) { base::AutoLock lock(out_ports_lock_); auto it = out_ports_.find(port_index); if (it == out_ports_.end()) return; int alsa_port = it->second; snd_seq_delete_simple_port(out_client_, alsa_port); out_ports_.erase(it); } bool MidiManagerAlsa::Subscribe(uint32 port_index, int client_id, int port_id) { // Activate port subscription. snd_seq_port_subscribe_t* subs; snd_seq_port_subscribe_alloca(&subs); snd_seq_addr_t sender; sender.client = client_id; sender.port = port_id; snd_seq_port_subscribe_set_sender(subs, &sender); snd_seq_addr_t dest; dest.client = in_client_id_; dest.port = in_port_id_; snd_seq_port_subscribe_set_dest(subs, &dest); int err = snd_seq_subscribe_port(in_client_, subs); if (err != 0) { VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err); return false; } // Update our map. source_map_[AddrToInt(client_id, port_id)] = port_index; return true; } MidiManager* MidiManager::Create() { return new MidiManagerAlsa(); } } // namespace media