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