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