• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
AddrToInt(const snd_seq_addr_t * addr)40 int AddrToInt(const snd_seq_addr_t* addr) {
41   return (addr->client << 8) | addr->port;
42 }
43 
44 class CardInfo {
45  public:
CardInfo(const std::string name,const std::string manufacturer,const std::string driver)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 
MidiManagerAlsa()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 
StartInitialization()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   }
188 
189   // Enumerate all ports in all clients.
190   snd_seq_client_info_t* client_info;
191   snd_seq_client_info_alloca(&client_info);
192   snd_seq_port_info_t* port_info;
193   snd_seq_port_info_alloca(&port_info);
194 
195   snd_seq_client_info_set_client(client_info, -1);
196   // Enumerate clients.
197   uint32 current_input = 0;
198   unsigned int current_card = 0;
199   while (!snd_seq_query_next_client(in_client_, client_info)) {
200     int client_id = snd_seq_client_info_get_client(client_info);
201     if ((client_id == in_client_id) || (client_id == out_client_id_)) {
202       // Skip our own clients.
203       continue;
204     }
205     const std::string client_name = snd_seq_client_info_get_name(client_info);
206     snd_seq_port_info_set_client(port_info, client_id);
207     snd_seq_port_info_set_port(port_info, -1);
208 
209     std::string manufacturer;
210     std::string driver;
211     // In the current Alsa kernel implementation, hardware clients match the
212     // cards in the same order.
213     if ((snd_seq_client_info_get_type(client_info) == SND_SEQ_KERNEL_CLIENT) &&
214         (current_card < cards.size())) {
215       const CardInfo* info = cards[current_card];
216       if (info->name_ == client_name) {
217         manufacturer = info->manufacturer_;
218         driver = info->driver_;
219         current_card++;
220       }
221     }
222     // Enumerate ports.
223     while (!snd_seq_query_next_port(in_client_, port_info)) {
224       unsigned int port_type = snd_seq_port_info_get_type(port_info);
225       if (port_type & SND_SEQ_PORT_TYPE_MIDI_GENERIC) {
226         const snd_seq_addr_t* addr = snd_seq_port_info_get_addr(port_info);
227         const std::string name = snd_seq_port_info_get_name(port_info);
228         const std::string id = base::StringPrintf("%d:%d %s",
229                                                   addr->client,
230                                                   addr->port,
231                                                   name.c_str());
232         std::string version;
233         if (driver != "") {
234           version = driver + " / ";
235         }
236         version += base::StringPrintf("ALSA library version %d.%d.%d",
237                                       SND_LIB_MAJOR,
238                                       SND_LIB_MINOR,
239                                       SND_LIB_SUBMINOR);
240         unsigned int caps = snd_seq_port_info_get_capability(port_info);
241         if ((caps & kRequiredInputPortCaps) == kRequiredInputPortCaps) {
242           // Subscribe to this port.
243           const snd_seq_addr_t* sender = snd_seq_port_info_get_addr(port_info);
244           snd_seq_addr_t dest;
245           dest.client = snd_seq_client_id(in_client_);
246           dest.port = in_port_;
247           snd_seq_port_subscribe_set_sender(subs, sender);
248           snd_seq_port_subscribe_set_dest(subs, &dest);
249           err = snd_seq_subscribe_port(in_client_, subs);
250           if (err != 0) {
251             VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
252           } else {
253             source_map_[AddrToInt(sender)] = current_input++;
254             AddInputPort(MidiPortInfo(id, manufacturer, name, version));
255           }
256         }
257         if ((caps & kRequiredOutputPortCaps) == kRequiredOutputPortCaps) {
258           // Create a port for us to send on.
259           int out_port =
260               snd_seq_create_simple_port(out_client_, NULL,
261                                          SND_SEQ_PORT_CAP_READ |
262                                          SND_SEQ_PORT_CAP_NO_EXPORT,
263                                          SND_SEQ_PORT_TYPE_MIDI_GENERIC |
264                                          SND_SEQ_PORT_TYPE_APPLICATION);
265           if (out_port < 0) {
266             VLOG(1) << "snd_seq_create_simple_port fails: "
267                     << snd_strerror(out_port);
268             // Skip this output port for now.
269             continue;
270           }
271 
272           // Activate port subscription.
273           snd_seq_addr_t sender;
274           const snd_seq_addr_t* dest = snd_seq_port_info_get_addr(port_info);
275           sender.client = snd_seq_client_id(out_client_);
276           sender.port = out_port;
277           snd_seq_port_subscribe_set_sender(subs, &sender);
278           snd_seq_port_subscribe_set_dest(subs, dest);
279           err = snd_seq_subscribe_port(out_client_, subs);
280           if (err != 0) {
281             VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
282             snd_seq_delete_simple_port(out_client_, out_port);
283           } else {
284             snd_midi_event_t* encoder;
285             snd_midi_event_new(kSendBufferSize, &encoder);
286             encoders_.push_back(encoder);
287             out_ports_.push_back(out_port);
288             AddOutputPort(MidiPortInfo(id, manufacturer, name, version));
289           }
290         }
291       }
292     }
293   }
294 
295   event_thread_.Start();
296   event_thread_.message_loop()->PostTask(
297       FROM_HERE,
298       base::Bind(&MidiManagerAlsa::EventReset, base::Unretained(this)));
299 
300   CompleteInitialization(MIDI_OK);
301 }
302 
~MidiManagerAlsa()303 MidiManagerAlsa::~MidiManagerAlsa() {
304   // Tell the event thread it will soon be time to shut down. This gives
305   // us assurance the thread will stop in case the SND_SEQ_EVENT_CLIENT_EXIT
306   // message is lost.
307   {
308     base::AutoLock lock(shutdown_lock_);
309     event_thread_shutdown_ = true;
310   }
311 
312   // Stop the send thread.
313   send_thread_.Stop();
314 
315   // Close the out client. This will trigger the event thread to stop,
316   // because of SND_SEQ_EVENT_CLIENT_EXIT.
317   if (out_client_)
318     snd_seq_close(out_client_);
319 
320   // Wait for the event thread to stop.
321   event_thread_.Stop();
322 
323   // Close the in client.
324   if (in_client_)
325     snd_seq_close(in_client_);
326 
327   // Free the decoder.
328   snd_midi_event_free(decoder_);
329 
330   // Free the encoders.
331   for (EncoderList::iterator i = encoders_.begin(); i != encoders_.end(); ++i)
332     snd_midi_event_free(*i);
333 }
334 
SendMidiData(uint32 port_index,const std::vector<uint8> & data)335 void MidiManagerAlsa::SendMidiData(uint32 port_index,
336                                    const std::vector<uint8>& data) {
337   DCHECK(send_thread_.message_loop_proxy()->BelongsToCurrentThread());
338 
339   snd_midi_event_t* encoder = encoders_[port_index];
340   for (unsigned int i = 0; i < data.size(); i++) {
341     snd_seq_event_t event;
342     int result = snd_midi_event_encode_byte(encoder, data[i], &event);
343     if (result == 1) {
344       // Full event, send it.
345       snd_seq_ev_set_source(&event, out_ports_[port_index]);
346       snd_seq_ev_set_subs(&event);
347       snd_seq_ev_set_direct(&event);
348       snd_seq_event_output_direct(out_client_, &event);
349     }
350   }
351 }
352 
DispatchSendMidiData(MidiManagerClient * client,uint32 port_index,const std::vector<uint8> & data,double timestamp)353 void MidiManagerAlsa::DispatchSendMidiData(MidiManagerClient* client,
354                                            uint32 port_index,
355                                            const std::vector<uint8>& data,
356                                            double timestamp) {
357   if (out_ports_.size() <= port_index)
358     return;
359 
360   // Not correct right now. http://crbug.com/374341.
361   if (!send_thread_.IsRunning())
362     send_thread_.Start();
363 
364   base::TimeDelta delay;
365   if (timestamp != 0.0) {
366     base::TimeTicks time_to_send =
367         base::TimeTicks() + base::TimeDelta::FromMicroseconds(
368             timestamp * base::Time::kMicrosecondsPerSecond);
369     delay = std::max(time_to_send - base::TimeTicks::Now(), base::TimeDelta());
370   }
371 
372   send_thread_.message_loop()->PostDelayedTask(
373       FROM_HERE,
374       base::Bind(&MidiManagerAlsa::SendMidiData, base::Unretained(this),
375                  port_index, data), delay);
376 
377   // Acknowledge send.
378   send_thread_.message_loop()->PostTask(
379       FROM_HERE,
380       base::Bind(&MidiManagerClient::AccumulateMidiBytesSent,
381                  base::Unretained(client), data.size()));
382 }
383 
EventReset()384 void MidiManagerAlsa::EventReset() {
385   event_thread_.message_loop()->PostTask(
386       FROM_HERE,
387       base::Bind(&MidiManagerAlsa::EventLoop, base::Unretained(this)));
388 }
389 
EventLoop()390 void MidiManagerAlsa::EventLoop() {
391   // Read available incoming MIDI data.
392   snd_seq_event_t* event;
393   int err = snd_seq_event_input(in_client_, &event);
394   double timestamp =
395       (base::TimeTicks::HighResNow() - base::TimeTicks()).InSecondsF();
396   if (err == -ENOSPC) {
397     VLOG(1) << "snd_seq_event_input detected buffer overrun";
398 
399       // We've lost events: check another way to see if we need to shut down.
400       base::AutoLock lock(shutdown_lock_);
401       if (event_thread_shutdown_) {
402         return;
403       }
404   } else if (err < 0) {
405       VLOG(1) << "snd_seq_event_input fails: " << snd_strerror(err);
406       return;
407   } else {
408     // Check for disconnection of out client. This means "shut down".
409     if (event->source.client == SND_SEQ_CLIENT_SYSTEM &&
410         event->source.port == SND_SEQ_PORT_SYSTEM_ANNOUNCE &&
411         event->type == SND_SEQ_EVENT_CLIENT_EXIT &&
412         event->data.addr.client == out_client_id_) {
413       return;
414     }
415 
416     std::map<int, uint32>::iterator source_it =
417         source_map_.find(AddrToInt(&event->source));
418     if (source_it != source_map_.end()) {
419       uint32 source = source_it->second;
420       if (event->type == SND_SEQ_EVENT_SYSEX) {
421         // Special! Variable-length sysex.
422         ReceiveMidiData(source, static_cast<const uint8*>(event->data.ext.ptr),
423                         event->data.ext.len,
424                         timestamp);
425       } else {
426         // Otherwise, decode this and send that on.
427         unsigned char buf[12];
428         long count = snd_midi_event_decode(decoder_, buf, sizeof(buf), event);
429         if (count <= 0) {
430           if (count != -ENOENT) {
431             // ENOENT means that it's not a MIDI message, which is not an
432             // error, but other negative values are errors for us.
433             VLOG(1) << "snd_midi_event_decoder fails " << snd_strerror(count);
434           }
435         } else {
436           ReceiveMidiData(source, buf, count, timestamp);
437         }
438       }
439     }
440   }
441 
442   // Do again.
443   event_thread_.message_loop()->PostTask(
444       FROM_HERE,
445       base::Bind(&MidiManagerAlsa::EventLoop, base::Unretained(this)));
446 }
447 
Create()448 MidiManager* MidiManager::Create() {
449   return new MidiManagerAlsa();
450 }
451 
452 }  // namespace media
453