blob: a3ab5e9767ea1a3e43ffa5dc5f297d7406ae4390 [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>
limasdfe59d0392015-11-19 20:28:57 -08009#include <map>
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000010#include <vector>
11
agoode55a8b522015-03-08 12:40:17 -070012#include "base/gtest_prod_util.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000013#include "base/memory/scoped_ptr.h"
agoode@chromium.org25227512014-06-08 05:12:05 +000014#include "base/synchronization/lock.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000015#include "base/threading/thread.h"
agoodef212b2a2015-03-19 12:53:23 -070016#include "base/values.h"
agoode7de413f2015-04-24 00:13:39 -070017#include "device/udev_linux/scoped_udev.h"
brettw49ff0172015-05-05 12:43:04 -070018#include "media/midi/midi_export.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000019#include "media/midi/midi_manager.h"
20
21namespace media {
toyoshime147c5e2015-05-07 21:58:31 -070022namespace midi {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000023
brettw49ff0172015-05-05 12:43:04 -070024class MIDI_EXPORT MidiManagerAlsa final : public MidiManager {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000025 public:
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +000026 MidiManagerAlsa();
dcheng9e8524d2014-10-27 15:18:50 -070027 ~MidiManagerAlsa() override;
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000028
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +000029 // MidiManager implementation.
dcheng9e8524d2014-10-27 15:18:50 -070030 void StartInitialization() override;
toyoshim8e7d6e02015-10-06 08:47:17 -070031 void Finalize() override;
dcheng9e8524d2014-10-27 15:18:50 -070032 void DispatchSendMidiData(MidiManagerClient* client,
Avi Drissman3528fd02015-12-18 20:11:31 -050033 uint32_t port_index,
34 const std::vector<uint8_t>& data,
dcheng9e8524d2014-10-27 15:18:50 -070035 double timestamp) override;
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000036
37 private:
agoode99d63292015-04-13 08:39:25 -070038 friend class MidiManagerAlsaTest;
agoode55a8b522015-03-08 12:40:17 -070039 FRIEND_TEST_ALL_PREFIXES(MidiManagerAlsaTest, ExtractManufacturer);
agoode99d63292015-04-13 08:39:25 -070040 FRIEND_TEST_ALL_PREFIXES(MidiManagerAlsaTest, ToMidiPortState);
agoode55a8b522015-03-08 12:40:17 -070041
agoodeb09423b2015-05-11 11:39:57 -070042 class AlsaCard;
limasdfe59d0392015-11-19 20:28:57 -080043 using AlsaCardMap = std::map<int, scoped_ptr<AlsaCard>>;
agoodeb09423b2015-05-11 11:39:57 -070044
agoode99d63292015-04-13 08:39:25 -070045 class MidiPort {
agoodef212b2a2015-03-19 12:53:23 -070046 public:
47 enum class Type { kInput, kOutput };
48
agooded87fc0f2015-05-21 08:29:31 -070049 // The Id class is used to keep the multiple strings separate
50 // but compare them all together for equality purposes.
51 // The individual strings that make up the Id can theoretically contain
52 // arbitrary characters, so unfortunately there is no simple way to
53 // concatenate them into a single string.
54 class Id final {
55 public:
56 Id();
57 Id(const std::string& bus,
58 const std::string& vendor_id,
59 const std::string& model_id,
60 const std::string& usb_interface_num,
61 const std::string& serial);
62 Id(const Id&);
63 ~Id();
64 bool operator==(const Id&) const;
65 bool empty() const;
66
67 std::string bus() const { return bus_; }
68 std::string vendor_id() const { return vendor_id_; }
69 std::string model_id() const { return model_id_; }
70 std::string usb_interface_num() const { return usb_interface_num_; }
71 std::string serial() const { return serial_; }
72
73 private:
74 std::string bus_;
75 std::string vendor_id_;
76 std::string model_id_;
77 std::string usb_interface_num_;
78 std::string serial_;
79 };
80
agoode99d63292015-04-13 08:39:25 -070081 MidiPort(const std::string& path,
agooded87fc0f2015-05-21 08:29:31 -070082 const Id& id,
agoode99d63292015-04-13 08:39:25 -070083 int client_id,
84 int port_id,
85 int midi_device,
86 const std::string& client_name,
87 const std::string& port_name,
88 const std::string& manufacturer,
89 const std::string& version,
90 Type type);
91 ~MidiPort();
agoodef212b2a2015-03-19 12:53:23 -070092
93 // Gets a Value representation of this object, suitable for serialization.
94 scoped_ptr<base::Value> Value() const;
95
96 // Gets a string version of Value in JSON format.
97 std::string JSONValue() const;
98
99 // Gets an opaque identifier for this object, suitable for using as the id
100 // field in MidiPort.id on the web. Note that this string does not store
101 // the full state.
102 std::string OpaqueKey() const;
103
agoode99d63292015-04-13 08:39:25 -0700104 // Checks for equality for connected ports.
105 bool MatchConnected(const MidiPort& query) const;
106 // Checks for equality for kernel cards with id, pass 1.
107 bool MatchCardPass1(const MidiPort& query) const;
108 // Checks for equality for kernel cards with id, pass 2.
109 bool MatchCardPass2(const MidiPort& query) const;
110 // Checks for equality for non-card clients, pass 1.
111 bool MatchNoCardPass1(const MidiPort& query) const;
112 // Checks for equality for non-card clients, pass 2.
113 bool MatchNoCardPass2(const MidiPort& query) const;
agoodef212b2a2015-03-19 12:53:23 -0700114
agoode99d63292015-04-13 08:39:25 -0700115 // accessors
116 std::string path() const { return path_; }
agooded87fc0f2015-05-21 08:29:31 -0700117 Id id() const { return id_; }
agoode99d63292015-04-13 08:39:25 -0700118 std::string client_name() const { return client_name_; }
119 std::string port_name() const { return port_name_; }
120 std::string manufacturer() const { return manufacturer_; }
121 std::string version() const { return version_; }
122 int client_id() const { return client_id_; }
123 int port_id() const { return port_id_; }
124 int midi_device() const { return midi_device_; }
125 Type type() const { return type_; }
Avi Drissman3528fd02015-12-18 20:11:31 -0500126 uint32_t web_port_index() const { return web_port_index_; }
agoode99d63292015-04-13 08:39:25 -0700127 bool connected() const { return connected_; }
128
129 // mutators
Avi Drissman3528fd02015-12-18 20:11:31 -0500130 void set_web_port_index(uint32_t web_port_index) {
agoode99d63292015-04-13 08:39:25 -0700131 web_port_index_ = web_port_index;
132 }
133 void set_connected(bool connected) { connected_ = connected; }
134 void Update(const std::string& path,
135 int client_id,
136 int port_id,
137 const std::string& client_name,
138 const std::string& port_name,
139 const std::string& manufacturer,
140 const std::string& version) {
141 path_ = path;
142 client_id_ = client_id;
143 port_id_ = port_id;
144 client_name_ = client_name;
145 port_name_ = port_name;
146 manufacturer_ = manufacturer;
147 version_ = version;
148 }
149
150 private:
151 // Immutable properties.
agooded87fc0f2015-05-21 08:29:31 -0700152 const Id id_;
agoode99d63292015-04-13 08:39:25 -0700153 const int midi_device_;
154
agoodef212b2a2015-03-19 12:53:23 -0700155 const Type type_;
156
agoode99d63292015-04-13 08:39:25 -0700157 // Mutable properties. These will get updated as ports move around or
158 // drivers change.
159 std::string path_;
160 int client_id_;
161 int port_id_;
162 std::string client_name_;
163 std::string port_name_;
164 std::string manufacturer_;
165 std::string version_;
166
167 // Index for MidiManager.
Avi Drissman3528fd02015-12-18 20:11:31 -0500168 uint32_t web_port_index_ = 0;
agoode99d63292015-04-13 08:39:25 -0700169
170 // Port is present in the ALSA system.
agoode5a1aa112015-06-21 20:51:00 -0700171 bool connected_ = true;
agoode99d63292015-04-13 08:39:25 -0700172
173 DISALLOW_COPY_AND_ASSIGN(MidiPort);
agoode55a8b522015-03-08 12:40:17 -0700174 };
175
agoode99d63292015-04-13 08:39:25 -0700176 class MidiPortStateBase {
177 public:
agoode5ebc4932015-12-01 08:25:12 -0800178 typedef std::vector<scoped_ptr<MidiPort>>::iterator iterator;
agoodebd4be9b2015-03-16 19:17:25 -0700179
agoode99d63292015-04-13 08:39:25 -0700180 virtual ~MidiPortStateBase();
181
182 // Given a port, finds a port in the internal store.
183 iterator Find(const MidiPort& port);
184
185 // Given a port, finds a connected port, using exact matching.
186 iterator FindConnected(const MidiPort& port);
187
188 // Given a port, finds a disconnected port, using heuristic matching.
189 iterator FindDisconnected(const MidiPort& port);
190
191 iterator begin() { return ports_.begin(); }
192 iterator end() { return ports_.end(); }
193
194 protected:
195 MidiPortStateBase();
agoode5ebc4932015-12-01 08:25:12 -0800196 iterator erase(iterator position) { return ports_.erase(position); }
197 void push_back(scoped_ptr<MidiPort> port) { ports_.push_back(port.Pass()); }
agoode99d63292015-04-13 08:39:25 -0700198
199 private:
agoode5ebc4932015-12-01 08:25:12 -0800200 std::vector<scoped_ptr<MidiPort>> ports_;
agoode99d63292015-04-13 08:39:25 -0700201
202 DISALLOW_COPY_AND_ASSIGN(MidiPortStateBase);
203 };
204
205 class TemporaryMidiPortState final : public MidiPortStateBase {
206 public:
agoode5ebc4932015-12-01 08:25:12 -0800207 iterator erase(iterator position) {
208 return MidiPortStateBase::erase(position);
209 };
210 void push_back(scoped_ptr<MidiPort> port) {
211 MidiPortStateBase::push_back(port.Pass());
212 }
agoode99d63292015-04-13 08:39:25 -0700213 };
214
215 class MidiPortState final : public MidiPortStateBase {
216 public:
217 MidiPortState();
218
agoode5ebc4932015-12-01 08:25:12 -0800219 // Inserts a port at the end. Returns web_port_index.
Avi Drissman3528fd02015-12-18 20:11:31 -0500220 uint32_t push_back(scoped_ptr<MidiPort> port);
agoode99d63292015-04-13 08:39:25 -0700221
222 private:
Avi Drissman3528fd02015-12-18 20:11:31 -0500223 uint32_t num_input_ports_ = 0;
224 uint32_t num_output_ports_ = 0;
agoode99d63292015-04-13 08:39:25 -0700225 };
226
227 class AlsaSeqState {
228 public:
229 enum class PortDirection { kInput, kOutput, kDuplex };
230
231 AlsaSeqState();
232 ~AlsaSeqState();
233
234 void ClientStart(int client_id,
235 const std::string& client_name,
236 snd_seq_client_type_t type);
237 bool ClientStarted(int client_id);
238 void ClientExit(int client_id);
239 void PortStart(int client_id,
240 int port_id,
241 const std::string& port_name,
242 PortDirection direction,
243 bool midi);
244 void PortExit(int client_id, int port_id);
245 snd_seq_client_type_t ClientType(int client_id) const;
agoodeb09423b2015-05-11 11:39:57 -0700246 scoped_ptr<TemporaryMidiPortState> ToMidiPortState(
247 const AlsaCardMap& alsa_cards);
248
249 int card_client_count() { return card_client_count_; }
agoode99d63292015-04-13 08:39:25 -0700250
251 private:
252 class Port {
253 public:
254 Port(const std::string& name, PortDirection direction, bool midi);
255 ~Port();
256
agoodeb0582872015-05-20 05:22:24 -0700257 std::string name() const { return name_; }
258 PortDirection direction() const { return direction_; }
agoode99d63292015-04-13 08:39:25 -0700259 // True if this port is a MIDI port, instead of another kind of ALSA port.
agoodeb0582872015-05-20 05:22:24 -0700260 bool midi() const { return midi_; }
agoode99d63292015-04-13 08:39:25 -0700261
262 private:
263 const std::string name_;
264 const PortDirection direction_;
265 const bool midi_;
266
267 DISALLOW_COPY_AND_ASSIGN(Port);
268 };
269
270 class Client {
271 public:
limasdfe59d0392015-11-19 20:28:57 -0800272 using PortMap = std::map<int, scoped_ptr<Port>>;
agoode99d63292015-04-13 08:39:25 -0700273
274 Client(const std::string& name, snd_seq_client_type_t type);
275 ~Client();
276
agoodeb0582872015-05-20 05:22:24 -0700277 std::string name() const { return name_; }
278 snd_seq_client_type_t type() const { return type_; }
agoode99d63292015-04-13 08:39:25 -0700279 void AddPort(int addr, scoped_ptr<Port> port);
280 void RemovePort(int addr);
281 PortMap::const_iterator begin() const;
282 PortMap::const_iterator end() const;
283
284 private:
285 const std::string name_;
286 const snd_seq_client_type_t type_;
287 PortMap ports_;
agoode99d63292015-04-13 08:39:25 -0700288
289 DISALLOW_COPY_AND_ASSIGN(Client);
290 };
291
limasdfe59d0392015-11-19 20:28:57 -0800292 std::map<int, scoped_ptr<Client>> clients_;
agoode99d63292015-04-13 08:39:25 -0700293
agoodeb09423b2015-05-11 11:39:57 -0700294 // This is the current number of clients we know about that have
295 // cards. When this number matches alsa_card_midi_count_, we know
296 // we are in sync between ALSA and udev. Until then, we cannot generate
297 // MIDIConnectionEvents to web clients.
agoodedf1b9ff2015-06-25 18:14:50 -0700298 int card_client_count_ = 0;
agoodeb09423b2015-05-11 11:39:57 -0700299
agoode99d63292015-04-13 08:39:25 -0700300 DISALLOW_COPY_AND_ASSIGN(AlsaSeqState);
301 };
302
agoodeb09423b2015-05-11 11:39:57 -0700303 class AlsaCard {
304 public:
305 AlsaCard(udev_device* dev,
agooded87fc0f2015-05-21 08:29:31 -0700306 const std::string& name,
307 const std::string& longname,
308 const std::string& driver,
agoodeb09423b2015-05-11 11:39:57 -0700309 int midi_device_count);
310 ~AlsaCard();
agooded87fc0f2015-05-21 08:29:31 -0700311 std::string name() const { return name_; }
312 std::string longname() const { return longname_; }
313 std::string driver() const { return driver_; }
314 std::string path() const { return path_; }
315 std::string bus() const { return bus_; }
316 std::string vendor_id() const { return vendor_id_; }
317 std::string model_id() const { return model_id_; }
318 std::string usb_interface_num() const { return usb_interface_num_; }
319 std::string serial() const { return serial_; }
agoodeb09423b2015-05-11 11:39:57 -0700320 int midi_device_count() const { return midi_device_count_; }
agooded87fc0f2015-05-21 08:29:31 -0700321 std::string manufacturer() const { return manufacturer_; }
agoodeb09423b2015-05-11 11:39:57 -0700322
323 private:
324 FRIEND_TEST_ALL_PREFIXES(MidiManagerAlsaTest, ExtractManufacturer);
325
326 // Extracts the manufacturer using heuristics and a variety of sources.
327 static std::string ExtractManufacturerString(
328 const std::string& udev_id_vendor,
329 const std::string& udev_id_vendor_id,
330 const std::string& udev_id_vendor_from_database,
agooded87fc0f2015-05-21 08:29:31 -0700331 const std::string& name,
332 const std::string& longname);
agoodeb09423b2015-05-11 11:39:57 -0700333
agooded87fc0f2015-05-21 08:29:31 -0700334 const std::string name_;
335 const std::string longname_;
336 const std::string driver_;
337 const std::string path_;
338 const std::string bus_;
339 const std::string vendor_id_;
340 const std::string model_id_;
341 const std::string usb_interface_num_;
342 const std::string serial_;
343 const int midi_device_count_;
344 const std::string manufacturer_;
agoodeb09423b2015-05-11 11:39:57 -0700345
346 DISALLOW_COPY_AND_ASSIGN(AlsaCard);
347 };
348
agoode5a1aa112015-06-21 20:51:00 -0700349 struct SndSeqDeleter {
350 void operator()(snd_seq_t* seq) const { snd_seq_close(seq); }
351 };
352
353 struct SndMidiEventDeleter {
354 void operator()(snd_midi_event_t* coder) const {
355 snd_midi_event_free(coder);
356 };
357 };
358
Avi Drissman3528fd02015-12-18 20:11:31 -0500359 typedef base::hash_map<int, uint32_t> SourceMap;
360 typedef base::hash_map<uint32_t, int> OutPortMap;
agoode99d63292015-04-13 08:39:25 -0700361
agoode@chromium.org25227512014-06-08 05:12:05 +0000362 // An internal callback that runs on MidiSendThread.
Avi Drissman3528fd02015-12-18 20:11:31 -0500363 void SendMidiData(uint32_t port_index, const std::vector<uint8_t>& data);
agoode@chromium.org25227512014-06-08 05:12:05 +0000364
agoodebd4be9b2015-03-16 19:17:25 -0700365 void ScheduleEventLoop();
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000366 void EventLoop();
agoodebd4be9b2015-03-16 19:17:25 -0700367 void ProcessSingleEvent(snd_seq_event_t* event, double timestamp);
agoode99d63292015-04-13 08:39:25 -0700368 void ProcessClientStartEvent(int client_id);
369 void ProcessPortStartEvent(const snd_seq_addr_t& addr);
370 void ProcessClientExitEvent(const snd_seq_addr_t& addr);
371 void ProcessPortExitEvent(const snd_seq_addr_t& addr);
agoode975043d2015-05-11 00:46:17 -0700372 void ProcessUdevEvent(udev_device* dev);
agoodeb09423b2015-05-11 11:39:57 -0700373 void AddCard(udev_device* dev);
374 void RemoveCard(int number);
agoode99d63292015-04-13 08:39:25 -0700375
376 // Updates port_state_ and Web MIDI state from alsa_seq_state_.
377 void UpdatePortStateAndGenerateEvents();
378
379 // Enumerates ports. Call once after subscribing to the announce port.
380 void EnumerateAlsaPorts();
agoode975043d2015-05-11 00:46:17 -0700381 // Enumerates udev cards. Call once after initializing the udev monitor.
382 bool EnumerateUdevCards();
agoode99d63292015-04-13 08:39:25 -0700383 // Returns true if successful.
Avi Drissman3528fd02015-12-18 20:11:31 -0500384 bool CreateAlsaOutputPort(uint32_t port_index, int client_id, int port_id);
385 void DeleteAlsaOutputPort(uint32_t port_index);
agoode99d63292015-04-13 08:39:25 -0700386 // Returns true if successful.
Avi Drissman3528fd02015-12-18 20:11:31 -0500387 bool Subscribe(uint32_t port_index, int client_id, int port_id);
agoode99d63292015-04-13 08:39:25 -0700388
389 AlsaSeqState alsa_seq_state_;
390 MidiPortState port_state_;
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000391
agoodebd4be9b2015-03-16 19:17:25 -0700392 // ALSA seq handles.
agoode5a1aa112015-06-21 20:51:00 -0700393 scoped_ptr<snd_seq_t, SndSeqDeleter> in_client_;
394 int in_client_id_ = -1;
395 scoped_ptr<snd_seq_t, SndSeqDeleter> out_client_;
396 int out_client_id_ = -1;
agoode@chromium.org25227512014-06-08 05:12:05 +0000397
398 // One input port, many output ports.
agoode5a1aa112015-06-21 20:51:00 -0700399 int in_port_id_ = -1;
agoode99d63292015-04-13 08:39:25 -0700400 OutPortMap out_ports_; // guarded by out_ports_lock_
401 base::Lock out_ports_lock_; // guards out_ports_
agoode@chromium.org25227512014-06-08 05:12:05 +0000402
agoodebd4be9b2015-03-16 19:17:25 -0700403 // Mapping from ALSA client:port to our index.
agoode@chromium.org25227512014-06-08 05:12:05 +0000404 SourceMap source_map_;
405
agoodeb09423b2015-05-11 11:39:57 -0700406 // Mapping from card to devices.
407 AlsaCardMap alsa_cards_;
agoodeb09423b2015-05-11 11:39:57 -0700408
409 // This is the current count of midi devices across all cards we know
410 // about. When this number matches card_client_count_ in AlsaSeqState,
411 // we are safe to generate MIDIConnectionEvents. Otherwise we need to
412 // wait for our information from ALSA and udev to get back in sync.
agoode5a1aa112015-06-21 20:51:00 -0700413 int alsa_card_midi_count_ = 0;
agoodeb09423b2015-05-11 11:39:57 -0700414
agoode99d63292015-04-13 08:39:25 -0700415 // ALSA event -> MIDI coder.
agoode5a1aa112015-06-21 20:51:00 -0700416 scoped_ptr<snd_midi_event_t, SndMidiEventDeleter> decoder_;
agoode@chromium.org25227512014-06-08 05:12:05 +0000417
agoode7de413f2015-04-24 00:13:39 -0700418 // udev, for querying hardware devices.
419 device::ScopedUdevPtr udev_;
agoode975043d2015-05-11 00:46:17 -0700420 device::ScopedUdevMonitorPtr udev_monitor_;
agoode7de413f2015-04-24 00:13:39 -0700421
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000422 base::Thread send_thread_;
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000423 base::Thread event_thread_;
424
agoode5a1aa112015-06-21 20:51:00 -0700425 bool event_thread_shutdown_ = false; // guarded by shutdown_lock_
426 base::Lock shutdown_lock_; // guards event_thread_shutdown_
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000427
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000428 DISALLOW_COPY_AND_ASSIGN(MidiManagerAlsa);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000429};
430
toyoshime147c5e2015-05-07 21:58:31 -0700431} // namespace midi
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000432} // namespace media
433
dnicoara@chromium.org9f2a6f02014-01-03 21:25:00 +0000434#endif // MEDIA_MIDI_MIDI_MANAGER_ALSA_H_