All instances of addresses as void* are changed to uintptr_t in
[chromium-blink-merge.git] / media / midi / midi_manager_win.cc
blob40f2fb854e5889fce82a2f4f4aacf2baa64d1a59
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 <ks.h>
9 #include <ksmedia.h>
10 #include <mmreg.h>
11 // Prevent unnecessary functions from being included from <mmsystem.h>
12 #define MMNODRV
13 #define MMNOSOUND
14 #define MMNOWAVE
15 #define MMNOAUX
16 #define MMNOMIXER
17 #define MMNOTIMER
18 #define MMNOJOY
19 #define MMNOMCI
20 #define MMNOMMIO
21 #include <mmsystem.h>
23 #include <algorithm>
24 #include <functional>
25 #include <queue>
26 #include <string>
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"
45 namespace media {
46 namespace midi {
47 namespace {
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;
62 return std::string();
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;
74 return std::string();
76 return base::WideToUTF8(text);
79 std::string MmversionToString(MMVERSION version) {
80 return base::StringPrintf("%d.%d", HIBYTE(version), LOBYTE(version));
83 class MIDIHDRDeleter {
84 public:
85 void operator()(MIDIHDR* header) {
86 if (!header)
87 return;
88 delete[] static_cast<char*>(header->lpData);
89 header->lpData = NULL;
90 header->dwBufferLength = 0;
91 delete header;
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
123 // cases:
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();
143 return;
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);
154 return;
157 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);
167 return;
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]) {
176 size_t len = 0;
177 for (len = 0; len < array_size; ++len) {
178 if (buffer[len] == L'\0')
179 break;
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.
237 struct Hasher {
238 size_t operator()(const MidiDeviceInfo& info) const {
239 size_t hash = info.manufacturer_id;
240 hash *= 131;
241 hash += info.product_id;
242 hash *= 131;
243 hash += info.driver_version;
244 hash *= 131;
245 hash += info.product_name.size();
246 hash *= 131;
247 if (!info.product_name.empty()) {
248 hash += info.product_name[0];
250 hash *= 131;
251 hash += info.usb_vendor_id;
252 hash *= 131;
253 hash += info.usb_product_id;
254 return hash;
258 private:
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))
272 return 0;
273 return EXTRACT_USBAUDIO_MID(&caps.ManufacturerGuid);
275 static uint16 ExtractUsbVendorIdIfExists(const MIDIOUTCAPS2W& caps) {
276 if (!IS_COMPATIBLE_USBAUDIO_MID(&caps.ManufacturerGuid))
277 return 0;
278 return EXTRACT_USBAUDIO_MID(&caps.ManufacturerGuid);
280 static uint16 ExtractUsbProductIdIfExists(const MIDIINCAPS2W& caps) {
281 if (!IS_COMPATIBLE_USBAUDIO_PID(&caps.ProductGuid))
282 return 0;
283 return EXTRACT_USBAUDIO_PID(&caps.ProductGuid);
285 static uint16 ExtractUsbProductIdIfExists(const MIDIOUTCAPS2W& caps) {
286 if (!IS_COMPATIBLE_USBAUDIO_PID(&caps.ProductGuid))
287 return 0;
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) {
299 case MM_MICROSOFT:
300 return "Microsoft Corporation";
301 default:
302 // TODO(toyoshim): Support other manufacture IDs. crbug.com/472341.
303 return "";
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<
314 MidiDeviceInfo,
315 std::priority_queue<uint32, std::vector<uint32>, std::greater<uint32>>,
316 MidiDeviceInfo::Hasher>;
318 struct MidiInputDeviceState final
319 : base::RefCountedThreadSafe<MidiInputDeviceState> {
320 explicit MidiInputDeviceState(const MidiDeviceInfo& device_info)
321 : device_info(device_info),
322 midi_handle(kInvalidMidiInHandle),
323 port_index(0),
324 port_age(0),
325 start_time_initialized(false) {}
327 const MidiDeviceInfo device_info;
328 HMIDIIN midi_handle;
329 ScopedMIDIHDR midi_header;
330 // Since Win32 multimedia system uses a relative time offset from when
331 // |midiInStart| API is called, we need to record when it is called.
332 base::TimeTicks start_time;
333 // 0-based port index. We will try to reuse the previous port index when the
334 // MIDI device is closed then reopened.
335 uint32 port_index;
336 // A sequence number which represents how many times |port_index| is reused.
337 // We can remove this field if we decide not to clear unsent events
338 // when the device is disconnected.
339 // See https://github.com/WebAudio/web-midi-api/issues/133
340 uint64 port_age;
341 // True if |start_time| is initialized. This field is not used so far, but
342 // kept for the debugging purpose.
343 bool start_time_initialized;
345 private:
346 friend class base::RefCountedThreadSafe<MidiInputDeviceState>;
347 ~MidiInputDeviceState() {}
350 struct MidiOutputDeviceState final
351 : base::RefCountedThreadSafe<MidiOutputDeviceState> {
352 explicit MidiOutputDeviceState(const MidiDeviceInfo& device_info)
353 : device_info(device_info),
354 midi_handle(kInvalidMidiOutHandle),
355 port_index(0),
356 port_age(0),
357 closed(false) {}
359 const MidiDeviceInfo device_info;
360 HMIDIOUT midi_handle;
361 // 0-based port index. We will try to reuse the previous port index when the
362 // MIDI device is closed then reopened.
363 uint32 port_index;
364 // A sequence number which represents how many times |port_index| is reused.
365 // We can remove this field if we decide not to clear unsent events
366 // when the device is disconnected.
367 // See https://github.com/WebAudio/web-midi-api/issues/133
368 uint64 port_age;
369 // True if the device is already closed and |midi_handle| is considered to be
370 // invalid.
371 // TODO(toyoshim): Use std::atomic<bool> when it is allowed in Chromium
372 // project.
373 volatile bool closed;
375 private:
376 friend class base::RefCountedThreadSafe<MidiOutputDeviceState>;
377 ~MidiOutputDeviceState() {}
380 // The core logic of MIDI device handling for Windows. Basically this class is
381 // shared among following 4 threads:
382 // 1. Chrome IO Thread
383 // 2. OS Multimedia Thread
384 // 3. Task Thread
385 // 4. Sender Thread
387 // Chrome IO Thread:
388 // MidiManager runs on Chrome IO thread. Device change notification is
389 // delivered to the thread through the SystemMonitor service.
390 // OnDevicesChanged() callback is invoked to update the MIDI device list.
391 // Note that in the current implementation we will try to open all the
392 // existing devices in practice. This is OK because trying to reopen a MIDI
393 // device that is already opened would simply fail, and there is no unwilling
394 // side effect.
396 // OS Multimedia Thread:
397 // This thread is maintained by the OS as a part of MIDI runtime, and
398 // responsible for receiving all the system initiated events such as device
399 // close, and receiving data. For performance reasons, most of potentially
400 // blocking operations will be dispatched into Task Thread.
402 // Task Thread:
403 // This thread will be used to call back following methods of MidiManager.
404 // - MidiManager::CompleteInitialization
405 // - MidiManager::AddInputPort
406 // - MidiManager::AddOutputPort
407 // - MidiManager::SetInputPortState
408 // - MidiManager::SetOutputPortState
409 // - MidiManager::ReceiveMidiData
411 // Sender Thread:
412 // This thread will be used to call Win32 APIs to send MIDI message at the
413 // specified time. We don't want to call MIDI send APIs on Task Thread
414 // because those APIs could be performed synchronously, hence they could block
415 // the caller thread for a while. See the comment in
416 // SendLongMidiMessageInternal for details. Currently we expect that the
417 // blocking time would be less than 1 second.
418 class MidiServiceWinImpl : public MidiServiceWin,
419 public base::SystemMonitor::DevicesChangedObserver {
420 public:
421 MidiServiceWinImpl()
422 : delegate_(nullptr),
423 sender_thread_("Windows MIDI sender thread"),
424 task_thread_("Windows MIDI task thread"),
425 destructor_started(false) {}
427 ~MidiServiceWinImpl() final {
428 // Start() and Stop() of the threads, and AddDevicesChangeObserver() and
429 // RemoveDevicesChangeObserver() should be called on the same thread.
430 CHECK(thread_checker_.CalledOnValidThread());
432 destructor_started = true;
433 base::SystemMonitor::Get()->RemoveDevicesChangedObserver(this);
435 std::vector<HMIDIIN> input_devices;
437 base::AutoLock auto_lock(input_ports_lock_);
438 for (auto it : input_device_map_)
439 input_devices.push_back(it.first);
442 for (const auto handle : input_devices) {
443 MMRESULT result = midiInClose(handle);
444 if (result == MIDIERR_STILLPLAYING) {
445 result = midiInReset(handle);
446 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
447 << "midiInReset failed: " << GetInErrorMessage(result);
448 result = midiInClose(handle);
450 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
451 << "midiInClose failed: " << GetInErrorMessage(result);
456 std::vector<HMIDIOUT> output_devices;
458 base::AutoLock auto_lock(output_ports_lock_);
459 for (auto it : output_device_map_)
460 output_devices.push_back(it.first);
463 for (const auto handle : output_devices) {
464 MMRESULT result = midiOutClose(handle);
465 if (result == MIDIERR_STILLPLAYING) {
466 result = midiOutReset(handle);
467 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
468 << "midiOutReset failed: " << GetOutErrorMessage(result);
469 result = midiOutClose(handle);
471 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
472 << "midiOutClose failed: " << GetOutErrorMessage(result);
476 sender_thread_.Stop();
477 task_thread_.Stop();
480 // MidiServiceWin overrides:
481 void InitializeAsync(MidiServiceWinDelegate* delegate) final {
482 // Start() and Stop() of the threads, and AddDevicesChangeObserver() and
483 // RemoveDevicesChangeObserver() should be called on the same thread.
484 CHECK(thread_checker_.CalledOnValidThread());
486 delegate_ = delegate;
488 sender_thread_.Start();
489 task_thread_.Start();
491 // Start monitoring device changes. This should start before the
492 // following UpdateDeviceList() call not to miss the event happening
493 // between the call and the observer registration.
494 base::SystemMonitor::Get()->AddDevicesChangedObserver(this);
496 UpdateDeviceList();
498 task_thread_.message_loop()->PostTask(
499 FROM_HERE,
500 base::Bind(&MidiServiceWinImpl::CompleteInitializationOnTaskThread,
501 base::Unretained(this), Result::OK));
504 void SendMidiDataAsync(uint32 port_number,
505 const std::vector<uint8>& data,
506 base::TimeTicks time) final {
507 if (destructor_started) {
508 LOG(ERROR) << "ThreadSafeSendData failed because MidiServiceWinImpl is "
509 "being destructed. port: " << port_number;
510 return;
512 auto state = GetOutputDeviceFromPort(port_number);
513 if (!state) {
514 LOG(ERROR) << "ThreadSafeSendData failed due to an invalid port number. "
515 << "port: " << port_number;
516 return;
518 if (state->closed) {
519 LOG(ERROR)
520 << "ThreadSafeSendData failed because target port is already closed."
521 << "port: " << port_number;
522 return;
524 const auto now = base::TimeTicks::Now();
525 if (now < time) {
526 sender_thread_.message_loop()->PostDelayedTask(
527 FROM_HERE, base::Bind(&MidiServiceWinImpl::SendOnSenderThread,
528 base::Unretained(this), port_number,
529 state->port_age, data, time),
530 time - now);
531 } else {
532 sender_thread_.message_loop()->PostTask(
533 FROM_HERE, base::Bind(&MidiServiceWinImpl::SendOnSenderThread,
534 base::Unretained(this), port_number,
535 state->port_age, data, time));
539 // base::SystemMonitor::DevicesChangedObserver overrides:
540 void OnDevicesChanged(base::SystemMonitor::DeviceType device_type) final {
541 CHECK(thread_checker_.CalledOnValidThread());
542 if (destructor_started)
543 return;
545 switch (device_type) {
546 case base::SystemMonitor::DEVTYPE_AUDIO_CAPTURE:
547 case base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE:
548 // Add case of other unrelated device types here.
549 return;
550 case base::SystemMonitor::DEVTYPE_UNKNOWN:
551 // Interested in MIDI devices. Try updating the device list.
552 UpdateDeviceList();
553 break;
554 // No default here to capture new DeviceType by compile time.
558 private:
559 scoped_refptr<MidiInputDeviceState> GetInputDeviceFromHandle(
560 HMIDIIN midi_handle) {
561 base::AutoLock auto_lock(input_ports_lock_);
562 const auto it = input_device_map_.find(midi_handle);
563 return (it != input_device_map_.end() ? it->second : nullptr);
566 scoped_refptr<MidiOutputDeviceState> GetOutputDeviceFromHandle(
567 HMIDIOUT midi_handle) {
568 base::AutoLock auto_lock(output_ports_lock_);
569 const auto it = output_device_map_.find(midi_handle);
570 return (it != output_device_map_.end() ? it->second : nullptr);
573 scoped_refptr<MidiOutputDeviceState> GetOutputDeviceFromPort(
574 uint32 port_number) {
575 base::AutoLock auto_lock(output_ports_lock_);
576 if (output_ports_.size() <= port_number)
577 return nullptr;
578 return output_ports_[port_number];
581 void UpdateDeviceList() {
582 task_thread_.message_loop()->PostTask(
583 FROM_HERE, base::Bind(&MidiServiceWinImpl::UpdateDeviceListOnTaskThread,
584 base::Unretained(this)));
587 /////////////////////////////////////////////////////////////////////////////
588 // Callbacks on the OS multimedia thread.
589 /////////////////////////////////////////////////////////////////////////////
591 static void CALLBACK
592 OnMidiInEventOnMainlyMultimediaThread(HMIDIIN midi_in_handle,
593 UINT message,
594 DWORD_PTR instance,
595 DWORD_PTR param1,
596 DWORD_PTR param2) {
597 MidiServiceWinImpl* self = reinterpret_cast<MidiServiceWinImpl*>(instance);
598 if (!self)
599 return;
600 switch (message) {
601 case MIM_OPEN:
602 self->OnMidiInOpen(midi_in_handle);
603 break;
604 case MIM_DATA:
605 self->OnMidiInDataOnMultimediaThread(midi_in_handle, param1, param2);
606 break;
607 case MIM_LONGDATA:
608 self->OnMidiInLongDataOnMultimediaThread(midi_in_handle, param1,
609 param2);
610 break;
611 case MIM_CLOSE:
612 self->OnMidiInCloseOnMultimediaThread(midi_in_handle);
613 break;
617 void OnMidiInOpen(HMIDIIN midi_in_handle) {
618 UINT device_id = 0;
619 MMRESULT result = midiInGetID(midi_in_handle, &device_id);
620 if (result != MMSYSERR_NOERROR) {
621 DLOG(ERROR) << "midiInGetID failed: " << GetInErrorMessage(result);
622 return;
624 MIDIINCAPS2W caps = {};
625 result = midiInGetDevCaps(device_id, reinterpret_cast<LPMIDIINCAPSW>(&caps),
626 sizeof(caps));
627 if (result != MMSYSERR_NOERROR) {
628 DLOG(ERROR) << "midiInGetDevCaps failed: " << GetInErrorMessage(result);
629 return;
631 auto state =
632 make_scoped_refptr(new MidiInputDeviceState(MidiDeviceInfo(caps)));
633 state->midi_handle = midi_in_handle;
634 state->midi_header = CreateMIDIHDR(kBufferLength);
635 const auto& state_device_info = state->device_info;
636 bool add_new_port = false;
637 uint32 port_number = 0;
639 base::AutoLock auto_lock(input_ports_lock_);
640 const auto it = unused_input_ports_.find(state_device_info);
641 if (it == unused_input_ports_.end()) {
642 port_number = static_cast<uint32>(input_ports_.size());
643 add_new_port = true;
644 input_ports_.push_back(nullptr);
645 input_ports_ages_.push_back(0);
646 } else {
647 port_number = it->second.top();
648 it->second.pop();
649 if (it->second.empty()) {
650 unused_input_ports_.erase(it);
653 input_ports_[port_number] = state;
655 input_ports_ages_[port_number] += 1;
656 input_device_map_[input_ports_[port_number]->midi_handle] =
657 input_ports_[port_number];
658 input_ports_[port_number]->port_index = port_number;
659 input_ports_[port_number]->port_age = input_ports_ages_[port_number];
661 // Several initial startup tasks cannot be done in MIM_OPEN handler.
662 task_thread_.message_loop()->PostTask(
663 FROM_HERE, base::Bind(&MidiServiceWinImpl::StartInputDeviceOnTaskThread,
664 base::Unretained(this), midi_in_handle));
665 if (add_new_port) {
666 const MidiPortInfo port_info(
667 // TODO(toyoshim): Use a hash ID insted crbug.com/467448
668 base::IntToString(static_cast<int>(port_number)),
669 GetManufacturerName(state_device_info),
670 base::WideToUTF8(state_device_info.product_name),
671 MmversionToString(state_device_info.driver_version),
672 MIDI_PORT_OPENED);
673 task_thread_.message_loop()->PostTask(
674 FROM_HERE, base::Bind(&MidiServiceWinImpl::AddInputPortOnTaskThread,
675 base::Unretained(this), port_info));
676 } else {
677 task_thread_.message_loop()->PostTask(
678 FROM_HERE,
679 base::Bind(&MidiServiceWinImpl::SetInputPortStateOnTaskThread,
680 base::Unretained(this), port_number,
681 MidiPortState::MIDI_PORT_CONNECTED));
685 void OnMidiInDataOnMultimediaThread(HMIDIIN midi_in_handle,
686 DWORD_PTR param1,
687 DWORD_PTR param2) {
688 auto state = GetInputDeviceFromHandle(midi_in_handle);
689 if (!state)
690 return;
691 const uint8 status_byte = static_cast<uint8>(param1 & 0xff);
692 const uint8 first_data_byte = static_cast<uint8>((param1 >> 8) & 0xff);
693 const uint8 second_data_byte = static_cast<uint8>((param1 >> 16) & 0xff);
694 const DWORD elapsed_ms = param2;
695 const size_t len = GetMidiMessageLength(status_byte);
696 const uint8 kData[] = {status_byte, first_data_byte, second_data_byte};
697 std::vector<uint8> data;
698 data.assign(kData, kData + len);
699 DCHECK_LE(len, arraysize(kData));
700 // MIM_DATA/MIM_LONGDATA message treats the time when midiInStart() is
701 // called as the origin of |elapsed_ms|.
702 // http://msdn.microsoft.com/en-us/library/windows/desktop/dd757284.aspx
703 // http://msdn.microsoft.com/en-us/library/windows/desktop/dd757286.aspx
704 const base::TimeTicks event_time =
705 state->start_time + base::TimeDelta::FromMilliseconds(elapsed_ms);
706 task_thread_.message_loop()->PostTask(
707 FROM_HERE, base::Bind(&MidiServiceWinImpl::ReceiveMidiDataOnTaskThread,
708 base::Unretained(this), state->port_index, data,
709 event_time));
712 void OnMidiInLongDataOnMultimediaThread(HMIDIIN midi_in_handle,
713 DWORD_PTR param1,
714 DWORD_PTR param2) {
715 auto state = GetInputDeviceFromHandle(midi_in_handle);
716 if (!state)
717 return;
718 MIDIHDR* header = reinterpret_cast<MIDIHDR*>(param1);
719 const DWORD elapsed_ms = param2;
720 MMRESULT result = MMSYSERR_NOERROR;
721 if (destructor_started) {
722 if (state->midi_header &&
723 (state->midi_header->dwFlags & MHDR_PREPARED) == MHDR_PREPARED) {
724 result =
725 midiInUnprepareHeader(state->midi_handle, state->midi_header.get(),
726 sizeof(*state->midi_header));
727 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
728 << "Failed to uninitialize input buffer: "
729 << GetInErrorMessage(result);
731 return;
733 if (header->dwBytesRecorded > 0) {
734 const uint8* src = reinterpret_cast<const uint8*>(header->lpData);
735 std::vector<uint8> data;
736 data.assign(src, src + header->dwBytesRecorded);
737 // MIM_DATA/MIM_LONGDATA message treats the time when midiInStart() is
738 // called as the origin of |elapsed_ms|.
739 // http://msdn.microsoft.com/en-us/library/windows/desktop/dd757284.aspx
740 // http://msdn.microsoft.com/en-us/library/windows/desktop/dd757286.aspx
741 const base::TimeTicks event_time =
742 state->start_time + base::TimeDelta::FromMilliseconds(elapsed_ms);
743 task_thread_.message_loop()->PostTask(
744 FROM_HERE,
745 base::Bind(&MidiServiceWinImpl::ReceiveMidiDataOnTaskThread,
746 base::Unretained(this), state->port_index, data,
747 event_time));
749 result = midiInAddBuffer(state->midi_handle, header, sizeof(*header));
750 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
751 << "Failed to attach input buffer: " << GetInErrorMessage(result)
752 << "port number:" << state->port_index;
755 void OnMidiInCloseOnMultimediaThread(HMIDIIN midi_in_handle) {
756 auto state = GetInputDeviceFromHandle(midi_in_handle);
757 if (!state)
758 return;
759 const uint32 port_number = state->port_index;
760 const auto device_info(state->device_info);
762 base::AutoLock auto_lock(input_ports_lock_);
763 input_device_map_.erase(state->midi_handle);
764 input_ports_[port_number] = nullptr;
765 input_ports_ages_[port_number] += 1;
766 unused_input_ports_[device_info].push(port_number);
768 task_thread_.message_loop()->PostTask(
769 FROM_HERE,
770 base::Bind(&MidiServiceWinImpl::SetInputPortStateOnTaskThread,
771 base::Unretained(this), port_number,
772 MIDI_PORT_DISCONNECTED));
775 static void CALLBACK
776 OnMidiOutEventOnMainlyMultimediaThread(HMIDIOUT midi_out_handle,
777 UINT message,
778 DWORD_PTR instance,
779 DWORD_PTR param1,
780 DWORD_PTR param2) {
781 MidiServiceWinImpl* self = reinterpret_cast<MidiServiceWinImpl*>(instance);
782 if (!self)
783 return;
784 switch (message) {
785 case MOM_OPEN:
786 self->OnMidiOutOpen(midi_out_handle, param1, param2);
787 break;
788 case MOM_DONE:
789 self->OnMidiOutDoneOnMultimediaThread(midi_out_handle, param1);
790 break;
791 case MOM_CLOSE:
792 self->OnMidiOutCloseOnMultimediaThread(midi_out_handle);
793 break;
797 void OnMidiOutOpen(HMIDIOUT midi_out_handle,
798 DWORD_PTR param1,
799 DWORD_PTR param2) {
800 UINT device_id = 0;
801 MMRESULT result = midiOutGetID(midi_out_handle, &device_id);
802 if (result != MMSYSERR_NOERROR) {
803 DLOG(ERROR) << "midiOutGetID failed: " << GetOutErrorMessage(result);
804 return;
806 MIDIOUTCAPS2W caps = {};
807 result = midiOutGetDevCaps(
808 device_id, reinterpret_cast<LPMIDIOUTCAPSW>(&caps), sizeof(caps));
809 if (result != MMSYSERR_NOERROR) {
810 DLOG(ERROR) << "midiInGetDevCaps failed: " << GetOutErrorMessage(result);
811 return;
813 auto state =
814 make_scoped_refptr(new MidiOutputDeviceState(MidiDeviceInfo(caps)));
815 state->midi_handle = midi_out_handle;
816 const auto& state_device_info = state->device_info;
817 if (IsUnsupportedDevice(state_device_info))
818 return;
819 bool add_new_port = false;
820 uint32 port_number = 0;
822 base::AutoLock auto_lock(output_ports_lock_);
823 const auto it = unused_output_ports_.find(state_device_info);
824 if (it == unused_output_ports_.end()) {
825 port_number = static_cast<uint32>(output_ports_.size());
826 add_new_port = true;
827 output_ports_.push_back(nullptr);
828 output_ports_ages_.push_back(0);
829 } else {
830 port_number = it->second.top();
831 it->second.pop();
832 if (it->second.empty())
833 unused_output_ports_.erase(it);
835 output_ports_[port_number] = state;
836 output_ports_ages_[port_number] += 1;
837 output_device_map_[output_ports_[port_number]->midi_handle] =
838 output_ports_[port_number];
839 output_ports_[port_number]->port_index = port_number;
840 output_ports_[port_number]->port_age = output_ports_ages_[port_number];
842 if (add_new_port) {
843 const MidiPortInfo port_info(
844 // TODO(toyoshim): Use a hash ID insted. crbug.com/467448
845 base::IntToString(static_cast<int>(port_number)),
846 GetManufacturerName(state_device_info),
847 base::WideToUTF8(state_device_info.product_name),
848 MmversionToString(state_device_info.driver_version),
849 MIDI_PORT_OPENED);
850 task_thread_.message_loop()->PostTask(
851 FROM_HERE, base::Bind(&MidiServiceWinImpl::AddOutputPortOnTaskThread,
852 base::Unretained(this), port_info));
853 } else {
854 task_thread_.message_loop()->PostTask(
855 FROM_HERE,
856 base::Bind(&MidiServiceWinImpl::SetOutputPortStateOnTaskThread,
857 base::Unretained(this), port_number, MIDI_PORT_CONNECTED));
861 void OnMidiOutDoneOnMultimediaThread(HMIDIOUT midi_out_handle,
862 DWORD_PTR param1) {
863 auto state = GetOutputDeviceFromHandle(midi_out_handle);
864 if (!state)
865 return;
866 // Take ownership of the MIDIHDR object.
867 ScopedMIDIHDR header(reinterpret_cast<MIDIHDR*>(param1));
868 if (!header)
869 return;
870 MMRESULT result = midiOutUnprepareHeader(state->midi_handle, header.get(),
871 sizeof(*header));
872 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
873 << "Failed to uninitialize output buffer: "
874 << GetOutErrorMessage(result);
877 void OnMidiOutCloseOnMultimediaThread(HMIDIOUT midi_out_handle) {
878 auto state = GetOutputDeviceFromHandle(midi_out_handle);
879 if (!state)
880 return;
881 const uint32 port_number = state->port_index;
882 const auto device_info(state->device_info);
884 base::AutoLock auto_lock(output_ports_lock_);
885 output_device_map_.erase(state->midi_handle);
886 output_ports_[port_number] = nullptr;
887 output_ports_ages_[port_number] += 1;
888 unused_output_ports_[device_info].push(port_number);
889 state->closed = true;
891 task_thread_.message_loop()->PostTask(
892 FROM_HERE,
893 base::Bind(&MidiServiceWinImpl::SetOutputPortStateOnTaskThread,
894 base::Unretained(this), port_number,
895 MIDI_PORT_DISCONNECTED));
898 /////////////////////////////////////////////////////////////////////////////
899 // Callbacks on the sender thread.
900 /////////////////////////////////////////////////////////////////////////////
902 void AssertOnSenderThread() {
903 DCHECK_EQ(sender_thread_.GetThreadId(), base::PlatformThread::CurrentId());
906 void SendOnSenderThread(uint32 port_number,
907 uint64 port_age,
908 const std::vector<uint8>& data,
909 base::TimeTicks time) {
910 AssertOnSenderThread();
911 if (destructor_started) {
912 LOG(ERROR) << "ThreadSafeSendData failed because MidiServiceWinImpl is "
913 "being destructed. port: " << port_number;
915 auto state = GetOutputDeviceFromPort(port_number);
916 if (!state) {
917 LOG(ERROR) << "ThreadSafeSendData failed due to an invalid port number. "
918 << "port: " << port_number;
919 return;
921 if (state->closed) {
922 LOG(ERROR)
923 << "ThreadSafeSendData failed because target port is already closed."
924 << "port: " << port_number;
925 return;
927 if (state->port_age != port_age) {
928 LOG(ERROR)
929 << "ThreadSafeSendData failed because target port is being closed."
930 << "port: " << port_number << "expected port age: " << port_age
931 << "actual port age: " << state->port_age;
934 // MIDI Running status must be filtered out.
935 MidiMessageQueue message_queue(false);
936 message_queue.Add(data);
937 std::vector<uint8> message;
938 while (true) {
939 if (destructor_started)
940 break;
941 if (state->closed)
942 break;
943 message_queue.Get(&message);
944 if (message.empty())
945 break;
946 // SendShortMidiMessageInternal can send a MIDI message up to 3 bytes.
947 if (message.size() <= 3)
948 SendShortMidiMessageInternal(state->midi_handle, message);
949 else
950 SendLongMidiMessageInternal(state->midi_handle, message);
954 /////////////////////////////////////////////////////////////////////////////
955 // Callbacks on the task thread.
956 /////////////////////////////////////////////////////////////////////////////
958 void AssertOnTaskThread() {
959 DCHECK_EQ(task_thread_.GetThreadId(), base::PlatformThread::CurrentId());
962 void UpdateDeviceListOnTaskThread() {
963 AssertOnTaskThread();
964 const UINT num_in_devices = midiInGetNumDevs();
965 for (UINT device_id = 0; device_id < num_in_devices; ++device_id) {
966 // Here we use |CALLBACK_FUNCTION| to subscribe MIM_DATA, MIM_LONGDATA,
967 // MIM_OPEN, and MIM_CLOSE events.
968 // - MIM_DATA: This is the only way to get a short MIDI message with
969 // timestamp information.
970 // - MIM_LONGDATA: This is the only way to get a long MIDI message with
971 // timestamp information.
972 // - MIM_OPEN: This event is sent the input device is opened. Note that
973 // this message is called on the caller thread.
974 // - MIM_CLOSE: This event is sent when 1) midiInClose() is called, or 2)
975 // the MIDI device becomes unavailable for some reasons, e.g., the
976 // cable is disconnected. As for the former case, HMIDIOUT will be
977 // invalidated soon after the callback is finished. As for the later
978 // case, however, HMIDIOUT continues to be valid until midiInClose()
979 // is called.
980 HMIDIIN midi_handle = kInvalidMidiInHandle;
981 const MMRESULT result = midiInOpen(
982 &midi_handle, device_id,
983 reinterpret_cast<DWORD_PTR>(&OnMidiInEventOnMainlyMultimediaThread),
984 reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION);
985 DLOG_IF(ERROR, result != MMSYSERR_NOERROR && result != MMSYSERR_ALLOCATED)
986 << "Failed to open output device. "
987 << " id: " << device_id << " message: " << GetInErrorMessage(result);
990 const UINT num_out_devices = midiOutGetNumDevs();
991 for (UINT device_id = 0; device_id < num_out_devices; ++device_id) {
992 // Here we use |CALLBACK_FUNCTION| to subscribe MOM_DONE, MOM_OPEN, and
993 // MOM_CLOSE events.
994 // - MOM_DONE: SendLongMidiMessageInternal() relies on this event to clean
995 // up the backing store where a long MIDI message is stored.
996 // - MOM_OPEN: This event is sent the output device is opened. Note that
997 // this message is called on the caller thread.
998 // - MOM_CLOSE: This event is sent when 1) midiOutClose() is called, or 2)
999 // the MIDI device becomes unavailable for some reasons, e.g., the
1000 // cable is disconnected. As for the former case, HMIDIOUT will be
1001 // invalidated soon after the callback is finished. As for the later
1002 // case, however, HMIDIOUT continues to be valid until midiOutClose()
1003 // is called.
1004 HMIDIOUT midi_handle = kInvalidMidiOutHandle;
1005 const MMRESULT result = midiOutOpen(
1006 &midi_handle, device_id,
1007 reinterpret_cast<DWORD_PTR>(&OnMidiOutEventOnMainlyMultimediaThread),
1008 reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION);
1009 DLOG_IF(ERROR, result != MMSYSERR_NOERROR && result != MMSYSERR_ALLOCATED)
1010 << "Failed to open output device. "
1011 << " id: " << device_id << " message: " << GetOutErrorMessage(result);
1015 void StartInputDeviceOnTaskThread(HMIDIIN midi_in_handle) {
1016 AssertOnTaskThread();
1017 auto state = GetInputDeviceFromHandle(midi_in_handle);
1018 if (!state)
1019 return;
1020 MMRESULT result =
1021 midiInPrepareHeader(state->midi_handle, state->midi_header.get(),
1022 sizeof(*state->midi_header));
1023 if (result != MMSYSERR_NOERROR) {
1024 DLOG(ERROR) << "Failed to initialize input buffer: "
1025 << GetInErrorMessage(result);
1026 return;
1028 result = midiInAddBuffer(state->midi_handle, state->midi_header.get(),
1029 sizeof(*state->midi_header));
1030 if (result != MMSYSERR_NOERROR) {
1031 DLOG(ERROR) << "Failed to attach input buffer: "
1032 << GetInErrorMessage(result);
1033 return;
1035 result = midiInStart(state->midi_handle);
1036 if (result != MMSYSERR_NOERROR) {
1037 DLOG(ERROR) << "Failed to start input port: "
1038 << GetInErrorMessage(result);
1039 return;
1041 state->start_time = base::TimeTicks::Now();
1042 state->start_time_initialized = true;
1045 void CompleteInitializationOnTaskThread(Result result) {
1046 AssertOnTaskThread();
1047 delegate_->OnCompleteInitialization(result);
1050 void ReceiveMidiDataOnTaskThread(uint32 port_index,
1051 std::vector<uint8> data,
1052 base::TimeTicks time) {
1053 AssertOnTaskThread();
1054 delegate_->OnReceiveMidiData(port_index, data, time);
1057 void AddInputPortOnTaskThread(MidiPortInfo info) {
1058 AssertOnTaskThread();
1059 delegate_->OnAddInputPort(info);
1062 void AddOutputPortOnTaskThread(MidiPortInfo info) {
1063 AssertOnTaskThread();
1064 delegate_->OnAddOutputPort(info);
1067 void SetInputPortStateOnTaskThread(uint32 port_index, MidiPortState state) {
1068 AssertOnTaskThread();
1069 delegate_->OnSetInputPortState(port_index, state);
1072 void SetOutputPortStateOnTaskThread(uint32 port_index, MidiPortState state) {
1073 AssertOnTaskThread();
1074 delegate_->OnSetOutputPortState(port_index, state);
1077 /////////////////////////////////////////////////////////////////////////////
1078 // Fields:
1079 /////////////////////////////////////////////////////////////////////////////
1081 // Does not take ownership.
1082 MidiServiceWinDelegate* delegate_;
1084 base::ThreadChecker thread_checker_;
1086 base::Thread sender_thread_;
1087 base::Thread task_thread_;
1089 base::Lock input_ports_lock_;
1090 base::hash_map<HMIDIIN, scoped_refptr<MidiInputDeviceState>>
1091 input_device_map_; // GUARDED_BY(input_ports_lock_)
1092 PortNumberCache unused_input_ports_; // GUARDED_BY(input_ports_lock_)
1093 std::vector<scoped_refptr<MidiInputDeviceState>>
1094 input_ports_; // GUARDED_BY(input_ports_lock_)
1095 std::vector<uint64> input_ports_ages_; // GUARDED_BY(input_ports_lock_)
1097 base::Lock output_ports_lock_;
1098 base::hash_map<HMIDIOUT, scoped_refptr<MidiOutputDeviceState>>
1099 output_device_map_; // GUARDED_BY(output_ports_lock_)
1100 PortNumberCache unused_output_ports_; // GUARDED_BY(output_ports_lock_)
1101 std::vector<scoped_refptr<MidiOutputDeviceState>>
1102 output_ports_; // GUARDED_BY(output_ports_lock_)
1103 std::vector<uint64> output_ports_ages_; // GUARDED_BY(output_ports_lock_)
1105 // True if one thread reached MidiServiceWinImpl::~MidiServiceWinImpl(). Note
1106 // that MidiServiceWinImpl::~MidiServiceWinImpl() is blocked until
1107 // |sender_thread_|, and |task_thread_| are stopped.
1108 // This flag can be used as the signal that when background tasks must be
1109 // interrupted.
1110 // TODO(toyoshim): Use std::atomic<bool> when it is allowed.
1111 volatile bool destructor_started;
1113 DISALLOW_COPY_AND_ASSIGN(MidiServiceWinImpl);
1116 } // namespace
1118 MidiManagerWin::MidiManagerWin() {
1121 MidiManagerWin::~MidiManagerWin() {
1122 midi_service_.reset();
1125 void MidiManagerWin::StartInitialization() {
1126 midi_service_.reset(new MidiServiceWinImpl);
1127 // Note that |CompleteInitialization()| will be called from the callback.
1128 midi_service_->InitializeAsync(this);
1131 void MidiManagerWin::DispatchSendMidiData(MidiManagerClient* client,
1132 uint32 port_index,
1133 const std::vector<uint8>& data,
1134 double timestamp) {
1135 if (!midi_service_)
1136 return;
1138 base::TimeTicks time_to_send = base::TimeTicks::Now();
1139 if (timestamp != 0.0) {
1140 time_to_send =
1141 base::TimeTicks() + base::TimeDelta::FromMicroseconds(
1142 timestamp * base::Time::kMicrosecondsPerSecond);
1144 midi_service_->SendMidiDataAsync(port_index, data, time_to_send);
1146 // TOOD(toyoshim): This calculation should be done when the date is actually
1147 // sent.
1148 client->AccumulateMidiBytesSent(data.size());
1151 void MidiManagerWin::OnCompleteInitialization(Result result) {
1152 CompleteInitialization(result);
1155 void MidiManagerWin::OnAddInputPort(MidiPortInfo info) {
1156 AddInputPort(info);
1159 void MidiManagerWin::OnAddOutputPort(MidiPortInfo info) {
1160 AddOutputPort(info);
1163 void MidiManagerWin::OnSetInputPortState(uint32 port_index,
1164 MidiPortState state) {
1165 SetInputPortState(port_index, state);
1168 void MidiManagerWin::OnSetOutputPortState(uint32 port_index,
1169 MidiPortState state) {
1170 SetOutputPortState(port_index, state);
1173 void MidiManagerWin::OnReceiveMidiData(uint32 port_index,
1174 const std::vector<uint8>& data,
1175 base::TimeTicks time) {
1176 ReceiveMidiData(port_index, &data[0], data.size(), time);
1179 MidiManager* MidiManager::Create() {
1180 return new MidiManagerWin();
1183 } // namespace midi
1184 } // namespace media