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"
11 // Prevent unnecessary functions from being included from <mmsystem.h>
28 #include "base/bind.h"
29 #include "base/containers/hash_tables.h"
30 #include "base/message_loop/message_loop.h"
31 #include "base/strings/string16.h"
32 #include "base/strings/string_number_conversions.h"
33 #include "base/strings/string_piece.h"
34 #include "base/strings/stringprintf.h"
35 #include "base/strings/utf_string_conversions.h"
36 #include "base/system_monitor/system_monitor.h"
37 #include "base/threading/thread_checker.h"
38 #include "base/timer/timer.h"
39 #include "base/win/message_window.h"
40 #include "device/usb/usb_ids.h"
41 #include "media/midi/midi_message_queue.h"
42 #include "media/midi/midi_message_util.h"
43 #include "media/midi/midi_port_info.h"
49 static const size_t kBufferLength
= 32 * 1024;
51 // We assume that nullpter represents an invalid MIDI handle.
52 const HMIDIIN kInvalidMidiInHandle
= nullptr;
53 const HMIDIOUT kInvalidMidiOutHandle
= nullptr;
55 std::string
GetInErrorMessage(MMRESULT result
) {
56 wchar_t text
[MAXERRORLENGTH
];
57 MMRESULT get_result
= midiInGetErrorText(result
, text
, arraysize(text
));
58 if (get_result
!= MMSYSERR_NOERROR
) {
59 DLOG(ERROR
) << "Failed to get error message."
60 << " original error: " << result
61 << " midiInGetErrorText error: " << get_result
;
64 return base::WideToUTF8(text
);
67 std::string
GetOutErrorMessage(MMRESULT result
) {
68 wchar_t text
[MAXERRORLENGTH
];
69 MMRESULT get_result
= midiOutGetErrorText(result
, text
, arraysize(text
));
70 if (get_result
!= MMSYSERR_NOERROR
) {
71 DLOG(ERROR
) << "Failed to get error message."
72 << " original error: " << result
73 << " midiOutGetErrorText error: " << get_result
;
76 return base::WideToUTF8(text
);
79 std::string
MmversionToString(MMVERSION version
) {
80 return base::StringPrintf("%d.%d", HIBYTE(version
), LOBYTE(version
));
83 class MIDIHDRDeleter
{
85 void operator()(MIDIHDR
* header
) {
88 delete[] static_cast<char*>(header
->lpData
);
89 header
->lpData
= NULL
;
90 header
->dwBufferLength
= 0;
95 typedef scoped_ptr
<MIDIHDR
, MIDIHDRDeleter
> ScopedMIDIHDR
;
97 ScopedMIDIHDR
CreateMIDIHDR(size_t size
) {
98 ScopedMIDIHDR
header(new MIDIHDR
);
99 ZeroMemory(header
.get(), sizeof(*header
));
100 header
->lpData
= new char[size
];
101 header
->dwBufferLength
= static_cast<DWORD
>(size
);
102 return header
.Pass();
105 void SendShortMidiMessageInternal(HMIDIOUT midi_out_handle
,
106 const std::vector
<uint8
>& message
) {
107 DCHECK_LE(message
.size(), static_cast<size_t>(3))
108 << "A short MIDI message should be up to 3 bytes.";
110 DWORD packed_message
= 0;
111 for (size_t i
= 0; i
< message
.size(); ++i
)
112 packed_message
|= (static_cast<uint32
>(message
[i
]) << (i
* 8));
113 MMRESULT result
= midiOutShortMsg(midi_out_handle
, packed_message
);
114 DLOG_IF(ERROR
, result
!= MMSYSERR_NOERROR
)
115 << "Failed to output short message: " << GetOutErrorMessage(result
);
118 void SendLongMidiMessageInternal(HMIDIOUT midi_out_handle
,
119 const std::vector
<uint8
>& message
) {
120 // Implementation note:
121 // Sending a long MIDI message can be performed synchronously or
122 // asynchronously depending on the driver. There are 2 options to support both
124 // 1) Call midiOutLongMsg() API and wait for its completion within this
125 // function. In this approach, we can avoid memory copy by directly pointing
126 // |message| as the data buffer to be sent.
127 // 2) Allocate a buffer and copy |message| to it, then call midiOutLongMsg()
128 // API. The buffer will be freed in the MOM_DONE event hander, which tells
129 // us that the task of midiOutLongMsg() API is completed.
130 // Here we choose option 2) in favor of asynchronous design.
132 // Note for built-in USB-MIDI driver:
133 // From an observation on Windows 7/8.1 with a USB-MIDI keyboard,
134 // midiOutLongMsg() will be always blocked. Sending 64 bytes or less data
135 // takes roughly 300 usecs. Sending 2048 bytes or more data takes roughly
136 // |message.size() / (75 * 1024)| secs in practice. Here we put 60 KB size
137 // limit on SysEx message, with hoping that midiOutLongMsg will be blocked at
138 // most 1 sec or so with a typical USB-MIDI device.
139 const size_t kSysExSizeLimit
= 60 * 1024;
140 if (message
.size() >= kSysExSizeLimit
) {
141 DVLOG(1) << "Ingnoreing SysEx message due to the size limit"
142 << ", size = " << message
.size();
146 ScopedMIDIHDR
midi_header(CreateMIDIHDR(message
.size()));
147 std::copy(message
.begin(), message
.end(), midi_header
->lpData
);
149 MMRESULT result
= midiOutPrepareHeader(midi_out_handle
, midi_header
.get(),
150 sizeof(*midi_header
));
151 if (result
!= MMSYSERR_NOERROR
) {
152 DLOG(ERROR
) << "Failed to prepare output buffer: "
153 << GetOutErrorMessage(result
);
158 midiOutLongMsg(midi_out_handle
, midi_header
.get(), sizeof(*midi_header
));
159 if (result
!= MMSYSERR_NOERROR
) {
160 DLOG(ERROR
) << "Failed to output long message: "
161 << GetOutErrorMessage(result
);
162 result
= midiOutUnprepareHeader(midi_out_handle
, midi_header
.get(),
163 sizeof(*midi_header
));
164 DLOG_IF(ERROR
, result
!= MMSYSERR_NOERROR
)
165 << "Failed to uninitialize output buffer: "
166 << GetOutErrorMessage(result
);
170 // The ownership of |midi_header| is moved to MOM_DONE event handler.
171 midi_header
.release();
174 template <size_t array_size
>
175 base::string16
AsString16(const wchar_t(&buffer
)[array_size
]) {
177 for (len
= 0; len
< array_size
; ++len
) {
178 if (buffer
[len
] == L
'\0')
181 return base::string16(buffer
, len
);
184 struct MidiDeviceInfo final
{
185 explicit MidiDeviceInfo(const MIDIINCAPS2W
& caps
)
186 : manufacturer_id(caps
.wMid
),
187 product_id(caps
.wPid
),
188 driver_version(caps
.vDriverVersion
),
189 product_name(AsString16(caps
.szPname
)),
190 usb_vendor_id(ExtractUsbVendorIdIfExists(caps
)),
191 usb_product_id(ExtractUsbProductIdIfExists(caps
)),
192 is_usb_device(IsUsbDevice(caps
)),
193 is_software_synth(false) {}
194 explicit MidiDeviceInfo(const MIDIOUTCAPS2W
& caps
)
195 : manufacturer_id(caps
.wMid
),
196 product_id(caps
.wPid
),
197 driver_version(caps
.vDriverVersion
),
198 product_name(AsString16(caps
.szPname
)),
199 usb_vendor_id(ExtractUsbVendorIdIfExists(caps
)),
200 usb_product_id(ExtractUsbProductIdIfExists(caps
)),
201 is_usb_device(IsUsbDevice(caps
)),
202 is_software_synth(IsSoftwareSynth(caps
)) {}
203 explicit MidiDeviceInfo(const MidiDeviceInfo
& info
)
204 : manufacturer_id(info
.manufacturer_id
),
205 product_id(info
.product_id
),
206 driver_version(info
.driver_version
),
207 product_name(info
.product_name
),
208 usb_vendor_id(info
.usb_vendor_id
),
209 usb_product_id(info
.usb_product_id
),
210 is_usb_device(info
.is_usb_device
),
211 is_software_synth(info
.is_software_synth
) {}
212 // Currently only following entities are considered when testing the equality
213 // of two MIDI devices.
214 // TODO(toyoshim): Consider to calculate MIDIPort.id here and use it as the
215 // key. See crbug.com/467448. Then optimize the data for |MidiPortInfo|.
216 const uint16 manufacturer_id
;
217 const uint16 product_id
;
218 const uint32 driver_version
;
219 const base::string16 product_name
;
220 const uint16 usb_vendor_id
;
221 const uint16 usb_product_id
;
222 const bool is_usb_device
;
223 const bool is_software_synth
;
225 // Required to be used as the key of base::hash_map.
226 bool operator==(const MidiDeviceInfo
& that
) const {
227 return manufacturer_id
== that
.manufacturer_id
&&
228 product_id
== that
.product_id
&&
229 driver_version
== that
.driver_version
&&
230 product_name
== that
.product_name
&&
231 is_usb_device
== that
.is_usb_device
&&
232 (is_usb_device
&& usb_vendor_id
== that
.usb_vendor_id
&&
233 usb_product_id
== that
.usb_product_id
);
236 // Hash function to be used in base::hash_map.
238 size_t operator()(const MidiDeviceInfo
& info
) const {
239 size_t hash
= info
.manufacturer_id
;
241 hash
+= info
.product_id
;
243 hash
+= info
.driver_version
;
245 hash
+= info
.product_name
.size();
247 if (!info
.product_name
.empty()) {
248 hash
+= info
.product_name
[0];
251 hash
+= info
.usb_vendor_id
;
253 hash
+= info
.usb_product_id
;
259 static bool IsUsbDevice(const MIDIINCAPS2W
& caps
) {
260 return IS_COMPATIBLE_USBAUDIO_MID(&caps
.ManufacturerGuid
) &&
261 IS_COMPATIBLE_USBAUDIO_PID(&caps
.ProductGuid
);
263 static bool IsUsbDevice(const MIDIOUTCAPS2W
& caps
) {
264 return IS_COMPATIBLE_USBAUDIO_MID(&caps
.ManufacturerGuid
) &&
265 IS_COMPATIBLE_USBAUDIO_PID(&caps
.ProductGuid
);
267 static bool IsSoftwareSynth(const MIDIOUTCAPS2W
& caps
) {
268 return caps
.wTechnology
== MOD_SWSYNTH
;
270 static uint16
ExtractUsbVendorIdIfExists(const MIDIINCAPS2W
& caps
) {
271 if (!IS_COMPATIBLE_USBAUDIO_MID(&caps
.ManufacturerGuid
))
273 return EXTRACT_USBAUDIO_MID(&caps
.ManufacturerGuid
);
275 static uint16
ExtractUsbVendorIdIfExists(const MIDIOUTCAPS2W
& caps
) {
276 if (!IS_COMPATIBLE_USBAUDIO_MID(&caps
.ManufacturerGuid
))
278 return EXTRACT_USBAUDIO_MID(&caps
.ManufacturerGuid
);
280 static uint16
ExtractUsbProductIdIfExists(const MIDIINCAPS2W
& caps
) {
281 if (!IS_COMPATIBLE_USBAUDIO_PID(&caps
.ProductGuid
))
283 return EXTRACT_USBAUDIO_PID(&caps
.ProductGuid
);
285 static uint16
ExtractUsbProductIdIfExists(const MIDIOUTCAPS2W
& caps
) {
286 if (!IS_COMPATIBLE_USBAUDIO_PID(&caps
.ProductGuid
))
288 return EXTRACT_USBAUDIO_PID(&caps
.ProductGuid
);
292 std::string
GetManufacturerName(const MidiDeviceInfo
& info
) {
293 if (info
.is_usb_device
) {
294 const char* name
= device::UsbIds::GetVendorName(info
.usb_vendor_id
);
295 return std::string(name
? name
: "");
298 switch (info
.manufacturer_id
) {
300 return "Microsoft Corporation";
302 // TODO(toyoshim): Support other manufacture IDs. crbug.com/472341.
307 bool IsUnsupportedDevice(const MidiDeviceInfo
& info
) {
308 return info
.is_software_synth
&& info
.manufacturer_id
== MM_MICROSOFT
&&
309 (info
.product_id
== MM_MSFT_WDMAUDIO_MIDIOUT
||
310 info
.product_id
== MM_MSFT_GENERIC_MIDISYNTH
);
313 using PortNumberCache
= base::hash_map
<
315 std::priority_queue
<uint32
, std::vector
<uint32
>, std::greater
<uint32
>>,
316 MidiDeviceInfo::Hasher
>;
318 struct MidiInputDeviceState final
: base::RefCounted
<MidiInputDeviceState
> {
319 explicit MidiInputDeviceState(const MidiDeviceInfo
& device_info
)
320 : device_info(device_info
),
321 midi_handle(kInvalidMidiInHandle
),
324 start_time_initialized(false) {}
326 const MidiDeviceInfo device_info
;
328 ScopedMIDIHDR midi_header
;
329 // Since Win32 multimedia system uses a relative time offset from when
330 // |midiInStart| API is called, we need to record when it is called.
331 base::TimeTicks start_time
;
332 // 0-based port index. We will try to reuse the previous port index when the
333 // MIDI device is closed then reopened.
335 // A sequence number which represents how many times |port_index| is reused.
336 // We can remove this field if we decide not to clear unsent events
337 // when the device is disconnected.
338 // See https://github.com/WebAudio/web-midi-api/issues/133
340 // True if |start_time| is initialized. This field is not used so far, but
341 // kept for the debugging purpose.
342 bool start_time_initialized
;
345 friend class base::RefCounted
<MidiInputDeviceState
>;
346 ~MidiInputDeviceState() {}
349 struct MidiOutputDeviceState final
: base::RefCounted
<MidiOutputDeviceState
> {
350 explicit MidiOutputDeviceState(const MidiDeviceInfo
& device_info
)
351 : device_info(device_info
),
352 midi_handle(kInvalidMidiOutHandle
),
357 const MidiDeviceInfo device_info
;
358 HMIDIOUT midi_handle
;
359 // 0-based port index. We will try to reuse the previous port index when the
360 // MIDI device is closed then reopened.
362 // A sequence number which represents how many times |port_index| is reused.
363 // We can remove this field if we decide not to clear unsent events
364 // when the device is disconnected.
365 // See https://github.com/WebAudio/web-midi-api/issues/133
367 // True if the device is already closed and |midi_handle| is considered to be
369 // TODO(toyoshim): Use std::atomic<bool> when it is allowed in Chromium
371 volatile bool closed
;
374 friend class base::RefCounted
<MidiOutputDeviceState
>;
375 ~MidiOutputDeviceState() {}
378 // The core logic of MIDI device handling for Windows. Basically this class is
379 // shared among following 4 threads:
380 // 1. Chrome IO Thread
381 // 2. OS Multimedia Thread
386 // MidiManager runs on Chrome IO thread. Device change notification is
387 // delivered to the thread through the SystemMonitor service.
388 // OnDevicesChanged() callback is invoked to update the MIDI device list.
389 // Note that in the current implementation we will try to open all the
390 // existing devices in practice. This is OK because trying to reopen a MIDI
391 // device that is already opened would simply fail, and there is no unwilling
394 // OS Multimedia Thread:
395 // This thread is maintained by the OS as a part of MIDI runtime, and
396 // responsible for receiving all the system initiated events such as device
397 // close, and receiving data. For performance reasons, most of potentially
398 // blocking operations will be dispatched into Task Thread.
401 // This thread will be used to call back following methods of MidiManager.
402 // - MidiManager::CompleteInitialization
403 // - MidiManager::AddInputPort
404 // - MidiManager::AddOutputPort
405 // - MidiManager::SetInputPortState
406 // - MidiManager::SetOutputPortState
407 // - MidiManager::ReceiveMidiData
410 // This thread will be used to call Win32 APIs to send MIDI message at the
411 // specified time. We don't want to call MIDI send APIs on Task Thread
412 // because those APIs could be performed synchronously, hence they could block
413 // the caller thread for a while. See the comment in
414 // SendLongMidiMessageInternal for details. Currently we expect that the
415 // blocking time would be less than 1 second.
416 class MidiServiceWinImpl
: public MidiServiceWin
,
417 public base::SystemMonitor::DevicesChangedObserver
{
420 : delegate_(nullptr),
421 sender_thread_("Windows MIDI sender thread"),
422 task_thread_("Windows MIDI task thread"),
423 destructor_started(false) {}
425 ~MidiServiceWinImpl() final
{
426 // Start() and Stop() of the threads, and AddDevicesChangeObserver() and
427 // RemoveDevicesChangeObserver() should be called on the same thread.
428 CHECK(thread_checker_
.CalledOnValidThread());
430 destructor_started
= true;
431 base::SystemMonitor::Get()->RemoveDevicesChangedObserver(this);
433 std::vector
<HMIDIIN
> input_devices
;
435 base::AutoLock
auto_lock(input_ports_lock_
);
436 for (auto it
: input_device_map_
)
437 input_devices
.push_back(it
.first
);
440 for (const auto handle
: input_devices
) {
441 MMRESULT result
= midiInClose(handle
);
442 if (result
== MIDIERR_STILLPLAYING
) {
443 result
= midiInReset(handle
);
444 DLOG_IF(ERROR
, result
!= MMSYSERR_NOERROR
)
445 << "midiInReset failed: " << GetInErrorMessage(result
);
446 result
= midiInClose(handle
);
448 DLOG_IF(ERROR
, result
!= MMSYSERR_NOERROR
)
449 << "midiInClose failed: " << GetInErrorMessage(result
);
454 std::vector
<HMIDIOUT
> output_devices
;
456 base::AutoLock
auto_lock(output_ports_lock_
);
457 for (auto it
: output_device_map_
)
458 output_devices
.push_back(it
.first
);
461 for (const auto handle
: output_devices
) {
462 MMRESULT result
= midiOutClose(handle
);
463 if (result
== MIDIERR_STILLPLAYING
) {
464 result
= midiOutReset(handle
);
465 DLOG_IF(ERROR
, result
!= MMSYSERR_NOERROR
)
466 << "midiOutReset failed: " << GetOutErrorMessage(result
);
467 result
= midiOutClose(handle
);
469 DLOG_IF(ERROR
, result
!= MMSYSERR_NOERROR
)
470 << "midiOutClose failed: " << GetOutErrorMessage(result
);
474 sender_thread_
.Stop();
478 // MidiServiceWin overrides:
479 void InitializeAsync(MidiServiceWinDelegate
* delegate
) final
{
480 // Start() and Stop() of the threads, and AddDevicesChangeObserver() and
481 // RemoveDevicesChangeObserver() should be called on the same thread.
482 CHECK(thread_checker_
.CalledOnValidThread());
484 delegate_
= delegate
;
486 sender_thread_
.Start();
487 task_thread_
.Start();
489 // Start monitoring device changes. This should start before the
490 // following UpdateDeviceList() call not to miss the event happening
491 // between the call and the observer registration.
492 base::SystemMonitor::Get()->AddDevicesChangedObserver(this);
496 task_thread_
.message_loop()->PostTask(
498 base::Bind(&MidiServiceWinImpl::CompleteInitializationOnTaskThread
,
499 base::Unretained(this), Result::OK
));
502 void SendMidiDataAsync(uint32 port_number
,
503 const std::vector
<uint8
>& data
,
504 base::TimeTicks time
) final
{
505 if (destructor_started
) {
506 LOG(ERROR
) << "ThreadSafeSendData failed because MidiServiceWinImpl is "
507 "being destructed. port: " << port_number
;
510 auto state
= GetOutputDeviceFromPort(port_number
);
512 LOG(ERROR
) << "ThreadSafeSendData failed due to an invalid port number. "
513 << "port: " << port_number
;
518 << "ThreadSafeSendData failed because target port is already closed."
519 << "port: " << port_number
;
522 const auto now
= base::TimeTicks::Now();
524 sender_thread_
.message_loop()->PostDelayedTask(
525 FROM_HERE
, base::Bind(&MidiServiceWinImpl::SendOnSenderThread
,
526 base::Unretained(this), port_number
,
527 state
->port_age
, data
, time
),
530 sender_thread_
.message_loop()->PostTask(
531 FROM_HERE
, base::Bind(&MidiServiceWinImpl::SendOnSenderThread
,
532 base::Unretained(this), port_number
,
533 state
->port_age
, data
, time
));
537 // base::SystemMonitor::DevicesChangedObserver overrides:
538 void OnDevicesChanged(base::SystemMonitor::DeviceType device_type
) final
{
539 CHECK(thread_checker_
.CalledOnValidThread());
540 if (destructor_started
)
543 switch (device_type
) {
544 case base::SystemMonitor::DEVTYPE_AUDIO_CAPTURE
:
545 case base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE
:
546 // Add case of other unrelated device types here.
548 case base::SystemMonitor::DEVTYPE_UNKNOWN
:
549 // Interested in MIDI devices. Try updating the device list.
552 // No default here to capture new DeviceType by compile time.
557 scoped_refptr
<MidiInputDeviceState
> GetInputDeviceFromHandle(
558 HMIDIIN midi_handle
) {
559 base::AutoLock
auto_lock(input_ports_lock_
);
560 const auto it
= input_device_map_
.find(midi_handle
);
561 return (it
!= input_device_map_
.end() ? it
->second
: nullptr);
564 scoped_refptr
<MidiOutputDeviceState
> GetOutputDeviceFromHandle(
565 HMIDIOUT midi_handle
) {
566 base::AutoLock
auto_lock(output_ports_lock_
);
567 const auto it
= output_device_map_
.find(midi_handle
);
568 return (it
!= output_device_map_
.end() ? it
->second
: nullptr);
571 scoped_refptr
<MidiOutputDeviceState
> GetOutputDeviceFromPort(
572 uint32 port_number
) {
573 base::AutoLock
auto_lock(output_ports_lock_
);
574 if (output_ports_
.size() <= port_number
)
576 return output_ports_
[port_number
];
579 void UpdateDeviceList() {
580 task_thread_
.message_loop()->PostTask(
581 FROM_HERE
, base::Bind(&MidiServiceWinImpl::UpdateDeviceListOnTaskThread
,
582 base::Unretained(this)));
585 /////////////////////////////////////////////////////////////////////////////
586 // Callbacks on the OS multimedia thread.
587 /////////////////////////////////////////////////////////////////////////////
590 OnMidiInEventOnMainlyMultimediaThread(HMIDIIN midi_in_handle
,
595 MidiServiceWinImpl
* self
= reinterpret_cast<MidiServiceWinImpl
*>(instance
);
600 self
->OnMidiInOpen(midi_in_handle
);
603 self
->OnMidiInDataOnMultimediaThread(midi_in_handle
, param1
, param2
);
606 self
->OnMidiInLongDataOnMultimediaThread(midi_in_handle
, param1
,
610 self
->OnMidiInCloseOnMultimediaThread(midi_in_handle
);
615 void OnMidiInOpen(HMIDIIN midi_in_handle
) {
617 MMRESULT result
= midiInGetID(midi_in_handle
, &device_id
);
618 if (result
!= MMSYSERR_NOERROR
) {
619 DLOG(ERROR
) << "midiInGetID failed: " << GetInErrorMessage(result
);
622 MIDIINCAPS2W caps
= {};
623 result
= midiInGetDevCaps(device_id
, reinterpret_cast<LPMIDIINCAPSW
>(&caps
),
625 if (result
!= MMSYSERR_NOERROR
) {
626 DLOG(ERROR
) << "midiInGetDevCaps failed: " << GetInErrorMessage(result
);
630 make_scoped_refptr(new MidiInputDeviceState(MidiDeviceInfo(caps
)));
631 state
->midi_handle
= midi_in_handle
;
632 state
->midi_header
= CreateMIDIHDR(kBufferLength
);
633 const auto& state_device_info
= state
->device_info
;
634 bool add_new_port
= false;
635 uint32 port_number
= 0;
637 base::AutoLock
auto_lock(input_ports_lock_
);
638 const auto it
= unused_input_ports_
.find(state_device_info
);
639 if (it
== unused_input_ports_
.end()) {
640 port_number
= static_cast<uint32
>(input_ports_
.size());
642 input_ports_
.push_back(nullptr);
643 input_ports_ages_
.push_back(0);
645 port_number
= it
->second
.top();
647 if (it
->second
.empty()) {
648 unused_input_ports_
.erase(it
);
651 input_ports_
[port_number
] = state
;
653 input_ports_ages_
[port_number
] += 1;
654 input_device_map_
[input_ports_
[port_number
]->midi_handle
] =
655 input_ports_
[port_number
];
656 input_ports_
[port_number
]->port_index
= port_number
;
657 input_ports_
[port_number
]->port_age
= input_ports_ages_
[port_number
];
659 // Several initial startup tasks cannot be done in MIM_OPEN handler.
660 task_thread_
.message_loop()->PostTask(
661 FROM_HERE
, base::Bind(&MidiServiceWinImpl::StartInputDeviceOnTaskThread
,
662 base::Unretained(this), midi_in_handle
));
664 const MidiPortInfo
port_info(
665 // TODO(toyoshim): Use a hash ID insted crbug.com/467448
666 base::IntToString(static_cast<int>(port_number
)),
667 GetManufacturerName(state_device_info
),
668 base::WideToUTF8(state_device_info
.product_name
),
669 MmversionToString(state_device_info
.driver_version
),
671 task_thread_
.message_loop()->PostTask(
672 FROM_HERE
, base::Bind(&MidiServiceWinImpl::AddInputPortOnTaskThread
,
673 base::Unretained(this), port_info
));
675 task_thread_
.message_loop()->PostTask(
677 base::Bind(&MidiServiceWinImpl::SetInputPortStateOnTaskThread
,
678 base::Unretained(this), port_number
,
679 MidiPortState::MIDI_PORT_CONNECTED
));
683 void OnMidiInDataOnMultimediaThread(HMIDIIN midi_in_handle
,
686 auto state
= GetInputDeviceFromHandle(midi_in_handle
);
689 const uint8 status_byte
= static_cast<uint8
>(param1
& 0xff);
690 const uint8 first_data_byte
= static_cast<uint8
>((param1
>> 8) & 0xff);
691 const uint8 second_data_byte
= static_cast<uint8
>((param1
>> 16) & 0xff);
692 const DWORD elapsed_ms
= param2
;
693 const size_t len
= GetMidiMessageLength(status_byte
);
694 const uint8 kData
[] = {status_byte
, first_data_byte
, second_data_byte
};
695 std::vector
<uint8
> data
;
696 data
.assign(kData
, kData
+ len
);
697 DCHECK_LE(len
, arraysize(kData
));
698 // MIM_DATA/MIM_LONGDATA message treats the time when midiInStart() is
699 // called as the origin of |elapsed_ms|.
700 // http://msdn.microsoft.com/en-us/library/windows/desktop/dd757284.aspx
701 // http://msdn.microsoft.com/en-us/library/windows/desktop/dd757286.aspx
702 const base::TimeTicks event_time
=
703 state
->start_time
+ base::TimeDelta::FromMilliseconds(elapsed_ms
);
704 task_thread_
.message_loop()->PostTask(
705 FROM_HERE
, base::Bind(&MidiServiceWinImpl::ReceiveMidiDataOnTaskThread
,
706 base::Unretained(this), state
->port_index
, data
,
710 void OnMidiInLongDataOnMultimediaThread(HMIDIIN midi_in_handle
,
713 auto state
= GetInputDeviceFromHandle(midi_in_handle
);
716 MIDIHDR
* header
= reinterpret_cast<MIDIHDR
*>(param1
);
717 const DWORD elapsed_ms
= param2
;
718 MMRESULT result
= MMSYSERR_NOERROR
;
719 if (destructor_started
) {
720 if (state
->midi_header
&&
721 (state
->midi_header
->dwFlags
& MHDR_PREPARED
) == MHDR_PREPARED
) {
723 midiInUnprepareHeader(state
->midi_handle
, state
->midi_header
.get(),
724 sizeof(*state
->midi_header
));
725 DLOG_IF(ERROR
, result
!= MMSYSERR_NOERROR
)
726 << "Failed to uninitialize input buffer: "
727 << GetInErrorMessage(result
);
731 if (header
->dwBytesRecorded
> 0) {
732 const uint8
* src
= reinterpret_cast<const uint8
*>(header
->lpData
);
733 std::vector
<uint8
> data
;
734 data
.assign(src
, src
+ header
->dwBytesRecorded
);
735 // MIM_DATA/MIM_LONGDATA message treats the time when midiInStart() is
736 // called as the origin of |elapsed_ms|.
737 // http://msdn.microsoft.com/en-us/library/windows/desktop/dd757284.aspx
738 // http://msdn.microsoft.com/en-us/library/windows/desktop/dd757286.aspx
739 const base::TimeTicks event_time
=
740 state
->start_time
+ base::TimeDelta::FromMilliseconds(elapsed_ms
);
741 task_thread_
.message_loop()->PostTask(
743 base::Bind(&MidiServiceWinImpl::ReceiveMidiDataOnTaskThread
,
744 base::Unretained(this), state
->port_index
, data
,
747 result
= midiInAddBuffer(state
->midi_handle
, header
, sizeof(*header
));
748 DLOG_IF(ERROR
, result
!= MMSYSERR_NOERROR
)
749 << "Failed to attach input buffer: " << GetInErrorMessage(result
)
750 << "port number:" << state
->port_index
;
753 void OnMidiInCloseOnMultimediaThread(HMIDIIN midi_in_handle
) {
754 auto state
= GetInputDeviceFromHandle(midi_in_handle
);
757 const uint32 port_number
= state
->port_index
;
758 const auto device_info(state
->device_info
);
760 base::AutoLock
auto_lock(input_ports_lock_
);
761 input_device_map_
.erase(state
->midi_handle
);
762 input_ports_
[port_number
] = nullptr;
763 input_ports_ages_
[port_number
] += 1;
764 unused_input_ports_
[device_info
].push(port_number
);
766 task_thread_
.message_loop()->PostTask(
768 base::Bind(&MidiServiceWinImpl::SetInputPortStateOnTaskThread
,
769 base::Unretained(this), port_number
,
770 MIDI_PORT_DISCONNECTED
));
774 OnMidiOutEventOnMainlyMultimediaThread(HMIDIOUT midi_out_handle
,
779 MidiServiceWinImpl
* self
= reinterpret_cast<MidiServiceWinImpl
*>(instance
);
784 self
->OnMidiOutOpen(midi_out_handle
, param1
, param2
);
787 self
->OnMidiOutDoneOnMultimediaThread(midi_out_handle
, param1
);
790 self
->OnMidiOutCloseOnMultimediaThread(midi_out_handle
);
795 void OnMidiOutOpen(HMIDIOUT midi_out_handle
,
799 MMRESULT result
= midiOutGetID(midi_out_handle
, &device_id
);
800 if (result
!= MMSYSERR_NOERROR
) {
801 DLOG(ERROR
) << "midiOutGetID failed: " << GetOutErrorMessage(result
);
804 MIDIOUTCAPS2W caps
= {};
805 result
= midiOutGetDevCaps(
806 device_id
, reinterpret_cast<LPMIDIOUTCAPSW
>(&caps
), sizeof(caps
));
807 if (result
!= MMSYSERR_NOERROR
) {
808 DLOG(ERROR
) << "midiInGetDevCaps failed: " << GetOutErrorMessage(result
);
812 make_scoped_refptr(new MidiOutputDeviceState(MidiDeviceInfo(caps
)));
813 state
->midi_handle
= midi_out_handle
;
814 const auto& state_device_info
= state
->device_info
;
815 if (IsUnsupportedDevice(state_device_info
))
817 bool add_new_port
= false;
818 uint32 port_number
= 0;
820 base::AutoLock
auto_lock(output_ports_lock_
);
821 const auto it
= unused_output_ports_
.find(state_device_info
);
822 if (it
== unused_output_ports_
.end()) {
823 port_number
= static_cast<uint32
>(output_ports_
.size());
825 output_ports_
.push_back(nullptr);
826 output_ports_ages_
.push_back(0);
828 port_number
= it
->second
.top();
830 if (it
->second
.empty())
831 unused_output_ports_
.erase(it
);
833 output_ports_
[port_number
] = state
;
834 output_ports_ages_
[port_number
] += 1;
835 output_device_map_
[output_ports_
[port_number
]->midi_handle
] =
836 output_ports_
[port_number
];
837 output_ports_
[port_number
]->port_index
= port_number
;
838 output_ports_
[port_number
]->port_age
= output_ports_ages_
[port_number
];
841 const MidiPortInfo
port_info(
842 // TODO(toyoshim): Use a hash ID insted. crbug.com/467448
843 base::IntToString(static_cast<int>(port_number
)),
844 GetManufacturerName(state_device_info
),
845 base::WideToUTF8(state_device_info
.product_name
),
846 MmversionToString(state_device_info
.driver_version
),
848 task_thread_
.message_loop()->PostTask(
849 FROM_HERE
, base::Bind(&MidiServiceWinImpl::AddOutputPortOnTaskThread
,
850 base::Unretained(this), port_info
));
852 task_thread_
.message_loop()->PostTask(
854 base::Bind(&MidiServiceWinImpl::SetOutputPortStateOnTaskThread
,
855 base::Unretained(this), port_number
, MIDI_PORT_CONNECTED
));
859 void OnMidiOutDoneOnMultimediaThread(HMIDIOUT midi_out_handle
,
861 auto state
= GetOutputDeviceFromHandle(midi_out_handle
);
864 // Take ownership of the MIDIHDR object.
865 ScopedMIDIHDR
header(reinterpret_cast<MIDIHDR
*>(param1
));
868 MMRESULT result
= midiOutUnprepareHeader(state
->midi_handle
, header
.get(),
870 DLOG_IF(ERROR
, result
!= MMSYSERR_NOERROR
)
871 << "Failed to uninitialize output buffer: "
872 << GetOutErrorMessage(result
);
875 void OnMidiOutCloseOnMultimediaThread(HMIDIOUT midi_out_handle
) {
876 auto state
= GetOutputDeviceFromHandle(midi_out_handle
);
879 const uint32 port_number
= state
->port_index
;
880 const auto device_info(state
->device_info
);
882 base::AutoLock
auto_lock(output_ports_lock_
);
883 output_device_map_
.erase(state
->midi_handle
);
884 output_ports_
[port_number
] = nullptr;
885 output_ports_ages_
[port_number
] += 1;
886 unused_output_ports_
[device_info
].push(port_number
);
887 state
->closed
= true;
889 task_thread_
.message_loop()->PostTask(
891 base::Bind(&MidiServiceWinImpl::SetOutputPortStateOnTaskThread
,
892 base::Unretained(this), port_number
,
893 MIDI_PORT_DISCONNECTED
));
896 /////////////////////////////////////////////////////////////////////////////
897 // Callbacks on the sender thread.
898 /////////////////////////////////////////////////////////////////////////////
900 void AssertOnSenderThread() {
901 DCHECK_EQ(sender_thread_
.GetThreadId(), base::PlatformThread::CurrentId());
904 void SendOnSenderThread(uint32 port_number
,
906 const std::vector
<uint8
>& data
,
907 base::TimeTicks time
) {
908 AssertOnSenderThread();
909 if (destructor_started
) {
910 LOG(ERROR
) << "ThreadSafeSendData failed because MidiServiceWinImpl is "
911 "being destructed. port: " << port_number
;
913 auto state
= GetOutputDeviceFromPort(port_number
);
915 LOG(ERROR
) << "ThreadSafeSendData failed due to an invalid port number. "
916 << "port: " << port_number
;
921 << "ThreadSafeSendData failed because target port is already closed."
922 << "port: " << port_number
;
925 if (state
->port_age
!= port_age
) {
927 << "ThreadSafeSendData failed because target port is being closed."
928 << "port: " << port_number
<< "expected port age: " << port_age
929 << "actual port age: " << state
->port_age
;
932 // MIDI Running status must be filtered out.
933 MidiMessageQueue
message_queue(false);
934 message_queue
.Add(data
);
935 std::vector
<uint8
> message
;
937 if (destructor_started
)
941 message_queue
.Get(&message
);
944 // SendShortMidiMessageInternal can send a MIDI message up to 3 bytes.
945 if (message
.size() <= 3)
946 SendShortMidiMessageInternal(state
->midi_handle
, message
);
948 SendLongMidiMessageInternal(state
->midi_handle
, message
);
952 /////////////////////////////////////////////////////////////////////////////
953 // Callbacks on the task thread.
954 /////////////////////////////////////////////////////////////////////////////
956 void AssertOnTaskThread() {
957 DCHECK_EQ(task_thread_
.GetThreadId(), base::PlatformThread::CurrentId());
960 void UpdateDeviceListOnTaskThread() {
961 AssertOnTaskThread();
962 const UINT num_in_devices
= midiInGetNumDevs();
963 for (UINT device_id
= 0; device_id
< num_in_devices
; ++device_id
) {
964 // Here we use |CALLBACK_FUNCTION| to subscribe MIM_DATA, MIM_LONGDATA,
965 // MIM_OPEN, and MIM_CLOSE events.
966 // - MIM_DATA: This is the only way to get a short MIDI message with
967 // timestamp information.
968 // - MIM_LONGDATA: This is the only way to get a long MIDI message with
969 // timestamp information.
970 // - MIM_OPEN: This event is sent the input device is opened. Note that
971 // this message is called on the caller thread.
972 // - MIM_CLOSE: This event is sent when 1) midiInClose() is called, or 2)
973 // the MIDI device becomes unavailable for some reasons, e.g., the
974 // cable is disconnected. As for the former case, HMIDIOUT will be
975 // invalidated soon after the callback is finished. As for the later
976 // case, however, HMIDIOUT continues to be valid until midiInClose()
978 HMIDIIN midi_handle
= kInvalidMidiInHandle
;
979 const MMRESULT result
= midiInOpen(
980 &midi_handle
, device_id
,
981 reinterpret_cast<DWORD_PTR
>(&OnMidiInEventOnMainlyMultimediaThread
),
982 reinterpret_cast<DWORD_PTR
>(this), CALLBACK_FUNCTION
);
983 DLOG_IF(ERROR
, result
!= MMSYSERR_NOERROR
&& result
!= MMSYSERR_ALLOCATED
)
984 << "Failed to open output device. "
985 << " id: " << device_id
<< " message: " << GetInErrorMessage(result
);
988 const UINT num_out_devices
= midiOutGetNumDevs();
989 for (UINT device_id
= 0; device_id
< num_out_devices
; ++device_id
) {
990 // Here we use |CALLBACK_FUNCTION| to subscribe MOM_DONE, MOM_OPEN, and
992 // - MOM_DONE: SendLongMidiMessageInternal() relies on this event to clean
993 // up the backing store where a long MIDI message is stored.
994 // - MOM_OPEN: This event is sent the output device is opened. Note that
995 // this message is called on the caller thread.
996 // - MOM_CLOSE: This event is sent when 1) midiOutClose() is called, or 2)
997 // the MIDI device becomes unavailable for some reasons, e.g., the
998 // cable is disconnected. As for the former case, HMIDIOUT will be
999 // invalidated soon after the callback is finished. As for the later
1000 // case, however, HMIDIOUT continues to be valid until midiOutClose()
1002 HMIDIOUT midi_handle
= kInvalidMidiOutHandle
;
1003 const MMRESULT result
= midiOutOpen(
1004 &midi_handle
, device_id
,
1005 reinterpret_cast<DWORD_PTR
>(&OnMidiOutEventOnMainlyMultimediaThread
),
1006 reinterpret_cast<DWORD_PTR
>(this), CALLBACK_FUNCTION
);
1007 DLOG_IF(ERROR
, result
!= MMSYSERR_NOERROR
&& result
!= MMSYSERR_ALLOCATED
)
1008 << "Failed to open output device. "
1009 << " id: " << device_id
<< " message: " << GetOutErrorMessage(result
);
1013 void StartInputDeviceOnTaskThread(HMIDIIN midi_in_handle
) {
1014 AssertOnTaskThread();
1015 auto state
= GetInputDeviceFromHandle(midi_in_handle
);
1019 midiInPrepareHeader(state
->midi_handle
, state
->midi_header
.get(),
1020 sizeof(*state
->midi_header
));
1021 if (result
!= MMSYSERR_NOERROR
) {
1022 DLOG(ERROR
) << "Failed to initialize input buffer: "
1023 << GetInErrorMessage(result
);
1026 result
= midiInAddBuffer(state
->midi_handle
, state
->midi_header
.get(),
1027 sizeof(*state
->midi_header
));
1028 if (result
!= MMSYSERR_NOERROR
) {
1029 DLOG(ERROR
) << "Failed to attach input buffer: "
1030 << GetInErrorMessage(result
);
1033 result
= midiInStart(state
->midi_handle
);
1034 if (result
!= MMSYSERR_NOERROR
) {
1035 DLOG(ERROR
) << "Failed to start input port: "
1036 << GetInErrorMessage(result
);
1039 state
->start_time
= base::TimeTicks::Now();
1040 state
->start_time_initialized
= true;
1043 void CompleteInitializationOnTaskThread(Result result
) {
1044 AssertOnTaskThread();
1045 delegate_
->OnCompleteInitialization(result
);
1048 void ReceiveMidiDataOnTaskThread(uint32 port_index
,
1049 std::vector
<uint8
> data
,
1050 base::TimeTicks time
) {
1051 AssertOnTaskThread();
1052 delegate_
->OnReceiveMidiData(port_index
, data
, time
);
1055 void AddInputPortOnTaskThread(MidiPortInfo info
) {
1056 AssertOnTaskThread();
1057 delegate_
->OnAddInputPort(info
);
1060 void AddOutputPortOnTaskThread(MidiPortInfo info
) {
1061 AssertOnTaskThread();
1062 delegate_
->OnAddOutputPort(info
);
1065 void SetInputPortStateOnTaskThread(uint32 port_index
, MidiPortState state
) {
1066 AssertOnTaskThread();
1067 delegate_
->OnSetInputPortState(port_index
, state
);
1070 void SetOutputPortStateOnTaskThread(uint32 port_index
, MidiPortState state
) {
1071 AssertOnTaskThread();
1072 delegate_
->OnSetOutputPortState(port_index
, state
);
1075 /////////////////////////////////////////////////////////////////////////////
1077 /////////////////////////////////////////////////////////////////////////////
1079 // Does not take ownership.
1080 MidiServiceWinDelegate
* delegate_
;
1082 base::ThreadChecker thread_checker_
;
1084 base::Thread sender_thread_
;
1085 base::Thread task_thread_
;
1087 base::Lock input_ports_lock_
;
1088 base::hash_map
<HMIDIIN
, scoped_refptr
<MidiInputDeviceState
>>
1089 input_device_map_
; // GUARDED_BY(input_ports_lock_)
1090 PortNumberCache unused_input_ports_
; // GUARDED_BY(input_ports_lock_)
1091 std::vector
<scoped_refptr
<MidiInputDeviceState
>>
1092 input_ports_
; // GUARDED_BY(input_ports_lock_)
1093 std::vector
<uint64
> input_ports_ages_
; // GUARDED_BY(input_ports_lock_)
1095 base::Lock output_ports_lock_
;
1096 base::hash_map
<HMIDIOUT
, scoped_refptr
<MidiOutputDeviceState
>>
1097 output_device_map_
; // GUARDED_BY(output_ports_lock_)
1098 PortNumberCache unused_output_ports_
; // GUARDED_BY(output_ports_lock_)
1099 std::vector
<scoped_refptr
<MidiOutputDeviceState
>>
1100 output_ports_
; // GUARDED_BY(output_ports_lock_)
1101 std::vector
<uint64
> output_ports_ages_
; // GUARDED_BY(output_ports_lock_)
1103 // True if one thread reached MidiServiceWinImpl::~MidiServiceWinImpl(). Note
1104 // that MidiServiceWinImpl::~MidiServiceWinImpl() is blocked until
1105 // |sender_thread_|, and |task_thread_| are stopped.
1106 // This flag can be used as the signal that when background tasks must be
1108 // TODO(toyoshim): Use std::atomic<bool> when it is allowed.
1109 volatile bool destructor_started
;
1111 DISALLOW_COPY_AND_ASSIGN(MidiServiceWinImpl
);
1116 MidiManagerWin::MidiManagerWin() {
1119 MidiManagerWin::~MidiManagerWin() {
1120 midi_service_
.reset();
1123 void MidiManagerWin::StartInitialization() {
1124 midi_service_
.reset(new MidiServiceWinImpl
);
1125 // Note that |CompleteInitialization()| will be called from the callback.
1126 midi_service_
->InitializeAsync(this);
1129 void MidiManagerWin::DispatchSendMidiData(MidiManagerClient
* client
,
1131 const std::vector
<uint8
>& data
,
1136 base::TimeTicks time_to_send
= base::TimeTicks::Now();
1137 if (timestamp
!= 0.0) {
1139 base::TimeTicks() + base::TimeDelta::FromMicroseconds(
1140 timestamp
* base::Time::kMicrosecondsPerSecond
);
1142 midi_service_
->SendMidiDataAsync(port_index
, data
, time_to_send
);
1144 // TOOD(toyoshim): This calculation should be done when the date is actually
1146 client
->AccumulateMidiBytesSent(data
.size());
1149 void MidiManagerWin::OnCompleteInitialization(Result result
) {
1150 CompleteInitialization(result
);
1153 void MidiManagerWin::OnAddInputPort(MidiPortInfo info
) {
1157 void MidiManagerWin::OnAddOutputPort(MidiPortInfo info
) {
1158 AddOutputPort(info
);
1161 void MidiManagerWin::OnSetInputPortState(uint32 port_index
,
1162 MidiPortState state
) {
1163 SetInputPortState(port_index
, state
);
1166 void MidiManagerWin::OnSetOutputPortState(uint32 port_index
,
1167 MidiPortState state
) {
1168 SetOutputPortState(port_index
, state
);
1171 void MidiManagerWin::OnReceiveMidiData(uint32 port_index
,
1172 const std::vector
<uint8
>& data
,
1173 base::TimeTicks time
) {
1174 ReceiveMidiData(port_index
, &data
[0], data
.size(), time
);
1177 MidiManager
* MidiManager::Create() {
1178 return new MidiManagerWin();
1182 } // namespace media