| OLD | NEW |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "media/midi/midi_manager_alsa.h" | 5 #include "media/midi/midi_manager_alsa.h" |
| 6 | 6 |
| 7 #include <alsa/asoundlib.h> | 7 #include <alsa/asoundlib.h> |
| 8 #include <stdlib.h> | 8 #include <stdlib.h> |
| 9 #include <algorithm> | 9 #include <algorithm> |
| 10 #include <string> | 10 #include <string> |
| 11 | 11 |
| 12 #include "base/bind.h" | 12 #include "base/bind.h" |
| 13 #include "base/logging.h" | 13 #include "base/logging.h" |
| 14 #include "base/memory/ref_counted.h" | 14 #include "base/memory/ref_counted.h" |
| 15 #include "base/memory/scoped_vector.h" |
| 15 #include "base/message_loop/message_loop.h" | 16 #include "base/message_loop/message_loop.h" |
| 16 #include "base/posix/eintr_wrapper.h" | 17 #include "base/posix/eintr_wrapper.h" |
| 17 #include "base/strings/stringprintf.h" | 18 #include "base/strings/stringprintf.h" |
| 18 #include "base/threading/thread.h" | 19 #include "base/threading/thread.h" |
| 19 #include "base/time/time.h" | 20 #include "base/time/time.h" |
| 20 #include "media/midi/midi_port_info.h" | 21 #include "media/midi/midi_port_info.h" |
| 21 | 22 |
| 22 namespace media { | 23 namespace media { |
| 23 | 24 |
| 24 namespace { | 25 namespace { |
| 25 | 26 |
| 26 const size_t kReceiveBufferSize = 4096; | 27 // Per-output buffer. This can be smaller, but then large sysex messages |
| 27 const unsigned short kPollEventMask = POLLIN | POLLERR | POLLNVAL; | 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 }; |
| 28 | 54 |
| 29 } // namespace | 55 } // namespace |
| 30 | 56 |
| 31 class MidiManagerAlsa::MidiDeviceInfo | |
| 32 : public base::RefCounted<MidiDeviceInfo> { | |
| 33 public: | |
| 34 MidiDeviceInfo(MidiManagerAlsa* manager, | |
| 35 const std::string& bus_id, | |
| 36 snd_ctl_card_info_t* card, | |
| 37 const snd_rawmidi_info_t* midi, | |
| 38 int device) { | |
| 39 opened_ = !snd_rawmidi_open(&midi_in_, &midi_out_, bus_id.c_str(), 0); | |
| 40 if (!opened_) | |
| 41 return; | |
| 42 | |
| 43 const std::string id = base::StringPrintf("%s:%d", bus_id.c_str(), device); | |
| 44 const std::string name = snd_rawmidi_info_get_name(midi); | |
| 45 // We assume that card longname is in the format of | |
| 46 // "<manufacturer> <name> at <bus>". Otherwise, we give up to detect | |
| 47 // a manufacturer name here. | |
| 48 std::string manufacturer; | |
| 49 const std::string card_name = snd_ctl_card_info_get_longname(card); | |
| 50 size_t name_index = card_name.find(name); | |
| 51 if (std::string::npos != name_index) | |
| 52 manufacturer = card_name.substr(0, name_index - 1); | |
| 53 const std::string version = | |
| 54 base::StringPrintf("%s / ALSA library version %d.%d.%d", | |
| 55 snd_ctl_card_info_get_driver(card), | |
| 56 SND_LIB_MAJOR, SND_LIB_MINOR, SND_LIB_SUBMINOR); | |
| 57 port_info_ = MidiPortInfo(id, manufacturer, name, version); | |
| 58 } | |
| 59 | |
| 60 void Send(MidiManagerClient* client, const std::vector<uint8>& data) { | |
| 61 ssize_t result = snd_rawmidi_write( | |
| 62 midi_out_, reinterpret_cast<const void*>(&data[0]), data.size()); | |
| 63 if (static_cast<size_t>(result) != data.size()) { | |
| 64 // TODO(toyoshim): Handle device disconnection. | |
| 65 VLOG(1) << "snd_rawmidi_write fails: " << strerror(-result); | |
| 66 } | |
| 67 base::MessageLoop::current()->PostTask( | |
| 68 FROM_HERE, | |
| 69 base::Bind(&MidiManagerClient::AccumulateMidiBytesSent, | |
| 70 base::Unretained(client), data.size())); | |
| 71 } | |
| 72 | |
| 73 // Read input data from a MIDI input device which is ready to read through | |
| 74 // the ALSA library. Called from EventLoop() and read data will be sent to | |
| 75 // blink through MIDIManager base class. | |
| 76 size_t Receive(uint8* data, size_t length) { | |
| 77 return snd_rawmidi_read(midi_in_, reinterpret_cast<void*>(data), length); | |
| 78 } | |
| 79 | |
| 80 const MidiPortInfo& GetMidiPortInfo() const { return port_info_; } | |
| 81 | |
| 82 // Get the number of descriptors which is required to call poll() on the | |
| 83 // device. The ALSA library always returns 1 here now, but it may be changed | |
| 84 // in the future. | |
| 85 int GetPollDescriptorsCount() { | |
| 86 return snd_rawmidi_poll_descriptors_count(midi_in_); | |
| 87 } | |
| 88 | |
| 89 // Following API initializes pollfds for polling the device, and returns the | |
| 90 // number of descriptors they are initialized. It must be the same value with | |
| 91 // snd_rawmidi_poll_descriptors_count(). | |
| 92 int SetupPollDescriptors(struct pollfd* pfds, unsigned int count) { | |
| 93 return snd_rawmidi_poll_descriptors(midi_in_, pfds, count); | |
| 94 } | |
| 95 | |
| 96 unsigned short GetPollDescriptorsRevents(struct pollfd* pfds) { | |
| 97 unsigned short revents; | |
| 98 snd_rawmidi_poll_descriptors_revents(midi_in_, | |
| 99 pfds, | |
| 100 GetPollDescriptorsCount(), | |
| 101 &revents); | |
| 102 return revents; | |
| 103 } | |
| 104 | |
| 105 bool IsOpened() const { return opened_; } | |
| 106 | |
| 107 private: | |
| 108 friend class base::RefCounted<MidiDeviceInfo>; | |
| 109 virtual ~MidiDeviceInfo() { | |
| 110 if (opened_) { | |
| 111 snd_rawmidi_close(midi_in_); | |
| 112 snd_rawmidi_close(midi_out_); | |
| 113 } | |
| 114 } | |
| 115 | |
| 116 bool opened_; | |
| 117 MidiPortInfo port_info_; | |
| 118 snd_rawmidi_t* midi_in_; | |
| 119 snd_rawmidi_t* midi_out_; | |
| 120 | |
| 121 DISALLOW_COPY_AND_ASSIGN(MidiDeviceInfo); | |
| 122 }; | |
| 123 | |
| 124 MidiManagerAlsa::MidiManagerAlsa() | 57 MidiManagerAlsa::MidiManagerAlsa() |
| 125 : send_thread_("MidiSendThread"), | 58 : in_client_(NULL), |
| 126 event_thread_("MidiEventThread") { | 59 out_client_(NULL), |
| 127 for (size_t i = 0; i < arraysize(pipe_fd_); ++i) | 60 out_client_id_(-1), |
| 128 pipe_fd_[i] = -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); |
| 129 } | 69 } |
| 130 | 70 |
| 131 void MidiManagerAlsa::StartInitialization() { | 71 void MidiManagerAlsa::StartInitialization() { |
| 132 // Enumerate only hardware MIDI devices because software MIDIs running in | 72 // TODO(agoode): Move off I/O thread. See http://crbug.com/374341. |
| 133 // the browser process is not secure. | 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; |
| 134 snd_ctl_card_info_t* card; | 135 snd_ctl_card_info_t* card; |
| 135 snd_rawmidi_info_t* midi_out; | 136 snd_rawmidi_info_t* midi_out; |
| 136 snd_rawmidi_info_t* midi_in; | 137 snd_rawmidi_info_t* midi_in; |
| 137 snd_ctl_card_info_alloca(&card); | 138 snd_ctl_card_info_alloca(&card); |
| 138 snd_rawmidi_info_alloca(&midi_out); | 139 snd_rawmidi_info_alloca(&midi_out); |
| 139 snd_rawmidi_info_alloca(&midi_in); | 140 snd_rawmidi_info_alloca(&midi_in); |
| 140 for (int index = -1; !snd_card_next(&index) && index >= 0; ) { | 141 for (int index = -1; !snd_card_next(&index) && index >= 0; ) { |
| 141 const std::string id = base::StringPrintf("hw:CARD=%i", index); | 142 const std::string id = base::StringPrintf("hw:CARD=%i", index); |
| 142 snd_ctl_t* handle; | 143 snd_ctl_t* handle; |
| 143 int err = snd_ctl_open(&handle, id.c_str(), 0); | 144 int err = snd_ctl_open(&handle, id.c_str(), 0); |
| 144 if (err != 0) { | 145 if (err != 0) { |
| 145 VLOG(1) << "snd_ctl_open fails: " << snd_strerror(err); | 146 VLOG(1) << "snd_ctl_open fails: " << snd_strerror(err); |
| 146 continue; | 147 continue; |
| 147 } | 148 } |
| 148 err = snd_ctl_card_info(handle, card); | 149 err = snd_ctl_card_info(handle, card); |
| 149 if (err != 0) { | 150 if (err != 0) { |
| 150 VLOG(1) << "snd_ctl_card_info fails: " << snd_strerror(err); | 151 VLOG(1) << "snd_ctl_card_info fails: " << snd_strerror(err); |
| 151 snd_ctl_close(handle); | 152 snd_ctl_close(handle); |
| 152 continue; | 153 continue; |
| 153 } | 154 } |
| 155 // Enumerate any rawmidi devices (not subdevices) and extract CardInfo. |
| 154 for (int device = -1; | 156 for (int device = -1; |
| 155 !snd_ctl_rawmidi_next_device(handle, &device) && device >= 0; ) { | 157 !snd_ctl_rawmidi_next_device(handle, &device) && device >= 0; ) { |
| 156 bool output; | 158 bool output; |
| 157 bool input; | 159 bool input; |
| 158 snd_rawmidi_info_set_device(midi_out, device); | 160 snd_rawmidi_info_set_device(midi_out, device); |
| 159 snd_rawmidi_info_set_subdevice(midi_out, 0); | 161 snd_rawmidi_info_set_subdevice(midi_out, 0); |
| 160 snd_rawmidi_info_set_stream(midi_out, SND_RAWMIDI_STREAM_OUTPUT); | 162 snd_rawmidi_info_set_stream(midi_out, SND_RAWMIDI_STREAM_OUTPUT); |
| 161 output = snd_ctl_rawmidi_info(handle, midi_out) == 0; | 163 output = snd_ctl_rawmidi_info(handle, midi_out) == 0; |
| 162 snd_rawmidi_info_set_device(midi_in, device); | 164 snd_rawmidi_info_set_device(midi_in, device); |
| 163 snd_rawmidi_info_set_subdevice(midi_in, 0); | 165 snd_rawmidi_info_set_subdevice(midi_in, 0); |
| 164 snd_rawmidi_info_set_stream(midi_in, SND_RAWMIDI_STREAM_INPUT); | 166 snd_rawmidi_info_set_stream(midi_in, SND_RAWMIDI_STREAM_INPUT); |
| 165 input = snd_ctl_rawmidi_info(handle, midi_in) == 0; | 167 input = snd_ctl_rawmidi_info(handle, midi_in) == 0; |
| 166 if (!output && !input) | 168 if (!output && !input) |
| 167 continue; | 169 continue; |
| 168 scoped_refptr<MidiDeviceInfo> port = new MidiDeviceInfo( | 170 |
| 169 this, id, card, output ? midi_out : midi_in, device); | 171 snd_rawmidi_info_t* midi = midi_out ? midi_out : midi_in; |
| 170 if (!port->IsOpened()) { | 172 const std::string name = snd_rawmidi_info_get_name(midi); |
| 171 VLOG(1) << "MidiDeviceInfo open fails"; | 173 // We assume that card longname is in the format of |
| 172 continue; | 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); |
| 173 } | 183 } |
| 174 if (input) { | 184 const std::string driver = snd_ctl_card_info_get_driver(card); |
| 175 in_devices_.push_back(port); | 185 cards.push_back(new CardInfo(name, manufacturer, driver)); |
| 176 AddInputPort(port->GetMidiPortInfo()); | 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++; |
| 177 } | 220 } |
| 178 if (output) { | 221 } |
| 179 out_devices_.push_back(port); | 222 // Enumerate ports. |
| 180 AddOutputPort(port->GetMidiPortInfo()); | 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 } |
| 181 } | 291 } |
| 182 } | 292 } |
| 183 snd_ctl_close(handle); | 293 } |
| 184 } | 294 |
| 185 | 295 event_thread_.Start(); |
| 186 if (pipe(pipe_fd_) < 0) { | 296 event_thread_.message_loop()->PostTask( |
| 187 VPLOG(1) << "pipe() failed"; | 297 FROM_HERE, |
| 188 CompleteInitialization(MIDI_INITIALIZATION_ERROR); | 298 base::Bind(&MidiManagerAlsa::EventReset, base::Unretained(this))); |
| 189 } else { | 299 |
| 190 event_thread_.Start(); | 300 CompleteInitialization(MIDI_OK); |
| 191 event_thread_.message_loop()->PostTask( | |
| 192 FROM_HERE, | |
| 193 base::Bind(&MidiManagerAlsa::EventReset, base::Unretained(this))); | |
| 194 CompleteInitialization(MIDI_OK); | |
| 195 } | |
| 196 } | 301 } |
| 197 | 302 |
| 198 MidiManagerAlsa::~MidiManagerAlsa() { | 303 MidiManagerAlsa::~MidiManagerAlsa() { |
| 199 // Send a shutdown message to awake |event_thread_| from poll(). | 304 // Tell the event thread it will soon be time to shut down. This gives |
| 200 if (pipe_fd_[1] >= 0) | 305 // us assurance the thread will stop in case the SND_SEQ_EVENT_CLIENT_EXIT |
| 201 HANDLE_EINTR(write(pipe_fd_[1], "Q", 1)); | 306 // message is lost. |
| 202 | 307 { |
| 203 // Stop receiving messages. | 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. |
| 204 event_thread_.Stop(); | 321 event_thread_.Stop(); |
| 205 | 322 |
| 206 for (int i = 0; i < 2; ++i) { | 323 // Close the in client. |
| 207 if (pipe_fd_[i] >= 0) | 324 if (in_client_) |
| 208 close(pipe_fd_[i]); | 325 snd_seq_close(in_client_); |
| 209 } | 326 |
| 210 send_thread_.Stop(); | 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 |
| 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 } |
| 211 } | 351 } |
| 212 | 352 |
| 213 void MidiManagerAlsa::DispatchSendMidiData(MidiManagerClient* client, | 353 void MidiManagerAlsa::DispatchSendMidiData(MidiManagerClient* client, |
| 214 uint32 port_index, | 354 uint32 port_index, |
| 215 const std::vector<uint8>& data, | 355 const std::vector<uint8>& data, |
| 216 double timestamp) { | 356 double timestamp) { |
| 217 if (out_devices_.size() <= port_index) | 357 if (out_ports_.size() <= port_index) |
| 218 return; | 358 return; |
| 219 | 359 |
| 360 // Not correct right now. http://crbug.com/374341. |
| 361 if (!send_thread_.IsRunning()) |
| 362 send_thread_.Start(); |
| 363 |
| 220 base::TimeDelta delay; | 364 base::TimeDelta delay; |
| 221 if (timestamp != 0.0) { | 365 if (timestamp != 0.0) { |
| 222 base::TimeTicks time_to_send = | 366 base::TimeTicks time_to_send = |
| 223 base::TimeTicks() + base::TimeDelta::FromMicroseconds( | 367 base::TimeTicks() + base::TimeDelta::FromMicroseconds( |
| 224 timestamp * base::Time::kMicrosecondsPerSecond); | 368 timestamp * base::Time::kMicrosecondsPerSecond); |
| 225 delay = std::max(time_to_send - base::TimeTicks::Now(), base::TimeDelta()); | 369 delay = std::max(time_to_send - base::TimeTicks::Now(), base::TimeDelta()); |
| 226 } | 370 } |
| 227 | 371 |
| 228 if (!send_thread_.IsRunning()) | |
| 229 send_thread_.Start(); | |
| 230 | |
| 231 scoped_refptr<MidiDeviceInfo> device = out_devices_[port_index]; | |
| 232 send_thread_.message_loop()->PostDelayedTask( | 372 send_thread_.message_loop()->PostDelayedTask( |
| 233 FROM_HERE, | 373 FROM_HERE, |
| 234 base::Bind(&MidiDeviceInfo::Send, device, client, data), | 374 base::Bind(&MidiManagerAlsa::SendMidiData, base::Unretained(this), |
| 235 delay); | 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())); |
| 236 } | 382 } |
| 237 | 383 |
| 238 void MidiManagerAlsa::EventReset() { | 384 void MidiManagerAlsa::EventReset() { |
| 239 CHECK_GE(pipe_fd_[0], 0); | |
| 240 | |
| 241 // Sum up descriptors which are needed to poll input devices and a shutdown | |
| 242 // message. | |
| 243 // Keep the first one descriptor for a shutdown message. | |
| 244 size_t poll_fds_size = 1; | |
| 245 for (size_t i = 0; i < in_devices_.size(); ++i) | |
| 246 poll_fds_size += in_devices_[i]->GetPollDescriptorsCount(); | |
| 247 poll_fds_.resize(poll_fds_size); | |
| 248 | |
| 249 // Setup struct pollfd to poll input MIDI devices and a shutdown message. | |
| 250 // The first pollfd is for a shutdown message. | |
| 251 poll_fds_[0].fd = pipe_fd_[0]; | |
| 252 poll_fds_[0].events = kPollEventMask; | |
| 253 int fds_index = 1; | |
| 254 for (size_t i = 0; i < in_devices_.size(); ++i) { | |
| 255 fds_index += in_devices_[i]->SetupPollDescriptors( | |
| 256 &poll_fds_[fds_index], poll_fds_.size() - fds_index); | |
| 257 } | |
| 258 | |
| 259 event_thread_.message_loop()->PostTask( | 385 event_thread_.message_loop()->PostTask( |
| 260 FROM_HERE, | 386 FROM_HERE, |
| 261 base::Bind(&MidiManagerAlsa::EventLoop, base::Unretained(this))); | 387 base::Bind(&MidiManagerAlsa::EventLoop, base::Unretained(this))); |
| 262 } | 388 } |
| 263 | 389 |
| 264 void MidiManagerAlsa::EventLoop() { | 390 void MidiManagerAlsa::EventLoop() { |
| 265 if (HANDLE_EINTR(poll(&poll_fds_[0], poll_fds_.size(), -1)) < 0) { | 391 // Read available incoming MIDI data. |
| 266 VPLOG(1) << "Couldn't poll(). Stop to poll input MIDI devices."; | 392 snd_seq_event_t* event; |
| 267 // TODO(toyoshim): Handle device disconnection, and try to reconnect? | 393 int err = snd_seq_event_input(in_client_, &event); |
| 268 return; | 394 double timestamp = |
| 269 } | 395 (base::TimeTicks::HighResNow() - base::TimeTicks()).InSecondsF(); |
| 396 if (err == -ENOSPC) { |
| 397 VLOG(1) << "snd_seq_event_input detected buffer overrun"; |
| 270 | 398 |
| 271 // Check timestamp as soon as possible because the API requires accurate | 399 // We've lost events: check another way to see if we need to shut down. |
| 272 // timestamp as possible. It will be useful for recording MIDI events. | 400 base::AutoLock lock(shutdown_lock_); |
| 273 base::TimeTicks now = base::TimeTicks::HighResNow(); | 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 } |
| 274 | 415 |
| 275 // Is this thread going to be shutdown? | 416 std::map<int, uint32>::iterator source_it = |
| 276 if (poll_fds_[0].revents & kPollEventMask) | 417 source_map_.find(AddrToInt(&event->source)); |
| 277 return; | 418 if (source_it != source_map_.end()) { |
| 278 | 419 uint32 source = source_it->second; |
| 279 // Read available incoming MIDI data. | 420 if (event->type == SND_SEQ_EVENT_SYSEX) { |
| 280 int fds_index = 1; | 421 // Special! Variable-length sysex. |
| 281 uint8 buffer[kReceiveBufferSize]; | 422 ReceiveMidiData(source, static_cast<const uint8*>(event->data.ext.ptr), |
| 282 | 423 event->data.ext.len, |
| 283 for (size_t i = 0; i < in_devices_.size(); ++i) { | 424 timestamp); |
| 284 unsigned short revents = | 425 } else { |
| 285 in_devices_[i]->GetPollDescriptorsRevents(&poll_fds_[fds_index]); | 426 // Otherwise, decode this and send that on. |
| 286 if (revents & (POLLERR | POLLNVAL)) { | 427 unsigned char buf[12]; |
| 287 // TODO(toyoshim): Handle device disconnection. | 428 long count = snd_midi_event_decode(decoder_, buf, sizeof(buf), event); |
| 288 VLOG(1) << "snd_rawmidi_descriptors_revents fails"; | 429 if (count <= 0) { |
| 289 poll_fds_[fds_index].events = 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 } |
| 290 } | 439 } |
| 291 if (revents & POLLIN) { | |
| 292 size_t read_size = in_devices_[i]->Receive(buffer, kReceiveBufferSize); | |
| 293 ReceiveMidiData(i, buffer, read_size, now); | |
| 294 } | |
| 295 fds_index += in_devices_[i]->GetPollDescriptorsCount(); | |
| 296 } | 440 } |
| 297 | 441 |
| 298 // Do again. | 442 // Do again. |
| 299 event_thread_.message_loop()->PostTask( | 443 event_thread_.message_loop()->PostTask( |
| 300 FROM_HERE, | 444 FROM_HERE, |
| 301 base::Bind(&MidiManagerAlsa::EventLoop, base::Unretained(this))); | 445 base::Bind(&MidiManagerAlsa::EventLoop, base::Unretained(this))); |
| 302 } | 446 } |
| 303 | 447 |
| 304 MidiManager* MidiManager::Create() { | 448 MidiManager* MidiManager::Create() { |
| 305 return new MidiManagerAlsa(); | 449 return new MidiManagerAlsa(); |
| 306 } | 450 } |
| 307 | 451 |
| 308 } // namespace media | 452 } // namespace media |
| OLD | NEW |