blob: 271a6b8661b4aa288a99bebdbf679229047c3cb3 [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.
#ifndef MEDIA_MIDI_MIDI_MANAGER_ALSA_H_
#define MEDIA_MIDI_MIDI_MANAGER_ALSA_H_
#include <alsa/asoundlib.h>
#include <stdint.h>
#include <map>
#include <memory>
#include <unordered_map>
#include <utility>
#include <vector>
#include "base/gtest_prod_util.h"
#include "base/macros.h"
#include "base/synchronization/lock.h"
#include "base/thread_annotations.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 midi {
class MIDI_EXPORT MidiManagerAlsa final : public MidiManager {
public:
explicit MidiManagerAlsa(MidiService* service);
MidiManagerAlsa(const MidiManagerAlsa&) = delete;
MidiManagerAlsa& operator=(const MidiManagerAlsa&) = delete;
~MidiManagerAlsa() override;
// MidiManager implementation.
void StartInitialization() override;
void DispatchSendMidiData(MidiManagerClient* client,
uint32_t port_index,
const std::vector<uint8_t>& data,
base::TimeTicks timestamp) override;
private:
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 {
public:
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 {
public:
Id();
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&);
~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_; }
private:
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);
~MidiPort();
// 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 MidiPort.id 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;
}
private:
// 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;
DISALLOW_COPY_AND_ASSIGN(MidiPort);
};
class MidiPortStateBase {
public:
typedef std::vector<std::unique_ptr<MidiPort>>::iterator iterator;
MidiPortStateBase(const MidiPortStateBase&) = delete;
MidiPortStateBase& operator=(const MidiPortStateBase&) = delete;
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(); }
protected:
MidiPortStateBase();
iterator erase(iterator position) { return ports_.erase(position); }
void push_back(std::unique_ptr<MidiPort> port) {
ports_.push_back(std::move(port));
}
private:
std::vector<std::unique_ptr<MidiPort>> ports_;
};
class TemporaryMidiPortState final : public MidiPortStateBase {
public:
iterator erase(iterator position) {
return MidiPortStateBase::erase(position);
}
void push_back(std::unique_ptr<MidiPort> port) {
MidiPortStateBase::push_back(std::move(port));
}
};
class MidiPortState final : public MidiPortStateBase {
public:
MidiPortState();
// Inserts a port at the end. Returns web_port_index.
uint32_t push_back(std::unique_ptr<MidiPort> port);
private:
uint32_t num_input_ports_ = 0;
uint32_t num_output_ports_ = 0;
};
class AlsaSeqState {
public:
enum class PortDirection { kInput, kOutput, kDuplex };
AlsaSeqState();
AlsaSeqState(const AlsaSeqState&) = delete;
AlsaSeqState& operator=(const AlsaSeqState&) = delete;
~AlsaSeqState();
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_; }
private:
class Port {
public:
Port(const std::string& name, PortDirection direction, bool midi);
Port(const Port&) = delete;
Port& operator=(const Port&) = delete;
~Port();
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_; }
private:
const std::string name_;
const PortDirection direction_;
const bool midi_;
};
class Client {
public:
using PortMap = std::map<int, std::unique_ptr<Port>>;
Client(const std::string& name, snd_seq_client_type_t type);
Client(const Client&) = delete;
Client& operator=(const Client&) = delete;
~Client();
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;
private:
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 {
public:
AlsaCard(udev_device* dev,
const std::string& name,
const std::string& longname,
const std::string& driver,
int midi_device_count);
AlsaCard(const AlsaCard&) = delete;
AlsaCard& operator=(const AlsaCard&) = delete;
~AlsaCard();
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_; }
private:
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 {
snd_midi_event_free(coder);
}
};
using SourceMap = std::unordered_map<int, uint32_t>;
using OutPortMap = std::unordered_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(MidiManagerClient* client,
uint32_t port_index,
const std::vector<uint8_t>& data);
void EventLoop();
void ProcessSingleEvent(snd_seq_event_t* event, base::TimeTicks 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);
// Allocates new snd_midi_event_t instance and wraps it to return as
// ScopedSndMidiEventPtr.
ScopedSndMidiEventPtr CreateScopedSndMidiEventPtr(size_t size);
// 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_;
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;
// ALSA seq handles and ids.
ScopedSndSeqPtr in_client_;
int in_client_id_;
ScopedSndSeqPtr out_client_ GUARDED_BY(out_client_lock_);
base::Lock out_client_lock_;
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_;
};
} // namespace midi
#endif // MEDIA_MIDI_MIDI_MANAGER_ALSA_H_