blob: 6228e574db3df3021706b9cdcd535f921e13f355 [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>
9#include <map>
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000010#include <vector>
11
12#include "base/basictypes.h"
agoode55a8b522015-03-08 12:40:17 -070013#include "base/gtest_prod_util.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000014#include "base/memory/scoped_ptr.h"
agoode99d63292015-04-13 08:39:25 -070015#include "base/memory/scoped_vector.h"
16#include "base/stl_util.h"
agoode@chromium.org25227512014-06-08 05:12:05 +000017#include "base/synchronization/lock.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000018#include "base/threading/thread.h"
agoodef212b2a2015-03-19 12:53:23 -070019#include "base/values.h"
agoode7de413f2015-04-24 00:13:39 -070020#include "device/udev_linux/scoped_udev.h"
brettw49ff0172015-05-05 12:43:04 -070021#include "media/midi/midi_export.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000022#include "media/midi/midi_manager.h"
23
24namespace media {
toyoshime147c5e2015-05-07 21:58:31 -070025namespace midi {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000026
brettw49ff0172015-05-05 12:43:04 -070027class MIDI_EXPORT MidiManagerAlsa final : public MidiManager {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000028 public:
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +000029 MidiManagerAlsa();
dcheng9e8524d2014-10-27 15:18:50 -070030 ~MidiManagerAlsa() override;
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000031
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +000032 // MidiManager implementation.
dcheng9e8524d2014-10-27 15:18:50 -070033 void StartInitialization() override;
34 void DispatchSendMidiData(MidiManagerClient* client,
35 uint32 port_index,
36 const std::vector<uint8>& data,
37 double timestamp) override;
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000038
39 private:
agoode99d63292015-04-13 08:39:25 -070040 friend class MidiManagerAlsaTest;
agoode55a8b522015-03-08 12:40:17 -070041 FRIEND_TEST_ALL_PREFIXES(MidiManagerAlsaTest, ExtractManufacturer);
agoode99d63292015-04-13 08:39:25 -070042 FRIEND_TEST_ALL_PREFIXES(MidiManagerAlsaTest, ToMidiPortState);
agoode55a8b522015-03-08 12:40:17 -070043
agoodeb09423b2015-05-11 11:39:57 -070044 class AlsaCard;
45 typedef std::map<int, AlsaCard*> AlsaCardMap;
46
agoode99d63292015-04-13 08:39:25 -070047 class MidiPort {
agoodef212b2a2015-03-19 12:53:23 -070048 public:
49 enum class Type { kInput, kOutput };
50
agoode99d63292015-04-13 08:39:25 -070051 MidiPort(const std::string& path,
52 const std::string& id,
53 int client_id,
54 int port_id,
55 int midi_device,
56 const std::string& client_name,
57 const std::string& port_name,
58 const std::string& manufacturer,
59 const std::string& version,
60 Type type);
61 ~MidiPort();
agoodef212b2a2015-03-19 12:53:23 -070062
63 // Gets a Value representation of this object, suitable for serialization.
64 scoped_ptr<base::Value> Value() const;
65
66 // Gets a string version of Value in JSON format.
67 std::string JSONValue() const;
68
69 // Gets an opaque identifier for this object, suitable for using as the id
70 // field in MidiPort.id on the web. Note that this string does not store
71 // the full state.
72 std::string OpaqueKey() const;
73
agoode99d63292015-04-13 08:39:25 -070074 // Checks for equality for connected ports.
75 bool MatchConnected(const MidiPort& query) const;
76 // Checks for equality for kernel cards with id, pass 1.
77 bool MatchCardPass1(const MidiPort& query) const;
78 // Checks for equality for kernel cards with id, pass 2.
79 bool MatchCardPass2(const MidiPort& query) const;
80 // Checks for equality for non-card clients, pass 1.
81 bool MatchNoCardPass1(const MidiPort& query) const;
82 // Checks for equality for non-card clients, pass 2.
83 bool MatchNoCardPass2(const MidiPort& query) const;
agoodef212b2a2015-03-19 12:53:23 -070084
agoode99d63292015-04-13 08:39:25 -070085 // accessors
86 std::string path() const { return path_; }
87 std::string id() const { return id_; }
88 std::string client_name() const { return client_name_; }
89 std::string port_name() const { return port_name_; }
90 std::string manufacturer() const { return manufacturer_; }
91 std::string version() const { return version_; }
92 int client_id() const { return client_id_; }
93 int port_id() const { return port_id_; }
94 int midi_device() const { return midi_device_; }
95 Type type() const { return type_; }
96 uint32 web_port_index() const { return web_port_index_; }
97 bool connected() const { return connected_; }
98
99 // mutators
100 void set_web_port_index(uint32 web_port_index) {
101 web_port_index_ = web_port_index;
102 }
103 void set_connected(bool connected) { connected_ = connected; }
104 void Update(const std::string& path,
105 int client_id,
106 int port_id,
107 const std::string& client_name,
108 const std::string& port_name,
109 const std::string& manufacturer,
110 const std::string& version) {
111 path_ = path;
112 client_id_ = client_id;
113 port_id_ = port_id;
114 client_name_ = client_name;
115 port_name_ = port_name;
116 manufacturer_ = manufacturer;
117 version_ = version;
118 }
119
120 private:
121 // Immutable properties.
agoodef212b2a2015-03-19 12:53:23 -0700122 const std::string id_;
agoode99d63292015-04-13 08:39:25 -0700123 const int midi_device_;
124
agoodef212b2a2015-03-19 12:53:23 -0700125 const Type type_;
126
agoode99d63292015-04-13 08:39:25 -0700127 // Mutable properties. These will get updated as ports move around or
128 // drivers change.
129 std::string path_;
130 int client_id_;
131 int port_id_;
132 std::string client_name_;
133 std::string port_name_;
134 std::string manufacturer_;
135 std::string version_;
136
137 // Index for MidiManager.
138 uint32 web_port_index_;
139
140 // Port is present in the ALSA system.
141 bool connected_;
142
143 DISALLOW_COPY_AND_ASSIGN(MidiPort);
agoode55a8b522015-03-08 12:40:17 -0700144 };
145
agoode99d63292015-04-13 08:39:25 -0700146 class MidiPortStateBase {
147 public:
148 typedef ScopedVector<MidiPort>::iterator iterator;
agoodebd4be9b2015-03-16 19:17:25 -0700149
agoode99d63292015-04-13 08:39:25 -0700150 virtual ~MidiPortStateBase();
151
152 // Given a port, finds a port in the internal store.
153 iterator Find(const MidiPort& port);
154
155 // Given a port, finds a connected port, using exact matching.
156 iterator FindConnected(const MidiPort& port);
157
158 // Given a port, finds a disconnected port, using heuristic matching.
159 iterator FindDisconnected(const MidiPort& port);
160
161 iterator begin() { return ports_.begin(); }
162 iterator end() { return ports_.end(); }
163
164 protected:
165 MidiPortStateBase();
166
agoodeb0582872015-05-20 05:22:24 -0700167 ScopedVector<MidiPort>* ports() { return &ports_; }
agoode99d63292015-04-13 08:39:25 -0700168
169 private:
170 ScopedVector<MidiPort> ports_;
171
172 DISALLOW_COPY_AND_ASSIGN(MidiPortStateBase);
173 };
174
175 class TemporaryMidiPortState final : public MidiPortStateBase {
176 public:
177 // Removes a port from the list without deleting it.
178 iterator weak_erase(iterator position) {
179 return ports()->weak_erase(position);
180 }
181
182 void Insert(scoped_ptr<MidiPort> port);
183 };
184
185 class MidiPortState final : public MidiPortStateBase {
186 public:
187 MidiPortState();
188
189 // Inserts a port. Returns web_port_index.
190 uint32 Insert(scoped_ptr<MidiPort> port);
191
192 private:
193 uint32 num_input_ports_;
194 uint32 num_output_ports_;
195 };
196
197 class AlsaSeqState {
198 public:
199 enum class PortDirection { kInput, kOutput, kDuplex };
200
201 AlsaSeqState();
202 ~AlsaSeqState();
203
204 void ClientStart(int client_id,
205 const std::string& client_name,
206 snd_seq_client_type_t type);
207 bool ClientStarted(int client_id);
208 void ClientExit(int client_id);
209 void PortStart(int client_id,
210 int port_id,
211 const std::string& port_name,
212 PortDirection direction,
213 bool midi);
214 void PortExit(int client_id, int port_id);
215 snd_seq_client_type_t ClientType(int client_id) const;
agoodeb09423b2015-05-11 11:39:57 -0700216 scoped_ptr<TemporaryMidiPortState> ToMidiPortState(
217 const AlsaCardMap& alsa_cards);
218
219 int card_client_count() { return card_client_count_; }
agoode99d63292015-04-13 08:39:25 -0700220
221 private:
222 class Port {
223 public:
224 Port(const std::string& name, PortDirection direction, bool midi);
225 ~Port();
226
agoodeb0582872015-05-20 05:22:24 -0700227 std::string name() const { return name_; }
228 PortDirection direction() const { return direction_; }
agoode99d63292015-04-13 08:39:25 -0700229 // True if this port is a MIDI port, instead of another kind of ALSA port.
agoodeb0582872015-05-20 05:22:24 -0700230 bool midi() const { return midi_; }
agoode99d63292015-04-13 08:39:25 -0700231
232 private:
233 const std::string name_;
234 const PortDirection direction_;
235 const bool midi_;
236
237 DISALLOW_COPY_AND_ASSIGN(Port);
238 };
239
240 class Client {
241 public:
242 typedef std::map<int, Port*> PortMap;
243
244 Client(const std::string& name, snd_seq_client_type_t type);
245 ~Client();
246
agoodeb0582872015-05-20 05:22:24 -0700247 std::string name() const { return name_; }
248 snd_seq_client_type_t type() const { return type_; }
agoode99d63292015-04-13 08:39:25 -0700249 void AddPort(int addr, scoped_ptr<Port> port);
250 void RemovePort(int addr);
251 PortMap::const_iterator begin() const;
252 PortMap::const_iterator end() const;
253
254 private:
255 const std::string name_;
256 const snd_seq_client_type_t type_;
257 PortMap ports_;
258 STLValueDeleter<PortMap> ports_deleter_;
259
260 DISALLOW_COPY_AND_ASSIGN(Client);
261 };
262
263 typedef std::map<int, Client*> ClientMap;
264
265 ClientMap clients_;
266 STLValueDeleter<ClientMap> clients_deleter_;
267
agoodeb09423b2015-05-11 11:39:57 -0700268 // This is the current number of clients we know about that have
269 // cards. When this number matches alsa_card_midi_count_, we know
270 // we are in sync between ALSA and udev. Until then, we cannot generate
271 // MIDIConnectionEvents to web clients.
272 int card_client_count_;
273
agoode99d63292015-04-13 08:39:25 -0700274 DISALLOW_COPY_AND_ASSIGN(AlsaSeqState);
275 };
276
agoodeb09423b2015-05-11 11:39:57 -0700277 class AlsaCard {
278 public:
279 AlsaCard(udev_device* dev,
280 const std::string& alsa_name,
281 const std::string& alsa_longname,
282 const std::string& alsa_driver,
283 int midi_device_count);
284 ~AlsaCard();
285 const std::string alsa_name() const { return alsa_name_; }
286 const std::string alsa_longname() const { return alsa_longname_; }
287 const std::string manufacturer() const { return manufacturer_; }
288 const std::string alsa_driver() const { return alsa_driver_; }
289 int midi_device_count() const { return midi_device_count_; }
290 // Returns hardware path.
291 const std::string path() const;
292 // Returns the id we can use to try to match hardware across different
293 // paths.
294 const std::string id() const;
295
296 private:
297 FRIEND_TEST_ALL_PREFIXES(MidiManagerAlsaTest, ExtractManufacturer);
298
299 // Extracts the manufacturer using heuristics and a variety of sources.
300 static std::string ExtractManufacturerString(
301 const std::string& udev_id_vendor,
302 const std::string& udev_id_vendor_id,
303 const std::string& udev_id_vendor_from_database,
304 const std::string& alsa_name,
305 const std::string& alsa_longname);
306
307 std::string alsa_name_;
308 std::string alsa_longname_;
309 std::string manufacturer_;
310 std::string alsa_driver_;
311 std::string path_;
312 std::string bus_;
313 std::string serial_;
314 std::string vendor_id_;
315 std::string model_id_;
316 std::string usb_interface_num_;
317 int midi_device_count_;
318
319 DISALLOW_COPY_AND_ASSIGN(AlsaCard);
320 };
321
agoode99d63292015-04-13 08:39:25 -0700322 typedef base::hash_map<int, uint32> SourceMap;
323 typedef base::hash_map<uint32, int> OutPortMap;
324
agoode@chromium.org25227512014-06-08 05:12:05 +0000325 // An internal callback that runs on MidiSendThread.
agoode99d63292015-04-13 08:39:25 -0700326 void SendMidiData(uint32 port_index, const std::vector<uint8>& data);
agoode@chromium.org25227512014-06-08 05:12:05 +0000327
agoodebd4be9b2015-03-16 19:17:25 -0700328 void ScheduleEventLoop();
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000329 void EventLoop();
agoodebd4be9b2015-03-16 19:17:25 -0700330 void ProcessSingleEvent(snd_seq_event_t* event, double timestamp);
agoode99d63292015-04-13 08:39:25 -0700331 void ProcessClientStartEvent(int client_id);
332 void ProcessPortStartEvent(const snd_seq_addr_t& addr);
333 void ProcessClientExitEvent(const snd_seq_addr_t& addr);
334 void ProcessPortExitEvent(const snd_seq_addr_t& addr);
agoode975043d2015-05-11 00:46:17 -0700335 void ProcessUdevEvent(udev_device* dev);
agoodeb09423b2015-05-11 11:39:57 -0700336 void AddCard(udev_device* dev);
337 void RemoveCard(int number);
agoode99d63292015-04-13 08:39:25 -0700338
339 // Updates port_state_ and Web MIDI state from alsa_seq_state_.
340 void UpdatePortStateAndGenerateEvents();
341
342 // Enumerates ports. Call once after subscribing to the announce port.
343 void EnumerateAlsaPorts();
agoode975043d2015-05-11 00:46:17 -0700344 // Enumerates udev cards. Call once after initializing the udev monitor.
345 bool EnumerateUdevCards();
agoode99d63292015-04-13 08:39:25 -0700346 // Returns true if successful.
347 bool CreateAlsaOutputPort(uint32 port_index, int client_id, int port_id);
348 void DeleteAlsaOutputPort(uint32 port_index);
349 // Returns true if successful.
350 bool Subscribe(uint32 port_index, int client_id, int port_id);
351
352 AlsaSeqState alsa_seq_state_;
353 MidiPortState port_state_;
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000354
agoodebd4be9b2015-03-16 19:17:25 -0700355 // ALSA seq handles.
agoode@chromium.org25227512014-06-08 05:12:05 +0000356 snd_seq_t* in_client_;
agoode99d63292015-04-13 08:39:25 -0700357 int in_client_id_;
agoode@chromium.org25227512014-06-08 05:12:05 +0000358 snd_seq_t* out_client_;
359 int out_client_id_;
360
361 // One input port, many output ports.
agoode99d63292015-04-13 08:39:25 -0700362 int in_port_id_;
363 OutPortMap out_ports_; // guarded by out_ports_lock_
364 base::Lock out_ports_lock_; // guards out_ports_
agoode@chromium.org25227512014-06-08 05:12:05 +0000365
agoodebd4be9b2015-03-16 19:17:25 -0700366 // Mapping from ALSA client:port to our index.
agoode@chromium.org25227512014-06-08 05:12:05 +0000367 SourceMap source_map_;
368
agoodeb09423b2015-05-11 11:39:57 -0700369 // Mapping from card to devices.
370 AlsaCardMap alsa_cards_;
371 STLValueDeleter<AlsaCardMap> alsa_cards_deleter_;
372
373 // This is the current count of midi devices across all cards we know
374 // about. When this number matches card_client_count_ in AlsaSeqState,
375 // we are safe to generate MIDIConnectionEvents. Otherwise we need to
376 // wait for our information from ALSA and udev to get back in sync.
377 int alsa_card_midi_count_;
378
agoode99d63292015-04-13 08:39:25 -0700379 // ALSA event -> MIDI coder.
agoode@chromium.org25227512014-06-08 05:12:05 +0000380 snd_midi_event_t* decoder_;
agoode@chromium.org25227512014-06-08 05:12:05 +0000381
agoode7de413f2015-04-24 00:13:39 -0700382 // udev, for querying hardware devices.
383 device::ScopedUdevPtr udev_;
agoode975043d2015-05-11 00:46:17 -0700384 device::ScopedUdevMonitorPtr udev_monitor_;
agoode7de413f2015-04-24 00:13:39 -0700385
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000386 base::Thread send_thread_;
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000387 base::Thread event_thread_;
388
agoode99d63292015-04-13 08:39:25 -0700389 bool event_thread_shutdown_; // guarded by shutdown_lock_
390 base::Lock shutdown_lock_; // guards event_thread_shutdown_
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000391
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000392 DISALLOW_COPY_AND_ASSIGN(MidiManagerAlsa);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000393};
394
toyoshime147c5e2015-05-07 21:58:31 -0700395} // namespace midi
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000396} // namespace media
397
dnicoara@chromium.org9f2a6f02014-01-03 21:25:00 +0000398#endif // MEDIA_MIDI_MIDI_MANAGER_ALSA_H_