blob: efae376f2207597fe6b15bd97331633cecc0d36d [file] [log] [blame]
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +00001// 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.
4
dnicoara@chromium.org9f2a6f02014-01-03 21:25:00 +00005#ifndef MEDIA_MIDI_MIDI_MANAGER_ALSA_H_
6#define MEDIA_MIDI_MIDI_MANAGER_ALSA_H_
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +00007
agoode@chromium.org25227512014-06-08 05:12:05 +00008#include <alsa/asoundlib.h>
avi793390d2015-12-22 22:22:36 -08009#include <stdint.h>
limasdfe59d0392015-11-19 20:28:57 -080010#include <map>
dchengc2aeece2015-12-27 00:54:00 -080011#include <utility>
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000012#include <vector>
13
bnc628660d2016-02-05 19:58:14 -080014#include "base/containers/hash_tables.h"
agoode55a8b522015-03-08 12:40:17 -070015#include "base/gtest_prod_util.h"
avieea95e82015-12-18 20:27:08 -080016#include "base/macros.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000017#include "base/memory/scoped_ptr.h"
agoode@chromium.org25227512014-06-08 05:12:05 +000018#include "base/synchronization/lock.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000019#include "base/threading/thread.h"
agoodef212b2a2015-03-19 12:53:23 -070020#include "base/values.h"
agoode7de413f2015-04-24 00:13:39 -070021#include "device/udev_linux/scoped_udev.h"
brettw49ff0172015-05-05 12:43:04 -070022#include "media/midi/midi_export.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000023#include "media/midi/midi_manager.h"
24
25namespace media {
toyoshime147c5e2015-05-07 21:58:31 -070026namespace midi {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000027
brettw49ff0172015-05-05 12:43:04 -070028class MIDI_EXPORT MidiManagerAlsa final : public MidiManager {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000029 public:
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +000030 MidiManagerAlsa();
dcheng9e8524d2014-10-27 15:18:50 -070031 ~MidiManagerAlsa() override;
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000032
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +000033 // MidiManager implementation.
dcheng9e8524d2014-10-27 15:18:50 -070034 void StartInitialization() override;
toyoshim8e7d6e02015-10-06 08:47:17 -070035 void Finalize() override;
dcheng9e8524d2014-10-27 15:18:50 -070036 void DispatchSendMidiData(MidiManagerClient* client,
Avi Drissman3528fd02015-12-18 20:11:31 -050037 uint32_t port_index,
38 const std::vector<uint8_t>& data,
dcheng9e8524d2014-10-27 15:18:50 -070039 double timestamp) override;
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000040
41 private:
agoode99d63292015-04-13 08:39:25 -070042 friend class MidiManagerAlsaTest;
agoode55a8b522015-03-08 12:40:17 -070043 FRIEND_TEST_ALL_PREFIXES(MidiManagerAlsaTest, ExtractManufacturer);
agoode99d63292015-04-13 08:39:25 -070044 FRIEND_TEST_ALL_PREFIXES(MidiManagerAlsaTest, ToMidiPortState);
agoode55a8b522015-03-08 12:40:17 -070045
agoodeb09423b2015-05-11 11:39:57 -070046 class AlsaCard;
limasdfe59d0392015-11-19 20:28:57 -080047 using AlsaCardMap = std::map<int, scoped_ptr<AlsaCard>>;
agoodeb09423b2015-05-11 11:39:57 -070048
agoode99d63292015-04-13 08:39:25 -070049 class MidiPort {
agoodef212b2a2015-03-19 12:53:23 -070050 public:
51 enum class Type { kInput, kOutput };
52
agooded87fc0f2015-05-21 08:29:31 -070053 // The Id class is used to keep the multiple strings separate
54 // but compare them all together for equality purposes.
55 // The individual strings that make up the Id can theoretically contain
56 // arbitrary characters, so unfortunately there is no simple way to
57 // concatenate them into a single string.
58 class Id final {
59 public:
60 Id();
61 Id(const std::string& bus,
62 const std::string& vendor_id,
63 const std::string& model_id,
64 const std::string& usb_interface_num,
65 const std::string& serial);
66 Id(const Id&);
67 ~Id();
68 bool operator==(const Id&) const;
69 bool empty() const;
70
71 std::string bus() const { return bus_; }
72 std::string vendor_id() const { return vendor_id_; }
73 std::string model_id() const { return model_id_; }
74 std::string usb_interface_num() const { return usb_interface_num_; }
75 std::string serial() const { return serial_; }
76
77 private:
78 std::string bus_;
79 std::string vendor_id_;
80 std::string model_id_;
81 std::string usb_interface_num_;
82 std::string serial_;
83 };
84
agoode99d63292015-04-13 08:39:25 -070085 MidiPort(const std::string& path,
agooded87fc0f2015-05-21 08:29:31 -070086 const Id& id,
agoode99d63292015-04-13 08:39:25 -070087 int client_id,
88 int port_id,
89 int midi_device,
90 const std::string& client_name,
91 const std::string& port_name,
92 const std::string& manufacturer,
93 const std::string& version,
94 Type type);
95 ~MidiPort();
agoodef212b2a2015-03-19 12:53:23 -070096
97 // Gets a Value representation of this object, suitable for serialization.
98 scoped_ptr<base::Value> Value() const;
99
100 // Gets a string version of Value in JSON format.
101 std::string JSONValue() const;
102
103 // Gets an opaque identifier for this object, suitable for using as the id
104 // field in MidiPort.id on the web. Note that this string does not store
105 // the full state.
106 std::string OpaqueKey() const;
107
agoode99d63292015-04-13 08:39:25 -0700108 // Checks for equality for connected ports.
109 bool MatchConnected(const MidiPort& query) const;
110 // Checks for equality for kernel cards with id, pass 1.
111 bool MatchCardPass1(const MidiPort& query) const;
112 // Checks for equality for kernel cards with id, pass 2.
113 bool MatchCardPass2(const MidiPort& query) const;
114 // Checks for equality for non-card clients, pass 1.
115 bool MatchNoCardPass1(const MidiPort& query) const;
116 // Checks for equality for non-card clients, pass 2.
117 bool MatchNoCardPass2(const MidiPort& query) const;
agoodef212b2a2015-03-19 12:53:23 -0700118
agoode99d63292015-04-13 08:39:25 -0700119 // accessors
120 std::string path() const { return path_; }
agooded87fc0f2015-05-21 08:29:31 -0700121 Id id() const { return id_; }
agoode99d63292015-04-13 08:39:25 -0700122 std::string client_name() const { return client_name_; }
123 std::string port_name() const { return port_name_; }
124 std::string manufacturer() const { return manufacturer_; }
125 std::string version() const { return version_; }
126 int client_id() const { return client_id_; }
127 int port_id() const { return port_id_; }
128 int midi_device() const { return midi_device_; }
129 Type type() const { return type_; }
Avi Drissman3528fd02015-12-18 20:11:31 -0500130 uint32_t web_port_index() const { return web_port_index_; }
agoode99d63292015-04-13 08:39:25 -0700131 bool connected() const { return connected_; }
132
133 // mutators
Avi Drissman3528fd02015-12-18 20:11:31 -0500134 void set_web_port_index(uint32_t web_port_index) {
agoode99d63292015-04-13 08:39:25 -0700135 web_port_index_ = web_port_index;
136 }
137 void set_connected(bool connected) { connected_ = connected; }
138 void Update(const std::string& path,
139 int client_id,
140 int port_id,
141 const std::string& client_name,
142 const std::string& port_name,
143 const std::string& manufacturer,
144 const std::string& version) {
145 path_ = path;
146 client_id_ = client_id;
147 port_id_ = port_id;
148 client_name_ = client_name;
149 port_name_ = port_name;
150 manufacturer_ = manufacturer;
151 version_ = version;
152 }
153
154 private:
155 // Immutable properties.
agooded87fc0f2015-05-21 08:29:31 -0700156 const Id id_;
agoode99d63292015-04-13 08:39:25 -0700157 const int midi_device_;
158
agoodef212b2a2015-03-19 12:53:23 -0700159 const Type type_;
160
agoode99d63292015-04-13 08:39:25 -0700161 // Mutable properties. These will get updated as ports move around or
162 // drivers change.
163 std::string path_;
164 int client_id_;
165 int port_id_;
166 std::string client_name_;
167 std::string port_name_;
168 std::string manufacturer_;
169 std::string version_;
170
171 // Index for MidiManager.
Avi Drissman3528fd02015-12-18 20:11:31 -0500172 uint32_t web_port_index_ = 0;
agoode99d63292015-04-13 08:39:25 -0700173
174 // Port is present in the ALSA system.
agoode5a1aa112015-06-21 20:51:00 -0700175 bool connected_ = true;
agoode99d63292015-04-13 08:39:25 -0700176
177 DISALLOW_COPY_AND_ASSIGN(MidiPort);
agoode55a8b522015-03-08 12:40:17 -0700178 };
179
agoode99d63292015-04-13 08:39:25 -0700180 class MidiPortStateBase {
181 public:
agoode5ebc4932015-12-01 08:25:12 -0800182 typedef std::vector<scoped_ptr<MidiPort>>::iterator iterator;
agoodebd4be9b2015-03-16 19:17:25 -0700183
agoode99d63292015-04-13 08:39:25 -0700184 virtual ~MidiPortStateBase();
185
186 // Given a port, finds a port in the internal store.
187 iterator Find(const MidiPort& port);
188
189 // Given a port, finds a connected port, using exact matching.
190 iterator FindConnected(const MidiPort& port);
191
192 // Given a port, finds a disconnected port, using heuristic matching.
193 iterator FindDisconnected(const MidiPort& port);
194
195 iterator begin() { return ports_.begin(); }
196 iterator end() { return ports_.end(); }
197
198 protected:
199 MidiPortStateBase();
agoode5ebc4932015-12-01 08:25:12 -0800200 iterator erase(iterator position) { return ports_.erase(position); }
dchengc2aeece2015-12-27 00:54:00 -0800201 void push_back(scoped_ptr<MidiPort> port) {
202 ports_.push_back(std::move(port));
203 }
agoode99d63292015-04-13 08:39:25 -0700204
205 private:
agoode5ebc4932015-12-01 08:25:12 -0800206 std::vector<scoped_ptr<MidiPort>> ports_;
agoode99d63292015-04-13 08:39:25 -0700207
208 DISALLOW_COPY_AND_ASSIGN(MidiPortStateBase);
209 };
210
211 class TemporaryMidiPortState final : public MidiPortStateBase {
212 public:
agoode5ebc4932015-12-01 08:25:12 -0800213 iterator erase(iterator position) {
214 return MidiPortStateBase::erase(position);
215 };
216 void push_back(scoped_ptr<MidiPort> port) {
dchengc2aeece2015-12-27 00:54:00 -0800217 MidiPortStateBase::push_back(std::move(port));
agoode5ebc4932015-12-01 08:25:12 -0800218 }
agoode99d63292015-04-13 08:39:25 -0700219 };
220
221 class MidiPortState final : public MidiPortStateBase {
222 public:
223 MidiPortState();
224
agoode5ebc4932015-12-01 08:25:12 -0800225 // Inserts a port at the end. Returns web_port_index.
Avi Drissman3528fd02015-12-18 20:11:31 -0500226 uint32_t push_back(scoped_ptr<MidiPort> port);
agoode99d63292015-04-13 08:39:25 -0700227
228 private:
Avi Drissman3528fd02015-12-18 20:11:31 -0500229 uint32_t num_input_ports_ = 0;
230 uint32_t num_output_ports_ = 0;
agoode99d63292015-04-13 08:39:25 -0700231 };
232
233 class AlsaSeqState {
234 public:
235 enum class PortDirection { kInput, kOutput, kDuplex };
236
237 AlsaSeqState();
238 ~AlsaSeqState();
239
240 void ClientStart(int client_id,
241 const std::string& client_name,
242 snd_seq_client_type_t type);
243 bool ClientStarted(int client_id);
244 void ClientExit(int client_id);
245 void PortStart(int client_id,
246 int port_id,
247 const std::string& port_name,
248 PortDirection direction,
249 bool midi);
250 void PortExit(int client_id, int port_id);
251 snd_seq_client_type_t ClientType(int client_id) const;
agoodeb09423b2015-05-11 11:39:57 -0700252 scoped_ptr<TemporaryMidiPortState> ToMidiPortState(
253 const AlsaCardMap& alsa_cards);
254
255 int card_client_count() { return card_client_count_; }
agoode99d63292015-04-13 08:39:25 -0700256
257 private:
258 class Port {
259 public:
260 Port(const std::string& name, PortDirection direction, bool midi);
261 ~Port();
262
agoodeb0582872015-05-20 05:22:24 -0700263 std::string name() const { return name_; }
264 PortDirection direction() const { return direction_; }
agoode99d63292015-04-13 08:39:25 -0700265 // True if this port is a MIDI port, instead of another kind of ALSA port.
agoodeb0582872015-05-20 05:22:24 -0700266 bool midi() const { return midi_; }
agoode99d63292015-04-13 08:39:25 -0700267
268 private:
269 const std::string name_;
270 const PortDirection direction_;
271 const bool midi_;
272
273 DISALLOW_COPY_AND_ASSIGN(Port);
274 };
275
276 class Client {
277 public:
limasdfe59d0392015-11-19 20:28:57 -0800278 using PortMap = std::map<int, scoped_ptr<Port>>;
agoode99d63292015-04-13 08:39:25 -0700279
280 Client(const std::string& name, snd_seq_client_type_t type);
281 ~Client();
282
agoodeb0582872015-05-20 05:22:24 -0700283 std::string name() const { return name_; }
284 snd_seq_client_type_t type() const { return type_; }
agoode99d63292015-04-13 08:39:25 -0700285 void AddPort(int addr, scoped_ptr<Port> port);
286 void RemovePort(int addr);
287 PortMap::const_iterator begin() const;
288 PortMap::const_iterator end() const;
289
290 private:
291 const std::string name_;
292 const snd_seq_client_type_t type_;
293 PortMap ports_;
agoode99d63292015-04-13 08:39:25 -0700294
295 DISALLOW_COPY_AND_ASSIGN(Client);
296 };
297
limasdfe59d0392015-11-19 20:28:57 -0800298 std::map<int, scoped_ptr<Client>> clients_;
agoode99d63292015-04-13 08:39:25 -0700299
agoodeb09423b2015-05-11 11:39:57 -0700300 // This is the current number of clients we know about that have
301 // cards. When this number matches alsa_card_midi_count_, we know
302 // we are in sync between ALSA and udev. Until then, we cannot generate
303 // MIDIConnectionEvents to web clients.
agoodedf1b9ff2015-06-25 18:14:50 -0700304 int card_client_count_ = 0;
agoodeb09423b2015-05-11 11:39:57 -0700305
agoode99d63292015-04-13 08:39:25 -0700306 DISALLOW_COPY_AND_ASSIGN(AlsaSeqState);
307 };
308
agoodeb09423b2015-05-11 11:39:57 -0700309 class AlsaCard {
310 public:
311 AlsaCard(udev_device* dev,
agooded87fc0f2015-05-21 08:29:31 -0700312 const std::string& name,
313 const std::string& longname,
314 const std::string& driver,
agoodeb09423b2015-05-11 11:39:57 -0700315 int midi_device_count);
316 ~AlsaCard();
agooded87fc0f2015-05-21 08:29:31 -0700317 std::string name() const { return name_; }
318 std::string longname() const { return longname_; }
319 std::string driver() const { return driver_; }
320 std::string path() const { return path_; }
321 std::string bus() const { return bus_; }
322 std::string vendor_id() const { return vendor_id_; }
323 std::string model_id() const { return model_id_; }
324 std::string usb_interface_num() const { return usb_interface_num_; }
325 std::string serial() const { return serial_; }
agoodeb09423b2015-05-11 11:39:57 -0700326 int midi_device_count() const { return midi_device_count_; }
agooded87fc0f2015-05-21 08:29:31 -0700327 std::string manufacturer() const { return manufacturer_; }
agoodeb09423b2015-05-11 11:39:57 -0700328
329 private:
330 FRIEND_TEST_ALL_PREFIXES(MidiManagerAlsaTest, ExtractManufacturer);
331
332 // Extracts the manufacturer using heuristics and a variety of sources.
333 static std::string ExtractManufacturerString(
334 const std::string& udev_id_vendor,
335 const std::string& udev_id_vendor_id,
336 const std::string& udev_id_vendor_from_database,
agooded87fc0f2015-05-21 08:29:31 -0700337 const std::string& name,
338 const std::string& longname);
agoodeb09423b2015-05-11 11:39:57 -0700339
agooded87fc0f2015-05-21 08:29:31 -0700340 const std::string name_;
341 const std::string longname_;
342 const std::string driver_;
343 const std::string path_;
344 const std::string bus_;
345 const std::string vendor_id_;
346 const std::string model_id_;
347 const std::string usb_interface_num_;
348 const std::string serial_;
349 const int midi_device_count_;
350 const std::string manufacturer_;
agoodeb09423b2015-05-11 11:39:57 -0700351
352 DISALLOW_COPY_AND_ASSIGN(AlsaCard);
353 };
354
agoode5a1aa112015-06-21 20:51:00 -0700355 struct SndSeqDeleter {
356 void operator()(snd_seq_t* seq) const { snd_seq_close(seq); }
357 };
358
359 struct SndMidiEventDeleter {
360 void operator()(snd_midi_event_t* coder) const {
361 snd_midi_event_free(coder);
362 };
363 };
364
Avi Drissman3528fd02015-12-18 20:11:31 -0500365 typedef base::hash_map<int, uint32_t> SourceMap;
366 typedef base::hash_map<uint32_t, int> OutPortMap;
agoode99d63292015-04-13 08:39:25 -0700367
agoode@chromium.org25227512014-06-08 05:12:05 +0000368 // An internal callback that runs on MidiSendThread.
Avi Drissman3528fd02015-12-18 20:11:31 -0500369 void SendMidiData(uint32_t port_index, const std::vector<uint8_t>& data);
agoode@chromium.org25227512014-06-08 05:12:05 +0000370
agoodebd4be9b2015-03-16 19:17:25 -0700371 void ScheduleEventLoop();
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000372 void EventLoop();
agoodebd4be9b2015-03-16 19:17:25 -0700373 void ProcessSingleEvent(snd_seq_event_t* event, double timestamp);
agoode99d63292015-04-13 08:39:25 -0700374 void ProcessClientStartEvent(int client_id);
375 void ProcessPortStartEvent(const snd_seq_addr_t& addr);
376 void ProcessClientExitEvent(const snd_seq_addr_t& addr);
377 void ProcessPortExitEvent(const snd_seq_addr_t& addr);
agoode975043d2015-05-11 00:46:17 -0700378 void ProcessUdevEvent(udev_device* dev);
agoodeb09423b2015-05-11 11:39:57 -0700379 void AddCard(udev_device* dev);
380 void RemoveCard(int number);
agoode99d63292015-04-13 08:39:25 -0700381
382 // Updates port_state_ and Web MIDI state from alsa_seq_state_.
383 void UpdatePortStateAndGenerateEvents();
384
385 // Enumerates ports. Call once after subscribing to the announce port.
386 void EnumerateAlsaPorts();
agoode975043d2015-05-11 00:46:17 -0700387 // Enumerates udev cards. Call once after initializing the udev monitor.
388 bool EnumerateUdevCards();
agoode99d63292015-04-13 08:39:25 -0700389 // Returns true if successful.
Avi Drissman3528fd02015-12-18 20:11:31 -0500390 bool CreateAlsaOutputPort(uint32_t port_index, int client_id, int port_id);
391 void DeleteAlsaOutputPort(uint32_t port_index);
agoode99d63292015-04-13 08:39:25 -0700392 // Returns true if successful.
Avi Drissman3528fd02015-12-18 20:11:31 -0500393 bool Subscribe(uint32_t port_index, int client_id, int port_id);
agoode99d63292015-04-13 08:39:25 -0700394
395 AlsaSeqState alsa_seq_state_;
396 MidiPortState port_state_;
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000397
agoodebd4be9b2015-03-16 19:17:25 -0700398 // ALSA seq handles.
agoode5a1aa112015-06-21 20:51:00 -0700399 scoped_ptr<snd_seq_t, SndSeqDeleter> in_client_;
400 int in_client_id_ = -1;
401 scoped_ptr<snd_seq_t, SndSeqDeleter> out_client_;
402 int out_client_id_ = -1;
agoode@chromium.org25227512014-06-08 05:12:05 +0000403
404 // One input port, many output ports.
agoode5a1aa112015-06-21 20:51:00 -0700405 int in_port_id_ = -1;
agoode99d63292015-04-13 08:39:25 -0700406 OutPortMap out_ports_; // guarded by out_ports_lock_
407 base::Lock out_ports_lock_; // guards out_ports_
agoode@chromium.org25227512014-06-08 05:12:05 +0000408
agoodebd4be9b2015-03-16 19:17:25 -0700409 // Mapping from ALSA client:port to our index.
agoode@chromium.org25227512014-06-08 05:12:05 +0000410 SourceMap source_map_;
411
agoodeb09423b2015-05-11 11:39:57 -0700412 // Mapping from card to devices.
413 AlsaCardMap alsa_cards_;
agoodeb09423b2015-05-11 11:39:57 -0700414
415 // This is the current count of midi devices across all cards we know
416 // about. When this number matches card_client_count_ in AlsaSeqState,
417 // we are safe to generate MIDIConnectionEvents. Otherwise we need to
418 // wait for our information from ALSA and udev to get back in sync.
agoode5a1aa112015-06-21 20:51:00 -0700419 int alsa_card_midi_count_ = 0;
agoodeb09423b2015-05-11 11:39:57 -0700420
agoode99d63292015-04-13 08:39:25 -0700421 // ALSA event -> MIDI coder.
agoode5a1aa112015-06-21 20:51:00 -0700422 scoped_ptr<snd_midi_event_t, SndMidiEventDeleter> decoder_;
agoode@chromium.org25227512014-06-08 05:12:05 +0000423
agoode7de413f2015-04-24 00:13:39 -0700424 // udev, for querying hardware devices.
425 device::ScopedUdevPtr udev_;
agoode975043d2015-05-11 00:46:17 -0700426 device::ScopedUdevMonitorPtr udev_monitor_;
agoode7de413f2015-04-24 00:13:39 -0700427
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000428 base::Thread send_thread_;
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000429 base::Thread event_thread_;
430
agoode5a1aa112015-06-21 20:51:00 -0700431 bool event_thread_shutdown_ = false; // guarded by shutdown_lock_
432 base::Lock shutdown_lock_; // guards event_thread_shutdown_
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000433
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000434 DISALLOW_COPY_AND_ASSIGN(MidiManagerAlsa);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000435};
436
toyoshime147c5e2015-05-07 21:58:31 -0700437} // namespace midi
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000438} // namespace media
439
dnicoara@chromium.org9f2a6f02014-01-03 21:25:00 +0000440#endif // MEDIA_MIDI_MIDI_MANAGER_ALSA_H_