blob: 75cba5cca149e8892a943c0f47d3a129fafaefdd [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>
danakj75afea02016-04-25 20:36:04 -070010
limasdfe59d0392015-11-19 20:28:57 -080011#include <map>
danakj75afea02016-04-25 20:36:04 -070012#include <memory>
Takuto Ikutafd795cb2019-01-05 00:32:48 +000013#include <unordered_map>
dchengc2aeece2015-12-27 00:54:00 -080014#include <utility>
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000015#include <vector>
16
bnc628660d2016-02-05 19:58:14 -080017#include "base/containers/hash_tables.h"
agoode55a8b522015-03-08 12:40:17 -070018#include "base/gtest_prod_util.h"
avieea95e82015-12-18 20:27:08 -080019#include "base/macros.h"
agoode@chromium.org25227512014-06-08 05:12:05 +000020#include "base/synchronization/lock.h"
Takashi Toyoshimae8240ab2018-10-03 09:30:11 +000021#include "base/thread_annotations.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000022#include "base/threading/thread.h"
agoodef212b2a2015-03-19 12:53:23 -070023#include "base/values.h"
agoode7de413f2015-04-24 00:13:39 -070024#include "device/udev_linux/scoped_udev.h"
brettw49ff0172015-05-05 12:43:04 -070025#include "media/midi/midi_export.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000026#include "media/midi/midi_manager.h"
27
toyoshime147c5e2015-05-07 21:58:31 -070028namespace midi {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000029
brettw49ff0172015-05-05 12:43:04 -070030class MIDI_EXPORT MidiManagerAlsa final : public MidiManager {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000031 public:
toyoshimf4d61522017-02-10 02:03:32 -080032 explicit MidiManagerAlsa(MidiService* service);
dcheng9e8524d2014-10-27 15:18:50 -070033 ~MidiManagerAlsa() override;
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000034
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +000035 // MidiManager implementation.
dcheng9e8524d2014-10-27 15:18:50 -070036 void StartInitialization() override;
37 void DispatchSendMidiData(MidiManagerClient* client,
Avi Drissman3528fd02015-12-18 20:11:31 -050038 uint32_t port_index,
39 const std::vector<uint8_t>& data,
tzik925e2c62018-02-02 07:39:45 +000040 base::TimeTicks timestamp) override;
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000041
42 private:
agoode99d63292015-04-13 08:39:25 -070043 friend class MidiManagerAlsaTest;
agoode55a8b522015-03-08 12:40:17 -070044 FRIEND_TEST_ALL_PREFIXES(MidiManagerAlsaTest, ExtractManufacturer);
agoode99d63292015-04-13 08:39:25 -070045 FRIEND_TEST_ALL_PREFIXES(MidiManagerAlsaTest, ToMidiPortState);
agoode55a8b522015-03-08 12:40:17 -070046
agoodeb09423b2015-05-11 11:39:57 -070047 class AlsaCard;
danakj75afea02016-04-25 20:36:04 -070048 using AlsaCardMap = std::map<int, std::unique_ptr<AlsaCard>>;
agoodeb09423b2015-05-11 11:39:57 -070049
agoode99d63292015-04-13 08:39:25 -070050 class MidiPort {
agoodef212b2a2015-03-19 12:53:23 -070051 public:
52 enum class Type { kInput, kOutput };
53
agooded87fc0f2015-05-21 08:29:31 -070054 // The Id class is used to keep the multiple strings separate
55 // but compare them all together for equality purposes.
56 // The individual strings that make up the Id can theoretically contain
57 // arbitrary characters, so unfortunately there is no simple way to
58 // concatenate them into a single string.
59 class Id final {
60 public:
61 Id();
62 Id(const std::string& bus,
63 const std::string& vendor_id,
64 const std::string& model_id,
65 const std::string& usb_interface_num,
66 const std::string& serial);
67 Id(const Id&);
68 ~Id();
69 bool operator==(const Id&) const;
70 bool empty() const;
71
72 std::string bus() const { return bus_; }
73 std::string vendor_id() const { return vendor_id_; }
74 std::string model_id() const { return model_id_; }
75 std::string usb_interface_num() const { return usb_interface_num_; }
76 std::string serial() const { return serial_; }
77
78 private:
79 std::string bus_;
80 std::string vendor_id_;
81 std::string model_id_;
82 std::string usb_interface_num_;
83 std::string serial_;
84 };
85
agoode99d63292015-04-13 08:39:25 -070086 MidiPort(const std::string& path,
agooded87fc0f2015-05-21 08:29:31 -070087 const Id& id,
agoode99d63292015-04-13 08:39:25 -070088 int client_id,
89 int port_id,
90 int midi_device,
91 const std::string& client_name,
92 const std::string& port_name,
93 const std::string& manufacturer,
94 const std::string& version,
95 Type type);
96 ~MidiPort();
agoodef212b2a2015-03-19 12:53:23 -070097
98 // Gets a Value representation of this object, suitable for serialization.
danakj75afea02016-04-25 20:36:04 -070099 std::unique_ptr<base::Value> Value() const;
agoodef212b2a2015-03-19 12:53:23 -0700100
101 // Gets a string version of Value in JSON format.
102 std::string JSONValue() const;
103
104 // Gets an opaque identifier for this object, suitable for using as the id
105 // field in MidiPort.id on the web. Note that this string does not store
106 // the full state.
107 std::string OpaqueKey() const;
108
agoode99d63292015-04-13 08:39:25 -0700109 // Checks for equality for connected ports.
110 bool MatchConnected(const MidiPort& query) const;
111 // Checks for equality for kernel cards with id, pass 1.
112 bool MatchCardPass1(const MidiPort& query) const;
113 // Checks for equality for kernel cards with id, pass 2.
114 bool MatchCardPass2(const MidiPort& query) const;
115 // Checks for equality for non-card clients, pass 1.
116 bool MatchNoCardPass1(const MidiPort& query) const;
117 // Checks for equality for non-card clients, pass 2.
118 bool MatchNoCardPass2(const MidiPort& query) const;
agoodef212b2a2015-03-19 12:53:23 -0700119
agoode99d63292015-04-13 08:39:25 -0700120 // accessors
121 std::string path() const { return path_; }
agooded87fc0f2015-05-21 08:29:31 -0700122 Id id() const { return id_; }
agoode99d63292015-04-13 08:39:25 -0700123 std::string client_name() const { return client_name_; }
124 std::string port_name() const { return port_name_; }
125 std::string manufacturer() const { return manufacturer_; }
126 std::string version() const { return version_; }
127 int client_id() const { return client_id_; }
128 int port_id() const { return port_id_; }
129 int midi_device() const { return midi_device_; }
130 Type type() const { return type_; }
Avi Drissman3528fd02015-12-18 20:11:31 -0500131 uint32_t web_port_index() const { return web_port_index_; }
agoode99d63292015-04-13 08:39:25 -0700132 bool connected() const { return connected_; }
133
134 // mutators
Avi Drissman3528fd02015-12-18 20:11:31 -0500135 void set_web_port_index(uint32_t web_port_index) {
agoode99d63292015-04-13 08:39:25 -0700136 web_port_index_ = web_port_index;
137 }
138 void set_connected(bool connected) { connected_ = connected; }
139 void Update(const std::string& path,
140 int client_id,
141 int port_id,
142 const std::string& client_name,
143 const std::string& port_name,
144 const std::string& manufacturer,
145 const std::string& version) {
146 path_ = path;
147 client_id_ = client_id;
148 port_id_ = port_id;
149 client_name_ = client_name;
150 port_name_ = port_name;
151 manufacturer_ = manufacturer;
152 version_ = version;
153 }
154
155 private:
156 // Immutable properties.
agooded87fc0f2015-05-21 08:29:31 -0700157 const Id id_;
agoode99d63292015-04-13 08:39:25 -0700158 const int midi_device_;
159
agoodef212b2a2015-03-19 12:53:23 -0700160 const Type type_;
161
agoode99d63292015-04-13 08:39:25 -0700162 // Mutable properties. These will get updated as ports move around or
163 // drivers change.
164 std::string path_;
165 int client_id_;
166 int port_id_;
167 std::string client_name_;
168 std::string port_name_;
169 std::string manufacturer_;
170 std::string version_;
171
172 // Index for MidiManager.
Avi Drissman3528fd02015-12-18 20:11:31 -0500173 uint32_t web_port_index_ = 0;
agoode99d63292015-04-13 08:39:25 -0700174
175 // Port is present in the ALSA system.
agoode5a1aa112015-06-21 20:51:00 -0700176 bool connected_ = true;
agoode99d63292015-04-13 08:39:25 -0700177
178 DISALLOW_COPY_AND_ASSIGN(MidiPort);
agoode55a8b522015-03-08 12:40:17 -0700179 };
180
agoode99d63292015-04-13 08:39:25 -0700181 class MidiPortStateBase {
182 public:
danakj75afea02016-04-25 20:36:04 -0700183 typedef std::vector<std::unique_ptr<MidiPort>>::iterator iterator;
agoodebd4be9b2015-03-16 19:17:25 -0700184
agoode99d63292015-04-13 08:39:25 -0700185 virtual ~MidiPortStateBase();
186
187 // Given a port, finds a port in the internal store.
188 iterator Find(const MidiPort& port);
189
190 // Given a port, finds a connected port, using exact matching.
191 iterator FindConnected(const MidiPort& port);
192
193 // Given a port, finds a disconnected port, using heuristic matching.
194 iterator FindDisconnected(const MidiPort& port);
195
196 iterator begin() { return ports_.begin(); }
197 iterator end() { return ports_.end(); }
198
199 protected:
200 MidiPortStateBase();
agoode5ebc4932015-12-01 08:25:12 -0800201 iterator erase(iterator position) { return ports_.erase(position); }
danakj75afea02016-04-25 20:36:04 -0700202 void push_back(std::unique_ptr<MidiPort> port) {
dchengc2aeece2015-12-27 00:54:00 -0800203 ports_.push_back(std::move(port));
204 }
agoode99d63292015-04-13 08:39:25 -0700205
206 private:
danakj75afea02016-04-25 20:36:04 -0700207 std::vector<std::unique_ptr<MidiPort>> ports_;
agoode99d63292015-04-13 08:39:25 -0700208
209 DISALLOW_COPY_AND_ASSIGN(MidiPortStateBase);
210 };
211
212 class TemporaryMidiPortState final : public MidiPortStateBase {
213 public:
agoode5ebc4932015-12-01 08:25:12 -0800214 iterator erase(iterator position) {
215 return MidiPortStateBase::erase(position);
216 };
danakj75afea02016-04-25 20:36:04 -0700217 void push_back(std::unique_ptr<MidiPort> port) {
dchengc2aeece2015-12-27 00:54:00 -0800218 MidiPortStateBase::push_back(std::move(port));
agoode5ebc4932015-12-01 08:25:12 -0800219 }
agoode99d63292015-04-13 08:39:25 -0700220 };
221
222 class MidiPortState final : public MidiPortStateBase {
223 public:
224 MidiPortState();
225
agoode5ebc4932015-12-01 08:25:12 -0800226 // Inserts a port at the end. Returns web_port_index.
danakj75afea02016-04-25 20:36:04 -0700227 uint32_t push_back(std::unique_ptr<MidiPort> port);
agoode99d63292015-04-13 08:39:25 -0700228
229 private:
Avi Drissman3528fd02015-12-18 20:11:31 -0500230 uint32_t num_input_ports_ = 0;
231 uint32_t num_output_ports_ = 0;
agoode99d63292015-04-13 08:39:25 -0700232 };
233
234 class AlsaSeqState {
235 public:
236 enum class PortDirection { kInput, kOutput, kDuplex };
237
238 AlsaSeqState();
239 ~AlsaSeqState();
240
241 void ClientStart(int client_id,
242 const std::string& client_name,
243 snd_seq_client_type_t type);
244 bool ClientStarted(int client_id);
245 void ClientExit(int client_id);
246 void PortStart(int client_id,
247 int port_id,
248 const std::string& port_name,
249 PortDirection direction,
250 bool midi);
251 void PortExit(int client_id, int port_id);
252 snd_seq_client_type_t ClientType(int client_id) const;
danakj75afea02016-04-25 20:36:04 -0700253 std::unique_ptr<TemporaryMidiPortState> ToMidiPortState(
agoodeb09423b2015-05-11 11:39:57 -0700254 const AlsaCardMap& alsa_cards);
255
256 int card_client_count() { return card_client_count_; }
agoode99d63292015-04-13 08:39:25 -0700257
258 private:
259 class Port {
260 public:
261 Port(const std::string& name, PortDirection direction, bool midi);
262 ~Port();
263
agoodeb0582872015-05-20 05:22:24 -0700264 std::string name() const { return name_; }
265 PortDirection direction() const { return direction_; }
agoode99d63292015-04-13 08:39:25 -0700266 // True if this port is a MIDI port, instead of another kind of ALSA port.
agoodeb0582872015-05-20 05:22:24 -0700267 bool midi() const { return midi_; }
agoode99d63292015-04-13 08:39:25 -0700268
269 private:
270 const std::string name_;
271 const PortDirection direction_;
272 const bool midi_;
273
274 DISALLOW_COPY_AND_ASSIGN(Port);
275 };
276
277 class Client {
278 public:
danakj75afea02016-04-25 20:36:04 -0700279 using PortMap = std::map<int, std::unique_ptr<Port>>;
agoode99d63292015-04-13 08:39:25 -0700280
281 Client(const std::string& name, snd_seq_client_type_t type);
282 ~Client();
283
agoodeb0582872015-05-20 05:22:24 -0700284 std::string name() const { return name_; }
285 snd_seq_client_type_t type() const { return type_; }
danakj75afea02016-04-25 20:36:04 -0700286 void AddPort(int addr, std::unique_ptr<Port> port);
agoode99d63292015-04-13 08:39:25 -0700287 void RemovePort(int addr);
288 PortMap::const_iterator begin() const;
289 PortMap::const_iterator end() const;
290
291 private:
292 const std::string name_;
293 const snd_seq_client_type_t type_;
294 PortMap ports_;
agoode99d63292015-04-13 08:39:25 -0700295
296 DISALLOW_COPY_AND_ASSIGN(Client);
297 };
298
danakj75afea02016-04-25 20:36:04 -0700299 std::map<int, std::unique_ptr<Client>> clients_;
agoode99d63292015-04-13 08:39:25 -0700300
agoodeb09423b2015-05-11 11:39:57 -0700301 // This is the current number of clients we know about that have
302 // cards. When this number matches alsa_card_midi_count_, we know
303 // we are in sync between ALSA and udev. Until then, we cannot generate
304 // MIDIConnectionEvents to web clients.
agoodedf1b9ff2015-06-25 18:14:50 -0700305 int card_client_count_ = 0;
agoodeb09423b2015-05-11 11:39:57 -0700306
agoode99d63292015-04-13 08:39:25 -0700307 DISALLOW_COPY_AND_ASSIGN(AlsaSeqState);
308 };
309
agoodeb09423b2015-05-11 11:39:57 -0700310 class AlsaCard {
311 public:
312 AlsaCard(udev_device* dev,
agooded87fc0f2015-05-21 08:29:31 -0700313 const std::string& name,
314 const std::string& longname,
315 const std::string& driver,
agoodeb09423b2015-05-11 11:39:57 -0700316 int midi_device_count);
317 ~AlsaCard();
agooded87fc0f2015-05-21 08:29:31 -0700318 std::string name() const { return name_; }
319 std::string longname() const { return longname_; }
320 std::string driver() const { return driver_; }
321 std::string path() const { return path_; }
322 std::string bus() const { return bus_; }
323 std::string vendor_id() const { return vendor_id_; }
324 std::string model_id() const { return model_id_; }
325 std::string usb_interface_num() const { return usb_interface_num_; }
326 std::string serial() const { return serial_; }
agoodeb09423b2015-05-11 11:39:57 -0700327 int midi_device_count() const { return midi_device_count_; }
agooded87fc0f2015-05-21 08:29:31 -0700328 std::string manufacturer() const { return manufacturer_; }
agoodeb09423b2015-05-11 11:39:57 -0700329
330 private:
331 FRIEND_TEST_ALL_PREFIXES(MidiManagerAlsaTest, ExtractManufacturer);
332
333 // Extracts the manufacturer using heuristics and a variety of sources.
334 static std::string ExtractManufacturerString(
335 const std::string& udev_id_vendor,
336 const std::string& udev_id_vendor_id,
337 const std::string& udev_id_vendor_from_database,
agooded87fc0f2015-05-21 08:29:31 -0700338 const std::string& name,
339 const std::string& longname);
agoodeb09423b2015-05-11 11:39:57 -0700340
agooded87fc0f2015-05-21 08:29:31 -0700341 const std::string name_;
342 const std::string longname_;
343 const std::string driver_;
344 const std::string path_;
345 const std::string bus_;
346 const std::string vendor_id_;
347 const std::string model_id_;
348 const std::string usb_interface_num_;
349 const std::string serial_;
350 const int midi_device_count_;
351 const std::string manufacturer_;
agoodeb09423b2015-05-11 11:39:57 -0700352
353 DISALLOW_COPY_AND_ASSIGN(AlsaCard);
354 };
355
agoode5a1aa112015-06-21 20:51:00 -0700356 struct SndSeqDeleter {
357 void operator()(snd_seq_t* seq) const { snd_seq_close(seq); }
358 };
359
360 struct SndMidiEventDeleter {
361 void operator()(snd_midi_event_t* coder) const {
362 snd_midi_event_free(coder);
363 };
364 };
365
Takuto Ikutafd795cb2019-01-05 00:32:48 +0000366 using SourceMap = std::unordered_map<int, uint32_t>;
367 using OutPortMap = std::unordered_map<uint32_t, int>;
danakj75afea02016-04-25 20:36:04 -0700368 using ScopedSndSeqPtr = std::unique_ptr<snd_seq_t, SndSeqDeleter>;
agoodeb2ead822016-03-11 12:14:35 -0800369 using ScopedSndMidiEventPtr =
danakj75afea02016-04-25 20:36:04 -0700370 std::unique_ptr<snd_midi_event_t, SndMidiEventDeleter>;
agoode99d63292015-04-13 08:39:25 -0700371
agoode@chromium.org25227512014-06-08 05:12:05 +0000372 // An internal callback that runs on MidiSendThread.
toyoshimefcee5e2017-06-14 07:14:39 -0700373 void SendMidiData(MidiManagerClient* client,
toyoshimf4d61522017-02-10 02:03:32 -0800374 uint32_t port_index,
375 const std::vector<uint8_t>& data);
agoode@chromium.org25227512014-06-08 05:12:05 +0000376
toyoshimefcee5e2017-06-14 07:14:39 -0700377 void EventLoop();
tzik925e2c62018-02-02 07:39:45 +0000378 void ProcessSingleEvent(snd_seq_event_t* event, base::TimeTicks timestamp);
agoode99d63292015-04-13 08:39:25 -0700379 void ProcessClientStartEvent(int client_id);
380 void ProcessPortStartEvent(const snd_seq_addr_t& addr);
381 void ProcessClientExitEvent(const snd_seq_addr_t& addr);
382 void ProcessPortExitEvent(const snd_seq_addr_t& addr);
agoode975043d2015-05-11 00:46:17 -0700383 void ProcessUdevEvent(udev_device* dev);
agoodeb09423b2015-05-11 11:39:57 -0700384 void AddCard(udev_device* dev);
385 void RemoveCard(int number);
agoode99d63292015-04-13 08:39:25 -0700386
387 // Updates port_state_ and Web MIDI state from alsa_seq_state_.
388 void UpdatePortStateAndGenerateEvents();
389
390 // Enumerates ports. Call once after subscribing to the announce port.
391 void EnumerateAlsaPorts();
agoode975043d2015-05-11 00:46:17 -0700392 // Enumerates udev cards. Call once after initializing the udev monitor.
393 bool EnumerateUdevCards();
agoode99d63292015-04-13 08:39:25 -0700394 // Returns true if successful.
Avi Drissman3528fd02015-12-18 20:11:31 -0500395 bool CreateAlsaOutputPort(uint32_t port_index, int client_id, int port_id);
396 void DeleteAlsaOutputPort(uint32_t port_index);
agoode99d63292015-04-13 08:39:25 -0700397 // Returns true if successful.
Avi Drissman3528fd02015-12-18 20:11:31 -0500398 bool Subscribe(uint32_t port_index, int client_id, int port_id);
agoode99d63292015-04-13 08:39:25 -0700399
Takashi Toyoshima0ae49702017-09-01 04:59:51 +0000400 // Allocates new snd_midi_event_t instance and wraps it to return as
401 // ScopedSndMidiEventPtr.
402 ScopedSndMidiEventPtr CreateScopedSndMidiEventPtr(size_t size);
403
agoodeb2ead822016-03-11 12:14:35 -0800404 // Members initialized in the constructor are below.
405 // Our copies of the internal state of the ports of seq and udev.
agoode99d63292015-04-13 08:39:25 -0700406 AlsaSeqState alsa_seq_state_;
407 MidiPortState port_state_;
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000408
agoode@chromium.org25227512014-06-08 05:12:05 +0000409 // One input port, many output ports.
Takashi Toyoshimae8240ab2018-10-03 09:30:11 +0000410 base::Lock out_ports_lock_;
411 OutPortMap out_ports_ GUARDED_BY(out_ports_lock_);
agoode@chromium.org25227512014-06-08 05:12:05 +0000412
agoodebd4be9b2015-03-16 19:17:25 -0700413 // Mapping from ALSA client:port to our index.
agoode@chromium.org25227512014-06-08 05:12:05 +0000414 SourceMap source_map_;
415
agoodeb09423b2015-05-11 11:39:57 -0700416 // Mapping from card to devices.
417 AlsaCardMap alsa_cards_;
agoodeb09423b2015-05-11 11:39:57 -0700418
419 // This is the current count of midi devices across all cards we know
420 // about. When this number matches card_client_count_ in AlsaSeqState,
421 // we are safe to generate MIDIConnectionEvents. Otherwise we need to
422 // wait for our information from ALSA and udev to get back in sync.
agoode5a1aa112015-06-21 20:51:00 -0700423 int alsa_card_midi_count_ = 0;
agoodeb09423b2015-05-11 11:39:57 -0700424
agoodeb2ead822016-03-11 12:14:35 -0800425 // ALSA seq handles and ids.
426 ScopedSndSeqPtr in_client_;
427 int in_client_id_;
Takashi Toyoshimae8240ab2018-10-03 09:30:11 +0000428 ScopedSndSeqPtr out_client_ GUARDED_BY(out_client_lock_);
429 base::Lock out_client_lock_;
agoodeb2ead822016-03-11 12:14:35 -0800430 int out_client_id_;
431 int in_port_id_;
432
agoode99d63292015-04-13 08:39:25 -0700433 // ALSA event -> MIDI coder.
agoodeb2ead822016-03-11 12:14:35 -0800434 ScopedSndMidiEventPtr decoder_;
agoode@chromium.org25227512014-06-08 05:12:05 +0000435
agoode7de413f2015-04-24 00:13:39 -0700436 // udev, for querying hardware devices.
437 device::ScopedUdevPtr udev_;
agoode975043d2015-05-11 00:46:17 -0700438 device::ScopedUdevMonitorPtr udev_monitor_;
agoode7de413f2015-04-24 00:13:39 -0700439
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000440 DISALLOW_COPY_AND_ASSIGN(MidiManagerAlsa);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000441};
442
toyoshime147c5e2015-05-07 21:58:31 -0700443} // namespace midi
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000444
dnicoara@chromium.org9f2a6f02014-01-03 21:25:00 +0000445#endif // MEDIA_MIDI_MIDI_MANAGER_ALSA_H_