Home | History | Annotate | Download | only in midi
      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 
     23 namespace media {
     24 
     25 namespace {
     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.
     31 const 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.
     35 const unsigned int kRequiredInputPortCaps =
     36     SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ;
     37 const unsigned int kRequiredOutputPortCaps =
     38     SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE;
     39 
     40 int AddrToInt(const snd_seq_addr_t* addr) {
     41   return (addr->client << 8) | addr->port;
     42 }
     43 
     44 class 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 
     57 MidiManagerAlsa::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 
     71 void 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 
    304 MidiManagerAlsa::~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 
    336 void 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 
    354 void 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 
    385 void MidiManagerAlsa::EventReset() {
    386   event_thread_.message_loop()->PostTask(
    387       FROM_HERE,
    388       base::Bind(&MidiManagerAlsa::EventLoop, base::Unretained(this)));
    389 }
    390 
    391 void 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 
    449 MidiManager* MidiManager::Create() {
    450   return new MidiManagerAlsa();
    451 }
    452 
    453 }  // namespace media
    454