1// 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
5#include "media/midi/midi_manager_alsa.h"
6
7#include <alsa/asoundlib.h>
8#include <stdlib.h>
9#include <algorithm>
10#include <string>
11
12#include "base/bind.h"
13#include "base/logging.h"
14#include "base/memory/ref_counted.h"
15#include "base/memory/scoped_vector.h"
16#include "base/message_loop/message_loop.h"
17#include "base/posix/eintr_wrapper.h"
18#include "base/strings/stringprintf.h"
19#include "base/threading/thread.h"
20#include "base/time/time.h"
21#include "media/midi/midi_port_info.h"
22
23namespace media {
24
25namespace {
26
27// Per-output buffer. This can be smaller, but then large sysex messages
28// will be (harmlessly) split across multiple seq events. This should
29// not have any real practical effect, except perhaps to slightly reorder
30// realtime messages with respect to sysex.
31const size_t kSendBufferSize = 256;
32
33// Constants for the capabilities we search for in inputs and outputs.
34// See http://www.alsa-project.org/alsa-doc/alsa-lib/seq.html.
35const unsigned int kRequiredInputPortCaps =
36    SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ;
37const unsigned int kRequiredOutputPortCaps =
38    SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE;
39
40int AddrToInt(const snd_seq_addr_t* addr) {
41  return (addr->client << 8) | addr->port;
42}
43
44class CardInfo {
45 public:
46  CardInfo(const std::string name, const std::string manufacturer,
47           const std::string driver)
48      : name_(name), manufacturer_(manufacturer), driver_(driver) {
49  }
50  const std::string name_;
51  const std::string manufacturer_;
52  const std::string driver_;
53};
54
55}  // namespace
56
57MidiManagerAlsa::MidiManagerAlsa()
58    : in_client_(NULL),
59      out_client_(NULL),
60      out_client_id_(-1),
61      in_port_(-1),
62      decoder_(NULL),
63      send_thread_("MidiSendThread"),
64      event_thread_("MidiEventThread"),
65      event_thread_shutdown_(false) {
66  // Initialize decoder.
67  snd_midi_event_new(0, &decoder_);
68  snd_midi_event_no_status(decoder_, 1);
69}
70
71void MidiManagerAlsa::StartInitialization() {
72  // TODO(agoode): Move off I/O thread. See http://crbug.com/374341.
73
74  // Create client handles.
75  int err = snd_seq_open(&in_client_, "hw", SND_SEQ_OPEN_INPUT, 0);
76  if (err != 0) {
77    VLOG(1) << "snd_seq_open fails: " << snd_strerror(err);
78    return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
79  }
80  int in_client_id = snd_seq_client_id(in_client_);
81  err = snd_seq_open(&out_client_, "hw", SND_SEQ_OPEN_OUTPUT, 0);
82  if (err != 0) {
83    VLOG(1) << "snd_seq_open fails: " << snd_strerror(err);
84    return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
85  }
86  out_client_id_ = snd_seq_client_id(out_client_);
87
88  // Name the clients.
89  err = snd_seq_set_client_name(in_client_, "Chrome (input)");
90  if (err != 0) {
91    VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err);
92    return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
93  }
94  err = snd_seq_set_client_name(out_client_, "Chrome (output)");
95  if (err != 0) {
96    VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err);
97    return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
98  }
99
100  // Create input port.
101  in_port_ = snd_seq_create_simple_port(in_client_, NULL,
102                                        SND_SEQ_PORT_CAP_WRITE |
103                                        SND_SEQ_PORT_CAP_NO_EXPORT,
104                                        SND_SEQ_PORT_TYPE_MIDI_GENERIC |
105                                        SND_SEQ_PORT_TYPE_APPLICATION);
106  if (in_port_ < 0) {
107    VLOG(1) << "snd_seq_create_simple_port fails: " << snd_strerror(in_port_);
108    return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
109  }
110
111  // Subscribe to the announce port.
112  snd_seq_port_subscribe_t* subs;
113  snd_seq_port_subscribe_alloca(&subs);
114  snd_seq_addr_t announce_sender;
115  snd_seq_addr_t announce_dest;
116  announce_sender.client = SND_SEQ_CLIENT_SYSTEM;
117  announce_sender.port = SND_SEQ_PORT_SYSTEM_ANNOUNCE;
118  announce_dest.client = in_client_id;
119  announce_dest.port = in_port_;
120  snd_seq_port_subscribe_set_sender(subs, &announce_sender);
121  snd_seq_port_subscribe_set_dest(subs, &announce_dest);
122  err = snd_seq_subscribe_port(in_client_, subs);
123  if (err != 0) {
124    VLOG(1) << "snd_seq_subscribe_port on the announce port fails: "
125            << snd_strerror(err);
126    return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
127  }
128
129  // Use a heuristic to extract the list of manufacturers for the hardware MIDI
130  // devices. This won't work for all devices. It is also brittle until
131  // hotplug is implemented. (See http://crbug.com/279097.)
132  // TODO(agoode): Make manufacturer extraction simple and reliable.
133  // http://crbug.com/377250.
134  ScopedVector<CardInfo> cards;
135  snd_ctl_card_info_t* card;
136  snd_rawmidi_info_t* midi_out;
137  snd_rawmidi_info_t* midi_in;
138  snd_ctl_card_info_alloca(&card);
139  snd_rawmidi_info_alloca(&midi_out);
140  snd_rawmidi_info_alloca(&midi_in);
141  for (int index = -1; !snd_card_next(&index) && index >= 0; ) {
142    const std::string id = base::StringPrintf("hw:CARD=%i", index);
143    snd_ctl_t* handle;
144    int err = snd_ctl_open(&handle, id.c_str(), 0);
145    if (err != 0) {
146      VLOG(1) << "snd_ctl_open fails: " << snd_strerror(err);
147      continue;
148    }
149    err = snd_ctl_card_info(handle, card);
150    if (err != 0) {
151      VLOG(1) << "snd_ctl_card_info fails: " << snd_strerror(err);
152      snd_ctl_close(handle);
153      continue;
154    }
155    // Enumerate any rawmidi devices (not subdevices) and extract CardInfo.
156    for (int device = -1;
157         !snd_ctl_rawmidi_next_device(handle, &device) && device >= 0; ) {
158      bool output;
159      bool input;
160      snd_rawmidi_info_set_device(midi_out, device);
161      snd_rawmidi_info_set_subdevice(midi_out, 0);
162      snd_rawmidi_info_set_stream(midi_out, SND_RAWMIDI_STREAM_OUTPUT);
163      output = snd_ctl_rawmidi_info(handle, midi_out) == 0;
164      snd_rawmidi_info_set_device(midi_in, device);
165      snd_rawmidi_info_set_subdevice(midi_in, 0);
166      snd_rawmidi_info_set_stream(midi_in, SND_RAWMIDI_STREAM_INPUT);
167      input = snd_ctl_rawmidi_info(handle, midi_in) == 0;
168      if (!output && !input)
169        continue;
170
171      snd_rawmidi_info_t* midi = midi_out ? midi_out : midi_in;
172      const std::string name = snd_rawmidi_info_get_name(midi);
173      // We assume that card longname is in the format of
174      // "<manufacturer> <name> at <bus>". Otherwise, we give up to detect
175      // a manufacturer name here.
176      std::string manufacturer;
177      const std::string card_name = snd_ctl_card_info_get_longname(card);
178      size_t at_index = card_name.rfind(" at ");
179      if (std::string::npos != at_index) {
180        size_t name_index = card_name.rfind(name, at_index - 1);
181        if (std::string::npos != name_index)
182          manufacturer = card_name.substr(0, name_index - 1);
183      }
184      const std::string driver = snd_ctl_card_info_get_driver(card);
185      cards.push_back(new CardInfo(name, manufacturer, driver));
186    }
187    snd_ctl_close(handle);
188  }
189
190  // Enumerate all ports in all clients.
191  snd_seq_client_info_t* client_info;
192  snd_seq_client_info_alloca(&client_info);
193  snd_seq_port_info_t* port_info;
194  snd_seq_port_info_alloca(&port_info);
195
196  snd_seq_client_info_set_client(client_info, -1);
197  // Enumerate clients.
198  uint32 current_input = 0;
199  unsigned int current_card = 0;
200  while (!snd_seq_query_next_client(in_client_, client_info)) {
201    int client_id = snd_seq_client_info_get_client(client_info);
202    if ((client_id == in_client_id) || (client_id == out_client_id_)) {
203      // Skip our own clients.
204      continue;
205    }
206    const std::string client_name = snd_seq_client_info_get_name(client_info);
207    snd_seq_port_info_set_client(port_info, client_id);
208    snd_seq_port_info_set_port(port_info, -1);
209
210    std::string manufacturer;
211    std::string driver;
212    // In the current Alsa kernel implementation, hardware clients match the
213    // cards in the same order.
214    if ((snd_seq_client_info_get_type(client_info) == SND_SEQ_KERNEL_CLIENT) &&
215        (current_card < cards.size())) {
216      const CardInfo* info = cards[current_card];
217      if (info->name_ == client_name) {
218        manufacturer = info->manufacturer_;
219        driver = info->driver_;
220        current_card++;
221      }
222    }
223    // Enumerate ports.
224    while (!snd_seq_query_next_port(in_client_, port_info)) {
225      unsigned int port_type = snd_seq_port_info_get_type(port_info);
226      if (port_type & SND_SEQ_PORT_TYPE_MIDI_GENERIC) {
227        const snd_seq_addr_t* addr = snd_seq_port_info_get_addr(port_info);
228        const std::string name = snd_seq_port_info_get_name(port_info);
229        const std::string id = base::StringPrintf("%d:%d %s",
230                                                  addr->client,
231                                                  addr->port,
232                                                  name.c_str());
233        std::string version;
234        if (driver != "") {
235          version = driver + " / ";
236        }
237        version += base::StringPrintf("ALSA library version %d.%d.%d",
238                                      SND_LIB_MAJOR,
239                                      SND_LIB_MINOR,
240                                      SND_LIB_SUBMINOR);
241        unsigned int caps = snd_seq_port_info_get_capability(port_info);
242        if ((caps & kRequiredInputPortCaps) == kRequiredInputPortCaps) {
243          // Subscribe to this port.
244          const snd_seq_addr_t* sender = snd_seq_port_info_get_addr(port_info);
245          snd_seq_addr_t dest;
246          dest.client = snd_seq_client_id(in_client_);
247          dest.port = in_port_;
248          snd_seq_port_subscribe_set_sender(subs, sender);
249          snd_seq_port_subscribe_set_dest(subs, &dest);
250          err = snd_seq_subscribe_port(in_client_, subs);
251          if (err != 0) {
252            VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
253          } else {
254            source_map_[AddrToInt(sender)] = current_input++;
255            AddInputPort(MidiPortInfo(id, manufacturer, name, version));
256          }
257        }
258        if ((caps & kRequiredOutputPortCaps) == kRequiredOutputPortCaps) {
259          // Create a port for us to send on.
260          int out_port =
261              snd_seq_create_simple_port(out_client_, NULL,
262                                         SND_SEQ_PORT_CAP_READ |
263                                         SND_SEQ_PORT_CAP_NO_EXPORT,
264                                         SND_SEQ_PORT_TYPE_MIDI_GENERIC |
265                                         SND_SEQ_PORT_TYPE_APPLICATION);
266          if (out_port < 0) {
267            VLOG(1) << "snd_seq_create_simple_port fails: "
268                    << snd_strerror(out_port);
269            // Skip this output port for now.
270            continue;
271          }
272
273          // Activate port subscription.
274          snd_seq_addr_t sender;
275          const snd_seq_addr_t* dest = snd_seq_port_info_get_addr(port_info);
276          sender.client = snd_seq_client_id(out_client_);
277          sender.port = out_port;
278          snd_seq_port_subscribe_set_sender(subs, &sender);
279          snd_seq_port_subscribe_set_dest(subs, dest);
280          err = snd_seq_subscribe_port(out_client_, subs);
281          if (err != 0) {
282            VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
283            snd_seq_delete_simple_port(out_client_, out_port);
284          } else {
285            snd_midi_event_t* encoder;
286            snd_midi_event_new(kSendBufferSize, &encoder);
287            encoders_.push_back(encoder);
288            out_ports_.push_back(out_port);
289            AddOutputPort(MidiPortInfo(id, manufacturer, name, version));
290          }
291        }
292      }
293    }
294  }
295
296  event_thread_.Start();
297  event_thread_.message_loop()->PostTask(
298      FROM_HERE,
299      base::Bind(&MidiManagerAlsa::EventReset, base::Unretained(this)));
300
301  CompleteInitialization(MIDI_OK);
302}
303
304MidiManagerAlsa::~MidiManagerAlsa() {
305  // Tell the event thread it will soon be time to shut down. This gives
306  // us assurance the thread will stop in case the SND_SEQ_EVENT_CLIENT_EXIT
307  // message is lost.
308  {
309    base::AutoLock lock(shutdown_lock_);
310    event_thread_shutdown_ = true;
311  }
312
313  // Stop the send thread.
314  send_thread_.Stop();
315
316  // Close the out client. This will trigger the event thread to stop,
317  // because of SND_SEQ_EVENT_CLIENT_EXIT.
318  if (out_client_)
319    snd_seq_close(out_client_);
320
321  // Wait for the event thread to stop.
322  event_thread_.Stop();
323
324  // Close the in client.
325  if (in_client_)
326    snd_seq_close(in_client_);
327
328  // Free the decoder.
329  snd_midi_event_free(decoder_);
330
331  // Free the encoders.
332  for (EncoderList::iterator i = encoders_.begin(); i != encoders_.end(); ++i)
333    snd_midi_event_free(*i);
334}
335
336void MidiManagerAlsa::SendMidiData(uint32 port_index,
337                                   const std::vector<uint8>& data) {
338  DCHECK(send_thread_.message_loop_proxy()->BelongsToCurrentThread());
339
340  snd_midi_event_t* encoder = encoders_[port_index];
341  for (unsigned int i = 0; i < data.size(); i++) {
342    snd_seq_event_t event;
343    int result = snd_midi_event_encode_byte(encoder, data[i], &event);
344    if (result == 1) {
345      // Full event, send it.
346      snd_seq_ev_set_source(&event, out_ports_[port_index]);
347      snd_seq_ev_set_subs(&event);
348      snd_seq_ev_set_direct(&event);
349      snd_seq_event_output_direct(out_client_, &event);
350    }
351  }
352}
353
354void MidiManagerAlsa::DispatchSendMidiData(MidiManagerClient* client,
355                                           uint32 port_index,
356                                           const std::vector<uint8>& data,
357                                           double timestamp) {
358  if (out_ports_.size() <= port_index)
359    return;
360
361  // Not correct right now. http://crbug.com/374341.
362  if (!send_thread_.IsRunning())
363    send_thread_.Start();
364
365  base::TimeDelta delay;
366  if (timestamp != 0.0) {
367    base::TimeTicks time_to_send =
368        base::TimeTicks() + base::TimeDelta::FromMicroseconds(
369            timestamp * base::Time::kMicrosecondsPerSecond);
370    delay = std::max(time_to_send - base::TimeTicks::Now(), base::TimeDelta());
371  }
372
373  send_thread_.message_loop()->PostDelayedTask(
374      FROM_HERE,
375      base::Bind(&MidiManagerAlsa::SendMidiData, base::Unretained(this),
376                 port_index, data), delay);
377
378  // Acknowledge send.
379  send_thread_.message_loop()->PostTask(
380      FROM_HERE,
381      base::Bind(&MidiManagerClient::AccumulateMidiBytesSent,
382                 base::Unretained(client), data.size()));
383}
384
385void MidiManagerAlsa::EventReset() {
386  event_thread_.message_loop()->PostTask(
387      FROM_HERE,
388      base::Bind(&MidiManagerAlsa::EventLoop, base::Unretained(this)));
389}
390
391void MidiManagerAlsa::EventLoop() {
392  // Read available incoming MIDI data.
393  snd_seq_event_t* event;
394  int err = snd_seq_event_input(in_client_, &event);
395  double timestamp =
396      (base::TimeTicks::HighResNow() - base::TimeTicks()).InSecondsF();
397  if (err == -ENOSPC) {
398    VLOG(1) << "snd_seq_event_input detected buffer overrun";
399
400      // We've lost events: check another way to see if we need to shut down.
401      base::AutoLock lock(shutdown_lock_);
402      if (event_thread_shutdown_) {
403        return;
404      }
405  } else if (err < 0) {
406      VLOG(1) << "snd_seq_event_input fails: " << snd_strerror(err);
407      return;
408  } else {
409    // Check for disconnection of out client. This means "shut down".
410    if (event->source.client == SND_SEQ_CLIENT_SYSTEM &&
411        event->source.port == SND_SEQ_PORT_SYSTEM_ANNOUNCE &&
412        event->type == SND_SEQ_EVENT_CLIENT_EXIT &&
413        event->data.addr.client == out_client_id_) {
414      return;
415    }
416
417    std::map<int, uint32>::iterator source_it =
418        source_map_.find(AddrToInt(&event->source));
419    if (source_it != source_map_.end()) {
420      uint32 source = source_it->second;
421      if (event->type == SND_SEQ_EVENT_SYSEX) {
422        // Special! Variable-length sysex.
423        ReceiveMidiData(source, static_cast<const uint8*>(event->data.ext.ptr),
424                        event->data.ext.len,
425                        timestamp);
426      } else {
427        // Otherwise, decode this and send that on.
428        unsigned char buf[12];
429        long count = snd_midi_event_decode(decoder_, buf, sizeof(buf), event);
430        if (count <= 0) {
431          if (count != -ENOENT) {
432            // ENOENT means that it's not a MIDI message, which is not an
433            // error, but other negative values are errors for us.
434            VLOG(1) << "snd_midi_event_decoder fails " << snd_strerror(count);
435          }
436        } else {
437          ReceiveMidiData(source, buf, count, timestamp);
438        }
439      }
440    }
441  }
442
443  // Do again.
444  event_thread_.message_loop()->PostTask(
445      FROM_HERE,
446      base::Bind(&MidiManagerAlsa::EventLoop, base::Unretained(this)));
447}
448
449MidiManager* MidiManager::Create() {
450  return new MidiManagerAlsa();
451}
452
453}  // namespace media
454