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"
9 // Prevent unnecessary functions from being included from <mmsystem.h>
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"
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
;
42 return base::WideToUTF8(text
);
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
;
54 return base::WideToUTF8(text
);
57 class MIDIHDRDeleter
{
59 void operator()(MIDIHDR
* header
) {
62 delete[] static_cast<char*>(header
->lpData
);
63 header
->lpData
= NULL
;
64 header
->dwBufferLength
= 0;
69 typedef scoped_ptr
<MIDIHDR
, MIDIHDRDeleter
> ScopedMIDIHDR
;
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
;
79 void SendShortMidiMessageInternal(HMIDIOUT midi_out_handle
,
80 const std::vector
<uint8
>& message
) {
81 if (message
.size() >= 4)
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
);
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.
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();
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
]);
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
);
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
);
144 // The ownership of |midi_header| is moved to MOM_DONE event handler.
145 midi_header
.release();
150 class MidiManagerWin::InDeviceInfo
{
155 void set_port_index(int index
) {
158 int port_index() const {
161 bool device_to_be_closed() const {
162 return device_to_be_closed_
;
164 HMIDIIN
midi_handle() const {
168 static scoped_ptr
<InDeviceInfo
> Create(MidiManagerWin
* manager
,
170 scoped_ptr
<InDeviceInfo
> obj(new InDeviceInfo(manager
));
171 if (!obj
->Initialize(device_id
))
177 static const int kInvalidPortIndex
= -1;
178 static const size_t kBufferLength
= 32 * 1024;
180 explicit InDeviceInfo(MidiManagerWin
* manager
)
182 port_index_(kInvalidPortIndex
),
185 device_to_be_closed_(false) {
188 bool Initialize(DWORD device_id
) {
190 midi_header_
= CreateMIDIHDR(kBufferLength
);
192 // Here we use |CALLBACK_FUNCTION| to subscribe MIM_DATA, MIM_LONGDATA, and
194 // - MIM_DATA: This is the only way to get a short MIDI message with
195 // timestamp information.
196 // - MIM_LONGDATA: This is the only way to get a long MIDI message with
197 // timestamp information.
198 // - MIM_CLOSE: This event is sent when 1) midiInClose() is called, or 2)
199 // the MIDI device becomes unavailable for some reasons, e.g., the cable
200 // is disconnected. As for the former case, HMIDIOUT will be invalidated
201 // soon after the callback is finished. As for the later case, however,
202 // HMIDIOUT continues to be valid until midiInClose() is called.
203 MMRESULT result
= midiInOpen(&midi_handle_
,
205 reinterpret_cast<DWORD_PTR
>(&HandleMessage
),
206 reinterpret_cast<DWORD_PTR
>(this),
208 if (result
!= MMSYSERR_NOERROR
) {
209 DLOG(ERROR
) << "Failed to open output device. "
210 << " id: " << device_id
211 << " message: " << GetInErrorMessage(result
);
214 result
= midiInPrepareHeader(
215 midi_handle_
, midi_header_
.get(), sizeof(*midi_header_
));
216 if (result
!= MMSYSERR_NOERROR
) {
217 DLOG(ERROR
) << "Failed to initialize input buffer: "
218 << GetInErrorMessage(result
);
221 result
= midiInAddBuffer(
222 midi_handle_
, midi_header_
.get(), sizeof(*midi_header_
));
223 if (result
!= MMSYSERR_NOERROR
) {
224 DLOG(ERROR
) << "Failed to attach input buffer: "
225 << GetInErrorMessage(result
);
228 result
= midiInStart(midi_handle_
);
229 if (result
!= MMSYSERR_NOERROR
) {
230 DLOG(ERROR
) << "Failed to start input port: "
231 << GetInErrorMessage(result
);
235 start_time_
= base::TimeTicks::Now();
239 void Uninitialize() {
240 MMRESULT result
= MMSYSERR_NOERROR
;
241 if (midi_handle_
&& started_
) {
242 result
= midiInStop(midi_handle_
);
243 DLOG_IF(ERROR
, result
!= MMSYSERR_NOERROR
)
244 << "Failed to stop input port: " << GetInErrorMessage(result
);
246 start_time_
= base::TimeTicks();
249 // midiInReset flushes pending messages. We ignore these messages.
250 device_to_be_closed_
= true;
251 result
= midiInReset(midi_handle_
);
252 DLOG_IF(ERROR
, result
!= MMSYSERR_NOERROR
)
253 << "Failed to reset input port: " << GetInErrorMessage(result
);
254 result
= midiInClose(midi_handle_
);
255 device_to_be_closed_
= false;
256 DLOG_IF(ERROR
, result
!= MMSYSERR_NOERROR
)
257 << "Failed to close input port: " << GetInErrorMessage(result
);
258 midi_header_
.reset();
260 port_index_
= kInvalidPortIndex
;
264 static void CALLBACK
HandleMessage(HMIDIIN midi_in_handle
,
269 // This method can be called back on any thread depending on Windows
270 // multimedia subsystem and underlying MIDI drivers.
271 InDeviceInfo
* self
= reinterpret_cast<InDeviceInfo
*>(instance
);
274 if (self
->midi_handle() != midi_in_handle
)
279 self
->OnShortMessageReceived(static_cast<uint8
>(param1
& 0xff),
280 static_cast<uint8
>((param1
>> 8) & 0xff),
281 static_cast<uint8
>((param1
>> 16) & 0xff),
285 self
->OnLongMessageReceived(reinterpret_cast<MIDIHDR
*>(param1
),
289 // TODO(yukawa): Implement crbug.com/279097.
294 void OnShortMessageReceived(uint8 status_byte
,
295 uint8 first_data_byte
,
296 uint8 second_data_byte
,
298 if (device_to_be_closed())
300 const size_t len
= GetMidiMessageLength(status_byte
);
301 if (len
== 0 || port_index() == kInvalidPortIndex
)
303 const uint8 kData
[] = { status_byte
, first_data_byte
, second_data_byte
};
304 DCHECK_LE(len
, arraysize(kData
));
305 OnMessageReceived(kData
, len
, elapsed_ms
);
308 void OnLongMessageReceived(MIDIHDR
* header
, DWORD elapsed_ms
) {
309 if (header
!= midi_header_
.get())
311 MMRESULT result
= MMSYSERR_NOERROR
;
312 if (device_to_be_closed()) {
314 (midi_header_
->dwFlags
& MHDR_PREPARED
) == MHDR_PREPARED
) {
315 result
= midiInUnprepareHeader(
316 midi_handle_
, midi_header_
.get(), sizeof(*midi_header_
));
317 DLOG_IF(ERROR
, result
!= MMSYSERR_NOERROR
)
318 << "Failed to uninitialize input buffer: "
319 << GetInErrorMessage(result
);
323 if (header
->dwBytesRecorded
> 0 && port_index() != kInvalidPortIndex
) {
324 OnMessageReceived(reinterpret_cast<const uint8
*>(header
->lpData
),
325 header
->dwBytesRecorded
,
328 result
= midiInAddBuffer(midi_handle_
, header
, sizeof(*header
));
329 DLOG_IF(ERROR
, result
!= MMSYSERR_NOERROR
)
330 << "Failed to attach input port: " << GetInErrorMessage(result
);
333 void OnMessageReceived(const uint8
* data
, size_t length
, DWORD elapsed_ms
) {
334 // MIM_DATA/MIM_LONGDATA message treats the time when midiInStart() is
335 // called as the origin of |elapsed_ms|.
336 // http://msdn.microsoft.com/en-us/library/windows/desktop/dd757284.aspx
337 // http://msdn.microsoft.com/en-us/library/windows/desktop/dd757286.aspx
338 const base::TimeTicks event_time
=
339 start_time_
+ base::TimeDelta::FromMilliseconds(elapsed_ms
);
340 // MidiManager::ReceiveMidiData() expects |timestamp| as the elapsed seconds
341 // from base::TimeTicks::Now().
342 // TODO(yukawa): Update MidiManager::ReceiveMidiData() so that it can
343 // receive |event_time| directly if the precision of base::TimeTicks is
345 const double timestamp
= (event_time
- base::TimeTicks()).InSecondsF();
346 manager_
->ReceiveMidiData(port_index_
, data
, length
, timestamp
);
349 MidiManagerWin
* manager_
;
351 HMIDIIN midi_handle_
;
352 ScopedMIDIHDR midi_header_
;
353 base::TimeTicks start_time_
;
355 bool device_to_be_closed_
;
356 DISALLOW_COPY_AND_ASSIGN(MidiManagerWin::InDeviceInfo
);
359 class MidiManagerWin::OutDeviceInfo
{
365 static scoped_ptr
<OutDeviceInfo
> Create(UINT device_id
) {
366 scoped_ptr
<OutDeviceInfo
> obj(new OutDeviceInfo
);
367 if (!obj
->Initialize(device_id
))
372 HMIDIOUT
midi_handle() const {
380 void Send(const std::vector
<uint8
>& data
) {
381 // Check if the attached device is still available or not.
385 // Give up sending MIDI messages here if the device is already closed.
386 // Note that this check is optional. Regardless of that we check |closed_|
387 // or not, nothing harmful happens as long as |midi_handle_| is still valid.
391 // MIDI Running status must be filtered out.
392 MidiMessageQueue
message_queue(false);
393 message_queue
.Add(data
);
394 std::vector
<uint8
> message
;
396 message_queue
.Get(&message
);
399 // SendShortMidiMessageInternal can send a MIDI message up to 3 bytes.
400 if (message
.size() <= 3)
401 SendShortMidiMessageInternal(midi_handle_
, message
);
403 SendLongMidiMessageInternal(midi_handle_
, message
);
409 : midi_handle_(NULL
),
413 bool Initialize(DWORD device_id
) {
415 // Here we use |CALLBACK_FUNCTION| to subscribe MOM_DONE and MOM_CLOSE
417 // - MOM_DONE: SendLongMidiMessageInternal() relies on this event to clean
418 // up the backing store where a long MIDI message is stored.
419 // - MOM_CLOSE: This event is sent when 1) midiOutClose() is called, or 2)
420 // the MIDI device becomes unavailable for some reasons, e.g., the cable
421 // is disconnected. As for the former case, HMIDIOUT will be invalidated
422 // soon after the callback is finished. As for the later case, however,
423 // HMIDIOUT continues to be valid until midiOutClose() is called.
424 MMRESULT result
= midiOutOpen(&midi_handle_
,
426 reinterpret_cast<DWORD_PTR
>(&HandleMessage
),
427 reinterpret_cast<DWORD_PTR
>(this),
429 if (result
!= MMSYSERR_NOERROR
) {
430 DLOG(ERROR
) << "Failed to open output device. "
431 << " id: " << device_id
432 << " message: "<< GetOutErrorMessage(result
);
439 void Uninitialize() {
443 MMRESULT result
= midiOutReset(midi_handle_
);
444 DLOG_IF(ERROR
, result
!= MMSYSERR_NOERROR
)
445 << "Failed to reset output port: " << GetOutErrorMessage(result
);
446 result
= midiOutClose(midi_handle_
);
447 DLOG_IF(ERROR
, result
!= MMSYSERR_NOERROR
)
448 << "Failed to close output port: " << GetOutErrorMessage(result
);
453 static void CALLBACK
HandleMessage(HMIDIOUT midi_out_handle
,
458 // This method can be called back on any thread depending on Windows
459 // multimedia subsystem and underlying MIDI drivers.
461 OutDeviceInfo
* self
= reinterpret_cast<OutDeviceInfo
*>(instance
);
464 if (self
->midi_handle() != midi_out_handle
)
468 // Take ownership of the MIDIHDR object.
469 ScopedMIDIHDR
header(reinterpret_cast<MIDIHDR
*>(param1
));
472 MMRESULT result
= midiOutUnprepareHeader(
473 self
->midi_handle(), header
.get(), sizeof(*header
));
474 DLOG_IF(ERROR
, result
!= MMSYSERR_NOERROR
)
475 << "Failed to uninitialize output buffer: "
476 << GetOutErrorMessage(result
);
480 // No lock is required since this flag is just a hint to avoid
481 // unnecessary API calls that will result in failure anyway.
482 self
->closed_
= true;
483 // TODO(yukawa): Implement crbug.com/279097.
488 HMIDIOUT midi_handle_
;
490 // True if the device is already closed.
491 volatile bool closed_
;
493 // True if the MidiManagerWin is trying to stop the sender thread.
494 volatile bool quitting_
;
496 DISALLOW_COPY_AND_ASSIGN(MidiManagerWin::OutDeviceInfo
);
499 MidiManagerWin::MidiManagerWin()
500 : send_thread_("MidiSendThread") {
503 bool MidiManagerWin::Initialize() {
504 const UINT num_in_devices
= midiInGetNumDevs();
505 in_devices_
.reserve(num_in_devices
);
506 for (UINT device_id
= 0; device_id
< num_in_devices
; ++device_id
) {
507 MIDIINCAPS caps
= {};
508 MMRESULT result
= midiInGetDevCaps(device_id
, &caps
, sizeof(caps
));
509 if (result
!= MMSYSERR_NOERROR
) {
510 DLOG(ERROR
) << "Failed to obtain input device info: "
511 << GetInErrorMessage(result
);
514 scoped_ptr
<InDeviceInfo
> in_device(InDeviceInfo::Create(this, device_id
));
518 base::IntToString(static_cast<int>(device_id
)),
520 base::WideToUTF8(caps
.szPname
),
521 base::IntToString(static_cast<int>(caps
.vDriverVersion
)));
523 in_device
->set_port_index(input_ports_
.size() - 1);
524 in_devices_
.push_back(in_device
.Pass());
527 const UINT num_out_devices
= midiOutGetNumDevs();
528 out_devices_
.reserve(num_out_devices
);
529 for (UINT device_id
= 0; device_id
< num_out_devices
; ++device_id
) {
530 MIDIOUTCAPS caps
= {};
531 MMRESULT result
= midiOutGetDevCaps(device_id
, &caps
, sizeof(caps
));
532 if (result
!= MMSYSERR_NOERROR
) {
533 DLOG(ERROR
) << "Failed to obtain output device info: "
534 << GetOutErrorMessage(result
);
537 scoped_ptr
<OutDeviceInfo
> out_port(OutDeviceInfo::Create(device_id
));
541 base::IntToString(static_cast<int>(device_id
)),
543 base::WideToUTF8(caps
.szPname
),
544 base::IntToString(static_cast<int>(caps
.vDriverVersion
)));
546 out_devices_
.push_back(out_port
.Pass());
552 MidiManagerWin::~MidiManagerWin() {
553 // Cleanup order is important. |send_thread_| must be stopped before
554 // |out_devices_| is cleared.
555 for (size_t i
= 0; i
< output_ports_
.size(); ++i
)
556 out_devices_
[i
]->Quit();
559 out_devices_
.clear();
560 output_ports_
.clear();
562 input_ports_
.clear();
565 void MidiManagerWin::DispatchSendMidiData(MidiManagerClient
* client
,
567 const std::vector
<uint8
>& data
,
569 if (out_devices_
.size() <= port_index
)
572 base::TimeDelta delay
;
573 if (timestamp
!= 0.0) {
574 base::TimeTicks time_to_send
=
575 base::TimeTicks() + base::TimeDelta::FromMicroseconds(
576 timestamp
* base::Time::kMicrosecondsPerSecond
);
577 delay
= std::max(time_to_send
- base::TimeTicks::Now(), base::TimeDelta());
580 if (!send_thread_
.IsRunning())
581 send_thread_
.Start();
583 OutDeviceInfo
* out_port
= out_devices_
[port_index
].get();
584 send_thread_
.message_loop()->PostDelayedTask(
586 base::Bind(&OutDeviceInfo::Send
, base::Unretained(out_port
), data
),
589 // Call back AccumulateMidiBytesSent() on |send_thread_| to emulate the
590 // behavior of MidiManagerMac::SendMidiData.
591 // TODO(yukawa): Do this task in a platform-independent way if possible.
592 // See crbug.com/325810.
593 send_thread_
.message_loop()->PostTask(
595 base::Bind(&MidiManagerClient::AccumulateMidiBytesSent
,
596 base::Unretained(client
), data
.size()));
599 MidiManager
* MidiManager::Create() {
600 return new MidiManagerWin();