1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
media / midi / midi_manager_alsa.h [blame]
// Copyright 2014 The Chromium Authors
// 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/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(const MidiPort&) = delete;
MidiPort& operator=(const MidiPort&) = delete;
~MidiPort();
// Gets a Value representation of this object, suitable for serialization.
std::unique_ptr<base::Value::Dict> 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;
};
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_