blob: 66f427acf3bc6383f9e3e58139014d0b93f74a1b [file] [log] [blame]
// 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 <alsa/asoundlib.h>
#include <stdint.h>
#include <map>
#include <memory>
#include <utility>
#include <vector>
#include "base/containers/hash_tables.h"
#include "base/gtest_prod_util.h"
#include "base/macros.h"
#include "base/synchronization/lock.h"
#include "base/threading/thread.h"
#include "base/values.h"
#include "device/udev_linux/scoped_udev.h"
#include "media/midi/midi_export.h"
#include "media/midi/midi_manager.h"
namespace base {
class ThreadChecker;
namespace media {
namespace midi {
class MIDI_EXPORT MidiManagerAlsa final : public MidiManager {
~MidiManagerAlsa() override;
// MidiManager implementation.
void StartInitialization() override;
void Finalize() override;
void DispatchSendMidiData(MidiManagerClient* client,
uint32_t port_index,
const std::vector<uint8_t>& data,
double timestamp) override;
friend class MidiManagerAlsaTest;
FRIEND_TEST_ALL_PREFIXES(MidiManagerAlsaTest, ExtractManufacturer);
FRIEND_TEST_ALL_PREFIXES(MidiManagerAlsaTest, ToMidiPortState);
class AlsaCard;
using AlsaCardMap = std::map<int, std::unique_ptr<AlsaCard>>;
class MidiPort {
enum class Type { kInput, kOutput };
// The Id class is used to keep the multiple strings separate
// but compare them all together for equality purposes.
// The individual strings that make up the Id can theoretically contain
// arbitrary characters, so unfortunately there is no simple way to
// concatenate them into a single string.
class Id final {
Id(const std::string& bus,
const std::string& vendor_id,
const std::string& model_id,
const std::string& usb_interface_num,
const std::string& serial);
Id(const Id&);
bool operator==(const Id&) const;
bool empty() const;
std::string bus() const { return bus_; }
std::string vendor_id() const { return vendor_id_; }
std::string model_id() const { return model_id_; }
std::string usb_interface_num() const { return usb_interface_num_; }
std::string serial() const { return serial_; }
std::string bus_;
std::string vendor_id_;
std::string model_id_;
std::string usb_interface_num_;
std::string serial_;
MidiPort(const std::string& path,
const Id& 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);
// Gets a Value representation of this object, suitable for serialization.
std::unique_ptr<base::Value> Value() const;
// Gets a string version of Value in JSON format.
std::string JSONValue() const;
// Gets an opaque identifier for this object, suitable for using as the id
// field in on the web. Note that this string does not store
// the full state.
std::string OpaqueKey() const;
// Checks for equality for connected ports.
bool MatchConnected(const MidiPort& query) const;
// Checks for equality for kernel cards with id, pass 1.
bool MatchCardPass1(const MidiPort& query) const;
// Checks for equality for kernel cards with id, pass 2.
bool MatchCardPass2(const MidiPort& query) const;
// Checks for equality for non-card clients, pass 1.
bool MatchNoCardPass1(const MidiPort& query) const;
// Checks for equality for non-card clients, pass 2.
bool MatchNoCardPass2(const MidiPort& query) const;
// accessors
std::string path() const { return path_; }
Id id() const { return id_; }
std::string client_name() const { return client_name_; }
std::string port_name() const { return port_name_; }
std::string manufacturer() const { return manufacturer_; }
std::string version() const { return version_; }
int client_id() const { return client_id_; }
int port_id() const { return port_id_; }
int midi_device() const { return midi_device_; }
Type type() const { return type_; }
uint32_t web_port_index() const { return web_port_index_; }
bool connected() const { return connected_; }
// mutators
void set_web_port_index(uint32_t web_port_index) {
web_port_index_ = web_port_index;
void set_connected(bool connected) { connected_ = connected; }
void Update(const std::string& path,
int client_id,
int port_id,
const std::string& client_name,
const std::string& port_name,
const std::string& manufacturer,
const std::string& version) {
path_ = path;
client_id_ = client_id;
port_id_ = port_id;
client_name_ = client_name;
port_name_ = port_name;
manufacturer_ = manufacturer;
version_ = version;
// Immutable properties.
const Id id_;
const int midi_device_;
const Type type_;
// Mutable properties. These will get updated as ports move around or
// drivers change.
std::string path_;
int client_id_;
int port_id_;
std::string client_name_;
std::string port_name_;
std::string manufacturer_;
std::string version_;
// Index for MidiManager.
uint32_t web_port_index_ = 0;
// Port is present in the ALSA system.
bool connected_ = true;
class MidiPortStateBase {
typedef std::vector<std::unique_ptr<MidiPort>>::iterator iterator;
virtual ~MidiPortStateBase();
// Given a port, finds a port in the internal store.
iterator Find(const MidiPort& port);
// Given a port, finds a connected port, using exact matching.
iterator FindConnected(const MidiPort& port);
// Given a port, finds a disconnected port, using heuristic matching.
iterator FindDisconnected(const MidiPort& port);
iterator begin() { return ports_.begin(); }
iterator end() { return ports_.end(); }
iterator erase(iterator position) { return ports_.erase(position); }
void push_back(std::unique_ptr<MidiPort> port) {
std::vector<std::unique_ptr<MidiPort>> ports_;
class TemporaryMidiPortState final : public MidiPortStateBase {
iterator erase(iterator position) {
return MidiPortStateBase::erase(position);
void push_back(std::unique_ptr<MidiPort> port) {
class MidiPortState final : public MidiPortStateBase {
// Inserts a port at the end. Returns web_port_index.
uint32_t push_back(std::unique_ptr<MidiPort> port);
uint32_t num_input_ports_ = 0;
uint32_t num_output_ports_ = 0;
class AlsaSeqState {
enum class PortDirection { kInput, kOutput, kDuplex };
void ClientStart(int client_id,
const std::string& client_name,
snd_seq_client_type_t type);
bool ClientStarted(int client_id);
void ClientExit(int client_id);
void PortStart(int client_id,
int port_id,
const std::string& port_name,
PortDirection direction,
bool midi);
void PortExit(int client_id, int port_id);
snd_seq_client_type_t ClientType(int client_id) const;
std::unique_ptr<TemporaryMidiPortState> ToMidiPortState(
const AlsaCardMap& alsa_cards);
int card_client_count() { return card_client_count_; }
class Port {
Port(const std::string& name, PortDirection direction, bool midi);
std::string name() const { return name_; }
PortDirection direction() const { return direction_; }
// True if this port is a MIDI port, instead of another kind of ALSA port.
bool midi() const { return midi_; }
const std::string name_;
const PortDirection direction_;
const bool midi_;
class Client {
using PortMap = std::map<int, std::unique_ptr<Port>>;
Client(const std::string& name, snd_seq_client_type_t type);
std::string name() const { return name_; }
snd_seq_client_type_t type() const { return type_; }
void AddPort(int addr, std::unique_ptr<Port> port);
void RemovePort(int addr);
PortMap::const_iterator begin() const;
PortMap::const_iterator end() const;
const std::string name_;
const snd_seq_client_type_t type_;
PortMap ports_;
std::map<int, std::unique_ptr<Client>> clients_;
// This is the current number of clients we know about that have
// cards. When this number matches alsa_card_midi_count_, we know
// we are in sync between ALSA and udev. Until then, we cannot generate
// MIDIConnectionEvents to web clients.
int card_client_count_ = 0;
class AlsaCard {
AlsaCard(udev_device* dev,
const std::string& name,
const std::string& longname,
const std::string& driver,
int midi_device_count);
std::string name() const { return name_; }
std::string longname() const { return longname_; }
std::string driver() const { return driver_; }
std::string path() const { return path_; }
std::string bus() const { return bus_; }
std::string vendor_id() const { return vendor_id_; }
std::string model_id() const { return model_id_; }
std::string usb_interface_num() const { return usb_interface_num_; }
std::string serial() const { return serial_; }
int midi_device_count() const { return midi_device_count_; }
std::string manufacturer() const { return manufacturer_; }
FRIEND_TEST_ALL_PREFIXES(MidiManagerAlsaTest, ExtractManufacturer);
// Extracts the manufacturer using heuristics and a variety of sources.
static std::string 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& name,
const std::string& longname);
const std::string name_;
const std::string longname_;
const std::string driver_;
const std::string path_;
const std::string bus_;
const std::string vendor_id_;
const std::string model_id_;
const std::string usb_interface_num_;
const std::string serial_;
const int midi_device_count_;
const std::string manufacturer_;
struct SndSeqDeleter {
void operator()(snd_seq_t* seq) const { snd_seq_close(seq); }
struct SndMidiEventDeleter {
void operator()(snd_midi_event_t* coder) const {
using SourceMap = base::hash_map<int, uint32_t>;
using OutPortMap = base::hash_map<uint32_t, int>;
using ScopedSndSeqPtr = std::unique_ptr<snd_seq_t, SndSeqDeleter>;
using ScopedSndMidiEventPtr =
std::unique_ptr<snd_midi_event_t, SndMidiEventDeleter>;
// An internal callback that runs on MidiSendThread.
void SendMidiData(uint32_t port_index, const std::vector<uint8_t>& data);
void ScheduleEventLoop();
void EventLoop();
void ProcessSingleEvent(snd_seq_event_t* event, double timestamp);
void ProcessClientStartEvent(int client_id);
void ProcessPortStartEvent(const snd_seq_addr_t& addr);
void ProcessClientExitEvent(const snd_seq_addr_t& addr);
void ProcessPortExitEvent(const snd_seq_addr_t& addr);
void ProcessUdevEvent(udev_device* dev);
void AddCard(udev_device* dev);
void RemoveCard(int number);
// Updates port_state_ and Web MIDI state from alsa_seq_state_.
void UpdatePortStateAndGenerateEvents();
// Enumerates ports. Call once after subscribing to the announce port.
void EnumerateAlsaPorts();
// Enumerates udev cards. Call once after initializing the udev monitor.
bool EnumerateUdevCards();
// Returns true if successful.
bool CreateAlsaOutputPort(uint32_t port_index, int client_id, int port_id);
void DeleteAlsaOutputPort(uint32_t port_index);
// Returns true if successful.
bool Subscribe(uint32_t port_index, int client_id, int port_id);
// Members initialized in the constructor are below.
// Our copies of the internal state of the ports of seq and udev.
AlsaSeqState alsa_seq_state_;
MidiPortState port_state_;
// One input port, many output ports.
base::Lock out_ports_lock_; // guards out_ports_
OutPortMap out_ports_; // guarded by out_ports_lock_
// Mapping from ALSA client:port to our index.
SourceMap source_map_;
// Mapping from card to devices.
AlsaCardMap alsa_cards_;
// This is the current count of midi devices across all cards we know
// about. When this number matches card_client_count_ in AlsaSeqState,
// we are safe to generate MIDIConnectionEvents. Otherwise we need to
// wait for our information from ALSA and udev to get back in sync.
int alsa_card_midi_count_ = 0;
base::Lock shutdown_lock_; // guards event_thread_shutdown_
bool event_thread_shutdown_ = false; // guarded by shutdown_lock_
// This lock is needed to ensure that members destroyed in Finalize
// will be visibly destroyed before the destructor is run in the
// other thread. Otherwise, the same objects may have their destructors
// run multiple times in different threads.
base::Lock lazy_init_member_lock_; // guards members below
// Members initialized in StartInitialization() are below.
// Make sure to destroy these in Finalize()!
std::unique_ptr<base::ThreadChecker> initialization_thread_checker_;
// ALSA seq handles and ids.
ScopedSndSeqPtr in_client_;
int in_client_id_;
ScopedSndSeqPtr out_client_;
int out_client_id_;
int in_port_id_;
// ALSA event -> MIDI coder.
ScopedSndMidiEventPtr decoder_;
// udev, for querying hardware devices.
device::ScopedUdevPtr udev_;
device::ScopedUdevMonitorPtr udev_monitor_;
// Threads for sending and receiving. These are initialized in the
// constructor, but are started at the end of StartInitialization.
base::Thread event_thread_;
base::Thread send_thread_;
} // namespace midi
} // namespace media