Use UsbIds::GetVendorName to retrieve USB vendor name.
[chromium-blink-merge.git] / media / midi / midi_manager_win.cc
blobbc72ce41f9fc463196d9fc2eff84e57f316ec7d8
1 // Copyright 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.
5 #include "media/midi/midi_manager_win.h"
7 #include <windows.h>
8 #include <dbt.h>
9 #include <ks.h>
10 #include <ksmedia.h>
12 // Prevent unnecessary functions from being included from <mmsystem.h>
13 #define MMNODRV
14 #define MMNOSOUND
15 #define MMNOWAVE
16 #define MMNOAUX
17 #define MMNOMIXER
18 #define MMNOTIMER
19 #define MMNOJOY
20 #define MMNOMCI
21 #define MMNOMMIO
22 #include <mmsystem.h>
24 #include <algorithm>
25 #include <functional>
26 #include <queue>
27 #include <string>
29 #include "base/basictypes.h"
30 #include "base/bind.h"
31 #include "base/containers/hash_tables.h"
32 #include "base/memory/scoped_ptr.h"
33 #include "base/message_loop/message_loop.h"
34 #include "base/strings/string16.h"
35 #include "base/strings/string_number_conversions.h"
36 #include "base/strings/string_piece.h"
37 #include "base/strings/stringprintf.h"
38 #include "base/strings/utf_string_conversions.h"
39 #include "base/threading/thread.h"
40 #include "base/threading/thread_checker.h"
41 #include "base/timer/timer.h"
42 #include "base/win/message_window.h"
43 #include "device/usb/usb_ids.h"
44 #include "media/midi/midi_manager.h"
45 #include "media/midi/midi_message_queue.h"
46 #include "media/midi/midi_message_util.h"
47 #include "media/midi/midi_port_info.h"
49 namespace media {
50 namespace {
52 const wchar_t kWindowClassName[] = L"ChromeMidiDeviceMonitorWindow";
53 const int kOnDeviceArrivalDelayMilliSec = 500;
54 static const size_t kBufferLength = 32 * 1024;
56 // We assume that nullpter represents an invalid MIDI handle.
57 const HMIDIIN kInvalidMidiInHandle = nullptr;
58 const HMIDIOUT kInvalidMidiOutHandle = nullptr;
60 // Note that |RegisterDeviceNotificationW| returns nullptr as an invalid handle.
61 const HDEVNOTIFY kInvalidDeviceNotifyHandle = nullptr;
63 std::string GetInErrorMessage(MMRESULT result) {
64 wchar_t text[MAXERRORLENGTH];
65 MMRESULT get_result = midiInGetErrorText(result, text, arraysize(text));
66 if (get_result != MMSYSERR_NOERROR) {
67 DLOG(ERROR) << "Failed to get error message."
68 << " original error: " << result
69 << " midiInGetErrorText error: " << get_result;
70 return std::string();
72 return base::WideToUTF8(text);
75 std::string GetOutErrorMessage(MMRESULT result) {
76 wchar_t text[MAXERRORLENGTH];
77 MMRESULT get_result = midiOutGetErrorText(result, text, arraysize(text));
78 if (get_result != MMSYSERR_NOERROR) {
79 DLOG(ERROR) << "Failed to get error message."
80 << " original error: " << result
81 << " midiOutGetErrorText error: " << get_result;
82 return std::string();
84 return base::WideToUTF8(text);
87 std::string MmversionToString(MMVERSION version) {
88 return base::StringPrintf("%d.%d", HIBYTE(version), LOBYTE(version));
91 class MIDIHDRDeleter {
92 public:
93 void operator()(MIDIHDR* header) {
94 if (!header)
95 return;
96 delete[] static_cast<char*>(header->lpData);
97 header->lpData = NULL;
98 header->dwBufferLength = 0;
99 delete header;
103 typedef scoped_ptr<MIDIHDR, MIDIHDRDeleter> ScopedMIDIHDR;
105 ScopedMIDIHDR CreateMIDIHDR(size_t size) {
106 ScopedMIDIHDR header(new MIDIHDR);
107 ZeroMemory(header.get(), sizeof(*header));
108 header->lpData = new char[size];
109 header->dwBufferLength = size;
110 return header.Pass();
113 void SendShortMidiMessageInternal(HMIDIOUT midi_out_handle,
114 const std::vector<uint8>& message) {
115 DCHECK_LE(message.size(), static_cast<size_t>(3))
116 << "A short MIDI message should be up to 3 bytes.";
118 DWORD packed_message = 0;
119 for (size_t i = 0; i < message.size(); ++i)
120 packed_message |= (static_cast<uint32>(message[i]) << (i * 8));
121 MMRESULT result = midiOutShortMsg(midi_out_handle, packed_message);
122 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
123 << "Failed to output short message: " << GetOutErrorMessage(result);
126 void SendLongMidiMessageInternal(HMIDIOUT midi_out_handle,
127 const std::vector<uint8>& message) {
128 // Implementation note:
129 // Sending a long MIDI message can be performed synchronously or
130 // asynchronously depending on the driver. There are 2 options to support both
131 // cases:
132 // 1) Call midiOutLongMsg() API and wait for its completion within this
133 // function. In this approach, we can avoid memory copy by directly pointing
134 // |message| as the data buffer to be sent.
135 // 2) Allocate a buffer and copy |message| to it, then call midiOutLongMsg()
136 // API. The buffer will be freed in the MOM_DONE event hander, which tells
137 // us that the task of midiOutLongMsg() API is completed.
138 // Here we choose option 2) in favor of asynchronous design.
140 // Note for built-in USB-MIDI driver:
141 // From an observation on Windows 7/8.1 with a USB-MIDI keyboard,
142 // midiOutLongMsg() will be always blocked. Sending 64 bytes or less data
143 // takes roughly 300 usecs. Sending 2048 bytes or more data takes roughly
144 // |message.size() / (75 * 1024)| secs in practice. Here we put 60 KB size
145 // limit on SysEx message, with hoping that midiOutLongMsg will be blocked at
146 // most 1 sec or so with a typical USB-MIDI device.
147 const size_t kSysExSizeLimit = 60 * 1024;
148 if (message.size() >= kSysExSizeLimit) {
149 DVLOG(1) << "Ingnoreing SysEx message due to the size limit"
150 << ", size = " << message.size();
151 return;
154 ScopedMIDIHDR midi_header(CreateMIDIHDR(message.size()));
155 std::copy(message.begin(), message.end(), midi_header->lpData);
157 MMRESULT result = midiOutPrepareHeader(midi_out_handle, midi_header.get(),
158 sizeof(*midi_header));
159 if (result != MMSYSERR_NOERROR) {
160 DLOG(ERROR) << "Failed to prepare output buffer: "
161 << GetOutErrorMessage(result);
162 return;
165 result =
166 midiOutLongMsg(midi_out_handle, midi_header.get(), sizeof(*midi_header));
167 if (result != MMSYSERR_NOERROR) {
168 DLOG(ERROR) << "Failed to output long message: "
169 << GetOutErrorMessage(result);
170 result = midiOutUnprepareHeader(midi_out_handle, midi_header.get(),
171 sizeof(*midi_header));
172 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
173 << "Failed to uninitialize output buffer: "
174 << GetOutErrorMessage(result);
175 return;
178 // The ownership of |midi_header| is moved to MOM_DONE event handler.
179 midi_header.release();
182 template <size_t array_size>
183 base::string16 AsString16(const wchar_t(&buffer)[array_size]) {
184 size_t len = 0;
185 for (len = 0; len < array_size; ++len) {
186 if (buffer[len] == L'\0')
187 break;
189 return base::string16(buffer, len);
192 struct MidiDeviceInfo final {
193 explicit MidiDeviceInfo(const MIDIINCAPS2W& caps)
194 : manufacturer_id(caps.wMid),
195 product_id(caps.wPid),
196 driver_version(caps.vDriverVersion),
197 product_name(AsString16(caps.szPname)),
198 usb_vendor_id(ExtractUsbVendorIdIfExists(caps)),
199 usb_product_id(ExtractUsbProductIdIfExists(caps)),
200 is_usb_device(IsUsbDevice(caps)) {}
201 explicit MidiDeviceInfo(const MIDIOUTCAPS2W& caps)
202 : manufacturer_id(caps.wMid),
203 product_id(caps.wPid),
204 driver_version(caps.vDriverVersion),
205 product_name(AsString16(caps.szPname)),
206 usb_vendor_id(ExtractUsbVendorIdIfExists(caps)),
207 usb_product_id(ExtractUsbProductIdIfExists(caps)),
208 is_usb_device(IsUsbDevice(caps)) {}
209 explicit MidiDeviceInfo(const MidiDeviceInfo& info)
210 : manufacturer_id(info.manufacturer_id),
211 product_id(info.product_id),
212 driver_version(info.driver_version),
213 product_name(info.product_name),
214 usb_vendor_id(info.usb_vendor_id),
215 usb_product_id(info.usb_product_id),
216 is_usb_device(info.is_usb_device) {}
217 // Currently only following entities are considered when testing the equality
218 // of two MIDI devices.
219 // TODO(toyoshim): Consider to calculate MIDIPort.id here and use it as the
220 // key. See crbug.com/467448. Then optimize the data for |MidiPortInfo|.
221 const uint16 manufacturer_id;
222 const uint16 product_id;
223 const uint32 driver_version;
224 const base::string16 product_name;
225 const uint16 usb_vendor_id;
226 const uint16 usb_product_id;
227 const bool is_usb_device;
229 // Required to be used as the key of base::hash_map.
230 bool operator==(const MidiDeviceInfo& that) const {
231 return manufacturer_id == that.manufacturer_id &&
232 product_id == that.product_id &&
233 driver_version == that.driver_version &&
234 product_name == that.product_name &&
235 is_usb_device == that.is_usb_device &&
236 (is_usb_device && usb_vendor_id == that.usb_vendor_id &&
237 usb_product_id == that.usb_product_id);
240 // Hash function to be used in base::hash_map.
241 struct Hasher {
242 size_t operator()(const MidiDeviceInfo& info) const {
243 size_t hash = info.manufacturer_id;
244 hash *= 131;
245 hash += info.product_id;
246 hash *= 131;
247 hash += info.driver_version;
248 hash *= 131;
249 hash += info.product_name.size();
250 hash *= 131;
251 if (!info.product_name.empty()) {
252 hash += info.product_name[0];
254 hash *= 131;
255 hash += info.usb_vendor_id;
256 hash *= 131;
257 hash += info.usb_product_id;
258 return hash;
262 private:
263 static bool IsUsbDevice(const MIDIINCAPS2W& caps) {
264 return IS_COMPATIBLE_USBAUDIO_MID(&caps.ManufacturerGuid) &&
265 IS_COMPATIBLE_USBAUDIO_PID(&caps.ProductGuid);
267 static bool IsUsbDevice(const MIDIOUTCAPS2W& caps) {
268 return IS_COMPATIBLE_USBAUDIO_MID(&caps.ManufacturerGuid) &&
269 IS_COMPATIBLE_USBAUDIO_PID(&caps.ProductGuid);
271 static uint16 ExtractUsbVendorIdIfExists(const MIDIINCAPS2W& caps) {
272 if (!IS_COMPATIBLE_USBAUDIO_MID(&caps.ManufacturerGuid))
273 return 0;
274 return EXTRACT_USBAUDIO_MID(&caps.ManufacturerGuid);
276 static uint16 ExtractUsbVendorIdIfExists(const MIDIOUTCAPS2W& caps) {
277 if (!IS_COMPATIBLE_USBAUDIO_MID(&caps.ManufacturerGuid))
278 return 0;
279 return EXTRACT_USBAUDIO_MID(&caps.ManufacturerGuid);
281 static uint16 ExtractUsbProductIdIfExists(const MIDIINCAPS2W& caps) {
282 if (!IS_COMPATIBLE_USBAUDIO_PID(&caps.ProductGuid))
283 return 0;
284 return EXTRACT_USBAUDIO_PID(&caps.ProductGuid);
286 static uint16 ExtractUsbProductIdIfExists(const MIDIOUTCAPS2W& caps) {
287 if (!IS_COMPATIBLE_USBAUDIO_PID(&caps.ProductGuid))
288 return 0;
289 return EXTRACT_USBAUDIO_PID(&caps.ProductGuid);
293 std::string GetManufacturerName(const MidiDeviceInfo& info) {
294 if (info.is_usb_device)
295 return device::UsbIds::GetVendorName(info.usb_vendor_id);
297 // TODO(toyoshim): Support non USB-MIDI devices. crbug.com/472341.
298 return "";
301 using PortNumberCache = base::hash_map<
302 MidiDeviceInfo,
303 std::priority_queue<uint32, std::vector<uint32>, std::greater<uint32>>,
304 MidiDeviceInfo::Hasher>;
306 class DeviceMonitorWindow final {
307 public:
308 explicit DeviceMonitorWindow(base::Closure on_device_arrival)
309 : on_device_arrival_(on_device_arrival),
310 dev_notify_handle_(kInvalidDeviceNotifyHandle) {
311 window_.reset(new base::win::MessageWindow);
312 if (!window_->CreateNamed(base::Bind(&DeviceMonitorWindow::HandleMessage,
313 base::Unretained(this)),
314 base::string16(kWindowClassName))) {
315 LOG(ERROR) << "Failed to create message window: " << kWindowClassName;
316 window_.reset();
318 DEV_BROADCAST_DEVICEINTERFACE kBloadcastRequest = {
319 sizeof(DEV_BROADCAST_DEVICEINTERFACE), DBT_DEVTYP_DEVICEINTERFACE,
321 dev_notify_handle_ = RegisterDeviceNotificationW(
322 window_->hwnd(), &kBloadcastRequest,
323 DEVICE_NOTIFY_WINDOW_HANDLE | DEVICE_NOTIFY_ALL_INTERFACE_CLASSES);
326 ~DeviceMonitorWindow() {
327 if (dev_notify_handle_ != kInvalidDeviceNotifyHandle) {
328 BOOL result = UnregisterDeviceNotification(dev_notify_handle_);
329 if (!result) {
330 const DWORD last_error = GetLastError();
331 DLOG(ERROR) << "UnregisterDeviceNotification failed. error: "
332 << last_error;
334 dev_notify_handle_ = kInvalidDeviceNotifyHandle;
338 private:
339 bool HandleMessage(UINT message,
340 WPARAM wparam,
341 LPARAM lparam,
342 LRESULT* result) {
343 if (message == WM_DEVICECHANGE) {
344 switch (wparam) {
345 case DBT_DEVICEARRIVAL: {
346 device_arrival_timer_.Stop();
347 device_arrival_timer_.Start(
348 FROM_HERE,
349 base::TimeDelta::FromMilliseconds(kOnDeviceArrivalDelayMilliSec),
350 on_device_arrival_);
351 break;
355 return false;
358 base::Closure on_device_arrival_;
359 base::OneShotTimer<DeviceMonitorWindow> device_arrival_timer_;
360 base::ThreadChecker thread_checker_;
361 scoped_ptr<base::win::MessageWindow> window_;
362 HDEVNOTIFY dev_notify_handle_;
364 DISALLOW_COPY_AND_ASSIGN(DeviceMonitorWindow);
367 struct MidiInputDeviceState final : base::RefCounted<MidiInputDeviceState> {
368 explicit MidiInputDeviceState(const MidiDeviceInfo& device_info)
369 : device_info(device_info),
370 midi_handle(kInvalidMidiInHandle),
371 port_index(0),
372 port_age(0),
373 start_time_initialized(false) {}
375 const MidiDeviceInfo device_info;
376 HMIDIIN midi_handle;
377 ScopedMIDIHDR midi_header;
378 // Since Win32 multimedia system uses a relative time offset from when
379 // |midiInStart| API is called, we need to record when it is called.
380 base::TimeTicks start_time;
381 // 0-based port index. We will try to reuse the previous port index when the
382 // MIDI device is closed then reopened.
383 uint32 port_index;
384 // A sequence number which represents how many times |port_index| is reused.
385 // We can remove this field if we decide not to clear unsent events
386 // when the device is disconnected.
387 // See https://github.com/WebAudio/web-midi-api/issues/133
388 uint64 port_age;
389 // True if |start_time| is initialized. This field is not used so far, but
390 // kept for the debugging purpose.
391 bool start_time_initialized;
394 struct MidiOutputDeviceState final : base::RefCounted<MidiOutputDeviceState> {
395 explicit MidiOutputDeviceState(const MidiDeviceInfo& device_info)
396 : device_info(device_info),
397 midi_handle(kInvalidMidiOutHandle),
398 port_index(0),
399 port_age(0),
400 closed(false) {}
402 const MidiDeviceInfo device_info;
403 HMIDIOUT midi_handle;
404 // 0-based port index. We will try to reuse the previous port index when the
405 // MIDI device is closed then reopened.
406 uint32 port_index;
407 // A sequence number which represents how many times |port_index| is reused.
408 // We can remove this field if we decide not to clear unsent events
409 // when the device is disconnected.
410 // See https://github.com/WebAudio/web-midi-api/issues/133
411 uint64 port_age;
412 // True if the device is already closed and |midi_handle| is considered to be
413 // invalid.
414 // TODO(toyoshim): Use std::atomic<bool> when it is allowed in Chromium
415 // project.
416 volatile bool closed;
419 // The core logic of MIDI device handling for Windows. Basically this class is
420 // shared among following 4 threads:
421 // 1. Device Monitor Thread
422 // 2. OS Multimedia Thread
423 // 3. Misc. Task Thread
424 // 4. Sender Thread
426 // Device Monitor Thread:
427 // This is a standalone UI thread that is dedicated for a message-only window
428 // which will receive WM_DEVICECHANGE Win32 message whose
429 // wParam == DBT_DEVICEARRIVAL. This event will be used as the trigger to open
430 // all the new MIDI devices. Note that in the current implementation we will
431 // try to open all the existing devices in practice. This is OK because trying
432 // to reopen a MIDI device that is already opened would simply fail, and there
433 // is no unwilling side effect. Note also that this thread isn't responsible
434 // for updating the device database. It will be handled by MIM_OPEN/MOM_OPEN
435 // events dispatched to OS Multimedia Thread.
437 // OS Multimedia Thread:
438 // This thread is maintained by the OS as a part of MIDI runtime, and
439 // responsible for receiving all the system initiated events such as device
440 // open and close. For performance reasons, most of potentially blocking
441 // operations will be dispatched into Misc. Task Thread.
443 // Misc. Task Thread:
444 // This thread will be used to call back following methods of MidiManager.
445 // - MidiManager::CompleteInitialization
446 // - MidiManager::AddInputPort
447 // - MidiManager::AddOutputPort
448 // - MidiManager::SetInputPortState
449 // - MidiManager::SetOutputPortState
450 // - MidiManager::ReceiveMidiData
452 // Sender Thread:
453 // This thread will be used to call Win32 APIs to send MIDI message at the
454 // specified time. We don't want to call MIDI send APIs on Misc. Task Thread
455 // because those APIs could be performed synchronously, hence they could block
456 // the caller thread for a while. See the comment in
457 // SendLongMidiMessageInternal for details. Currently we expect that the
458 // blocking time would be less than 1 second.
459 class MidiServiceWinImpl : public MidiServiceWin {
460 public:
461 MidiServiceWinImpl()
462 : delegate_(nullptr),
463 monitor_thread_("Windows MIDI device monitor thread"),
464 sender_thread_("Windows MIDI sender thread"),
465 task_thread_("Windows MIDI task thread"),
466 destructor_started(false) {}
468 ~MidiServiceWinImpl() final {
469 destructor_started = true;
470 if (monitor_thread_.IsRunning()) {
471 monitor_thread_.message_loop()->PostTask(
472 FROM_HERE,
473 base::Bind(&MidiServiceWinImpl::StopDeviceMonitorOnMonitorThreadSync,
474 base::Unretained(this)));
477 std::vector<HMIDIIN> input_devices;
479 base::AutoLock auto_lock(input_ports_lock_);
480 for (auto it : input_device_map_)
481 input_devices.push_back(it.first);
484 for (const auto handle : input_devices) {
485 MMRESULT result = midiInClose(handle);
486 if (result == MIDIERR_STILLPLAYING) {
487 result = midiInReset(handle);
488 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
489 << "midiInReset failed: " << GetInErrorMessage(result);
490 result = midiInClose(handle);
492 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
493 << "midiInClose failed: " << GetInErrorMessage(result);
498 std::vector<HMIDIOUT> output_devices;
500 base::AutoLock auto_lock(output_ports_lock_);
501 for (auto it : output_device_map_)
502 output_devices.push_back(it.first);
505 for (const auto handle : output_devices) {
506 MMRESULT result = midiOutClose(handle);
507 if (result == MIDIERR_STILLPLAYING) {
508 result = midiOutReset(handle);
509 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
510 << "midiOutReset failed: " << GetOutErrorMessage(result);
511 result = midiOutClose(handle);
513 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
514 << "midiOutClose failed: " << GetOutErrorMessage(result);
518 monitor_thread_.Stop();
519 sender_thread_.Stop();
520 task_thread_.Stop();
523 // MidiServiceWin overrides:
524 void InitializeAsync(MidiServiceWinDelegate* delegate) final {
525 delegate_ = delegate;
526 sender_thread_.StartWithOptions(
527 base::Thread::Options(base::MessageLoop::TYPE_IO, 0));
528 task_thread_.StartWithOptions(
529 base::Thread::Options(base::MessageLoop::TYPE_IO, 0));
530 monitor_thread_.StartWithOptions(
531 base::Thread::Options(base::MessageLoop::TYPE_UI, 0));
532 monitor_thread_.message_loop()->PostTask(
533 FROM_HERE, base::Bind(&MidiServiceWinImpl::InitializeOnMonitorThread,
534 base::Unretained(this)));
537 void SendMidiDataAsync(uint32 port_number,
538 const std::vector<uint8>& data,
539 base::TimeTicks time) final {
540 if (destructor_started) {
541 LOG(ERROR) << "ThreadSafeSendData failed because MidiServiceWinImpl is "
542 "being destructed. port: " << port_number;
543 return;
545 auto state = GetOutputDeviceFromPort(port_number);
546 if (!state) {
547 LOG(ERROR) << "ThreadSafeSendData failed due to an invalid port number. "
548 << "port: " << port_number;
549 return;
551 if (state->closed) {
552 LOG(ERROR)
553 << "ThreadSafeSendData failed because target port is already closed."
554 << "port: " << port_number;
555 return;
557 const auto now = base::TimeTicks::Now();
558 if (now < time) {
559 sender_thread_.message_loop()->PostDelayedTask(
560 FROM_HERE, base::Bind(&MidiServiceWinImpl::SendOnSenderThread,
561 base::Unretained(this), port_number,
562 state->port_age, data, time),
563 time - now);
564 } else {
565 sender_thread_.message_loop()->PostTask(
566 FROM_HERE, base::Bind(&MidiServiceWinImpl::SendOnSenderThread,
567 base::Unretained(this), port_number,
568 state->port_age, data, time));
572 private:
573 scoped_refptr<MidiInputDeviceState> GetInputDeviceFromHandle(
574 HMIDIIN midi_handle) {
575 base::AutoLock auto_lock(input_ports_lock_);
576 const auto it = input_device_map_.find(midi_handle);
577 return (it != input_device_map_.end() ? it->second : nullptr);
580 scoped_refptr<MidiOutputDeviceState> GetOutputDeviceFromHandle(
581 HMIDIOUT midi_handle) {
582 base::AutoLock auto_lock(output_ports_lock_);
583 const auto it = output_device_map_.find(midi_handle);
584 return (it != output_device_map_.end() ? it->second : nullptr);
587 scoped_refptr<MidiOutputDeviceState> GetOutputDeviceFromPort(
588 uint32 port_number) {
589 base::AutoLock auto_lock(output_ports_lock_);
590 if (output_ports_.size() <= port_number)
591 return nullptr;
592 return output_ports_[port_number];
595 /////////////////////////////////////////////////////////////////////////////
596 // Callbacks on the OS multimedia thread.
597 /////////////////////////////////////////////////////////////////////////////
599 static void CALLBACK OnMidiInEventOnMultimediaThread(HMIDIIN midi_in_handle,
600 UINT message,
601 DWORD_PTR instance,
602 DWORD_PTR param1,
603 DWORD_PTR param2) {
604 MidiServiceWinImpl* self = reinterpret_cast<MidiServiceWinImpl*>(instance);
605 if (!self)
606 return;
607 switch (message) {
608 case MIM_OPEN:
609 self->OnMidiInOpenOnMultimediaThread(midi_in_handle);
610 break;
611 case MIM_DATA:
612 self->OnMidiInDataOnMultimediaThread(midi_in_handle, param1, param2);
613 break;
614 case MIM_LONGDATA:
615 self->OnMidiInLongDataOnMultimediaThread(midi_in_handle, param1,
616 param2);
617 break;
618 case MIM_CLOSE:
619 self->OnMidiInCloseOnMultimediaThread(midi_in_handle);
620 break;
624 void OnMidiInOpenOnMultimediaThread(HMIDIIN midi_in_handle) {
625 UINT device_id = 0;
626 MMRESULT result = midiInGetID(midi_in_handle, &device_id);
627 if (result != MMSYSERR_NOERROR) {
628 DLOG(ERROR) << "midiInGetID failed: " << GetInErrorMessage(result);
629 return;
631 MIDIINCAPS2W caps = {};
632 result = midiInGetDevCaps(device_id, reinterpret_cast<LPMIDIINCAPSW>(&caps),
633 sizeof(caps));
634 if (result != MMSYSERR_NOERROR) {
635 DLOG(ERROR) << "midiInGetDevCaps failed: " << GetInErrorMessage(result);
636 return;
638 auto state =
639 make_scoped_refptr(new MidiInputDeviceState(MidiDeviceInfo(caps)));
640 state->midi_handle = midi_in_handle;
641 state->midi_header = CreateMIDIHDR(kBufferLength);
642 const auto& state_device_info = state->device_info;
643 bool add_new_port = false;
644 uint32 port_number = 0;
646 base::AutoLock auto_lock(input_ports_lock_);
647 const auto it = unused_input_ports_.find(state_device_info);
648 if (it == unused_input_ports_.end()) {
649 port_number = input_ports_.size();
650 add_new_port = true;
651 input_ports_.push_back(nullptr);
652 input_ports_ages_.push_back(0);
653 } else {
654 port_number = it->second.top();
655 it->second.pop();
656 if (it->second.empty()) {
657 unused_input_ports_.erase(it);
660 input_ports_[port_number] = state;
662 input_ports_ages_[port_number] += 1;
663 input_device_map_[input_ports_[port_number]->midi_handle] =
664 input_ports_[port_number];
665 input_ports_[port_number]->port_index = port_number;
666 input_ports_[port_number]->port_age = input_ports_ages_[port_number];
668 // Several initial startup tasks cannot be done in MIM_OPEN handler.
669 task_thread_.message_loop()->PostTask(
670 FROM_HERE, base::Bind(&MidiServiceWinImpl::StartInputDeviceOnTaskThread,
671 base::Unretained(this), midi_in_handle));
672 if (add_new_port) {
673 const MidiPortInfo port_info(
674 // TODO(toyoshim): Use a hash ID insted crbug.com/467448
675 base::IntToString(static_cast<int>(port_number)),
676 GetManufacturerName(state_device_info),
677 base::WideToUTF8(state_device_info.product_name),
678 MmversionToString(state_device_info.driver_version),
679 MIDI_PORT_OPENED);
680 task_thread_.message_loop()->PostTask(
681 FROM_HERE, base::Bind(&MidiServiceWinImpl::AddInputPortOnTaskThread,
682 base::Unretained(this), port_info));
683 } else {
684 task_thread_.message_loop()->PostTask(
685 FROM_HERE,
686 base::Bind(&MidiServiceWinImpl::SetInputPortStateOnTaskThread,
687 base::Unretained(this), port_number,
688 MidiPortState::MIDI_PORT_CONNECTED));
692 void OnMidiInDataOnMultimediaThread(HMIDIIN midi_in_handle,
693 DWORD_PTR param1,
694 DWORD_PTR param2) {
695 auto state = GetInputDeviceFromHandle(midi_in_handle);
696 if (!state)
697 return;
698 const uint8 status_byte = static_cast<uint8>(param1 & 0xff);
699 const uint8 first_data_byte = static_cast<uint8>((param1 >> 8) & 0xff);
700 const uint8 second_data_byte = static_cast<uint8>((param1 >> 16) & 0xff);
701 const DWORD elapsed_ms = param2;
702 const size_t len = GetMidiMessageLength(status_byte);
703 const uint8 kData[] = {status_byte, first_data_byte, second_data_byte};
704 std::vector<uint8> data;
705 data.assign(kData, kData + len);
706 DCHECK_LE(len, arraysize(kData));
707 // MIM_DATA/MIM_LONGDATA message treats the time when midiInStart() is
708 // called as the origin of |elapsed_ms|.
709 // http://msdn.microsoft.com/en-us/library/windows/desktop/dd757284.aspx
710 // http://msdn.microsoft.com/en-us/library/windows/desktop/dd757286.aspx
711 const base::TimeTicks event_time =
712 state->start_time + base::TimeDelta::FromMilliseconds(elapsed_ms);
713 task_thread_.message_loop()->PostTask(
714 FROM_HERE, base::Bind(&MidiServiceWinImpl::ReceiveMidiDataOnTaskThread,
715 base::Unretained(this), state->port_index, data,
716 event_time));
719 void OnMidiInLongDataOnMultimediaThread(HMIDIIN midi_in_handle,
720 DWORD_PTR param1,
721 DWORD_PTR param2) {
722 auto state = GetInputDeviceFromHandle(midi_in_handle);
723 if (!state)
724 return;
725 MIDIHDR* header = reinterpret_cast<MIDIHDR*>(param1);
726 const DWORD elapsed_ms = param2;
727 MMRESULT result = MMSYSERR_NOERROR;
728 if (destructor_started) {
729 if (state->midi_header &&
730 (state->midi_header->dwFlags & MHDR_PREPARED) == MHDR_PREPARED) {
731 result =
732 midiInUnprepareHeader(state->midi_handle, state->midi_header.get(),
733 sizeof(*state->midi_header));
734 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
735 << "Failed to uninitialize input buffer: "
736 << GetInErrorMessage(result);
738 return;
740 if (header->dwBytesRecorded > 0) {
741 const uint8* src = reinterpret_cast<const uint8*>(header->lpData);
742 std::vector<uint8> data;
743 data.assign(src, src + header->dwBytesRecorded);
744 // MIM_DATA/MIM_LONGDATA message treats the time when midiInStart() is
745 // called as the origin of |elapsed_ms|.
746 // http://msdn.microsoft.com/en-us/library/windows/desktop/dd757284.aspx
747 // http://msdn.microsoft.com/en-us/library/windows/desktop/dd757286.aspx
748 const base::TimeTicks event_time =
749 state->start_time + base::TimeDelta::FromMilliseconds(elapsed_ms);
750 task_thread_.message_loop()->PostTask(
751 FROM_HERE,
752 base::Bind(&MidiServiceWinImpl::ReceiveMidiDataOnTaskThread,
753 base::Unretained(this), state->port_index, data,
754 event_time));
756 result = midiInAddBuffer(state->midi_handle, header, sizeof(*header));
757 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
758 << "Failed to attach input buffer: " << GetInErrorMessage(result)
759 << "port number:" << state->port_index;
762 void OnMidiInCloseOnMultimediaThread(HMIDIIN midi_in_handle) {
763 auto state = GetInputDeviceFromHandle(midi_in_handle);
764 if (!state)
765 return;
766 const uint32 port_number = state->port_index;
767 const auto device_info(state->device_info);
769 base::AutoLock auto_lock(input_ports_lock_);
770 input_device_map_.erase(state->midi_handle);
771 input_ports_[port_number] = nullptr;
772 input_ports_ages_[port_number] += 1;
773 unused_input_ports_[device_info].push(port_number);
775 task_thread_.message_loop()->PostTask(
776 FROM_HERE,
777 base::Bind(&MidiServiceWinImpl::SetInputPortStateOnTaskThread,
778 base::Unretained(this), port_number,
779 MIDI_PORT_DISCONNECTED));
782 static void CALLBACK
783 OnMidiOutEventOnMultimediaThread(HMIDIOUT midi_out_handle,
784 UINT message,
785 DWORD_PTR instance,
786 DWORD_PTR param1,
787 DWORD_PTR param2) {
788 MidiServiceWinImpl* self = reinterpret_cast<MidiServiceWinImpl*>(instance);
789 if (!self)
790 return;
791 switch (message) {
792 case MOM_OPEN:
793 self->OnMidiOutOpenOnMultimediaThread(midi_out_handle, param1, param2);
794 break;
795 case MOM_DONE:
796 self->OnMidiOutDoneOnMultimediaThread(midi_out_handle, param1);
797 break;
798 case MOM_CLOSE:
799 self->OnMidiOutCloseOnMultimediaThread(midi_out_handle);
800 break;
804 void OnMidiOutOpenOnMultimediaThread(HMIDIOUT midi_out_handle,
805 DWORD_PTR param1,
806 DWORD_PTR param2) {
807 UINT device_id = 0;
808 MMRESULT result = midiOutGetID(midi_out_handle, &device_id);
809 if (result != MMSYSERR_NOERROR) {
810 DLOG(ERROR) << "midiOutGetID failed: " << GetOutErrorMessage(result);
811 return;
813 MIDIOUTCAPS2W caps = {};
814 result = midiOutGetDevCaps(
815 device_id, reinterpret_cast<LPMIDIOUTCAPSW>(&caps), sizeof(caps));
816 if (result != MMSYSERR_NOERROR) {
817 DLOG(ERROR) << "midiInGetDevCaps failed: " << GetOutErrorMessage(result);
818 return;
820 auto state =
821 make_scoped_refptr(new MidiOutputDeviceState(MidiDeviceInfo(caps)));
822 state->midi_handle = midi_out_handle;
823 const auto& state_device_info = state->device_info;
824 bool add_new_port = false;
825 uint32 port_number = 0;
827 base::AutoLock auto_lock(output_ports_lock_);
828 const auto it = unused_output_ports_.find(state_device_info);
829 if (it == unused_output_ports_.end()) {
830 port_number = output_ports_.size();
831 add_new_port = true;
832 output_ports_.push_back(nullptr);
833 output_ports_ages_.push_back(0);
834 } else {
835 port_number = it->second.top();
836 it->second.pop();
837 if (it->second.empty())
838 unused_output_ports_.erase(it);
840 output_ports_[port_number] = state;
841 output_ports_ages_[port_number] += 1;
842 output_device_map_[output_ports_[port_number]->midi_handle] =
843 output_ports_[port_number];
844 output_ports_[port_number]->port_index = port_number;
845 output_ports_[port_number]->port_age = output_ports_ages_[port_number];
847 if (add_new_port) {
848 const MidiPortInfo port_info(
849 // TODO(toyoshim): Use a hash ID insted. crbug.com/467448
850 base::IntToString(static_cast<int>(port_number)),
851 GetManufacturerName(state_device_info),
852 base::WideToUTF8(state_device_info.product_name),
853 MmversionToString(state_device_info.driver_version),
854 MIDI_PORT_OPENED);
855 task_thread_.message_loop()->PostTask(
856 FROM_HERE, base::Bind(&MidiServiceWinImpl::AddOutputPortOnTaskThread,
857 base::Unretained(this), port_info));
858 } else {
859 task_thread_.message_loop()->PostTask(
860 FROM_HERE,
861 base::Bind(&MidiServiceWinImpl::SetOutputPortStateOnTaskThread,
862 base::Unretained(this), port_number, MIDI_PORT_CONNECTED));
866 void OnMidiOutDoneOnMultimediaThread(HMIDIOUT midi_out_handle,
867 DWORD_PTR param1) {
868 auto state = GetOutputDeviceFromHandle(midi_out_handle);
869 if (!state)
870 return;
871 // Take ownership of the MIDIHDR object.
872 ScopedMIDIHDR header(reinterpret_cast<MIDIHDR*>(param1));
873 if (!header)
874 return;
875 MMRESULT result = midiOutUnprepareHeader(state->midi_handle, header.get(),
876 sizeof(*header));
877 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
878 << "Failed to uninitialize output buffer: "
879 << GetOutErrorMessage(result);
882 void OnMidiOutCloseOnMultimediaThread(HMIDIOUT midi_out_handle) {
883 auto state = GetOutputDeviceFromHandle(midi_out_handle);
884 if (!state)
885 return;
886 const uint32 port_number = state->port_index;
887 const auto device_info(state->device_info);
889 base::AutoLock auto_lock(output_ports_lock_);
890 output_device_map_.erase(state->midi_handle);
891 output_ports_[port_number] = nullptr;
892 output_ports_ages_[port_number] += 1;
893 unused_output_ports_[device_info].push(port_number);
894 state->closed = true;
896 task_thread_.message_loop()->PostTask(
897 FROM_HERE,
898 base::Bind(&MidiServiceWinImpl::SetOutputPortStateOnTaskThread,
899 base::Unretained(this), port_number,
900 MIDI_PORT_DISCONNECTED));
903 /////////////////////////////////////////////////////////////////////////////
904 // Callbacks on the monitor thread.
905 /////////////////////////////////////////////////////////////////////////////
907 void AssertOnMonitorThread() {
908 DCHECK_EQ(monitor_thread_.thread_id(), base::PlatformThread::CurrentId());
911 void InitializeOnMonitorThread() {
912 AssertOnMonitorThread();
913 // Synchronously open all the existing MIDI devices.
914 TryOpenAllDevicesOnMonitorThreadSync();
915 // Since |TryOpenAllDevicesOnMonitorThreadSync()| is a blocking call, it is
916 // guaranteed that all the initial MIDI ports are already opened here, and
917 // corresponding tasks to call |AddInputPortOnTaskThread()| and
918 // |AddOutputPortOnTaskThread()| are already queued in the Misc. Task
919 // Thread. This means that the following task to call
920 // |CompleteInitializationOnTaskThread()| are always called after all the
921 // pending tasks to call |AddInputPortOnTaskThread()| and
922 // |AddOutputPortOnTaskThread()| are completed.
923 task_thread_.message_loop()->PostTask(
924 FROM_HERE,
925 base::Bind(&MidiServiceWinImpl::CompleteInitializationOnTaskThread,
926 base::Unretained(this), MIDI_OK));
927 // Start monitoring based on notifications generated by
928 // RegisterDeviceNotificationW API.
929 device_monitor_window_.reset(new DeviceMonitorWindow(
930 base::Bind(&MidiServiceWinImpl::TryOpenAllDevicesOnMonitorThreadSync,
931 base::Unretained(this))));
934 void StopDeviceMonitorOnMonitorThreadSync() {
935 device_monitor_window_.reset();
938 // Note that this is a blocking method.
939 void TryOpenAllDevicesOnMonitorThreadSync() {
940 AssertOnMonitorThread();
941 const UINT num_in_devices = midiInGetNumDevs();
942 for (UINT device_id = 0; device_id < num_in_devices; ++device_id) {
943 // Here we use |CALLBACK_FUNCTION| to subscribe MIM_DATA, MIM_LONGDATA,
944 // MIM_OPEN, and MIM_CLOSE events.
945 // - MIM_DATA: This is the only way to get a short MIDI message with
946 // timestamp information.
947 // - MIM_LONGDATA: This is the only way to get a long MIDI message with
948 // timestamp information.
949 // - MIM_OPEN: This event is sent the input device is opened.
950 // - MIM_CLOSE: This event is sent when 1) midiInClose() is called, or 2)
951 // the MIDI device becomes unavailable for some reasons, e.g., the
952 // cable is disconnected. As for the former case, HMIDIOUT will be
953 // invalidated soon after the callback is finished. As for the later
954 // case, however, HMIDIOUT continues to be valid until midiInClose()
955 // is called.
956 HMIDIIN midi_handle = kInvalidMidiInHandle;
957 const MMRESULT result = midiInOpen(
958 &midi_handle, device_id,
959 reinterpret_cast<DWORD_PTR>(&OnMidiInEventOnMultimediaThread),
960 reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION);
961 DLOG_IF(ERROR, result != MMSYSERR_NOERROR && result != MMSYSERR_ALLOCATED)
962 << "Failed to open output device. "
963 << " id: " << device_id << " message: " << GetInErrorMessage(result);
966 const UINT num_out_devices = midiOutGetNumDevs();
967 for (UINT device_id = 0; device_id < num_out_devices; ++device_id) {
968 // Here we use |CALLBACK_FUNCTION| to subscribe MOM_DONE, MOM_OPEN, and
969 // MOM_CLOSE events.
970 // - MOM_DONE: SendLongMidiMessageInternal() relies on this event to clean
971 // up the backing store where a long MIDI message is stored.
972 // - MOM_OPEN: This event is sent the output device is opened.
973 // - MOM_CLOSE: This event is sent when 1) midiOutClose() is called, or 2)
974 // the MIDI device becomes unavailable for some reasons, e.g., the
975 // cable is disconnected. As for the former case, HMIDIOUT will be
976 // invalidated soon after the callback is finished. As for the later
977 // case, however, HMIDIOUT continues to be valid until midiOutClose()
978 // is called.
979 HMIDIOUT midi_handle = kInvalidMidiOutHandle;
980 const MMRESULT result = midiOutOpen(
981 &midi_handle, device_id,
982 reinterpret_cast<DWORD_PTR>(&OnMidiOutEventOnMultimediaThread),
983 reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION);
984 if (result != MMSYSERR_NOERROR && result != MMSYSERR_ALLOCATED) {
985 DLOG(ERROR) << "Failed to open output device. "
986 << " id: " << device_id
987 << " message: " << GetOutErrorMessage(result);
992 /////////////////////////////////////////////////////////////////////////////
993 // Callbacks on the sender thread.
994 /////////////////////////////////////////////////////////////////////////////
996 void AssertOnSenderThread() {
997 DCHECK_EQ(sender_thread_.thread_id(), base::PlatformThread::CurrentId());
1000 void SendOnSenderThread(uint32 port_number,
1001 uint64 port_age,
1002 const std::vector<uint8>& data,
1003 base::TimeTicks time) {
1004 AssertOnSenderThread();
1005 if (destructor_started) {
1006 LOG(ERROR) << "ThreadSafeSendData failed because MidiServiceWinImpl is "
1007 "being destructed. port: " << port_number;
1009 auto state = GetOutputDeviceFromPort(port_number);
1010 if (!state) {
1011 LOG(ERROR) << "ThreadSafeSendData failed due to an invalid port number. "
1012 << "port: " << port_number;
1013 return;
1015 if (state->closed) {
1016 LOG(ERROR)
1017 << "ThreadSafeSendData failed because target port is already closed."
1018 << "port: " << port_number;
1019 return;
1021 if (state->port_age != port_age) {
1022 LOG(ERROR)
1023 << "ThreadSafeSendData failed because target port is being closed."
1024 << "port: " << port_number << "expected port age: " << port_age
1025 << "actual port age: " << state->port_age;
1028 // MIDI Running status must be filtered out.
1029 MidiMessageQueue message_queue(false);
1030 message_queue.Add(data);
1031 std::vector<uint8> message;
1032 while (true) {
1033 if (destructor_started)
1034 break;
1035 if (state->closed)
1036 break;
1037 message_queue.Get(&message);
1038 if (message.empty())
1039 break;
1040 // SendShortMidiMessageInternal can send a MIDI message up to 3 bytes.
1041 if (message.size() <= 3)
1042 SendShortMidiMessageInternal(state->midi_handle, message);
1043 else
1044 SendLongMidiMessageInternal(state->midi_handle, message);
1048 /////////////////////////////////////////////////////////////////////////////
1049 // Callbacks on the task thread.
1050 /////////////////////////////////////////////////////////////////////////////
1052 void AssertOnTaskThread() {
1053 DCHECK_EQ(task_thread_.thread_id(), base::PlatformThread::CurrentId());
1056 void StartInputDeviceOnTaskThread(HMIDIIN midi_in_handle) {
1057 AssertOnTaskThread();
1058 auto state = GetInputDeviceFromHandle(midi_in_handle);
1059 if (!state)
1060 return;
1061 MMRESULT result =
1062 midiInPrepareHeader(state->midi_handle, state->midi_header.get(),
1063 sizeof(*state->midi_header));
1064 if (result != MMSYSERR_NOERROR) {
1065 DLOG(ERROR) << "Failed to initialize input buffer: "
1066 << GetInErrorMessage(result);
1067 return;
1069 result = midiInAddBuffer(state->midi_handle, state->midi_header.get(),
1070 sizeof(*state->midi_header));
1071 if (result != MMSYSERR_NOERROR) {
1072 DLOG(ERROR) << "Failed to attach input buffer: "
1073 << GetInErrorMessage(result);
1074 return;
1076 result = midiInStart(state->midi_handle);
1077 if (result != MMSYSERR_NOERROR) {
1078 DLOG(ERROR) << "Failed to start input port: "
1079 << GetInErrorMessage(result);
1080 return;
1082 state->start_time = base::TimeTicks::Now();
1083 state->start_time_initialized = true;
1086 void CompleteInitializationOnTaskThread(MidiResult result) {
1087 AssertOnTaskThread();
1088 delegate_->OnCompleteInitialization(result);
1091 void ReceiveMidiDataOnTaskThread(uint32 port_index,
1092 std::vector<uint8> data,
1093 base::TimeTicks time) {
1094 AssertOnTaskThread();
1095 delegate_->OnReceiveMidiData(port_index, data, time);
1098 void AddInputPortOnTaskThread(MidiPortInfo info) {
1099 AssertOnTaskThread();
1100 delegate_->OnAddInputPort(info);
1103 void AddOutputPortOnTaskThread(MidiPortInfo info) {
1104 AssertOnTaskThread();
1105 delegate_->OnAddOutputPort(info);
1108 void SetInputPortStateOnTaskThread(uint32 port_index, MidiPortState state) {
1109 AssertOnTaskThread();
1110 delegate_->OnSetInputPortState(port_index, state);
1113 void SetOutputPortStateOnTaskThread(uint32 port_index, MidiPortState state) {
1114 AssertOnTaskThread();
1115 delegate_->OnSetOutputPortState(port_index, state);
1118 /////////////////////////////////////////////////////////////////////////////
1119 // Fields:
1120 /////////////////////////////////////////////////////////////////////////////
1122 // Does not take ownership.
1123 MidiServiceWinDelegate* delegate_;
1125 base::Thread monitor_thread_;
1126 base::Thread sender_thread_;
1127 base::Thread task_thread_;
1129 scoped_ptr<DeviceMonitorWindow> device_monitor_window_;
1131 base::Lock input_ports_lock_;
1132 base::hash_map<HMIDIIN, scoped_refptr<MidiInputDeviceState>>
1133 input_device_map_; // GUARDED_BY(input_ports_lock_)
1134 PortNumberCache unused_input_ports_; // GUARDED_BY(input_ports_lock_)
1135 std::vector<scoped_refptr<MidiInputDeviceState>>
1136 input_ports_; // GUARDED_BY(input_ports_lock_)
1137 std::vector<uint64> input_ports_ages_; // GUARDED_BY(input_ports_lock_)
1139 base::Lock output_ports_lock_;
1140 base::hash_map<HMIDIOUT, scoped_refptr<MidiOutputDeviceState>>
1141 output_device_map_; // GUARDED_BY(output_ports_lock_)
1142 PortNumberCache unused_output_ports_; // GUARDED_BY(output_ports_lock_)
1143 std::vector<scoped_refptr<MidiOutputDeviceState>>
1144 output_ports_; // GUARDED_BY(output_ports_lock_)
1145 std::vector<uint64> output_ports_ages_; // GUARDED_BY(output_ports_lock_)
1147 // True if one thread reached MidiServiceWinImpl::~MidiServiceWinImpl(). Note
1148 // that MidiServiceWinImpl::~MidiServiceWinImpl() is blocked until
1149 // |monitor_thread_|, |sender_thread_|, and |task_thread_| are stopped.
1150 // This flag can be used as the signal that when background tasks must be
1151 // interrupted.
1152 // TODO(toyoshim): Use std::atomic<bool> when it is allowed.
1153 volatile bool destructor_started;
1155 DISALLOW_COPY_AND_ASSIGN(MidiServiceWinImpl);
1158 } // namespace
1160 void MidiManagerWin::StartInitialization() {
1161 midi_service_.reset(new MidiServiceWinImpl);
1162 // Note that |CompleteInitialization()| will be called from the callback.
1163 midi_service_->InitializeAsync(this);
1166 void MidiManagerWin::DispatchSendMidiData(MidiManagerClient* client,
1167 uint32 port_index,
1168 const std::vector<uint8>& data,
1169 double timestamp) {
1170 if (!midi_service_)
1171 return;
1173 base::TimeTicks time_to_send = base::TimeTicks::Now();
1174 if (timestamp != 0.0) {
1175 time_to_send =
1176 base::TimeTicks() + base::TimeDelta::FromMicroseconds(
1177 timestamp * base::Time::kMicrosecondsPerSecond);
1179 midi_service_->SendMidiDataAsync(port_index, data, time_to_send);
1181 // TOOD(toyoshim): This calculation should be done when the date is actually
1182 // sent.
1183 client->AccumulateMidiBytesSent(data.size());
1186 MidiManager* MidiManager::Create() {
1187 return new MidiManagerWin();
1190 } // namespace media