OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2013 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_win.h" | |
6 | |
7 #include <windows.h> | |
8 | |
9 // Prevent unnecessary functions from being included from <mmsystem.h> | |
10 #define MMNODRV | |
11 #define MMNOSOUND | |
12 #define MMNOWAVE | |
13 #define MMNOAUX | |
14 #define MMNOMIXER | |
15 #define MMNOTIMER | |
16 #define MMNOJOY | |
17 #define MMNOMCI | |
18 #define MMNOMMIO | |
19 #include <mmsystem.h> | |
20 | |
21 #include "base/bind.h" | |
22 #include "base/message_loop/message_loop.h" | |
23 #include "base/strings/string_number_conversions.h" | |
24 #include "base/strings/utf_string_conversions.h" | |
25 #include "base/threading/thread.h" | |
26 #include "media/midi/midi_message_queue.h" | |
27 #include "media/midi/midi_message_util.h" | |
28 #include "media/midi/midi_port_info.h" | |
29 | |
30 namespace media { | |
31 namespace { | |
32 | |
33 std::string GetInErrorMessage(MMRESULT result) { | |
34 wchar_t text[MAXERRORLENGTH]; | |
35 MMRESULT get_result = midiInGetErrorText(result, text, arraysize(text)); | |
36 if (get_result != MMSYSERR_NOERROR) { | |
37 DLOG(ERROR) << "Failed to get error message." | |
38 << " original error: " << result | |
39 << " midiInGetErrorText error: " << get_result; | |
40 return std::string(); | |
41 } | |
42 return WideToUTF8(text); | |
43 } | |
44 | |
45 std::string GetOutErrorMessage(MMRESULT result) { | |
46 wchar_t text[MAXERRORLENGTH]; | |
47 MMRESULT get_result = midiOutGetErrorText(result, text, arraysize(text)); | |
48 if (get_result != MMSYSERR_NOERROR) { | |
49 DLOG(ERROR) << "Failed to get error message." | |
50 << " original error: " << result | |
51 << " midiOutGetErrorText error: " << get_result; | |
52 return std::string(); | |
53 } | |
54 return WideToUTF8(text); | |
55 } | |
56 | |
57 class MIDIHDRDeleter { | |
58 public: | |
59 void operator()(MIDIHDR* header) { | |
60 if (!header) | |
61 return; | |
62 delete[] static_cast<char*>(header->lpData); | |
63 header->lpData = NULL; | |
64 header->dwBufferLength = 0; | |
65 delete header; | |
66 } | |
67 }; | |
68 | |
69 typedef scoped_ptr<MIDIHDR, MIDIHDRDeleter> ScopedMIDIHDR; | |
70 | |
71 ScopedMIDIHDR CreateMIDIHDR(size_t size) { | |
72 ScopedMIDIHDR header(new MIDIHDR); | |
73 ZeroMemory(header.get(), sizeof(*header)); | |
74 header->lpData = new char[size]; | |
75 header->dwBufferLength = size; | |
76 return header.Pass(); | |
77 } | |
78 | |
79 void SendShortMIDIMessageInternal(HMIDIOUT midi_out_handle, | |
80 const std::vector<uint8>& message) { | |
81 if (message.size() >= 4) | |
82 return; | |
83 | |
84 DWORD packed_message = 0; | |
85 for (size_t i = 0; i < message.size(); ++i) | |
86 packed_message |= (static_cast<uint32>(message[i]) << (i * 8)); | |
87 MMRESULT result = midiOutShortMsg(midi_out_handle, packed_message); | |
88 DLOG_IF(ERROR, result != MMSYSERR_NOERROR) | |
89 << "Failed to output short message: " << GetOutErrorMessage(result); | |
90 } | |
91 | |
92 void SendLongMIDIMessageInternal(HMIDIOUT midi_out_handle, | |
93 const std::vector<uint8>& message) { | |
94 // Implementation note: | |
95 // Sending long MIDI message can be performed synchronously or asynchronously | |
96 // depending on the driver. There are 2 options to support both cases: | |
97 // 1) Call midiOutLongMsg() API and wait for its completion within this | |
98 // function. In this approach, we can avoid memory copy by directly pointing | |
99 // |message| as the data buffer to be sent. | |
100 // 2) Allocate a buffer and copy |message| to it, then call midiOutLongMsg() | |
101 // API. The buffer will be freed in the MOM_DONE event hander, which tells | |
102 // us that the task of midiOutLongMsg() API is completed. | |
103 // Here we choose option 2) in favor of asynchronous design. | |
104 | |
105 // Note for built-in USB-MIDI driver: | |
106 // From an observation on Windows 7/8.1 with a USB-MIDI keyboard, | |
107 // midiOutLongMsg() will be always blocked. Sending 64 bytes or less data | |
108 // takes roughly 300 usecs. Sending 2048 bytes or more data takes roughly | |
109 // |message.size() / (75 * 1024)| secs in practice. Here we put 60 KB size | |
110 // limit on SysEx message, with hoping that midiOutLongMsg will be blocked at | |
111 // most 1 sec or so with a typical USB-MIDI device. | |
112 const size_t kSysExSizeLimit = 60 * 1024; | |
113 if (message.size() >= kSysExSizeLimit) { | |
114 DVLOG(1) << "Ingnoreing SysEx message due to the size limit" | |
115 << ", size = " << message.size(); | |
116 return; | |
117 } | |
118 | |
119 ScopedMIDIHDR midi_header(CreateMIDIHDR(message.size())); | |
120 for (size_t i = 0; i < message.size(); ++i) | |
121 midi_header->lpData[i] = static_cast<char>(message[i]); | |
122 | |
123 MMRESULT result = midiOutPrepareHeader( | |
124 midi_out_handle, midi_header.get(), sizeof(*midi_header)); | |
125 if (result != MMSYSERR_NOERROR) { | |
126 DLOG(ERROR) << "Failed to prepare output buffer: " | |
127 << GetOutErrorMessage(result); | |
128 return; | |
129 } | |
130 | |
131 result = midiOutLongMsg( | |
132 midi_out_handle, midi_header.get(), sizeof(*midi_header)); | |
133 if (result != MMSYSERR_NOERROR) { | |
134 DLOG(ERROR) << "Failed to output long message: " | |
135 << GetOutErrorMessage(result); | |
136 result = midiOutUnprepareHeader( | |
137 midi_out_handle, midi_header.get(), sizeof(*midi_header)); | |
138 DLOG_IF(ERROR, result != MMSYSERR_NOERROR) | |
139 << "Failed to uninitialize output buffer: " | |
140 << GetOutErrorMessage(result); | |
141 return; | |
142 } | |
143 | |
144 // The ownership of |midi_header| is moved to MOM_DONE event handler. | |
145 midi_header.release(); | |
146 } | |
147 | |
148 } // namespace | |
149 | |
150 class MIDIManagerWin::InDeviceInfo { | |
151 public: | |
152 ~InDeviceInfo() { | |
153 Uninitialize(); | |
154 } | |
155 void set_port_index(int index) { | |
156 port_index_ = index; | |
157 } | |
158 int port_index() const { | |
159 return port_index_; | |
160 } | |
161 bool device_to_be_closed() const { | |
162 return device_to_be_closed_; | |
163 } | |
164 HMIDIIN midi_handle() const { | |
165 return midi_handle_; | |
166 } | |
167 const base::TimeDelta& start_time_offset() const { | |
168 return start_time_offset_; | |
169 } | |
170 | |
171 static scoped_ptr<InDeviceInfo> Create(MIDIManagerWin* manager, | |
172 UINT device_id) { | |
173 scoped_ptr<InDeviceInfo> obj(new InDeviceInfo(manager)); | |
174 if (!obj->Initialize(device_id)) | |
175 obj.reset(); | |
176 return obj.Pass(); | |
177 } | |
178 | |
179 private: | |
180 static const int kInvalidPortIndex = -1; | |
181 static const size_t kBufferLength = 32 * 1024; | |
182 | |
183 explicit InDeviceInfo(MIDIManagerWin* manager) | |
184 : manager_(manager), | |
185 port_index_(kInvalidPortIndex), | |
186 midi_handle_(NULL), | |
187 started_(false), | |
188 device_to_be_closed_(false) { | |
189 } | |
190 | |
191 bool Initialize(DWORD device_id) { | |
192 Uninitialize(); | |
193 midi_header_ = CreateMIDIHDR(kBufferLength); | |
194 | |
195 // Here we use |CALLBACK_FUNCTION| to subscribe MIM_DATA, MIM_LONGDATA, and | |
196 // MIM_CLOSE events. | |
197 // - MIM_DATA: This is the only way to get a short MIDI message with | |
198 // timestamp information. | |
199 // - MIM_LONGDATA: This is the only way to get a long MIDI message with | |
200 // timestamp information. | |
201 // - MIM_CLOSE: This event is sent when 1) midiInClose() is called, or 2) | |
202 // the MIDI device becomes unavailable for some reasons, e.g., the cable | |
203 // is disconnected. As for the former case, HMIDIOUT will be invalidated | |
204 // soon after the callback is finished. As for the later case, however, | |
205 // HMIDIOUT continues to be valid until midiInClose() is called. | |
206 MMRESULT result = midiInOpen(&midi_handle_, | |
207 device_id, | |
208 reinterpret_cast<DWORD_PTR>(&HandleMessage), | |
209 reinterpret_cast<DWORD_PTR>(this), | |
210 CALLBACK_FUNCTION); | |
211 if (result != MMSYSERR_NOERROR) { | |
212 DLOG(ERROR) << "Failed to open output device. " | |
213 << " id: " << device_id | |
214 << " message: " << GetInErrorMessage(result); | |
215 return false; | |
216 } | |
217 result = midiInPrepareHeader( | |
218 midi_handle_, midi_header_.get(), sizeof(*midi_header_)); | |
219 if (result != MMSYSERR_NOERROR) { | |
220 DLOG(ERROR) << "Failed to initialize input buffer: " | |
221 << GetInErrorMessage(result); | |
222 return false; | |
223 } | |
224 result = midiInAddBuffer( | |
225 midi_handle_, midi_header_.get(), sizeof(*midi_header_)); | |
226 if (result != MMSYSERR_NOERROR) { | |
227 DLOG(ERROR) << "Failed to attach input buffer: " | |
228 << GetInErrorMessage(result); | |
229 return false; | |
230 } | |
231 result = midiInStart(midi_handle_); | |
232 if (result != MMSYSERR_NOERROR) { | |
233 DLOG(ERROR) << "Failed to start input port: " | |
234 << GetInErrorMessage(result); | |
235 return false; | |
236 } | |
237 started_ = true; | |
238 start_time_offset_ = base::TimeTicks::Now() - base::TimeTicks(); | |
239 return true; | |
240 } | |
241 | |
242 void Uninitialize() { | |
243 MMRESULT result = MMSYSERR_NOERROR; | |
244 if (midi_handle_ && started_) { | |
245 result = midiInStop(midi_handle_); | |
246 DLOG_IF(ERROR, result != MMSYSERR_NOERROR) | |
247 << "Failed to stop input port: " << GetInErrorMessage(result); | |
248 started_ = false; | |
249 start_time_offset_ = base::TimeDelta(); | |
250 } | |
251 if (midi_handle_) { | |
252 // midiInReset flushes pending messages. We ignore these messages. | |
253 device_to_be_closed_ = true; | |
254 result = midiInReset(midi_handle_); | |
255 DLOG_IF(ERROR, result != MMSYSERR_NOERROR) | |
256 << "Failed to reset input port: " << GetInErrorMessage(result); | |
257 result = midiInClose(midi_handle_); | |
258 device_to_be_closed_ = false; | |
259 DLOG_IF(ERROR, result != MMSYSERR_NOERROR) | |
260 << "Failed to close input port: " << GetInErrorMessage(result); | |
261 midi_header_.reset(); | |
262 midi_handle_ = NULL; | |
263 port_index_ = kInvalidPortIndex; | |
264 } | |
265 } | |
266 | |
267 static void CALLBACK HandleMessage(HMIDIIN midi_in_handle, | |
268 UINT message, | |
269 DWORD_PTR instance, | |
270 DWORD_PTR param1, | |
271 DWORD_PTR param2) { | |
272 // This method can be called back on any thread depending on Windows | |
273 // multimedia subsystem and underlying MIDI drivers. | |
274 InDeviceInfo* self = reinterpret_cast<InDeviceInfo*>(instance); | |
275 if (!self) | |
276 return; | |
277 if (self->midi_handle() != midi_in_handle) | |
278 return; | |
279 | |
280 switch (message) { | |
281 case MIM_DATA: | |
282 self->OnShortMessageReceived(static_cast<uint8>(param1 & 0xff), | |
283 static_cast<uint8>((param1 >> 8) & 0xff), | |
284 static_cast<uint8>((param1 >> 16) & 0xff), | |
285 self->TickToTimeDelta(param2)); | |
286 return; | |
287 case MIM_LONGDATA: | |
288 self->OnLongMessageReceived(reinterpret_cast<MIDIHDR*>(param1), | |
289 self->TickToTimeDelta(param2)); | |
290 return; | |
291 case MIM_CLOSE: | |
292 // TODO(yukawa): Implement crbug.com/279097. | |
293 return; | |
294 } | |
295 } | |
296 | |
297 void OnShortMessageReceived(uint8 status_byte, | |
298 uint8 first_data_byte, | |
299 uint8 second_data_byte, | |
300 base::TimeDelta timestamp) { | |
301 if (device_to_be_closed()) | |
302 return; | |
303 const size_t len = GetMIDIMessageLength(status_byte); | |
304 if (len == 0 || port_index() == kInvalidPortIndex) | |
305 return; | |
306 const uint8 kData[] = { status_byte, first_data_byte, second_data_byte }; | |
307 DCHECK_LE(len, arraysize(kData)); | |
308 manager_->ReceiveMIDIData(port_index(), kData, len, timestamp.InSecondsF()); | |
309 } | |
310 | |
311 void OnLongMessageReceived(MIDIHDR* header, base::TimeDelta timestamp) { | |
312 if (header != midi_header_.get()) | |
313 return; | |
314 MMRESULT result = MMSYSERR_NOERROR; | |
315 if (device_to_be_closed()) { | |
316 if (midi_header_ && | |
317 (midi_header_->dwFlags & MHDR_PREPARED) == MHDR_PREPARED) { | |
318 result = midiInUnprepareHeader( | |
319 midi_handle_, midi_header_.get(), sizeof(*midi_header_)); | |
320 DLOG_IF(ERROR, result != MMSYSERR_NOERROR) | |
321 << "Failed to uninitialize input buffer: " | |
322 << GetInErrorMessage(result); | |
323 } | |
324 return; | |
325 } | |
326 if (header->dwBytesRecorded > 0 && port_index() != kInvalidPortIndex) { | |
327 manager_->ReceiveMIDIData(port_index_, | |
328 reinterpret_cast<const uint8*>(header->lpData), | |
329 header->dwBytesRecorded, | |
330 timestamp.InSecondsF()); | |
331 } | |
332 result = midiInAddBuffer(midi_handle(), header, sizeof(*header)); | |
333 DLOG_IF(ERROR, result != MMSYSERR_NOERROR) | |
334 << "Failed to attach input port: " << GetInErrorMessage(result); | |
335 } | |
336 | |
337 base::TimeDelta TickToTimeDelta(DWORD tick) const { | |
338 const base::TimeDelta delta = | |
339 base::TimeDelta::FromMicroseconds(static_cast<uint32>(tick)); | |
340 return start_time_offset_ + delta; | |
341 } | |
342 | |
343 MIDIManagerWin* manager_; | |
344 int port_index_; | |
345 HMIDIIN midi_handle_; | |
346 ScopedMIDIHDR midi_header_; | |
347 base::TimeDelta start_time_offset_; | |
348 bool started_; | |
349 bool device_to_be_closed_; | |
350 DISALLOW_COPY_AND_ASSIGN(MIDIManagerWin::InDeviceInfo); | |
351 }; | |
352 | |
353 class MIDIManagerWin::OutDeviceInfo { | |
354 public: | |
355 ~OutDeviceInfo() { | |
356 Uninitialize(); | |
357 } | |
358 | |
359 static scoped_ptr<OutDeviceInfo> Create(UINT device_id) { | |
360 scoped_ptr<OutDeviceInfo> obj(new OutDeviceInfo); | |
361 if (!obj->Initialize(device_id)) | |
362 obj.reset(); | |
363 return obj.Pass(); | |
364 } | |
365 | |
366 HMIDIOUT midi_handle() const { | |
367 return midi_handle_; | |
368 } | |
369 | |
370 void Quit() { | |
371 quitting_ = true; | |
372 } | |
373 | |
374 void Send(const std::vector<uint8>& data) { | |
375 // Check if the attached device is still available or not. | |
376 if (!midi_handle_) | |
377 return; | |
378 | |
379 // Give up sending MIDI messages here if the device is already closed. | |
380 // Note that this check is optional. Regardless of that we check |closed_| | |
381 // or not, nothing harmful happens as long as |midi_handle_| is still valid. | |
382 if (closed_) | |
383 return; | |
384 | |
385 // MIDI Running status must be filtered out. | |
386 MIDIMessageQueue message_queue(false); | |
387 message_queue.Add(data); | |
388 std::vector<uint8> message; | |
389 while (!quitting_) { | |
390 message_queue.Get(&message); | |
391 if (message.empty()) | |
392 break; | |
393 // SendShortMIDIMessageInternal can send a MIDI message up to 3 bytes. | |
394 if (message.size() <= 3) | |
395 SendShortMIDIMessageInternal(midi_handle_, message); | |
396 else | |
397 SendLongMIDIMessageInternal(midi_handle_, message); | |
398 } | |
399 } | |
400 | |
401 private: | |
402 OutDeviceInfo() | |
403 : midi_handle_(NULL), | |
404 closed_(false), | |
405 quitting_(false) {} | |
406 | |
407 bool Initialize(DWORD device_id) { | |
408 Uninitialize(); | |
409 // Here we use |CALLBACK_FUNCTION| to subscribe MOM_DONE and MOM_CLOSE | |
410 // events. | |
411 // - MOM_DONE: SendLongMIDIMessageInternal() relies on this event to clean | |
412 // up the backing store where a long MIDI message is stored. | |
413 // - MOM_CLOSE: This event is sent when 1) midiOutClose() is called, or 2) | |
414 // the MIDI device becomes unavailable for some reasons, e.g., the cable | |
415 // is disconnected. As for the former case, HMIDIOUT will be invalidated | |
416 // soon after the callback is finished. As for the later case, however, | |
417 // HMIDIOUT continues to be valid until midiOutClose() is called. | |
418 MMRESULT result = midiOutOpen(&midi_handle_, | |
419 device_id, | |
420 reinterpret_cast<DWORD_PTR>(&HandleMessage), | |
421 reinterpret_cast<DWORD_PTR>(this), | |
422 CALLBACK_FUNCTION); | |
423 if (result != MMSYSERR_NOERROR) { | |
424 DLOG(ERROR) << "Failed to open output device. " | |
425 << " id: " << device_id | |
426 << " message: "<< GetOutErrorMessage(result); | |
427 midi_handle_ = NULL; | |
428 return false; | |
429 } | |
430 return true; | |
431 } | |
432 | |
433 void Uninitialize() { | |
434 if (!midi_handle_) | |
435 return; | |
436 | |
437 MMRESULT result = midiOutReset(midi_handle_); | |
438 DLOG_IF(ERROR, result != MMSYSERR_NOERROR) | |
439 << "Failed to reset output port: " << GetOutErrorMessage(result); | |
440 result = midiOutClose(midi_handle_); | |
441 DLOG_IF(ERROR, result != MMSYSERR_NOERROR) | |
442 << "Failed to close output port: " << GetOutErrorMessage(result); | |
443 midi_handle_ = NULL; | |
444 closed_ = true; | |
445 } | |
446 | |
447 static void CALLBACK HandleMessage(HMIDIOUT midi_out_handle, | |
448 UINT message, | |
449 DWORD_PTR instance, | |
450 DWORD_PTR param1, | |
451 DWORD_PTR param2) { | |
452 // This method can be called back on any thread depending on Windows | |
453 // multimedia subsystem and underlying MIDI drivers. | |
454 | |
455 OutDeviceInfo* self = reinterpret_cast<OutDeviceInfo*>(instance); | |
456 if (!self) | |
457 return; | |
458 if (self->midi_handle() != midi_out_handle) | |
459 return; | |
460 switch (message) { | |
461 case MOM_DONE: { | |
462 // Take ownership of the MIDIHDR object. | |
463 ScopedMIDIHDR header(reinterpret_cast<MIDIHDR*>(param1)); | |
464 if (!header) | |
465 return; | |
466 MMRESULT result = midiOutUnprepareHeader( | |
467 self->midi_handle(), header.get(), sizeof(*header)); | |
468 DLOG_IF(ERROR, result != MMSYSERR_NOERROR) | |
469 << "Failed to uninitialize output buffer: " | |
470 << GetOutErrorMessage(result); | |
471 return; | |
472 } | |
473 case MOM_CLOSE: | |
474 // No lock is required since this flag is just a hint to avoid | |
475 // unnecessary API calls that will result in failure anyway. | |
476 self->closed_ = true; | |
477 // TODO(yukawa): Implement crbug.com/279097. | |
478 return; | |
479 } | |
480 } | |
481 | |
482 HMIDIOUT midi_handle_; | |
483 | |
484 // True if the device is already closed. | |
485 volatile bool closed_; | |
486 | |
487 // True if the MIDIManagerWin is trying to stop the sender thread. | |
488 volatile bool quitting_; | |
489 | |
490 DISALLOW_COPY_AND_ASSIGN(MIDIManagerWin::OutDeviceInfo); | |
491 }; | |
492 | |
493 MIDIManagerWin::MIDIManagerWin() | |
494 : send_thread_("MIDISendThread") { | |
495 } | |
496 | |
497 bool MIDIManagerWin::Initialize() { | |
498 const UINT num_in_devices = midiInGetNumDevs(); | |
499 in_devices_.reserve(num_in_devices); | |
500 for (UINT device_id = 0; device_id < num_in_devices; ++device_id) { | |
501 MIDIINCAPS caps = {}; | |
502 MMRESULT result = midiInGetDevCaps(device_id, &caps, sizeof(caps)); | |
503 if (result != MMSYSERR_NOERROR) { | |
504 DLOG(ERROR) << "Failed to obtain input device info: " | |
505 << GetInErrorMessage(result); | |
506 continue; | |
507 } | |
508 scoped_ptr<InDeviceInfo> in_device(InDeviceInfo::Create(this, device_id)); | |
509 if (!in_device) | |
510 continue; | |
511 MIDIPortInfo info( | |
512 base::IntToString(static_cast<int>(device_id)), | |
513 "", | |
514 base::WideToUTF8(caps.szPname), | |
515 base::IntToString(static_cast<int>(caps.vDriverVersion))); | |
516 AddInputPort(info); | |
517 in_device->set_port_index(input_ports_.size() - 1); | |
518 in_devices_.push_back(in_device.Pass()); | |
519 } | |
520 | |
521 const UINT num_out_devices = midiOutGetNumDevs(); | |
522 out_devices_.reserve(num_out_devices); | |
523 for (UINT device_id = 0; device_id < num_out_devices; ++device_id) { | |
524 MIDIOUTCAPS caps = {}; | |
525 MMRESULT result = midiOutGetDevCaps(device_id, &caps, sizeof(caps)); | |
526 if (result != MMSYSERR_NOERROR) { | |
527 DLOG(ERROR) << "Failed to obtain output device info: " | |
528 << GetOutErrorMessage(result); | |
529 continue; | |
530 } | |
531 scoped_ptr<OutDeviceInfo> out_port(OutDeviceInfo::Create(device_id)); | |
532 if (!out_port) | |
533 continue; | |
534 MIDIPortInfo info( | |
535 base::IntToString(static_cast<int>(device_id)), | |
536 "", | |
537 base::WideToUTF8(caps.szPname), | |
538 base::IntToString(static_cast<int>(caps.vDriverVersion))); | |
539 AddOutputPort(info); | |
540 out_devices_.push_back(out_port.Pass()); | |
541 } | |
542 | |
543 return true; | |
544 } | |
545 | |
546 MIDIManagerWin::~MIDIManagerWin() { | |
547 // Cleanup order is important. |send_thread_| must be stopped before | |
548 // |out_devices_| is cleared. | |
549 for (size_t i = 0; i < output_ports_.size(); ++i) | |
550 out_devices_[i]->Quit(); | |
551 send_thread_.Stop(); | |
552 | |
553 out_devices_.clear(); | |
554 output_ports_.clear(); | |
555 in_devices_.clear(); | |
556 input_ports_.clear(); | |
557 } | |
558 | |
559 void MIDIManagerWin::DispatchSendMIDIData(MIDIManagerClient* client, | |
560 uint32 port_index, | |
561 const std::vector<uint8>& data, | |
562 double timestamp) { | |
563 if (out_devices_.size() <= port_index) | |
564 return; | |
565 | |
566 base::TimeDelta delay; | |
567 if (timestamp != 0.0) { | |
568 base::TimeTicks time_to_send = | |
569 base::TimeTicks() + base::TimeDelta::FromMicroseconds( | |
570 timestamp * base::Time::kMicrosecondsPerSecond); | |
571 delay = std::max(time_to_send - base::TimeTicks::Now(), base::TimeDelta()); | |
572 } | |
573 | |
574 OutDeviceInfo* out_port = out_devices_[port_index].get(); | |
575 base::MessageLoop::current()->PostDelayedTask( | |
576 FROM_HERE, | |
577 base::Bind(&OutDeviceInfo::Send, base::Unretained(out_port), data), | |
578 delay); | |
579 | |
580 client->AccumulateMIDIBytesSent(data.size()); | |
scherkus (not reviewing)
2013/12/04 18:01:05
hmm.. it seems like on the mac we also call this i
yukawa
2013/12/04 18:40:21
Thanks. Left a TODO crbug.com/325810.
| |
581 } | |
582 | |
583 MIDIManager* MIDIManager::Create() { | |
584 return new MIDIManagerWin(); | |
585 } | |
586 | |
587 } // namespace media | |
OLD | NEW |