// 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 #include #include "base/basictypes.h" #include "base/containers/scoped_ptr_map.h" #include "base/gtest_prod_util.h" #include "base/memory/scoped_ptr.h" #include "base/memory/scoped_vector.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 media { namespace midi { class MIDI_EXPORT MidiManagerAlsa final : public MidiManager { public: MidiManagerAlsa(); ~MidiManagerAlsa() override; // MidiManager implementation. void StartInitialization() override; void DispatchSendMidiData(MidiManagerClient* client, uint32 port_index, const std::vector& data, double timestamp) override; private: friend class MidiManagerAlsaTest; FRIEND_TEST_ALL_PREFIXES(MidiManagerAlsaTest, ExtractManufacturer); FRIEND_TEST_ALL_PREFIXES(MidiManagerAlsaTest, ToMidiPortState); class AlsaCard; typedef base::ScopedPtrMap> AlsaCardMap; 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. scoped_ptr 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 web_port_index() const { return web_port_index_; } bool connected() const { return connected_; } // mutators void set_web_port_index(uint32 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 web_port_index_ = 0; // Port is present in the ALSA system. bool connected_ = true; DISALLOW_COPY_AND_ASSIGN(MidiPort); }; class MidiPortStateBase { public: typedef ScopedVector::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(); } protected: MidiPortStateBase(); std::vector& ports() { return ports_.get(); } private: ScopedVector ports_; DISALLOW_COPY_AND_ASSIGN(MidiPortStateBase); }; class TemporaryMidiPortState final : public MidiPortStateBase { public: // Removes a port from the list without deleting it. iterator weak_erase(iterator position) { return ports().erase(position); } void Insert(scoped_ptr port); }; class MidiPortState final : public MidiPortStateBase { public: MidiPortState(); // Inserts a port. Returns web_port_index. uint32 Insert(scoped_ptr port); private: uint32 num_input_ports_ = 0; uint32 num_output_ports_ = 0; }; class AlsaSeqState { public: enum class PortDirection { kInput, kOutput, kDuplex }; AlsaSeqState(); ~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; scoped_ptr 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(); 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_; DISALLOW_COPY_AND_ASSIGN(Port); }; class Client { public: typedef base::ScopedPtrMap> PortMap; Client(const std::string& name, snd_seq_client_type_t type); ~Client(); std::string name() const { return name_; } snd_seq_client_type_t type() const { return type_; } void AddPort(int addr, scoped_ptr 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_; DISALLOW_COPY_AND_ASSIGN(Client); }; typedef base::ScopedPtrMap> ClientMap; ClientMap 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; DISALLOW_COPY_AND_ASSIGN(AlsaSeqState); }; class AlsaCard { public: AlsaCard(udev_device* dev, const std::string& name, const std::string& longname, const std::string& driver, int midi_device_count); ~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_; DISALLOW_COPY_AND_ASSIGN(AlsaCard); }; 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); }; }; typedef base::hash_map SourceMap; typedef base::hash_map OutPortMap; // An internal callback that runs on MidiSendThread. void SendMidiData(uint32 port_index, const std::vector& 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 port_index, int client_id, int port_id); void DeleteAlsaOutputPort(uint32 port_index); // Returns true if successful. bool Subscribe(uint32 port_index, int client_id, int port_id); AlsaSeqState alsa_seq_state_; MidiPortState port_state_; // ALSA seq handles. scoped_ptr in_client_; int in_client_id_ = -1; scoped_ptr out_client_; int out_client_id_ = -1; // One input port, many output ports. int in_port_id_ = -1; OutPortMap out_ports_; // guarded by out_ports_lock_ base::Lock out_ports_lock_; // guards out_ports_ // 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 event -> MIDI coder. scoped_ptr decoder_; // udev, for querying hardware devices. device::ScopedUdevPtr udev_; device::ScopedUdevMonitorPtr udev_monitor_; base::Thread send_thread_; base::Thread event_thread_; bool event_thread_shutdown_ = false; // guarded by shutdown_lock_ base::Lock shutdown_lock_; // guards event_thread_shutdown_ DISALLOW_COPY_AND_ASSIGN(MidiManagerAlsa); }; } // namespace midi } // namespace media #endif // MEDIA_MIDI_MIDI_MANAGER_ALSA_H_