summaryrefslogtreecommitdiffstats
path: root/media/midi/midi_manager_alsa.cc
blob: c4be32ffae29428b918cdae2707d4e92c920675d (plain)
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
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/midi/midi_manager_alsa.h"

#include <alsa/asoundlib.h>
#include <stdlib.h>
#include <algorithm>
#include <string>

#include "base/bind.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/message_loop/message_loop.h"
#include "base/posix/eintr_wrapper.h"
#include "base/strings/stringprintf.h"
#include "base/threading/thread.h"
#include "base/time/time.h"
#include "media/midi/midi_port_info.h"

namespace media {

namespace {

const size_t kReceiveBufferSize = 4096;
const unsigned short kPollEventMask = POLLIN | POLLERR | POLLNVAL;

}  // namespace

class MidiManagerAlsa::MidiDeviceInfo
    : public base::RefCounted<MidiDeviceInfo> {
 public:
  MidiDeviceInfo(MidiManagerAlsa* manager,
                 const std::string& bus_id,
                 snd_ctl_card_info_t* card,
                 const snd_rawmidi_info_t* midi,
                 int device) {
    opened_ = !snd_rawmidi_open(&midi_in_, &midi_out_, bus_id.c_str(), 0);
    if (!opened_)
      return;

    const std::string id = base::StringPrintf("%s:%d", bus_id.c_str(), device);
    const std::string name = snd_rawmidi_info_get_name(midi);
    // We assume that card longname is in the format of
    // "<manufacturer> <name> at <bus>". Otherwise, we give up to detect
    // a manufacturer name here.
    std::string manufacturer;
    const std::string card_name = snd_ctl_card_info_get_longname(card);
    size_t name_index = card_name.find(name);
    if (std::string::npos != name_index)
      manufacturer = card_name.substr(0, name_index - 1);
    const std::string version =
        base::StringPrintf("%s / ALSA library version %d.%d.%d",
                           snd_ctl_card_info_get_driver(card),
                           SND_LIB_MAJOR, SND_LIB_MINOR, SND_LIB_SUBMINOR);
    port_info_ = MidiPortInfo(id, manufacturer, name, version);
  }

  void Send(MidiManagerClient* client, const std::vector<uint8>& data) {
    ssize_t result = snd_rawmidi_write(
        midi_out_, reinterpret_cast<const void*>(&data[0]), data.size());
    if (static_cast<size_t>(result) != data.size()) {
      // TODO(toyoshim): Handle device disconnection.
      VLOG(1) << "snd_rawmidi_write fails: " << strerror(-result);
    }
    base::MessageLoop::current()->PostTask(
        FROM_HERE,
        base::Bind(&MidiManagerClient::AccumulateMidiBytesSent,
                   base::Unretained(client), data.size()));
  }

  // Read input data from a MIDI input device which is ready to read through
  // the ALSA library. Called from EventLoop() and read data will be sent to
  // blink through MIDIManager base class.
  size_t Receive(uint8* data, size_t length) {
    return snd_rawmidi_read(midi_in_, reinterpret_cast<void*>(data), length);
  }

  const MidiPortInfo& GetMidiPortInfo() const { return port_info_; }

  // Get the number of descriptors which is required to call poll() on the
  // device. The ALSA library always returns 1 here now, but it may be changed
  // in the future.
  int GetPollDescriptorsCount() {
    return snd_rawmidi_poll_descriptors_count(midi_in_);
  }

  // Following API initializes pollfds for polling the device, and returns the
  // number of descriptors they are initialized. It must be the same value with
  // snd_rawmidi_poll_descriptors_count().
  int SetupPollDescriptors(struct pollfd* pfds, unsigned int count) {
    return snd_rawmidi_poll_descriptors(midi_in_, pfds, count);
  }

  unsigned short GetPollDescriptorsRevents(struct pollfd* pfds) {
    unsigned short revents;
    snd_rawmidi_poll_descriptors_revents(midi_in_,
                                         pfds,
                                         GetPollDescriptorsCount(),
                                         &revents);
    return revents;
  }

  bool IsOpened() const { return opened_; }

 private:
  friend class base::RefCounted<MidiDeviceInfo>;
  virtual ~MidiDeviceInfo() {
    if (opened_) {
      snd_rawmidi_close(midi_in_);
      snd_rawmidi_close(midi_out_);
    }
  }

  bool opened_;
  MidiPortInfo port_info_;
  snd_rawmidi_t* midi_in_;
  snd_rawmidi_t* midi_out_;

  DISALLOW_COPY_AND_ASSIGN(MidiDeviceInfo);
};

MidiManagerAlsa::MidiManagerAlsa()
    : send_thread_("MidiSendThread"),
      event_thread_("MidiEventThread") {
  for (size_t i = 0; i < arraysize(pipe_fd_); ++i)
    pipe_fd_[i] = -1;
}

void MidiManagerAlsa::StartInitialization() {
  // Enumerate only hardware MIDI devices because software MIDIs running in
  // the browser process is not secure.
  snd_ctl_card_info_t* card;
  snd_rawmidi_info_t* midi_out;
  snd_rawmidi_info_t* midi_in;
  snd_ctl_card_info_alloca(&card);
  snd_rawmidi_info_alloca(&midi_out);
  snd_rawmidi_info_alloca(&midi_in);
  for (int index = -1; !snd_card_next(&index) && index >= 0; ) {
    const std::string id = base::StringPrintf("hw:CARD=%i", index);
    snd_ctl_t* handle;
    int err = snd_ctl_open(&handle, id.c_str(), 0);
    if (err != 0) {
      VLOG(1) << "snd_ctl_open fails: " << snd_strerror(err);
      continue;
    }
    err = snd_ctl_card_info(handle, card);
    if (err != 0) {
      VLOG(1) << "snd_ctl_card_info fails: " << snd_strerror(err);
      snd_ctl_close(handle);
      continue;
    }
    for (int device = -1;
        !snd_ctl_rawmidi_next_device(handle, &device) && device >= 0; ) {
      bool output;
      bool input;
      snd_rawmidi_info_set_device(midi_out, device);
      snd_rawmidi_info_set_subdevice(midi_out, 0);
      snd_rawmidi_info_set_stream(midi_out, SND_RAWMIDI_STREAM_OUTPUT);
      output = snd_ctl_rawmidi_info(handle, midi_out) == 0;
      snd_rawmidi_info_set_device(midi_in, device);
      snd_rawmidi_info_set_subdevice(midi_in, 0);
      snd_rawmidi_info_set_stream(midi_in, SND_RAWMIDI_STREAM_INPUT);
      input = snd_ctl_rawmidi_info(handle, midi_in) == 0;
      if (!output && !input)
        continue;
      scoped_refptr<MidiDeviceInfo> port = new MidiDeviceInfo(
          this, id, card, output ? midi_out : midi_in, device);
      if (!port->IsOpened()) {
        VLOG(1) << "MidiDeviceInfo open fails";
        continue;
      }
      if (input) {
        in_devices_.push_back(port);
        AddInputPort(port->GetMidiPortInfo());
      }
      if (output) {
        out_devices_.push_back(port);
        AddOutputPort(port->GetMidiPortInfo());
      }
    }
    snd_ctl_close(handle);
  }

  if (pipe(pipe_fd_) < 0) {
    VPLOG(1) << "pipe() failed";
    CompleteInitialization(MIDI_INITIALIZATION_ERROR);
  } else {
    event_thread_.Start();
    event_thread_.message_loop()->PostTask(
        FROM_HERE,
        base::Bind(&MidiManagerAlsa::EventReset, base::Unretained(this)));
    CompleteInitialization(MIDI_OK);
  }
}

MidiManagerAlsa::~MidiManagerAlsa() {
  // Send a shutdown message to awake |event_thread_| from poll().
  if (pipe_fd_[1] >= 0)
    HANDLE_EINTR(write(pipe_fd_[1], "Q", 1));

  // Stop receiving messages.
  event_thread_.Stop();

  for (int i = 0; i < 2; ++i) {
    if (pipe_fd_[i] >= 0)
      close(pipe_fd_[i]);
  }
  send_thread_.Stop();
}

void MidiManagerAlsa::DispatchSendMidiData(MidiManagerClient* client,
                                           uint32 port_index,
                                           const std::vector<uint8>& data,
                                           double timestamp) {
  if (out_devices_.size() <= port_index)
    return;

  base::TimeDelta delay;
  if (timestamp != 0.0) {
    base::TimeTicks time_to_send =
        base::TimeTicks() + base::TimeDelta::FromMicroseconds(
            timestamp * base::Time::kMicrosecondsPerSecond);
    delay = std::max(time_to_send - base::TimeTicks::Now(), base::TimeDelta());
  }

  if (!send_thread_.IsRunning())
    send_thread_.Start();

  scoped_refptr<MidiDeviceInfo> device = out_devices_[port_index];
  send_thread_.message_loop()->PostDelayedTask(
      FROM_HERE,
      base::Bind(&MidiDeviceInfo::Send, device, client, data),
      delay);
}

void MidiManagerAlsa::EventReset() {
  CHECK_GE(pipe_fd_[0], 0);

  // Sum up descriptors which are needed to poll input devices and a shutdown
  // message.
  // Keep the first one descriptor for a shutdown message.
  size_t poll_fds_size = 1;
  for (size_t i = 0; i < in_devices_.size(); ++i)
    poll_fds_size += in_devices_[i]->GetPollDescriptorsCount();
  poll_fds_.resize(poll_fds_size);

  // Setup struct pollfd to poll input MIDI devices and a shutdown message.
  // The first pollfd is for a shutdown message.
  poll_fds_[0].fd = pipe_fd_[0];
  poll_fds_[0].events = kPollEventMask;
  int fds_index = 1;
  for (size_t i = 0; i < in_devices_.size(); ++i) {
    fds_index += in_devices_[i]->SetupPollDescriptors(
        &poll_fds_[fds_index], poll_fds_.size() - fds_index);
  }

  event_thread_.message_loop()->PostTask(
      FROM_HERE,
      base::Bind(&MidiManagerAlsa::EventLoop, base::Unretained(this)));
}

void MidiManagerAlsa::EventLoop() {
  if (HANDLE_EINTR(poll(&poll_fds_[0], poll_fds_.size(), -1)) < 0) {
    VPLOG(1) << "Couldn't poll(). Stop to poll input MIDI devices.";
    // TODO(toyoshim): Handle device disconnection, and try to reconnect?
    return;
  }

  // Check timestamp as soon as possible because the API requires accurate
  // timestamp as possible. It will be useful for recording MIDI events.
  base::TimeTicks now = base::TimeTicks::HighResNow();

  // Is this thread going to be shutdown?
  if (poll_fds_[0].revents & kPollEventMask)
    return;

  // Read available incoming MIDI data.
  int fds_index = 1;
  uint8 buffer[kReceiveBufferSize];

  for (size_t i = 0; i < in_devices_.size(); ++i) {
    unsigned short revents =
        in_devices_[i]->GetPollDescriptorsRevents(&poll_fds_[fds_index]);
    if (revents & (POLLERR | POLLNVAL)) {
      // TODO(toyoshim): Handle device disconnection.
      VLOG(1) << "snd_rawmidi_descriptors_revents fails";
      poll_fds_[fds_index].events = 0;
    }
    if (revents & POLLIN) {
      size_t read_size = in_devices_[i]->Receive(buffer, kReceiveBufferSize);
      ReceiveMidiData(i, buffer, read_size, now);
    }
    fds_index += in_devices_[i]->GetPollDescriptorsCount();
  }

  // Do again.
  event_thread_.message_loop()->PostTask(
      FROM_HERE,
      base::Bind(&MidiManagerAlsa::EventLoop, base::Unretained(this)));
}

MidiManager* MidiManager::Create() {
  return new MidiManagerAlsa();
}

}  // namespace media