blob: 8f580e1ba318c2e559e55dfb3770a6eb8fd77926 [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>
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +00009
10#include "base/bind.h"
11#include "base/debug/trace_event.h"
12#include "base/logging.h"
13#include "base/memory/ref_counted.h"
14#include "base/message_loop/message_loop.h"
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000015#include "base/posix/eintr_wrapper.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000016#include "base/strings/stringprintf.h"
17#include "base/threading/thread.h"
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000018#include "base/time/time.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000019#include "media/midi/midi_port_info.h"
20
21namespace media {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000022
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000023namespace {
24
25const size_t kReceiveBufferSize = 4096;
26const unsigned short kPollEventMask = POLLIN | POLLERR | POLLNVAL;
27
28} // namespace
29
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +000030class MidiManagerAlsa::MidiDeviceInfo
31 : public base::RefCounted<MidiDeviceInfo> {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000032 public:
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +000033 MidiDeviceInfo(MidiManagerAlsa* manager,
toyoshim@chromium.org9ebe19a2014-02-02 06:10:36 +000034 const std::string& bus_id,
35 snd_ctl_card_info_t* card,
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000036 const snd_rawmidi_info_t* midi,
37 int device) {
toyoshim@chromium.org9ebe19a2014-02-02 06:10:36 +000038 opened_ = !snd_rawmidi_open(&midi_in_, &midi_out_, bus_id.c_str(), 0);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000039 if (!opened_)
40 return;
41
toyoshim@chromium.org9ebe19a2014-02-02 06:10:36 +000042 const std::string id = base::StringPrintf("%s:%d", bus_id.c_str(), device);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000043 const std::string name = snd_rawmidi_info_get_name(midi);
toyoshim@chromium.org9ebe19a2014-02-02 06:10:36 +000044 // We assume that card longname is in the format of
45 // "<manufacturer> <name> at <bus>". Otherwise, we give up to detect
46 // a manufacturer name here.
47 std::string manufacturer;
48 const std::string card_name = snd_ctl_card_info_get_longname(card);
49 size_t name_index = card_name.find(name);
50 if (std::string::npos != name_index)
51 manufacturer = card_name.substr(0, name_index - 1);
52 const std::string version =
53 base::StringPrintf("%s / ALSA library version %d.%d.%d",
54 snd_ctl_card_info_get_driver(card),
55 SND_LIB_MAJOR, SND_LIB_MINOR, SND_LIB_SUBMINOR);
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +000056 port_info_ = MidiPortInfo(id, manufacturer, name, version);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000057 }
58
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +000059 void Send(MidiManagerClient* client, const std::vector<uint8>& data) {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000060 ssize_t result = snd_rawmidi_write(
61 midi_out_, reinterpret_cast<const void*>(&data[0]), data.size());
62 if (static_cast<size_t>(result) != data.size()) {
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000063 // TODO(toyoshim): Handle device disconnection.
toyoshim@chromium.org2b058e82014-02-26 06:10:46 +000064 VLOG(1) << "snd_rawmidi_write fails: " << strerror(-result);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000065 }
66 base::MessageLoop::current()->PostTask(
67 FROM_HERE,
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +000068 base::Bind(&MidiManagerClient::AccumulateMidiBytesSent,
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000069 base::Unretained(client), data.size()));
70 }
71
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000072 // Read input data from a MIDI input device which is ready to read through
73 // the ALSA library. Called from EventLoop() and read data will be sent to
74 // blink through MIDIManager base class.
75 size_t Receive(uint8* data, size_t length) {
76 return snd_rawmidi_read(midi_in_, reinterpret_cast<void*>(data), length);
77 }
78
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +000079 const MidiPortInfo& GetMidiPortInfo() const { return port_info_; }
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000080
81 // Get the number of descriptors which is required to call poll() on the
82 // device. The ALSA library always returns 1 here now, but it may be changed
83 // in the future.
84 int GetPollDescriptorsCount() {
85 return snd_rawmidi_poll_descriptors_count(midi_in_);
86 }
87
88 // Following API initializes pollfds for polling the device, and returns the
89 // number of descriptors they are initialized. It must be the same value with
90 // snd_rawmidi_poll_descriptors_count().
91 int SetupPollDescriptors(struct pollfd* pfds, unsigned int count) {
92 return snd_rawmidi_poll_descriptors(midi_in_, pfds, count);
93 }
94
95 unsigned short GetPollDescriptorsRevents(struct pollfd* pfds) {
96 unsigned short revents;
97 snd_rawmidi_poll_descriptors_revents(midi_in_,
98 pfds,
99 GetPollDescriptorsCount(),
100 &revents);
101 return revents;
102 }
103
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000104 bool IsOpened() const { return opened_; }
105
106 private:
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000107 friend class base::RefCounted<MidiDeviceInfo>;
108 virtual ~MidiDeviceInfo() {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000109 if (opened_) {
110 snd_rawmidi_close(midi_in_);
111 snd_rawmidi_close(midi_out_);
112 }
113 }
114
115 bool opened_;
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000116 MidiPortInfo port_info_;
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000117 snd_rawmidi_t* midi_in_;
118 snd_rawmidi_t* midi_out_;
119
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000120 DISALLOW_COPY_AND_ASSIGN(MidiDeviceInfo);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000121};
122
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000123MidiManagerAlsa::MidiManagerAlsa()
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000124 : send_thread_("MidiSendThread"),
125 event_thread_("MidiEventThread") {
126 for (size_t i = 0; i < arraysize(pipe_fd_); ++i)
127 pipe_fd_[i] = -1;
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000128}
129
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000130bool MidiManagerAlsa::Initialize() {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000131 // TODO(toyoshim): Make Initialize() asynchronous.
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000132 // See http://crbug.com/339746.
toyoshim@chromium.org2b058e82014-02-26 06:10:46 +0000133 TRACE_EVENT0("midi", "MidiManagerAlsa::Initialize");
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000134
135 // Enumerate only hardware MIDI devices because software MIDIs running in
136 // the browser process is not secure.
137 snd_ctl_card_info_t* card;
138 snd_rawmidi_info_t* midi_out;
139 snd_rawmidi_info_t* midi_in;
140 snd_ctl_card_info_alloca(&card);
141 snd_rawmidi_info_alloca(&midi_out);
142 snd_rawmidi_info_alloca(&midi_in);
143 for (int index = -1; !snd_card_next(&index) && index >= 0; ) {
144 const std::string id = base::StringPrintf("hw:CARD=%i", index);
145 snd_ctl_t* handle;
146 int err = snd_ctl_open(&handle, id.c_str(), 0);
147 if (err != 0) {
toyoshim@chromium.org2b058e82014-02-26 06:10:46 +0000148 VLOG(1) << "snd_ctl_open fails: " << snd_strerror(err);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000149 continue;
150 }
151 err = snd_ctl_card_info(handle, card);
152 if (err != 0) {
toyoshim@chromium.org2b058e82014-02-26 06:10:46 +0000153 VLOG(1) << "snd_ctl_card_info fails: " << snd_strerror(err);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000154 snd_ctl_close(handle);
155 continue;
156 }
157 for (int device = -1;
158 !snd_ctl_rawmidi_next_device(handle, &device) && device >= 0; ) {
159 bool output;
160 bool input;
161 snd_rawmidi_info_set_device(midi_out, device);
162 snd_rawmidi_info_set_subdevice(midi_out, 0);
163 snd_rawmidi_info_set_stream(midi_out, SND_RAWMIDI_STREAM_OUTPUT);
164 output = snd_ctl_rawmidi_info(handle, midi_out) == 0;
165 snd_rawmidi_info_set_device(midi_in, device);
166 snd_rawmidi_info_set_subdevice(midi_in, 0);
167 snd_rawmidi_info_set_stream(midi_in, SND_RAWMIDI_STREAM_INPUT);
168 input = snd_ctl_rawmidi_info(handle, midi_in) == 0;
169 if (!output && !input)
170 continue;
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000171 scoped_refptr<MidiDeviceInfo> port = new MidiDeviceInfo(
toyoshim@chromium.org9ebe19a2014-02-02 06:10:36 +0000172 this, id, card, output ? midi_out : midi_in, device);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000173 if (!port->IsOpened()) {
toyoshim@chromium.org2b058e82014-02-26 06:10:46 +0000174 VLOG(1) << "MidiDeviceInfo open fails";
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000175 continue;
176 }
177 if (input) {
178 in_devices_.push_back(port);
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000179 AddInputPort(port->GetMidiPortInfo());
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000180 }
181 if (output) {
182 out_devices_.push_back(port);
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000183 AddOutputPort(port->GetMidiPortInfo());
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000184 }
185 }
186 snd_ctl_close(handle);
187 }
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000188
189 if (pipe(pipe_fd_) < 0) {
toyoshim@chromium.org2b058e82014-02-26 06:10:46 +0000190 VPLOG(1) << "pipe() failed";
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000191 return false;
192 }
193 event_thread_.Start();
194 event_thread_.message_loop()->PostTask(
195 FROM_HERE,
196 base::Bind(&MidiManagerAlsa::EventReset, base::Unretained(this)));
197
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000198 return true;
199}
200
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000201MidiManagerAlsa::~MidiManagerAlsa() {
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000202 // Send a shutdown message to awake |event_thread_| from poll().
203 if (pipe_fd_[1] >= 0)
204 HANDLE_EINTR(write(pipe_fd_[1], "Q", 1));
205
206 // Stop receiving messages.
207 event_thread_.Stop();
208
209 for (int i = 0; i < 2; ++i) {
210 if (pipe_fd_[i] >= 0)
211 close(pipe_fd_[i]);
212 }
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000213 send_thread_.Stop();
214}
215
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000216void MidiManagerAlsa::DispatchSendMidiData(MidiManagerClient* client,
dnicoara@chromium.org9f2a6f02014-01-03 21:25:00 +0000217 uint32 port_index,
218 const std::vector<uint8>& data,
219 double timestamp) {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000220 if (out_devices_.size() <= port_index)
221 return;
222
223 base::TimeDelta delay;
224 if (timestamp != 0.0) {
225 base::TimeTicks time_to_send =
226 base::TimeTicks() + base::TimeDelta::FromMicroseconds(
227 timestamp * base::Time::kMicrosecondsPerSecond);
228 delay = std::max(time_to_send - base::TimeTicks::Now(), base::TimeDelta());
229 }
230
231 if (!send_thread_.IsRunning())
232 send_thread_.Start();
233
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000234 scoped_refptr<MidiDeviceInfo> device = out_devices_[port_index];
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000235 send_thread_.message_loop()->PostDelayedTask(
236 FROM_HERE,
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000237 base::Bind(&MidiDeviceInfo::Send, device, client, data),
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000238 delay);
239}
240
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000241void MidiManagerAlsa::EventReset() {
242 CHECK(pipe_fd_[0] >= 0);
243
244 // Sum up descriptors which are needed to poll input devices and a shutdown
245 // message.
246 // Keep the first one descriptor for a shutdown message.
247 size_t poll_fds_size = 1;
248 for (size_t i = 0; i < in_devices_.size(); ++i)
249 poll_fds_size += in_devices_[i]->GetPollDescriptorsCount();
250 poll_fds_.resize(poll_fds_size);
251
252 // Setup struct pollfd to poll input MIDI devices and a shutdown message.
253 // The first pollfd is for a shutdown message.
254 poll_fds_[0].fd = pipe_fd_[0];
255 poll_fds_[0].events = kPollEventMask;
256 int fds_index = 1;
257 for (size_t i = 0; i < in_devices_.size(); ++i) {
258 fds_index += in_devices_[i]->SetupPollDescriptors(
259 &poll_fds_[fds_index], poll_fds_.size() - fds_index);
260 }
261
262 event_thread_.message_loop()->PostTask(
263 FROM_HERE,
264 base::Bind(&MidiManagerAlsa::EventLoop, base::Unretained(this)));
265}
266
267void MidiManagerAlsa::EventLoop() {
268 if (HANDLE_EINTR(poll(&poll_fds_[0], poll_fds_.size(), -1)) < 0) {
toyoshim@chromium.org2b058e82014-02-26 06:10:46 +0000269 VPLOG(1) << "Couldn't poll(). Stop to poll input MIDI devices.";
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000270 // TODO(toyoshim): Handle device disconnection, and try to reconnect?
271 return;
272 }
273
274 // Check timestamp as soon as possible because the API requires accurate
275 // timestamp as possible. It will be useful for recording MIDI events.
276 base::TimeTicks now = base::TimeTicks::HighResNow();
277
278 // Is this thread going to be shutdown?
279 if (poll_fds_[0].revents & kPollEventMask)
280 return;
281
282 // Read available incoming MIDI data.
283 int fds_index = 1;
284 uint8 buffer[kReceiveBufferSize];
285 // TODO(toyoshim): Revisit if the following conversion is the best way.
286 base::TimeDelta timestamp_delta =
287 base::TimeDelta::FromInternalValue(now.ToInternalValue());
288 double timestamp = timestamp_delta.InSecondsF();
289
290 for (size_t i = 0; i < in_devices_.size(); ++i) {
291 unsigned short revents =
292 in_devices_[i]->GetPollDescriptorsRevents(&poll_fds_[fds_index]);
293 if (revents & (POLLERR | POLLNVAL)) {
294 // TODO(toyoshim): Handle device disconnection.
toyoshim@chromium.org2b058e82014-02-26 06:10:46 +0000295 VLOG(1) << "snd_rawmidi_descriptors_revents fails";
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000296 poll_fds_[fds_index].events = 0;
297 }
298 if (revents & POLLIN) {
299 size_t read_size = in_devices_[i]->Receive(buffer, kReceiveBufferSize);
300 ReceiveMidiData(i, buffer, read_size, timestamp);
301 }
302 fds_index += in_devices_[i]->GetPollDescriptorsCount();
303 }
304
305 // Do again.
306 event_thread_.message_loop()->PostTask(
307 FROM_HERE,
308 base::Bind(&MidiManagerAlsa::EventLoop, base::Unretained(this)));
309}
310
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000311MidiManager* MidiManager::Create() {
312 return new MidiManagerAlsa();
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000313}
314
315} // namespace media