Don't show supervised user as "already on this device" while they're being imported.
[chromium-blink-merge.git] / media / midi / midi_manager_win.cc
blob5c81386fc3fdea580d431b8a29c47ee8e5ebca52
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 explicit MidiDeviceInfo(const MIDIOUTCAPS2W& caps)
194 : manufacturer_id(caps.wMid),
195 product_id(caps.wPid),
196 driver_version(caps.vDriverVersion),
197 product_name(AsString16(caps.szPname)),
198 usb_vendor_id(ExtractUsbVendorIdIfExists(caps)),
199 usb_product_id(ExtractUsbProductIdIfExists(caps)),
200 is_usb_device(IsUsbDevice(caps)) {}
201 explicit MidiDeviceInfo(const MidiDeviceInfo& info)
202 : manufacturer_id(info.manufacturer_id),
203 product_id(info.product_id),
204 driver_version(info.driver_version),
205 product_name(info.product_name),
206 usb_vendor_id(info.usb_vendor_id),
207 usb_product_id(info.usb_product_id),
208 is_usb_device(info.is_usb_device) {}
209 // Currently only following entities are considered when testing the equality
210 // of two MIDI devices.
211 // TODO(toyoshim): Consider to calculate MIDIPort.id here and use it as the
212 // key. See crbug.com/467448. Then optimize the data for |MidiPortInfo|.
213 const uint16 manufacturer_id;
214 const uint16 product_id;
215 const uint32 driver_version;
216 const base::string16 product_name;
217 const uint16 usb_vendor_id;
218 const uint16 usb_product_id;
219 const bool is_usb_device;
221 // Required to be used as the key of base::hash_map.
222 bool operator==(const MidiDeviceInfo& that) const {
223 return manufacturer_id == that.manufacturer_id &&
224 product_id == that.product_id &&
225 driver_version == that.driver_version &&
226 product_name == that.product_name &&
227 is_usb_device == that.is_usb_device &&
228 (is_usb_device && usb_vendor_id == that.usb_vendor_id &&
229 usb_product_id == that.usb_product_id);
232 // Hash function to be used in base::hash_map.
233 struct Hasher {
234 size_t operator()(const MidiDeviceInfo& info) const {
235 size_t hash = info.manufacturer_id;
236 hash *= 131;
237 hash += info.product_id;
238 hash *= 131;
239 hash += info.driver_version;
240 hash *= 131;
241 hash += info.product_name.size();
242 hash *= 131;
243 if (!info.product_name.empty()) {
244 hash += info.product_name[0];
246 hash *= 131;
247 hash += info.usb_vendor_id;
248 hash *= 131;
249 hash += info.usb_product_id;
250 return hash;
254 private:
255 static bool IsUsbDevice(const MIDIINCAPS2W& caps) {
256 return IS_COMPATIBLE_USBAUDIO_MID(&caps.ManufacturerGuid) &&
257 IS_COMPATIBLE_USBAUDIO_PID(&caps.ProductGuid);
259 static bool IsUsbDevice(const MIDIOUTCAPS2W& caps) {
260 return IS_COMPATIBLE_USBAUDIO_MID(&caps.ManufacturerGuid) &&
261 IS_COMPATIBLE_USBAUDIO_PID(&caps.ProductGuid);
263 static uint16 ExtractUsbVendorIdIfExists(const MIDIINCAPS2W& caps) {
264 if (!IS_COMPATIBLE_USBAUDIO_MID(&caps.ManufacturerGuid))
265 return 0;
266 return EXTRACT_USBAUDIO_MID(&caps.ManufacturerGuid);
268 static uint16 ExtractUsbVendorIdIfExists(const MIDIOUTCAPS2W& caps) {
269 if (!IS_COMPATIBLE_USBAUDIO_MID(&caps.ManufacturerGuid))
270 return 0;
271 return EXTRACT_USBAUDIO_MID(&caps.ManufacturerGuid);
273 static uint16 ExtractUsbProductIdIfExists(const MIDIINCAPS2W& caps) {
274 if (!IS_COMPATIBLE_USBAUDIO_PID(&caps.ProductGuid))
275 return 0;
276 return EXTRACT_USBAUDIO_PID(&caps.ProductGuid);
278 static uint16 ExtractUsbProductIdIfExists(const MIDIOUTCAPS2W& caps) {
279 if (!IS_COMPATIBLE_USBAUDIO_PID(&caps.ProductGuid))
280 return 0;
281 return EXTRACT_USBAUDIO_PID(&caps.ProductGuid);
285 std::string GetManufacturerName(const MidiDeviceInfo& info) {
286 if (info.is_usb_device) {
287 const char* name = device::UsbIds::GetVendorName(info.usb_vendor_id);
288 return std::string(name ? name : "");
291 switch (info.manufacturer_id) {
292 case MM_MICROSOFT:
293 return "Microsoft Corporation";
294 default:
295 // TODO(toyoshim): Support other manufacture IDs. crbug.com/472341.
296 return "";
300 using PortNumberCache = base::hash_map<
301 MidiDeviceInfo,
302 std::priority_queue<uint32, std::vector<uint32>, std::greater<uint32>>,
303 MidiDeviceInfo::Hasher>;
305 struct MidiInputDeviceState final : base::RefCounted<MidiInputDeviceState> {
306 explicit MidiInputDeviceState(const MidiDeviceInfo& device_info)
307 : device_info(device_info),
308 midi_handle(kInvalidMidiInHandle),
309 port_index(0),
310 port_age(0),
311 start_time_initialized(false) {}
313 const MidiDeviceInfo device_info;
314 HMIDIIN midi_handle;
315 ScopedMIDIHDR midi_header;
316 // Since Win32 multimedia system uses a relative time offset from when
317 // |midiInStart| API is called, we need to record when it is called.
318 base::TimeTicks start_time;
319 // 0-based port index. We will try to reuse the previous port index when the
320 // MIDI device is closed then reopened.
321 uint32 port_index;
322 // A sequence number which represents how many times |port_index| is reused.
323 // We can remove this field if we decide not to clear unsent events
324 // when the device is disconnected.
325 // See https://github.com/WebAudio/web-midi-api/issues/133
326 uint64 port_age;
327 // True if |start_time| is initialized. This field is not used so far, but
328 // kept for the debugging purpose.
329 bool start_time_initialized;
331 private:
332 friend class base::RefCounted<MidiInputDeviceState>;
333 ~MidiInputDeviceState() {}
336 struct MidiOutputDeviceState final : base::RefCounted<MidiOutputDeviceState> {
337 explicit MidiOutputDeviceState(const MidiDeviceInfo& device_info)
338 : device_info(device_info),
339 midi_handle(kInvalidMidiOutHandle),
340 port_index(0),
341 port_age(0),
342 closed(false) {}
344 const MidiDeviceInfo device_info;
345 HMIDIOUT midi_handle;
346 // 0-based port index. We will try to reuse the previous port index when the
347 // MIDI device is closed then reopened.
348 uint32 port_index;
349 // A sequence number which represents how many times |port_index| is reused.
350 // We can remove this field if we decide not to clear unsent events
351 // when the device is disconnected.
352 // See https://github.com/WebAudio/web-midi-api/issues/133
353 uint64 port_age;
354 // True if the device is already closed and |midi_handle| is considered to be
355 // invalid.
356 // TODO(toyoshim): Use std::atomic<bool> when it is allowed in Chromium
357 // project.
358 volatile bool closed;
360 private:
361 friend class base::RefCounted<MidiOutputDeviceState>;
362 ~MidiOutputDeviceState() {}
365 // The core logic of MIDI device handling for Windows. Basically this class is
366 // shared among following 4 threads:
367 // 1. Chrome IO Thread
368 // 2. OS Multimedia Thread
369 // 3. Task Thread
370 // 4. Sender Thread
372 // Chrome IO Thread:
373 // MidiManager runs on Chrome IO thread. Device change notification is
374 // delivered to the thread through the SystemMonitor service.
375 // OnDevicesChanged() callback is invoked to update the MIDI device list.
376 // Note that in the current implementation we will try to open all the
377 // existing devices in practice. This is OK because trying to reopen a MIDI
378 // device that is already opened would simply fail, and there is no unwilling
379 // side effect.
381 // OS Multimedia Thread:
382 // This thread is maintained by the OS as a part of MIDI runtime, and
383 // responsible for receiving all the system initiated events such as device
384 // close, and receiving data. For performance reasons, most of potentially
385 // blocking operations will be dispatched into Task Thread.
387 // Task Thread:
388 // This thread will be used to call back following methods of MidiManager.
389 // - MidiManager::CompleteInitialization
390 // - MidiManager::AddInputPort
391 // - MidiManager::AddOutputPort
392 // - MidiManager::SetInputPortState
393 // - MidiManager::SetOutputPortState
394 // - MidiManager::ReceiveMidiData
396 // Sender Thread:
397 // This thread will be used to call Win32 APIs to send MIDI message at the
398 // specified time. We don't want to call MIDI send APIs on Task Thread
399 // because those APIs could be performed synchronously, hence they could block
400 // the caller thread for a while. See the comment in
401 // SendLongMidiMessageInternal for details. Currently we expect that the
402 // blocking time would be less than 1 second.
403 class MidiServiceWinImpl : public MidiServiceWin,
404 public base::SystemMonitor::DevicesChangedObserver {
405 public:
406 MidiServiceWinImpl()
407 : delegate_(nullptr),
408 sender_thread_("Windows MIDI sender thread"),
409 task_thread_("Windows MIDI task thread"),
410 destructor_started(false) {}
412 ~MidiServiceWinImpl() final {
413 // Start() and Stop() of the threads, and AddDevicesChangeObserver() and
414 // RemoveDevicesChangeObserver() should be called on the same thread.
415 CHECK(thread_checker_.CalledOnValidThread());
417 destructor_started = true;
418 base::SystemMonitor::Get()->RemoveDevicesChangedObserver(this);
420 std::vector<HMIDIIN> input_devices;
422 base::AutoLock auto_lock(input_ports_lock_);
423 for (auto it : input_device_map_)
424 input_devices.push_back(it.first);
427 for (const auto handle : input_devices) {
428 MMRESULT result = midiInClose(handle);
429 if (result == MIDIERR_STILLPLAYING) {
430 result = midiInReset(handle);
431 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
432 << "midiInReset failed: " << GetInErrorMessage(result);
433 result = midiInClose(handle);
435 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
436 << "midiInClose failed: " << GetInErrorMessage(result);
441 std::vector<HMIDIOUT> output_devices;
443 base::AutoLock auto_lock(output_ports_lock_);
444 for (auto it : output_device_map_)
445 output_devices.push_back(it.first);
448 for (const auto handle : output_devices) {
449 MMRESULT result = midiOutClose(handle);
450 if (result == MIDIERR_STILLPLAYING) {
451 result = midiOutReset(handle);
452 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
453 << "midiOutReset failed: " << GetOutErrorMessage(result);
454 result = midiOutClose(handle);
456 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
457 << "midiOutClose failed: " << GetOutErrorMessage(result);
461 sender_thread_.Stop();
462 task_thread_.Stop();
465 // MidiServiceWin overrides:
466 void InitializeAsync(MidiServiceWinDelegate* delegate) final {
467 // Start() and Stop() of the threads, and AddDevicesChangeObserver() and
468 // RemoveDevicesChangeObserver() should be called on the same thread.
469 CHECK(thread_checker_.CalledOnValidThread());
471 delegate_ = delegate;
473 sender_thread_.Start();
474 task_thread_.Start();
476 // Start monitoring device changes. This should start before the
477 // following UpdateDeviceList() call not to miss the event happening
478 // between the call and the observer registration.
479 base::SystemMonitor::Get()->AddDevicesChangedObserver(this);
481 UpdateDeviceList();
483 task_thread_.message_loop()->PostTask(
484 FROM_HERE,
485 base::Bind(&MidiServiceWinImpl::CompleteInitializationOnTaskThread,
486 base::Unretained(this), MIDI_OK));
489 void SendMidiDataAsync(uint32 port_number,
490 const std::vector<uint8>& data,
491 base::TimeTicks time) final {
492 if (destructor_started) {
493 LOG(ERROR) << "ThreadSafeSendData failed because MidiServiceWinImpl is "
494 "being destructed. port: " << port_number;
495 return;
497 auto state = GetOutputDeviceFromPort(port_number);
498 if (!state) {
499 LOG(ERROR) << "ThreadSafeSendData failed due to an invalid port number. "
500 << "port: " << port_number;
501 return;
503 if (state->closed) {
504 LOG(ERROR)
505 << "ThreadSafeSendData failed because target port is already closed."
506 << "port: " << port_number;
507 return;
509 const auto now = base::TimeTicks::Now();
510 if (now < time) {
511 sender_thread_.message_loop()->PostDelayedTask(
512 FROM_HERE, base::Bind(&MidiServiceWinImpl::SendOnSenderThread,
513 base::Unretained(this), port_number,
514 state->port_age, data, time),
515 time - now);
516 } else {
517 sender_thread_.message_loop()->PostTask(
518 FROM_HERE, base::Bind(&MidiServiceWinImpl::SendOnSenderThread,
519 base::Unretained(this), port_number,
520 state->port_age, data, time));
524 // base::SystemMonitor::DevicesChangedObserver overrides:
525 void OnDevicesChanged(base::SystemMonitor::DeviceType device_type) final {
526 CHECK(thread_checker_.CalledOnValidThread());
527 if (destructor_started)
528 return;
530 switch (device_type) {
531 case base::SystemMonitor::DEVTYPE_AUDIO_CAPTURE:
532 case base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE:
533 // Add case of other unrelated device types here.
534 return;
535 case base::SystemMonitor::DEVTYPE_UNKNOWN:
536 // Interested in MIDI devices. Try updating the device list.
537 UpdateDeviceList();
538 break;
539 // No default here to capture new DeviceType by compile time.
543 private:
544 scoped_refptr<MidiInputDeviceState> GetInputDeviceFromHandle(
545 HMIDIIN midi_handle) {
546 base::AutoLock auto_lock(input_ports_lock_);
547 const auto it = input_device_map_.find(midi_handle);
548 return (it != input_device_map_.end() ? it->second : nullptr);
551 scoped_refptr<MidiOutputDeviceState> GetOutputDeviceFromHandle(
552 HMIDIOUT midi_handle) {
553 base::AutoLock auto_lock(output_ports_lock_);
554 const auto it = output_device_map_.find(midi_handle);
555 return (it != output_device_map_.end() ? it->second : nullptr);
558 scoped_refptr<MidiOutputDeviceState> GetOutputDeviceFromPort(
559 uint32 port_number) {
560 base::AutoLock auto_lock(output_ports_lock_);
561 if (output_ports_.size() <= port_number)
562 return nullptr;
563 return output_ports_[port_number];
566 void UpdateDeviceList() {
567 task_thread_.message_loop()->PostTask(
568 FROM_HERE, base::Bind(&MidiServiceWinImpl::UpdateDeviceListOnTaskThread,
569 base::Unretained(this)));
572 /////////////////////////////////////////////////////////////////////////////
573 // Callbacks on the OS multimedia thread.
574 /////////////////////////////////////////////////////////////////////////////
576 static void CALLBACK
577 OnMidiInEventOnMainlyMultimediaThread(HMIDIIN midi_in_handle,
578 UINT message,
579 DWORD_PTR instance,
580 DWORD_PTR param1,
581 DWORD_PTR param2) {
582 MidiServiceWinImpl* self = reinterpret_cast<MidiServiceWinImpl*>(instance);
583 if (!self)
584 return;
585 switch (message) {
586 case MIM_OPEN:
587 self->OnMidiInOpen(midi_in_handle);
588 break;
589 case MIM_DATA:
590 self->OnMidiInDataOnMultimediaThread(midi_in_handle, param1, param2);
591 break;
592 case MIM_LONGDATA:
593 self->OnMidiInLongDataOnMultimediaThread(midi_in_handle, param1,
594 param2);
595 break;
596 case MIM_CLOSE:
597 self->OnMidiInCloseOnMultimediaThread(midi_in_handle);
598 break;
602 void OnMidiInOpen(HMIDIIN midi_in_handle) {
603 UINT device_id = 0;
604 MMRESULT result = midiInGetID(midi_in_handle, &device_id);
605 if (result != MMSYSERR_NOERROR) {
606 DLOG(ERROR) << "midiInGetID failed: " << GetInErrorMessage(result);
607 return;
609 MIDIINCAPS2W caps = {};
610 result = midiInGetDevCaps(device_id, reinterpret_cast<LPMIDIINCAPSW>(&caps),
611 sizeof(caps));
612 if (result != MMSYSERR_NOERROR) {
613 DLOG(ERROR) << "midiInGetDevCaps failed: " << GetInErrorMessage(result);
614 return;
616 auto state =
617 make_scoped_refptr(new MidiInputDeviceState(MidiDeviceInfo(caps)));
618 state->midi_handle = midi_in_handle;
619 state->midi_header = CreateMIDIHDR(kBufferLength);
620 const auto& state_device_info = state->device_info;
621 bool add_new_port = false;
622 uint32 port_number = 0;
624 base::AutoLock auto_lock(input_ports_lock_);
625 const auto it = unused_input_ports_.find(state_device_info);
626 if (it == unused_input_ports_.end()) {
627 port_number = static_cast<uint32>(input_ports_.size());
628 add_new_port = true;
629 input_ports_.push_back(nullptr);
630 input_ports_ages_.push_back(0);
631 } else {
632 port_number = it->second.top();
633 it->second.pop();
634 if (it->second.empty()) {
635 unused_input_ports_.erase(it);
638 input_ports_[port_number] = state;
640 input_ports_ages_[port_number] += 1;
641 input_device_map_[input_ports_[port_number]->midi_handle] =
642 input_ports_[port_number];
643 input_ports_[port_number]->port_index = port_number;
644 input_ports_[port_number]->port_age = input_ports_ages_[port_number];
646 // Several initial startup tasks cannot be done in MIM_OPEN handler.
647 task_thread_.message_loop()->PostTask(
648 FROM_HERE, base::Bind(&MidiServiceWinImpl::StartInputDeviceOnTaskThread,
649 base::Unretained(this), midi_in_handle));
650 if (add_new_port) {
651 const MidiPortInfo port_info(
652 // TODO(toyoshim): Use a hash ID insted crbug.com/467448
653 base::IntToString(static_cast<int>(port_number)),
654 GetManufacturerName(state_device_info),
655 base::WideToUTF8(state_device_info.product_name),
656 MmversionToString(state_device_info.driver_version),
657 MIDI_PORT_OPENED);
658 task_thread_.message_loop()->PostTask(
659 FROM_HERE, base::Bind(&MidiServiceWinImpl::AddInputPortOnTaskThread,
660 base::Unretained(this), port_info));
661 } else {
662 task_thread_.message_loop()->PostTask(
663 FROM_HERE,
664 base::Bind(&MidiServiceWinImpl::SetInputPortStateOnTaskThread,
665 base::Unretained(this), port_number,
666 MidiPortState::MIDI_PORT_CONNECTED));
670 void OnMidiInDataOnMultimediaThread(HMIDIIN midi_in_handle,
671 DWORD_PTR param1,
672 DWORD_PTR param2) {
673 auto state = GetInputDeviceFromHandle(midi_in_handle);
674 if (!state)
675 return;
676 const uint8 status_byte = static_cast<uint8>(param1 & 0xff);
677 const uint8 first_data_byte = static_cast<uint8>((param1 >> 8) & 0xff);
678 const uint8 second_data_byte = static_cast<uint8>((param1 >> 16) & 0xff);
679 const DWORD elapsed_ms = param2;
680 const size_t len = GetMidiMessageLength(status_byte);
681 const uint8 kData[] = {status_byte, first_data_byte, second_data_byte};
682 std::vector<uint8> data;
683 data.assign(kData, kData + len);
684 DCHECK_LE(len, arraysize(kData));
685 // MIM_DATA/MIM_LONGDATA message treats the time when midiInStart() is
686 // called as the origin of |elapsed_ms|.
687 // http://msdn.microsoft.com/en-us/library/windows/desktop/dd757284.aspx
688 // http://msdn.microsoft.com/en-us/library/windows/desktop/dd757286.aspx
689 const base::TimeTicks event_time =
690 state->start_time + base::TimeDelta::FromMilliseconds(elapsed_ms);
691 task_thread_.message_loop()->PostTask(
692 FROM_HERE, base::Bind(&MidiServiceWinImpl::ReceiveMidiDataOnTaskThread,
693 base::Unretained(this), state->port_index, data,
694 event_time));
697 void OnMidiInLongDataOnMultimediaThread(HMIDIIN midi_in_handle,
698 DWORD_PTR param1,
699 DWORD_PTR param2) {
700 auto state = GetInputDeviceFromHandle(midi_in_handle);
701 if (!state)
702 return;
703 MIDIHDR* header = reinterpret_cast<MIDIHDR*>(param1);
704 const DWORD elapsed_ms = param2;
705 MMRESULT result = MMSYSERR_NOERROR;
706 if (destructor_started) {
707 if (state->midi_header &&
708 (state->midi_header->dwFlags & MHDR_PREPARED) == MHDR_PREPARED) {
709 result =
710 midiInUnprepareHeader(state->midi_handle, state->midi_header.get(),
711 sizeof(*state->midi_header));
712 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
713 << "Failed to uninitialize input buffer: "
714 << GetInErrorMessage(result);
716 return;
718 if (header->dwBytesRecorded > 0) {
719 const uint8* src = reinterpret_cast<const uint8*>(header->lpData);
720 std::vector<uint8> data;
721 data.assign(src, src + header->dwBytesRecorded);
722 // MIM_DATA/MIM_LONGDATA message treats the time when midiInStart() is
723 // called as the origin of |elapsed_ms|.
724 // http://msdn.microsoft.com/en-us/library/windows/desktop/dd757284.aspx
725 // http://msdn.microsoft.com/en-us/library/windows/desktop/dd757286.aspx
726 const base::TimeTicks event_time =
727 state->start_time + base::TimeDelta::FromMilliseconds(elapsed_ms);
728 task_thread_.message_loop()->PostTask(
729 FROM_HERE,
730 base::Bind(&MidiServiceWinImpl::ReceiveMidiDataOnTaskThread,
731 base::Unretained(this), state->port_index, data,
732 event_time));
734 result = midiInAddBuffer(state->midi_handle, header, sizeof(*header));
735 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
736 << "Failed to attach input buffer: " << GetInErrorMessage(result)
737 << "port number:" << state->port_index;
740 void OnMidiInCloseOnMultimediaThread(HMIDIIN midi_in_handle) {
741 auto state = GetInputDeviceFromHandle(midi_in_handle);
742 if (!state)
743 return;
744 const uint32 port_number = state->port_index;
745 const auto device_info(state->device_info);
747 base::AutoLock auto_lock(input_ports_lock_);
748 input_device_map_.erase(state->midi_handle);
749 input_ports_[port_number] = nullptr;
750 input_ports_ages_[port_number] += 1;
751 unused_input_ports_[device_info].push(port_number);
753 task_thread_.message_loop()->PostTask(
754 FROM_HERE,
755 base::Bind(&MidiServiceWinImpl::SetInputPortStateOnTaskThread,
756 base::Unretained(this), port_number,
757 MIDI_PORT_DISCONNECTED));
760 static void CALLBACK
761 OnMidiOutEventOnMainlyMultimediaThread(HMIDIOUT midi_out_handle,
762 UINT message,
763 DWORD_PTR instance,
764 DWORD_PTR param1,
765 DWORD_PTR param2) {
766 MidiServiceWinImpl* self = reinterpret_cast<MidiServiceWinImpl*>(instance);
767 if (!self)
768 return;
769 switch (message) {
770 case MOM_OPEN:
771 self->OnMidiOutOpen(midi_out_handle, param1, param2);
772 break;
773 case MOM_DONE:
774 self->OnMidiOutDoneOnMultimediaThread(midi_out_handle, param1);
775 break;
776 case MOM_CLOSE:
777 self->OnMidiOutCloseOnMultimediaThread(midi_out_handle);
778 break;
782 void OnMidiOutOpen(HMIDIOUT midi_out_handle,
783 DWORD_PTR param1,
784 DWORD_PTR param2) {
785 UINT device_id = 0;
786 MMRESULT result = midiOutGetID(midi_out_handle, &device_id);
787 if (result != MMSYSERR_NOERROR) {
788 DLOG(ERROR) << "midiOutGetID failed: " << GetOutErrorMessage(result);
789 return;
791 MIDIOUTCAPS2W caps = {};
792 result = midiOutGetDevCaps(
793 device_id, reinterpret_cast<LPMIDIOUTCAPSW>(&caps), sizeof(caps));
794 if (result != MMSYSERR_NOERROR) {
795 DLOG(ERROR) << "midiInGetDevCaps failed: " << GetOutErrorMessage(result);
796 return;
798 auto state =
799 make_scoped_refptr(new MidiOutputDeviceState(MidiDeviceInfo(caps)));
800 state->midi_handle = midi_out_handle;
801 const auto& state_device_info = state->device_info;
802 bool add_new_port = false;
803 uint32 port_number = 0;
805 base::AutoLock auto_lock(output_ports_lock_);
806 const auto it = unused_output_ports_.find(state_device_info);
807 if (it == unused_output_ports_.end()) {
808 port_number = static_cast<uint32>(output_ports_.size());
809 add_new_port = true;
810 output_ports_.push_back(nullptr);
811 output_ports_ages_.push_back(0);
812 } else {
813 port_number = it->second.top();
814 it->second.pop();
815 if (it->second.empty())
816 unused_output_ports_.erase(it);
818 output_ports_[port_number] = state;
819 output_ports_ages_[port_number] += 1;
820 output_device_map_[output_ports_[port_number]->midi_handle] =
821 output_ports_[port_number];
822 output_ports_[port_number]->port_index = port_number;
823 output_ports_[port_number]->port_age = output_ports_ages_[port_number];
825 if (add_new_port) {
826 const MidiPortInfo port_info(
827 // TODO(toyoshim): Use a hash ID insted. crbug.com/467448
828 base::IntToString(static_cast<int>(port_number)),
829 GetManufacturerName(state_device_info),
830 base::WideToUTF8(state_device_info.product_name),
831 MmversionToString(state_device_info.driver_version),
832 MIDI_PORT_OPENED);
833 task_thread_.message_loop()->PostTask(
834 FROM_HERE, base::Bind(&MidiServiceWinImpl::AddOutputPortOnTaskThread,
835 base::Unretained(this), port_info));
836 } else {
837 task_thread_.message_loop()->PostTask(
838 FROM_HERE,
839 base::Bind(&MidiServiceWinImpl::SetOutputPortStateOnTaskThread,
840 base::Unretained(this), port_number, MIDI_PORT_CONNECTED));
844 void OnMidiOutDoneOnMultimediaThread(HMIDIOUT midi_out_handle,
845 DWORD_PTR param1) {
846 auto state = GetOutputDeviceFromHandle(midi_out_handle);
847 if (!state)
848 return;
849 // Take ownership of the MIDIHDR object.
850 ScopedMIDIHDR header(reinterpret_cast<MIDIHDR*>(param1));
851 if (!header)
852 return;
853 MMRESULT result = midiOutUnprepareHeader(state->midi_handle, header.get(),
854 sizeof(*header));
855 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
856 << "Failed to uninitialize output buffer: "
857 << GetOutErrorMessage(result);
860 void OnMidiOutCloseOnMultimediaThread(HMIDIOUT midi_out_handle) {
861 auto state = GetOutputDeviceFromHandle(midi_out_handle);
862 if (!state)
863 return;
864 const uint32 port_number = state->port_index;
865 const auto device_info(state->device_info);
867 base::AutoLock auto_lock(output_ports_lock_);
868 output_device_map_.erase(state->midi_handle);
869 output_ports_[port_number] = nullptr;
870 output_ports_ages_[port_number] += 1;
871 unused_output_ports_[device_info].push(port_number);
872 state->closed = true;
874 task_thread_.message_loop()->PostTask(
875 FROM_HERE,
876 base::Bind(&MidiServiceWinImpl::SetOutputPortStateOnTaskThread,
877 base::Unretained(this), port_number,
878 MIDI_PORT_DISCONNECTED));
881 /////////////////////////////////////////////////////////////////////////////
882 // Callbacks on the sender thread.
883 /////////////////////////////////////////////////////////////////////////////
885 void AssertOnSenderThread() {
886 DCHECK_EQ(sender_thread_.thread_id(), base::PlatformThread::CurrentId());
889 void SendOnSenderThread(uint32 port_number,
890 uint64 port_age,
891 const std::vector<uint8>& data,
892 base::TimeTicks time) {
893 AssertOnSenderThread();
894 if (destructor_started) {
895 LOG(ERROR) << "ThreadSafeSendData failed because MidiServiceWinImpl is "
896 "being destructed. port: " << port_number;
898 auto state = GetOutputDeviceFromPort(port_number);
899 if (!state) {
900 LOG(ERROR) << "ThreadSafeSendData failed due to an invalid port number. "
901 << "port: " << port_number;
902 return;
904 if (state->closed) {
905 LOG(ERROR)
906 << "ThreadSafeSendData failed because target port is already closed."
907 << "port: " << port_number;
908 return;
910 if (state->port_age != port_age) {
911 LOG(ERROR)
912 << "ThreadSafeSendData failed because target port is being closed."
913 << "port: " << port_number << "expected port age: " << port_age
914 << "actual port age: " << state->port_age;
917 // MIDI Running status must be filtered out.
918 MidiMessageQueue message_queue(false);
919 message_queue.Add(data);
920 std::vector<uint8> message;
921 while (true) {
922 if (destructor_started)
923 break;
924 if (state->closed)
925 break;
926 message_queue.Get(&message);
927 if (message.empty())
928 break;
929 // SendShortMidiMessageInternal can send a MIDI message up to 3 bytes.
930 if (message.size() <= 3)
931 SendShortMidiMessageInternal(state->midi_handle, message);
932 else
933 SendLongMidiMessageInternal(state->midi_handle, message);
937 /////////////////////////////////////////////////////////////////////////////
938 // Callbacks on the task thread.
939 /////////////////////////////////////////////////////////////////////////////
941 void AssertOnTaskThread() {
942 DCHECK_EQ(task_thread_.thread_id(), base::PlatformThread::CurrentId());
945 void UpdateDeviceListOnTaskThread() {
946 AssertOnTaskThread();
947 const UINT num_in_devices = midiInGetNumDevs();
948 for (UINT device_id = 0; device_id < num_in_devices; ++device_id) {
949 // Here we use |CALLBACK_FUNCTION| to subscribe MIM_DATA, MIM_LONGDATA,
950 // MIM_OPEN, and MIM_CLOSE events.
951 // - MIM_DATA: This is the only way to get a short MIDI message with
952 // timestamp information.
953 // - MIM_LONGDATA: This is the only way to get a long MIDI message with
954 // timestamp information.
955 // - MIM_OPEN: This event is sent the input device is opened. Note that
956 // this message is called on the caller thread.
957 // - MIM_CLOSE: This event is sent when 1) midiInClose() is called, or 2)
958 // the MIDI device becomes unavailable for some reasons, e.g., the
959 // cable is disconnected. As for the former case, HMIDIOUT will be
960 // invalidated soon after the callback is finished. As for the later
961 // case, however, HMIDIOUT continues to be valid until midiInClose()
962 // is called.
963 HMIDIIN midi_handle = kInvalidMidiInHandle;
964 const MMRESULT result = midiInOpen(
965 &midi_handle, device_id,
966 reinterpret_cast<DWORD_PTR>(&OnMidiInEventOnMainlyMultimediaThread),
967 reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION);
968 DLOG_IF(ERROR, result != MMSYSERR_NOERROR && result != MMSYSERR_ALLOCATED)
969 << "Failed to open output device. "
970 << " id: " << device_id << " message: " << GetInErrorMessage(result);
973 const UINT num_out_devices = midiOutGetNumDevs();
974 for (UINT device_id = 0; device_id < num_out_devices; ++device_id) {
975 // Here we use |CALLBACK_FUNCTION| to subscribe MOM_DONE, MOM_OPEN, and
976 // MOM_CLOSE events.
977 // - MOM_DONE: SendLongMidiMessageInternal() relies on this event to clean
978 // up the backing store where a long MIDI message is stored.
979 // - MOM_OPEN: This event is sent the output device is opened. Note that
980 // this message is called on the caller thread.
981 // - MOM_CLOSE: This event is sent when 1) midiOutClose() is called, or 2)
982 // the MIDI device becomes unavailable for some reasons, e.g., the
983 // cable is disconnected. As for the former case, HMIDIOUT will be
984 // invalidated soon after the callback is finished. As for the later
985 // case, however, HMIDIOUT continues to be valid until midiOutClose()
986 // is called.
987 HMIDIOUT midi_handle = kInvalidMidiOutHandle;
988 const MMRESULT result = midiOutOpen(
989 &midi_handle, device_id,
990 reinterpret_cast<DWORD_PTR>(&OnMidiOutEventOnMainlyMultimediaThread),
991 reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION);
992 DLOG_IF(ERROR, result != MMSYSERR_NOERROR && result != MMSYSERR_ALLOCATED)
993 << "Failed to open output device. "
994 << " id: " << device_id << " message: " << GetOutErrorMessage(result);
998 void StartInputDeviceOnTaskThread(HMIDIIN midi_in_handle) {
999 AssertOnTaskThread();
1000 auto state = GetInputDeviceFromHandle(midi_in_handle);
1001 if (!state)
1002 return;
1003 MMRESULT result =
1004 midiInPrepareHeader(state->midi_handle, state->midi_header.get(),
1005 sizeof(*state->midi_header));
1006 if (result != MMSYSERR_NOERROR) {
1007 DLOG(ERROR) << "Failed to initialize input buffer: "
1008 << GetInErrorMessage(result);
1009 return;
1011 result = midiInAddBuffer(state->midi_handle, state->midi_header.get(),
1012 sizeof(*state->midi_header));
1013 if (result != MMSYSERR_NOERROR) {
1014 DLOG(ERROR) << "Failed to attach input buffer: "
1015 << GetInErrorMessage(result);
1016 return;
1018 result = midiInStart(state->midi_handle);
1019 if (result != MMSYSERR_NOERROR) {
1020 DLOG(ERROR) << "Failed to start input port: "
1021 << GetInErrorMessage(result);
1022 return;
1024 state->start_time = base::TimeTicks::Now();
1025 state->start_time_initialized = true;
1028 void CompleteInitializationOnTaskThread(MidiResult result) {
1029 AssertOnTaskThread();
1030 delegate_->OnCompleteInitialization(result);
1033 void ReceiveMidiDataOnTaskThread(uint32 port_index,
1034 std::vector<uint8> data,
1035 base::TimeTicks time) {
1036 AssertOnTaskThread();
1037 delegate_->OnReceiveMidiData(port_index, data, time);
1040 void AddInputPortOnTaskThread(MidiPortInfo info) {
1041 AssertOnTaskThread();
1042 delegate_->OnAddInputPort(info);
1045 void AddOutputPortOnTaskThread(MidiPortInfo info) {
1046 AssertOnTaskThread();
1047 delegate_->OnAddOutputPort(info);
1050 void SetInputPortStateOnTaskThread(uint32 port_index, MidiPortState state) {
1051 AssertOnTaskThread();
1052 delegate_->OnSetInputPortState(port_index, state);
1055 void SetOutputPortStateOnTaskThread(uint32 port_index, MidiPortState state) {
1056 AssertOnTaskThread();
1057 delegate_->OnSetOutputPortState(port_index, state);
1060 /////////////////////////////////////////////////////////////////////////////
1061 // Fields:
1062 /////////////////////////////////////////////////////////////////////////////
1064 // Does not take ownership.
1065 MidiServiceWinDelegate* delegate_;
1067 base::ThreadChecker thread_checker_;
1069 base::Thread sender_thread_;
1070 base::Thread task_thread_;
1072 base::Lock input_ports_lock_;
1073 base::hash_map<HMIDIIN, scoped_refptr<MidiInputDeviceState>>
1074 input_device_map_; // GUARDED_BY(input_ports_lock_)
1075 PortNumberCache unused_input_ports_; // GUARDED_BY(input_ports_lock_)
1076 std::vector<scoped_refptr<MidiInputDeviceState>>
1077 input_ports_; // GUARDED_BY(input_ports_lock_)
1078 std::vector<uint64> input_ports_ages_; // GUARDED_BY(input_ports_lock_)
1080 base::Lock output_ports_lock_;
1081 base::hash_map<HMIDIOUT, scoped_refptr<MidiOutputDeviceState>>
1082 output_device_map_; // GUARDED_BY(output_ports_lock_)
1083 PortNumberCache unused_output_ports_; // GUARDED_BY(output_ports_lock_)
1084 std::vector<scoped_refptr<MidiOutputDeviceState>>
1085 output_ports_; // GUARDED_BY(output_ports_lock_)
1086 std::vector<uint64> output_ports_ages_; // GUARDED_BY(output_ports_lock_)
1088 // True if one thread reached MidiServiceWinImpl::~MidiServiceWinImpl(). Note
1089 // that MidiServiceWinImpl::~MidiServiceWinImpl() is blocked until
1090 // |sender_thread_|, and |task_thread_| are stopped.
1091 // This flag can be used as the signal that when background tasks must be
1092 // interrupted.
1093 // TODO(toyoshim): Use std::atomic<bool> when it is allowed.
1094 volatile bool destructor_started;
1096 DISALLOW_COPY_AND_ASSIGN(MidiServiceWinImpl);
1099 } // namespace
1101 MidiManagerWin::MidiManagerWin() {
1104 MidiManagerWin::~MidiManagerWin() {
1105 midi_service_.reset();
1108 void MidiManagerWin::StartInitialization() {
1109 midi_service_.reset(new MidiServiceWinImpl);
1110 // Note that |CompleteInitialization()| will be called from the callback.
1111 midi_service_->InitializeAsync(this);
1114 void MidiManagerWin::DispatchSendMidiData(MidiManagerClient* client,
1115 uint32 port_index,
1116 const std::vector<uint8>& data,
1117 double timestamp) {
1118 if (!midi_service_)
1119 return;
1121 base::TimeTicks time_to_send = base::TimeTicks::Now();
1122 if (timestamp != 0.0) {
1123 time_to_send =
1124 base::TimeTicks() + base::TimeDelta::FromMicroseconds(
1125 timestamp * base::Time::kMicrosecondsPerSecond);
1127 midi_service_->SendMidiDataAsync(port_index, data, time_to_send);
1129 // TOOD(toyoshim): This calculation should be done when the date is actually
1130 // sent.
1131 client->AccumulateMidiBytesSent(data.size());
1134 void MidiManagerWin::OnCompleteInitialization(MidiResult result) {
1135 CompleteInitialization(result);
1138 void MidiManagerWin::OnAddInputPort(MidiPortInfo info) {
1139 AddInputPort(info);
1142 void MidiManagerWin::OnAddOutputPort(MidiPortInfo info) {
1143 AddOutputPort(info);
1146 void MidiManagerWin::OnSetInputPortState(uint32 port_index,
1147 MidiPortState state) {
1148 SetInputPortState(port_index, state);
1151 void MidiManagerWin::OnSetOutputPortState(uint32 port_index,
1152 MidiPortState state) {
1153 SetOutputPortState(port_index, state);
1156 void MidiManagerWin::OnReceiveMidiData(uint32 port_index,
1157 const std::vector<uint8>& data,
1158 base::TimeTicks time) {
1159 ReceiveMidiData(port_index, &data[0], data.size(), time);
1162 MidiManager* MidiManager::Create() {
1163 return new MidiManagerWin();
1166 } // namespace midi
1167 } // namespace media