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