blob: dd12fc0d55072666ecc3b998c346ba898edbeef9 [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#include "media/midi/midi_manager_alsa.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +00006
7#include <alsa/asoundlib.h>
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +00008#include <stdlib.h>
yhirano@chromium.orgcfa642c2014-05-01 08:54:41 +00009#include <algorithm>
10#include <string>
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000011
12#include "base/bind.h"
13#include "base/debug/trace_event.h"
14#include "base/logging.h"
15#include "base/memory/ref_counted.h"
16#include "base/message_loop/message_loop.h"
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000017#include "base/posix/eintr_wrapper.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000018#include "base/strings/stringprintf.h"
19#include "base/threading/thread.h"
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000020#include "base/time/time.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000021#include "media/midi/midi_port_info.h"
22
23namespace media {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000024
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000025namespace {
26
27const size_t kReceiveBufferSize = 4096;
28const unsigned short kPollEventMask = POLLIN | POLLERR | POLLNVAL;
29
30} // namespace
31
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +000032class MidiManagerAlsa::MidiDeviceInfo
33 : public base::RefCounted<MidiDeviceInfo> {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000034 public:
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +000035 MidiDeviceInfo(MidiManagerAlsa* manager,
toyoshim@chromium.org9ebe19a2014-02-02 06:10:36 +000036 const std::string& bus_id,
37 snd_ctl_card_info_t* card,
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000038 const snd_rawmidi_info_t* midi,
39 int device) {
toyoshim@chromium.org9ebe19a2014-02-02 06:10:36 +000040 opened_ = !snd_rawmidi_open(&midi_in_, &midi_out_, bus_id.c_str(), 0);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000041 if (!opened_)
42 return;
43
toyoshim@chromium.org9ebe19a2014-02-02 06:10:36 +000044 const std::string id = base::StringPrintf("%s:%d", bus_id.c_str(), device);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000045 const std::string name = snd_rawmidi_info_get_name(midi);
toyoshim@chromium.org9ebe19a2014-02-02 06:10:36 +000046 // We assume that card longname is in the format of
47 // "<manufacturer> <name> at <bus>". Otherwise, we give up to detect
48 // a manufacturer name here.
49 std::string manufacturer;
50 const std::string card_name = snd_ctl_card_info_get_longname(card);
51 size_t name_index = card_name.find(name);
52 if (std::string::npos != name_index)
53 manufacturer = card_name.substr(0, name_index - 1);
54 const std::string version =
55 base::StringPrintf("%s / ALSA library version %d.%d.%d",
56 snd_ctl_card_info_get_driver(card),
57 SND_LIB_MAJOR, SND_LIB_MINOR, SND_LIB_SUBMINOR);
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +000058 port_info_ = MidiPortInfo(id, manufacturer, name, version);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000059 }
60
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +000061 void Send(MidiManagerClient* client, const std::vector<uint8>& data) {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000062 ssize_t result = snd_rawmidi_write(
63 midi_out_, reinterpret_cast<const void*>(&data[0]), data.size());
64 if (static_cast<size_t>(result) != data.size()) {
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000065 // TODO(toyoshim): Handle device disconnection.
toyoshim@chromium.org2b058e82014-02-26 06:10:46 +000066 VLOG(1) << "snd_rawmidi_write fails: " << strerror(-result);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000067 }
68 base::MessageLoop::current()->PostTask(
69 FROM_HERE,
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +000070 base::Bind(&MidiManagerClient::AccumulateMidiBytesSent,
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000071 base::Unretained(client), data.size()));
72 }
73
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000074 // Read input data from a MIDI input device which is ready to read through
75 // the ALSA library. Called from EventLoop() and read data will be sent to
76 // blink through MIDIManager base class.
77 size_t Receive(uint8* data, size_t length) {
78 return snd_rawmidi_read(midi_in_, reinterpret_cast<void*>(data), length);
79 }
80
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +000081 const MidiPortInfo& GetMidiPortInfo() const { return port_info_; }
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000082
83 // Get the number of descriptors which is required to call poll() on the
84 // device. The ALSA library always returns 1 here now, but it may be changed
85 // in the future.
86 int GetPollDescriptorsCount() {
87 return snd_rawmidi_poll_descriptors_count(midi_in_);
88 }
89
90 // Following API initializes pollfds for polling the device, and returns the
91 // number of descriptors they are initialized. It must be the same value with
92 // snd_rawmidi_poll_descriptors_count().
93 int SetupPollDescriptors(struct pollfd* pfds, unsigned int count) {
94 return snd_rawmidi_poll_descriptors(midi_in_, pfds, count);
95 }
96
97 unsigned short GetPollDescriptorsRevents(struct pollfd* pfds) {
98 unsigned short revents;
99 snd_rawmidi_poll_descriptors_revents(midi_in_,
100 pfds,
101 GetPollDescriptorsCount(),
102 &revents);
103 return revents;
104 }
105
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000106 bool IsOpened() const { return opened_; }
107
108 private:
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000109 friend class base::RefCounted<MidiDeviceInfo>;
110 virtual ~MidiDeviceInfo() {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000111 if (opened_) {
112 snd_rawmidi_close(midi_in_);
113 snd_rawmidi_close(midi_out_);
114 }
115 }
116
117 bool opened_;
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000118 MidiPortInfo port_info_;
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000119 snd_rawmidi_t* midi_in_;
120 snd_rawmidi_t* midi_out_;
121
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000122 DISALLOW_COPY_AND_ASSIGN(MidiDeviceInfo);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000123};
124
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000125MidiManagerAlsa::MidiManagerAlsa()
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000126 : send_thread_("MidiSendThread"),
127 event_thread_("MidiEventThread") {
128 for (size_t i = 0; i < arraysize(pipe_fd_); ++i)
129 pipe_fd_[i] = -1;
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000130}
131
toyoshim@chromium.orgf77a1e62014-04-11 13:16:24 +0000132MidiResult MidiManagerAlsa::Initialize() {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000133 // TODO(toyoshim): Make Initialize() asynchronous.
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000134 // See http://crbug.com/339746.
toyoshim@chromium.org2b058e82014-02-26 06:10:46 +0000135 TRACE_EVENT0("midi", "MidiManagerAlsa::Initialize");
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000136
137 // Enumerate only hardware MIDI devices because software MIDIs running in
138 // the browser process is not secure.
139 snd_ctl_card_info_t* card;
140 snd_rawmidi_info_t* midi_out;
141 snd_rawmidi_info_t* midi_in;
142 snd_ctl_card_info_alloca(&card);
143 snd_rawmidi_info_alloca(&midi_out);
144 snd_rawmidi_info_alloca(&midi_in);
145 for (int index = -1; !snd_card_next(&index) && index >= 0; ) {
146 const std::string id = base::StringPrintf("hw:CARD=%i", index);
147 snd_ctl_t* handle;
148 int err = snd_ctl_open(&handle, id.c_str(), 0);
149 if (err != 0) {
toyoshim@chromium.org2b058e82014-02-26 06:10:46 +0000150 VLOG(1) << "snd_ctl_open fails: " << snd_strerror(err);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000151 continue;
152 }
153 err = snd_ctl_card_info(handle, card);
154 if (err != 0) {
toyoshim@chromium.org2b058e82014-02-26 06:10:46 +0000155 VLOG(1) << "snd_ctl_card_info fails: " << snd_strerror(err);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000156 snd_ctl_close(handle);
157 continue;
158 }
159 for (int device = -1;
160 !snd_ctl_rawmidi_next_device(handle, &device) && device >= 0; ) {
161 bool output;
162 bool input;
163 snd_rawmidi_info_set_device(midi_out, device);
164 snd_rawmidi_info_set_subdevice(midi_out, 0);
165 snd_rawmidi_info_set_stream(midi_out, SND_RAWMIDI_STREAM_OUTPUT);
166 output = snd_ctl_rawmidi_info(handle, midi_out) == 0;
167 snd_rawmidi_info_set_device(midi_in, device);
168 snd_rawmidi_info_set_subdevice(midi_in, 0);
169 snd_rawmidi_info_set_stream(midi_in, SND_RAWMIDI_STREAM_INPUT);
170 input = snd_ctl_rawmidi_info(handle, midi_in) == 0;
171 if (!output && !input)
172 continue;
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000173 scoped_refptr<MidiDeviceInfo> port = new MidiDeviceInfo(
toyoshim@chromium.org9ebe19a2014-02-02 06:10:36 +0000174 this, id, card, output ? midi_out : midi_in, device);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000175 if (!port->IsOpened()) {
toyoshim@chromium.org2b058e82014-02-26 06:10:46 +0000176 VLOG(1) << "MidiDeviceInfo open fails";
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000177 continue;
178 }
179 if (input) {
180 in_devices_.push_back(port);
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000181 AddInputPort(port->GetMidiPortInfo());
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000182 }
183 if (output) {
184 out_devices_.push_back(port);
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000185 AddOutputPort(port->GetMidiPortInfo());
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000186 }
187 }
188 snd_ctl_close(handle);
189 }
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000190
191 if (pipe(pipe_fd_) < 0) {
toyoshim@chromium.org2b058e82014-02-26 06:10:46 +0000192 VPLOG(1) << "pipe() failed";
toyoshim@chromium.orgf77a1e62014-04-11 13:16:24 +0000193 return MIDI_INITIALIZATION_ERROR;
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000194 }
195 event_thread_.Start();
196 event_thread_.message_loop()->PostTask(
197 FROM_HERE,
198 base::Bind(&MidiManagerAlsa::EventReset, base::Unretained(this)));
199
toyoshim@chromium.orgf77a1e62014-04-11 13:16:24 +0000200 return MIDI_OK;
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000201}
202
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000203MidiManagerAlsa::~MidiManagerAlsa() {
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000204 // Send a shutdown message to awake |event_thread_| from poll().
205 if (pipe_fd_[1] >= 0)
206 HANDLE_EINTR(write(pipe_fd_[1], "Q", 1));
207
208 // Stop receiving messages.
209 event_thread_.Stop();
210
211 for (int i = 0; i < 2; ++i) {
212 if (pipe_fd_[i] >= 0)
213 close(pipe_fd_[i]);
214 }
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000215 send_thread_.Stop();
216}
217
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000218void MidiManagerAlsa::DispatchSendMidiData(MidiManagerClient* client,
dnicoara@chromium.org9f2a6f02014-01-03 21:25:00 +0000219 uint32 port_index,
220 const std::vector<uint8>& data,
221 double timestamp) {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000222 if (out_devices_.size() <= port_index)
223 return;
224
225 base::TimeDelta delay;
226 if (timestamp != 0.0) {
227 base::TimeTicks time_to_send =
228 base::TimeTicks() + base::TimeDelta::FromMicroseconds(
229 timestamp * base::Time::kMicrosecondsPerSecond);
230 delay = std::max(time_to_send - base::TimeTicks::Now(), base::TimeDelta());
231 }
232
233 if (!send_thread_.IsRunning())
234 send_thread_.Start();
235
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000236 scoped_refptr<MidiDeviceInfo> device = out_devices_[port_index];
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000237 send_thread_.message_loop()->PostDelayedTask(
238 FROM_HERE,
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000239 base::Bind(&MidiDeviceInfo::Send, device, client, data),
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000240 delay);
241}
242
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000243void MidiManagerAlsa::EventReset() {
yhirano@chromium.orgcfa642c2014-05-01 08:54:41 +0000244 CHECK_GE(pipe_fd_[0], 0);
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000245
246 // Sum up descriptors which are needed to poll input devices and a shutdown
247 // message.
248 // Keep the first one descriptor for a shutdown message.
249 size_t poll_fds_size = 1;
250 for (size_t i = 0; i < in_devices_.size(); ++i)
251 poll_fds_size += in_devices_[i]->GetPollDescriptorsCount();
252 poll_fds_.resize(poll_fds_size);
253
254 // Setup struct pollfd to poll input MIDI devices and a shutdown message.
255 // The first pollfd is for a shutdown message.
256 poll_fds_[0].fd = pipe_fd_[0];
257 poll_fds_[0].events = kPollEventMask;
258 int fds_index = 1;
259 for (size_t i = 0; i < in_devices_.size(); ++i) {
260 fds_index += in_devices_[i]->SetupPollDescriptors(
261 &poll_fds_[fds_index], poll_fds_.size() - fds_index);
262 }
263
264 event_thread_.message_loop()->PostTask(
265 FROM_HERE,
266 base::Bind(&MidiManagerAlsa::EventLoop, base::Unretained(this)));
267}
268
269void MidiManagerAlsa::EventLoop() {
270 if (HANDLE_EINTR(poll(&poll_fds_[0], poll_fds_.size(), -1)) < 0) {
toyoshim@chromium.org2b058e82014-02-26 06:10:46 +0000271 VPLOG(1) << "Couldn't poll(). Stop to poll input MIDI devices.";
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000272 // TODO(toyoshim): Handle device disconnection, and try to reconnect?
273 return;
274 }
275
276 // Check timestamp as soon as possible because the API requires accurate
277 // timestamp as possible. It will be useful for recording MIDI events.
278 base::TimeTicks now = base::TimeTicks::HighResNow();
279
280 // Is this thread going to be shutdown?
281 if (poll_fds_[0].revents & kPollEventMask)
282 return;
283
284 // Read available incoming MIDI data.
285 int fds_index = 1;
286 uint8 buffer[kReceiveBufferSize];
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000287
288 for (size_t i = 0; i < in_devices_.size(); ++i) {
289 unsigned short revents =
290 in_devices_[i]->GetPollDescriptorsRevents(&poll_fds_[fds_index]);
291 if (revents & (POLLERR | POLLNVAL)) {
292 // TODO(toyoshim): Handle device disconnection.
toyoshim@chromium.org2b058e82014-02-26 06:10:46 +0000293 VLOG(1) << "snd_rawmidi_descriptors_revents fails";
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000294 poll_fds_[fds_index].events = 0;
295 }
296 if (revents & POLLIN) {
297 size_t read_size = in_devices_[i]->Receive(buffer, kReceiveBufferSize);
yhirano@chromium.orgcfa642c2014-05-01 08:54:41 +0000298 ReceiveMidiData(i, buffer, read_size, now);
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000299 }
300 fds_index += in_devices_[i]->GetPollDescriptorsCount();
301 }
302
303 // Do again.
304 event_thread_.message_loop()->PostTask(
305 FROM_HERE,
306 base::Bind(&MidiManagerAlsa::EventLoop, base::Unretained(this)));
307}
308
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000309MidiManager* MidiManager::Create() {
310 return new MidiManagerAlsa();
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000311}
312
313} // namespace media