Roll libvpx 861f35:1fff3e
[chromium-blink-merge.git] / media / midi / midi_manager_win.cc
bloba911ff6d5f371dcc93c4a4bda5ad357bdc566af5
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/basictypes.h"
29 #include "base/bind.h"
30 #include "base/containers/hash_tables.h"
31 #include "base/memory/scoped_ptr.h"
32 #include "base/message_loop/message_loop.h"
33 #include "base/strings/string16.h"
34 #include "base/strings/string_number_conversions.h"
35 #include "base/strings/string_piece.h"
36 #include "base/strings/stringprintf.h"
37 #include "base/strings/utf_string_conversions.h"
38 #include "base/system_monitor/system_monitor.h"
39 #include "base/threading/thread.h"
40 #include "base/threading/thread_checker.h"
41 #include "base/timer/timer.h"
42 #include "base/win/message_window.h"
43 #include "device/usb/usb_ids.h"
44 #include "media/midi/midi_manager.h"
45 #include "media/midi/midi_message_queue.h"
46 #include "media/midi/midi_message_util.h"
47 #include "media/midi/midi_port_info.h"
49 namespace media {
50 namespace {
52 static const size_t kBufferLength = 32 * 1024;
54 // We assume that nullpter represents an invalid MIDI handle.
55 const HMIDIIN kInvalidMidiInHandle = nullptr;
56 const HMIDIOUT kInvalidMidiOutHandle = nullptr;
58 std::string GetInErrorMessage(MMRESULT result) {
59 wchar_t text[MAXERRORLENGTH];
60 MMRESULT get_result = midiInGetErrorText(result, text, arraysize(text));
61 if (get_result != MMSYSERR_NOERROR) {
62 DLOG(ERROR) << "Failed to get error message."
63 << " original error: " << result
64 << " midiInGetErrorText error: " << get_result;
65 return std::string();
67 return base::WideToUTF8(text);
70 std::string GetOutErrorMessage(MMRESULT result) {
71 wchar_t text[MAXERRORLENGTH];
72 MMRESULT get_result = midiOutGetErrorText(result, text, arraysize(text));
73 if (get_result != MMSYSERR_NOERROR) {
74 DLOG(ERROR) << "Failed to get error message."
75 << " original error: " << result
76 << " midiOutGetErrorText error: " << get_result;
77 return std::string();
79 return base::WideToUTF8(text);
82 std::string MmversionToString(MMVERSION version) {
83 return base::StringPrintf("%d.%d", HIBYTE(version), LOBYTE(version));
86 class MIDIHDRDeleter {
87 public:
88 void operator()(MIDIHDR* header) {
89 if (!header)
90 return;
91 delete[] static_cast<char*>(header->lpData);
92 header->lpData = NULL;
93 header->dwBufferLength = 0;
94 delete header;
98 typedef scoped_ptr<MIDIHDR, MIDIHDRDeleter> ScopedMIDIHDR;
100 ScopedMIDIHDR CreateMIDIHDR(size_t size) {
101 ScopedMIDIHDR header(new MIDIHDR);
102 ZeroMemory(header.get(), sizeof(*header));
103 header->lpData = new char[size];
104 header->dwBufferLength = size;
105 return header.Pass();
108 void SendShortMidiMessageInternal(HMIDIOUT midi_out_handle,
109 const std::vector<uint8>& message) {
110 DCHECK_LE(message.size(), static_cast<size_t>(3))
111 << "A short MIDI message should be up to 3 bytes.";
113 DWORD packed_message = 0;
114 for (size_t i = 0; i < message.size(); ++i)
115 packed_message |= (static_cast<uint32>(message[i]) << (i * 8));
116 MMRESULT result = midiOutShortMsg(midi_out_handle, packed_message);
117 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
118 << "Failed to output short message: " << GetOutErrorMessage(result);
121 void SendLongMidiMessageInternal(HMIDIOUT midi_out_handle,
122 const std::vector<uint8>& message) {
123 // Implementation note:
124 // Sending a long MIDI message can be performed synchronously or
125 // asynchronously depending on the driver. There are 2 options to support both
126 // cases:
127 // 1) Call midiOutLongMsg() API and wait for its completion within this
128 // function. In this approach, we can avoid memory copy by directly pointing
129 // |message| as the data buffer to be sent.
130 // 2) Allocate a buffer and copy |message| to it, then call midiOutLongMsg()
131 // API. The buffer will be freed in the MOM_DONE event hander, which tells
132 // us that the task of midiOutLongMsg() API is completed.
133 // Here we choose option 2) in favor of asynchronous design.
135 // Note for built-in USB-MIDI driver:
136 // From an observation on Windows 7/8.1 with a USB-MIDI keyboard,
137 // midiOutLongMsg() will be always blocked. Sending 64 bytes or less data
138 // takes roughly 300 usecs. Sending 2048 bytes or more data takes roughly
139 // |message.size() / (75 * 1024)| secs in practice. Here we put 60 KB size
140 // limit on SysEx message, with hoping that midiOutLongMsg will be blocked at
141 // most 1 sec or so with a typical USB-MIDI device.
142 const size_t kSysExSizeLimit = 60 * 1024;
143 if (message.size() >= kSysExSizeLimit) {
144 DVLOG(1) << "Ingnoreing SysEx message due to the size limit"
145 << ", size = " << message.size();
146 return;
149 ScopedMIDIHDR midi_header(CreateMIDIHDR(message.size()));
150 std::copy(message.begin(), message.end(), midi_header->lpData);
152 MMRESULT result = midiOutPrepareHeader(midi_out_handle, midi_header.get(),
153 sizeof(*midi_header));
154 if (result != MMSYSERR_NOERROR) {
155 DLOG(ERROR) << "Failed to prepare output buffer: "
156 << GetOutErrorMessage(result);
157 return;
160 result =
161 midiOutLongMsg(midi_out_handle, midi_header.get(), sizeof(*midi_header));
162 if (result != MMSYSERR_NOERROR) {
163 DLOG(ERROR) << "Failed to output long message: "
164 << GetOutErrorMessage(result);
165 result = midiOutUnprepareHeader(midi_out_handle, midi_header.get(),
166 sizeof(*midi_header));
167 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
168 << "Failed to uninitialize output buffer: "
169 << GetOutErrorMessage(result);
170 return;
173 // The ownership of |midi_header| is moved to MOM_DONE event handler.
174 midi_header.release();
177 template <size_t array_size>
178 base::string16 AsString16(const wchar_t(&buffer)[array_size]) {
179 size_t len = 0;
180 for (len = 0; len < array_size; ++len) {
181 if (buffer[len] == L'\0')
182 break;
184 return base::string16(buffer, len);
187 struct MidiDeviceInfo final {
188 explicit MidiDeviceInfo(const MIDIINCAPS2W& caps)
189 : manufacturer_id(caps.wMid),
190 product_id(caps.wPid),
191 driver_version(caps.vDriverVersion),
192 product_name(AsString16(caps.szPname)),
193 usb_vendor_id(ExtractUsbVendorIdIfExists(caps)),
194 usb_product_id(ExtractUsbProductIdIfExists(caps)),
195 is_usb_device(IsUsbDevice(caps)) {}
196 explicit MidiDeviceInfo(const MIDIOUTCAPS2W& caps)
197 : manufacturer_id(caps.wMid),
198 product_id(caps.wPid),
199 driver_version(caps.vDriverVersion),
200 product_name(AsString16(caps.szPname)),
201 usb_vendor_id(ExtractUsbVendorIdIfExists(caps)),
202 usb_product_id(ExtractUsbProductIdIfExists(caps)),
203 is_usb_device(IsUsbDevice(caps)) {}
204 explicit MidiDeviceInfo(const MidiDeviceInfo& info)
205 : manufacturer_id(info.manufacturer_id),
206 product_id(info.product_id),
207 driver_version(info.driver_version),
208 product_name(info.product_name),
209 usb_vendor_id(info.usb_vendor_id),
210 usb_product_id(info.usb_product_id),
211 is_usb_device(info.is_usb_device) {}
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;
224 // Required to be used as the key of base::hash_map.
225 bool operator==(const MidiDeviceInfo& that) const {
226 return manufacturer_id == that.manufacturer_id &&
227 product_id == that.product_id &&
228 driver_version == that.driver_version &&
229 product_name == that.product_name &&
230 is_usb_device == that.is_usb_device &&
231 (is_usb_device && usb_vendor_id == that.usb_vendor_id &&
232 usb_product_id == that.usb_product_id);
235 // Hash function to be used in base::hash_map.
236 struct Hasher {
237 size_t operator()(const MidiDeviceInfo& info) const {
238 size_t hash = info.manufacturer_id;
239 hash *= 131;
240 hash += info.product_id;
241 hash *= 131;
242 hash += info.driver_version;
243 hash *= 131;
244 hash += info.product_name.size();
245 hash *= 131;
246 if (!info.product_name.empty()) {
247 hash += info.product_name[0];
249 hash *= 131;
250 hash += info.usb_vendor_id;
251 hash *= 131;
252 hash += info.usb_product_id;
253 return hash;
257 private:
258 static bool IsUsbDevice(const MIDIINCAPS2W& caps) {
259 return IS_COMPATIBLE_USBAUDIO_MID(&caps.ManufacturerGuid) &&
260 IS_COMPATIBLE_USBAUDIO_PID(&caps.ProductGuid);
262 static bool IsUsbDevice(const MIDIOUTCAPS2W& caps) {
263 return IS_COMPATIBLE_USBAUDIO_MID(&caps.ManufacturerGuid) &&
264 IS_COMPATIBLE_USBAUDIO_PID(&caps.ProductGuid);
266 static uint16 ExtractUsbVendorIdIfExists(const MIDIINCAPS2W& caps) {
267 if (!IS_COMPATIBLE_USBAUDIO_MID(&caps.ManufacturerGuid))
268 return 0;
269 return EXTRACT_USBAUDIO_MID(&caps.ManufacturerGuid);
271 static uint16 ExtractUsbVendorIdIfExists(const MIDIOUTCAPS2W& caps) {
272 if (!IS_COMPATIBLE_USBAUDIO_MID(&caps.ManufacturerGuid))
273 return 0;
274 return EXTRACT_USBAUDIO_MID(&caps.ManufacturerGuid);
276 static uint16 ExtractUsbProductIdIfExists(const MIDIINCAPS2W& caps) {
277 if (!IS_COMPATIBLE_USBAUDIO_PID(&caps.ProductGuid))
278 return 0;
279 return EXTRACT_USBAUDIO_PID(&caps.ProductGuid);
281 static uint16 ExtractUsbProductIdIfExists(const MIDIOUTCAPS2W& caps) {
282 if (!IS_COMPATIBLE_USBAUDIO_PID(&caps.ProductGuid))
283 return 0;
284 return EXTRACT_USBAUDIO_PID(&caps.ProductGuid);
288 std::string GetManufacturerName(const MidiDeviceInfo& info) {
289 if (info.is_usb_device) {
290 const char* name = device::UsbIds::GetVendorName(info.usb_vendor_id);
291 return std::string(name ? name : "");
294 switch (info.manufacturer_id) {
295 case MM_MICROSOFT:
296 return "Microsoft Corporation";
297 default:
298 // TODO(toyoshim): Support other manufacture IDs. crbug.com/472341.
299 return "";
303 using PortNumberCache = base::hash_map<
304 MidiDeviceInfo,
305 std::priority_queue<uint32, std::vector<uint32>, std::greater<uint32>>,
306 MidiDeviceInfo::Hasher>;
308 struct MidiInputDeviceState final : base::RefCounted<MidiInputDeviceState> {
309 explicit MidiInputDeviceState(const MidiDeviceInfo& device_info)
310 : device_info(device_info),
311 midi_handle(kInvalidMidiInHandle),
312 port_index(0),
313 port_age(0),
314 start_time_initialized(false) {}
316 const MidiDeviceInfo device_info;
317 HMIDIIN midi_handle;
318 ScopedMIDIHDR midi_header;
319 // Since Win32 multimedia system uses a relative time offset from when
320 // |midiInStart| API is called, we need to record when it is called.
321 base::TimeTicks start_time;
322 // 0-based port index. We will try to reuse the previous port index when the
323 // MIDI device is closed then reopened.
324 uint32 port_index;
325 // A sequence number which represents how many times |port_index| is reused.
326 // We can remove this field if we decide not to clear unsent events
327 // when the device is disconnected.
328 // See https://github.com/WebAudio/web-midi-api/issues/133
329 uint64 port_age;
330 // True if |start_time| is initialized. This field is not used so far, but
331 // kept for the debugging purpose.
332 bool start_time_initialized;
335 struct MidiOutputDeviceState final : base::RefCounted<MidiOutputDeviceState> {
336 explicit MidiOutputDeviceState(const MidiDeviceInfo& device_info)
337 : device_info(device_info),
338 midi_handle(kInvalidMidiOutHandle),
339 port_index(0),
340 port_age(0),
341 closed(false) {}
343 const MidiDeviceInfo device_info;
344 HMIDIOUT midi_handle;
345 // 0-based port index. We will try to reuse the previous port index when the
346 // MIDI device is closed then reopened.
347 uint32 port_index;
348 // A sequence number which represents how many times |port_index| is reused.
349 // We can remove this field if we decide not to clear unsent events
350 // when the device is disconnected.
351 // See https://github.com/WebAudio/web-midi-api/issues/133
352 uint64 port_age;
353 // True if the device is already closed and |midi_handle| is considered to be
354 // invalid.
355 // TODO(toyoshim): Use std::atomic<bool> when it is allowed in Chromium
356 // project.
357 volatile bool closed;
360 // The core logic of MIDI device handling for Windows. Basically this class is
361 // shared among following 4 threads:
362 // 1. Chrome IO Thread
363 // 2. OS Multimedia Thread
364 // 3. Task Thread
365 // 4. Sender Thread
367 // Chrome IO Thread:
368 // MidiManager runs on Chrome IO thread. Device change notification is
369 // delivered to the thread through the SystemMonitor service.
370 // OnDevicesChanged() callback is invoked to update the MIDI device list.
371 // Note that in the current implementation we will try to open all the
372 // existing devices in practice. This is OK because trying to reopen a MIDI
373 // device that is already opened would simply fail, and there is no unwilling
374 // side effect.
376 // OS Multimedia Thread:
377 // This thread is maintained by the OS as a part of MIDI runtime, and
378 // responsible for receiving all the system initiated events such as device
379 // close, and receiving data. For performance reasons, most of potentially
380 // blocking operations will be dispatched into Task Thread.
382 // Task Thread:
383 // This thread will be used to call back following methods of MidiManager.
384 // - MidiManager::CompleteInitialization
385 // - MidiManager::AddInputPort
386 // - MidiManager::AddOutputPort
387 // - MidiManager::SetInputPortState
388 // - MidiManager::SetOutputPortState
389 // - MidiManager::ReceiveMidiData
391 // Sender Thread:
392 // This thread will be used to call Win32 APIs to send MIDI message at the
393 // specified time. We don't want to call MIDI send APIs on Task Thread
394 // because those APIs could be performed synchronously, hence they could block
395 // the caller thread for a while. See the comment in
396 // SendLongMidiMessageInternal for details. Currently we expect that the
397 // blocking time would be less than 1 second.
398 class MidiServiceWinImpl : public MidiServiceWin,
399 public base::SystemMonitor::DevicesChangedObserver {
400 public:
401 MidiServiceWinImpl()
402 : delegate_(nullptr),
403 sender_thread_("Windows MIDI sender thread"),
404 task_thread_("Windows MIDI task thread"),
405 destructor_started(false) {}
407 ~MidiServiceWinImpl() final {
408 // Start() and Stop() of the threads, and AddDevicesChangeObserver() and
409 // RemoveDevicesChangeObserver() should be called on the same thread.
410 CHECK(thread_checker_.CalledOnValidThread());
412 destructor_started = true;
413 base::SystemMonitor::Get()->RemoveDevicesChangedObserver(this);
415 std::vector<HMIDIIN> input_devices;
417 base::AutoLock auto_lock(input_ports_lock_);
418 for (auto it : input_device_map_)
419 input_devices.push_back(it.first);
422 for (const auto handle : input_devices) {
423 MMRESULT result = midiInClose(handle);
424 if (result == MIDIERR_STILLPLAYING) {
425 result = midiInReset(handle);
426 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
427 << "midiInReset failed: " << GetInErrorMessage(result);
428 result = midiInClose(handle);
430 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
431 << "midiInClose failed: " << GetInErrorMessage(result);
436 std::vector<HMIDIOUT> output_devices;
438 base::AutoLock auto_lock(output_ports_lock_);
439 for (auto it : output_device_map_)
440 output_devices.push_back(it.first);
443 for (const auto handle : output_devices) {
444 MMRESULT result = midiOutClose(handle);
445 if (result == MIDIERR_STILLPLAYING) {
446 result = midiOutReset(handle);
447 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
448 << "midiOutReset failed: " << GetOutErrorMessage(result);
449 result = midiOutClose(handle);
451 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
452 << "midiOutClose failed: " << GetOutErrorMessage(result);
456 sender_thread_.Stop();
457 task_thread_.Stop();
460 // MidiServiceWin overrides:
461 void InitializeAsync(MidiServiceWinDelegate* delegate) final {
462 // Start() and Stop() of the threads, and AddDevicesChangeObserver() and
463 // RemoveDevicesChangeObserver() should be called on the same thread.
464 CHECK(thread_checker_.CalledOnValidThread());
466 delegate_ = delegate;
468 sender_thread_.Start();
469 task_thread_.Start();
471 // Start monitoring device changes. This should start before the
472 // following UpdateDeviceList() call not to miss the event happening
473 // between the call and the observer registration.
474 base::SystemMonitor::Get()->AddDevicesChangedObserver(this);
476 UpdateDeviceList();
478 task_thread_.message_loop()->PostTask(
479 FROM_HERE,
480 base::Bind(&MidiServiceWinImpl::CompleteInitializationOnTaskThread,
481 base::Unretained(this), MIDI_OK));
484 void SendMidiDataAsync(uint32 port_number,
485 const std::vector<uint8>& data,
486 base::TimeTicks time) final {
487 if (destructor_started) {
488 LOG(ERROR) << "ThreadSafeSendData failed because MidiServiceWinImpl is "
489 "being destructed. port: " << port_number;
490 return;
492 auto state = GetOutputDeviceFromPort(port_number);
493 if (!state) {
494 LOG(ERROR) << "ThreadSafeSendData failed due to an invalid port number. "
495 << "port: " << port_number;
496 return;
498 if (state->closed) {
499 LOG(ERROR)
500 << "ThreadSafeSendData failed because target port is already closed."
501 << "port: " << port_number;
502 return;
504 const auto now = base::TimeTicks::Now();
505 if (now < time) {
506 sender_thread_.message_loop()->PostDelayedTask(
507 FROM_HERE, base::Bind(&MidiServiceWinImpl::SendOnSenderThread,
508 base::Unretained(this), port_number,
509 state->port_age, data, time),
510 time - now);
511 } else {
512 sender_thread_.message_loop()->PostTask(
513 FROM_HERE, base::Bind(&MidiServiceWinImpl::SendOnSenderThread,
514 base::Unretained(this), port_number,
515 state->port_age, data, time));
519 // base::SystemMonitor::DevicesChangedObserver overrides:
520 void OnDevicesChanged(base::SystemMonitor::DeviceType device_type) final {
521 CHECK(thread_checker_.CalledOnValidThread());
522 if (destructor_started)
523 return;
525 switch (device_type) {
526 case base::SystemMonitor::DEVTYPE_AUDIO_CAPTURE:
527 case base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE:
528 // Add case of other unrelated device types here.
529 return;
530 case base::SystemMonitor::DEVTYPE_UNKNOWN:
531 // Interested in MIDI devices. Try updating the device list.
532 UpdateDeviceList();
533 break;
534 // No default here to capture new DeviceType by compile time.
538 private:
539 scoped_refptr<MidiInputDeviceState> GetInputDeviceFromHandle(
540 HMIDIIN midi_handle) {
541 base::AutoLock auto_lock(input_ports_lock_);
542 const auto it = input_device_map_.find(midi_handle);
543 return (it != input_device_map_.end() ? it->second : nullptr);
546 scoped_refptr<MidiOutputDeviceState> GetOutputDeviceFromHandle(
547 HMIDIOUT midi_handle) {
548 base::AutoLock auto_lock(output_ports_lock_);
549 const auto it = output_device_map_.find(midi_handle);
550 return (it != output_device_map_.end() ? it->second : nullptr);
553 scoped_refptr<MidiOutputDeviceState> GetOutputDeviceFromPort(
554 uint32 port_number) {
555 base::AutoLock auto_lock(output_ports_lock_);
556 if (output_ports_.size() <= port_number)
557 return nullptr;
558 return output_ports_[port_number];
561 void UpdateDeviceList() {
562 task_thread_.message_loop()->PostTask(
563 FROM_HERE, base::Bind(&MidiServiceWinImpl::UpdateDeviceListOnTaskThread,
564 base::Unretained(this)));
567 /////////////////////////////////////////////////////////////////////////////
568 // Callbacks on the OS multimedia thread.
569 /////////////////////////////////////////////////////////////////////////////
571 static void CALLBACK
572 OnMidiInEventOnMainlyMultimediaThread(HMIDIIN midi_in_handle,
573 UINT message,
574 DWORD_PTR instance,
575 DWORD_PTR param1,
576 DWORD_PTR param2) {
577 MidiServiceWinImpl* self = reinterpret_cast<MidiServiceWinImpl*>(instance);
578 if (!self)
579 return;
580 switch (message) {
581 case MIM_OPEN:
582 self->OnMidiInOpen(midi_in_handle);
583 break;
584 case MIM_DATA:
585 self->OnMidiInDataOnMultimediaThread(midi_in_handle, param1, param2);
586 break;
587 case MIM_LONGDATA:
588 self->OnMidiInLongDataOnMultimediaThread(midi_in_handle, param1,
589 param2);
590 break;
591 case MIM_CLOSE:
592 self->OnMidiInCloseOnMultimediaThread(midi_in_handle);
593 break;
597 void OnMidiInOpen(HMIDIIN midi_in_handle) {
598 UINT device_id = 0;
599 MMRESULT result = midiInGetID(midi_in_handle, &device_id);
600 if (result != MMSYSERR_NOERROR) {
601 DLOG(ERROR) << "midiInGetID failed: " << GetInErrorMessage(result);
602 return;
604 MIDIINCAPS2W caps = {};
605 result = midiInGetDevCaps(device_id, reinterpret_cast<LPMIDIINCAPSW>(&caps),
606 sizeof(caps));
607 if (result != MMSYSERR_NOERROR) {
608 DLOG(ERROR) << "midiInGetDevCaps failed: " << GetInErrorMessage(result);
609 return;
611 auto state =
612 make_scoped_refptr(new MidiInputDeviceState(MidiDeviceInfo(caps)));
613 state->midi_handle = midi_in_handle;
614 state->midi_header = CreateMIDIHDR(kBufferLength);
615 const auto& state_device_info = state->device_info;
616 bool add_new_port = false;
617 uint32 port_number = 0;
619 base::AutoLock auto_lock(input_ports_lock_);
620 const auto it = unused_input_ports_.find(state_device_info);
621 if (it == unused_input_ports_.end()) {
622 port_number = input_ports_.size();
623 add_new_port = true;
624 input_ports_.push_back(nullptr);
625 input_ports_ages_.push_back(0);
626 } else {
627 port_number = it->second.top();
628 it->second.pop();
629 if (it->second.empty()) {
630 unused_input_ports_.erase(it);
633 input_ports_[port_number] = state;
635 input_ports_ages_[port_number] += 1;
636 input_device_map_[input_ports_[port_number]->midi_handle] =
637 input_ports_[port_number];
638 input_ports_[port_number]->port_index = port_number;
639 input_ports_[port_number]->port_age = input_ports_ages_[port_number];
641 // Several initial startup tasks cannot be done in MIM_OPEN handler.
642 task_thread_.message_loop()->PostTask(
643 FROM_HERE, base::Bind(&MidiServiceWinImpl::StartInputDeviceOnTaskThread,
644 base::Unretained(this), midi_in_handle));
645 if (add_new_port) {
646 const MidiPortInfo port_info(
647 // TODO(toyoshim): Use a hash ID insted crbug.com/467448
648 base::IntToString(static_cast<int>(port_number)),
649 GetManufacturerName(state_device_info),
650 base::WideToUTF8(state_device_info.product_name),
651 MmversionToString(state_device_info.driver_version),
652 MIDI_PORT_OPENED);
653 task_thread_.message_loop()->PostTask(
654 FROM_HERE, base::Bind(&MidiServiceWinImpl::AddInputPortOnTaskThread,
655 base::Unretained(this), port_info));
656 } else {
657 task_thread_.message_loop()->PostTask(
658 FROM_HERE,
659 base::Bind(&MidiServiceWinImpl::SetInputPortStateOnTaskThread,
660 base::Unretained(this), port_number,
661 MidiPortState::MIDI_PORT_CONNECTED));
665 void OnMidiInDataOnMultimediaThread(HMIDIIN midi_in_handle,
666 DWORD_PTR param1,
667 DWORD_PTR param2) {
668 auto state = GetInputDeviceFromHandle(midi_in_handle);
669 if (!state)
670 return;
671 const uint8 status_byte = static_cast<uint8>(param1 & 0xff);
672 const uint8 first_data_byte = static_cast<uint8>((param1 >> 8) & 0xff);
673 const uint8 second_data_byte = static_cast<uint8>((param1 >> 16) & 0xff);
674 const DWORD elapsed_ms = param2;
675 const size_t len = GetMidiMessageLength(status_byte);
676 const uint8 kData[] = {status_byte, first_data_byte, second_data_byte};
677 std::vector<uint8> data;
678 data.assign(kData, kData + len);
679 DCHECK_LE(len, arraysize(kData));
680 // MIM_DATA/MIM_LONGDATA message treats the time when midiInStart() is
681 // called as the origin of |elapsed_ms|.
682 // http://msdn.microsoft.com/en-us/library/windows/desktop/dd757284.aspx
683 // http://msdn.microsoft.com/en-us/library/windows/desktop/dd757286.aspx
684 const base::TimeTicks event_time =
685 state->start_time + base::TimeDelta::FromMilliseconds(elapsed_ms);
686 task_thread_.message_loop()->PostTask(
687 FROM_HERE, base::Bind(&MidiServiceWinImpl::ReceiveMidiDataOnTaskThread,
688 base::Unretained(this), state->port_index, data,
689 event_time));
692 void OnMidiInLongDataOnMultimediaThread(HMIDIIN midi_in_handle,
693 DWORD_PTR param1,
694 DWORD_PTR param2) {
695 auto state = GetInputDeviceFromHandle(midi_in_handle);
696 if (!state)
697 return;
698 MIDIHDR* header = reinterpret_cast<MIDIHDR*>(param1);
699 const DWORD elapsed_ms = param2;
700 MMRESULT result = MMSYSERR_NOERROR;
701 if (destructor_started) {
702 if (state->midi_header &&
703 (state->midi_header->dwFlags & MHDR_PREPARED) == MHDR_PREPARED) {
704 result =
705 midiInUnprepareHeader(state->midi_handle, state->midi_header.get(),
706 sizeof(*state->midi_header));
707 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
708 << "Failed to uninitialize input buffer: "
709 << GetInErrorMessage(result);
711 return;
713 if (header->dwBytesRecorded > 0) {
714 const uint8* src = reinterpret_cast<const uint8*>(header->lpData);
715 std::vector<uint8> data;
716 data.assign(src, src + header->dwBytesRecorded);
717 // MIM_DATA/MIM_LONGDATA message treats the time when midiInStart() is
718 // called as the origin of |elapsed_ms|.
719 // http://msdn.microsoft.com/en-us/library/windows/desktop/dd757284.aspx
720 // http://msdn.microsoft.com/en-us/library/windows/desktop/dd757286.aspx
721 const base::TimeTicks event_time =
722 state->start_time + base::TimeDelta::FromMilliseconds(elapsed_ms);
723 task_thread_.message_loop()->PostTask(
724 FROM_HERE,
725 base::Bind(&MidiServiceWinImpl::ReceiveMidiDataOnTaskThread,
726 base::Unretained(this), state->port_index, data,
727 event_time));
729 result = midiInAddBuffer(state->midi_handle, header, sizeof(*header));
730 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
731 << "Failed to attach input buffer: " << GetInErrorMessage(result)
732 << "port number:" << state->port_index;
735 void OnMidiInCloseOnMultimediaThread(HMIDIIN midi_in_handle) {
736 auto state = GetInputDeviceFromHandle(midi_in_handle);
737 if (!state)
738 return;
739 const uint32 port_number = state->port_index;
740 const auto device_info(state->device_info);
742 base::AutoLock auto_lock(input_ports_lock_);
743 input_device_map_.erase(state->midi_handle);
744 input_ports_[port_number] = nullptr;
745 input_ports_ages_[port_number] += 1;
746 unused_input_ports_[device_info].push(port_number);
748 task_thread_.message_loop()->PostTask(
749 FROM_HERE,
750 base::Bind(&MidiServiceWinImpl::SetInputPortStateOnTaskThread,
751 base::Unretained(this), port_number,
752 MIDI_PORT_DISCONNECTED));
755 static void CALLBACK
756 OnMidiOutEventOnMainlyMultimediaThread(HMIDIOUT midi_out_handle,
757 UINT message,
758 DWORD_PTR instance,
759 DWORD_PTR param1,
760 DWORD_PTR param2) {
761 MidiServiceWinImpl* self = reinterpret_cast<MidiServiceWinImpl*>(instance);
762 if (!self)
763 return;
764 switch (message) {
765 case MOM_OPEN:
766 self->OnMidiOutOpen(midi_out_handle, param1, param2);
767 break;
768 case MOM_DONE:
769 self->OnMidiOutDoneOnMultimediaThread(midi_out_handle, param1);
770 break;
771 case MOM_CLOSE:
772 self->OnMidiOutCloseOnMultimediaThread(midi_out_handle);
773 break;
777 void OnMidiOutOpen(HMIDIOUT midi_out_handle,
778 DWORD_PTR param1,
779 DWORD_PTR param2) {
780 UINT device_id = 0;
781 MMRESULT result = midiOutGetID(midi_out_handle, &device_id);
782 if (result != MMSYSERR_NOERROR) {
783 DLOG(ERROR) << "midiOutGetID failed: " << GetOutErrorMessage(result);
784 return;
786 MIDIOUTCAPS2W caps = {};
787 result = midiOutGetDevCaps(
788 device_id, reinterpret_cast<LPMIDIOUTCAPSW>(&caps), sizeof(caps));
789 if (result != MMSYSERR_NOERROR) {
790 DLOG(ERROR) << "midiInGetDevCaps failed: " << GetOutErrorMessage(result);
791 return;
793 auto state =
794 make_scoped_refptr(new MidiOutputDeviceState(MidiDeviceInfo(caps)));
795 state->midi_handle = midi_out_handle;
796 const auto& state_device_info = state->device_info;
797 bool add_new_port = false;
798 uint32 port_number = 0;
800 base::AutoLock auto_lock(output_ports_lock_);
801 const auto it = unused_output_ports_.find(state_device_info);
802 if (it == unused_output_ports_.end()) {
803 port_number = output_ports_.size();
804 add_new_port = true;
805 output_ports_.push_back(nullptr);
806 output_ports_ages_.push_back(0);
807 } else {
808 port_number = it->second.top();
809 it->second.pop();
810 if (it->second.empty())
811 unused_output_ports_.erase(it);
813 output_ports_[port_number] = state;
814 output_ports_ages_[port_number] += 1;
815 output_device_map_[output_ports_[port_number]->midi_handle] =
816 output_ports_[port_number];
817 output_ports_[port_number]->port_index = port_number;
818 output_ports_[port_number]->port_age = output_ports_ages_[port_number];
820 if (add_new_port) {
821 const MidiPortInfo port_info(
822 // TODO(toyoshim): Use a hash ID insted. crbug.com/467448
823 base::IntToString(static_cast<int>(port_number)),
824 GetManufacturerName(state_device_info),
825 base::WideToUTF8(state_device_info.product_name),
826 MmversionToString(state_device_info.driver_version),
827 MIDI_PORT_OPENED);
828 task_thread_.message_loop()->PostTask(
829 FROM_HERE, base::Bind(&MidiServiceWinImpl::AddOutputPortOnTaskThread,
830 base::Unretained(this), port_info));
831 } else {
832 task_thread_.message_loop()->PostTask(
833 FROM_HERE,
834 base::Bind(&MidiServiceWinImpl::SetOutputPortStateOnTaskThread,
835 base::Unretained(this), port_number, MIDI_PORT_CONNECTED));
839 void OnMidiOutDoneOnMultimediaThread(HMIDIOUT midi_out_handle,
840 DWORD_PTR param1) {
841 auto state = GetOutputDeviceFromHandle(midi_out_handle);
842 if (!state)
843 return;
844 // Take ownership of the MIDIHDR object.
845 ScopedMIDIHDR header(reinterpret_cast<MIDIHDR*>(param1));
846 if (!header)
847 return;
848 MMRESULT result = midiOutUnprepareHeader(state->midi_handle, header.get(),
849 sizeof(*header));
850 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
851 << "Failed to uninitialize output buffer: "
852 << GetOutErrorMessage(result);
855 void OnMidiOutCloseOnMultimediaThread(HMIDIOUT midi_out_handle) {
856 auto state = GetOutputDeviceFromHandle(midi_out_handle);
857 if (!state)
858 return;
859 const uint32 port_number = state->port_index;
860 const auto device_info(state->device_info);
862 base::AutoLock auto_lock(output_ports_lock_);
863 output_device_map_.erase(state->midi_handle);
864 output_ports_[port_number] = nullptr;
865 output_ports_ages_[port_number] += 1;
866 unused_output_ports_[device_info].push(port_number);
867 state->closed = true;
869 task_thread_.message_loop()->PostTask(
870 FROM_HERE,
871 base::Bind(&MidiServiceWinImpl::SetOutputPortStateOnTaskThread,
872 base::Unretained(this), port_number,
873 MIDI_PORT_DISCONNECTED));
876 /////////////////////////////////////////////////////////////////////////////
877 // Callbacks on the sender thread.
878 /////////////////////////////////////////////////////////////////////////////
880 void AssertOnSenderThread() {
881 DCHECK_EQ(sender_thread_.thread_id(), base::PlatformThread::CurrentId());
884 void SendOnSenderThread(uint32 port_number,
885 uint64 port_age,
886 const std::vector<uint8>& data,
887 base::TimeTicks time) {
888 AssertOnSenderThread();
889 if (destructor_started) {
890 LOG(ERROR) << "ThreadSafeSendData failed because MidiServiceWinImpl is "
891 "being destructed. port: " << port_number;
893 auto state = GetOutputDeviceFromPort(port_number);
894 if (!state) {
895 LOG(ERROR) << "ThreadSafeSendData failed due to an invalid port number. "
896 << "port: " << port_number;
897 return;
899 if (state->closed) {
900 LOG(ERROR)
901 << "ThreadSafeSendData failed because target port is already closed."
902 << "port: " << port_number;
903 return;
905 if (state->port_age != port_age) {
906 LOG(ERROR)
907 << "ThreadSafeSendData failed because target port is being closed."
908 << "port: " << port_number << "expected port age: " << port_age
909 << "actual port age: " << state->port_age;
912 // MIDI Running status must be filtered out.
913 MidiMessageQueue message_queue(false);
914 message_queue.Add(data);
915 std::vector<uint8> message;
916 while (true) {
917 if (destructor_started)
918 break;
919 if (state->closed)
920 break;
921 message_queue.Get(&message);
922 if (message.empty())
923 break;
924 // SendShortMidiMessageInternal can send a MIDI message up to 3 bytes.
925 if (message.size() <= 3)
926 SendShortMidiMessageInternal(state->midi_handle, message);
927 else
928 SendLongMidiMessageInternal(state->midi_handle, message);
932 /////////////////////////////////////////////////////////////////////////////
933 // Callbacks on the task thread.
934 /////////////////////////////////////////////////////////////////////////////
936 void AssertOnTaskThread() {
937 DCHECK_EQ(task_thread_.thread_id(), base::PlatformThread::CurrentId());
940 void UpdateDeviceListOnTaskThread() {
941 AssertOnTaskThread();
942 const UINT num_in_devices = midiInGetNumDevs();
943 for (UINT device_id = 0; device_id < num_in_devices; ++device_id) {
944 // Here we use |CALLBACK_FUNCTION| to subscribe MIM_DATA, MIM_LONGDATA,
945 // MIM_OPEN, and MIM_CLOSE events.
946 // - MIM_DATA: This is the only way to get a short MIDI message with
947 // timestamp information.
948 // - MIM_LONGDATA: This is the only way to get a long MIDI message with
949 // timestamp information.
950 // - MIM_OPEN: This event is sent the input device is opened. Note that
951 // this message is called on the caller thread.
952 // - MIM_CLOSE: This event is sent when 1) midiInClose() is called, or 2)
953 // the MIDI device becomes unavailable for some reasons, e.g., the
954 // cable is disconnected. As for the former case, HMIDIOUT will be
955 // invalidated soon after the callback is finished. As for the later
956 // case, however, HMIDIOUT continues to be valid until midiInClose()
957 // is called.
958 HMIDIIN midi_handle = kInvalidMidiInHandle;
959 const MMRESULT result = midiInOpen(
960 &midi_handle, device_id,
961 reinterpret_cast<DWORD_PTR>(&OnMidiInEventOnMainlyMultimediaThread),
962 reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION);
963 DLOG_IF(ERROR, result != MMSYSERR_NOERROR && result != MMSYSERR_ALLOCATED)
964 << "Failed to open output device. "
965 << " id: " << device_id << " message: " << GetInErrorMessage(result);
968 const UINT num_out_devices = midiOutGetNumDevs();
969 for (UINT device_id = 0; device_id < num_out_devices; ++device_id) {
970 // Here we use |CALLBACK_FUNCTION| to subscribe MOM_DONE, MOM_OPEN, and
971 // MOM_CLOSE events.
972 // - MOM_DONE: SendLongMidiMessageInternal() relies on this event to clean
973 // up the backing store where a long MIDI message is stored.
974 // - MOM_OPEN: This event is sent the output device is opened. Note that
975 // this message is called on the caller thread.
976 // - MOM_CLOSE: This event is sent when 1) midiOutClose() is called, or 2)
977 // the MIDI device becomes unavailable for some reasons, e.g., the
978 // cable is disconnected. As for the former case, HMIDIOUT will be
979 // invalidated soon after the callback is finished. As for the later
980 // case, however, HMIDIOUT continues to be valid until midiOutClose()
981 // is called.
982 HMIDIOUT midi_handle = kInvalidMidiOutHandle;
983 const MMRESULT result = midiOutOpen(
984 &midi_handle, device_id,
985 reinterpret_cast<DWORD_PTR>(&OnMidiOutEventOnMainlyMultimediaThread),
986 reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION);
987 DLOG_IF(ERROR, result != MMSYSERR_NOERROR && result != MMSYSERR_ALLOCATED)
988 << "Failed to open output device. "
989 << " id: " << device_id << " message: " << GetOutErrorMessage(result);
993 void StartInputDeviceOnTaskThread(HMIDIIN midi_in_handle) {
994 AssertOnTaskThread();
995 auto state = GetInputDeviceFromHandle(midi_in_handle);
996 if (!state)
997 return;
998 MMRESULT result =
999 midiInPrepareHeader(state->midi_handle, state->midi_header.get(),
1000 sizeof(*state->midi_header));
1001 if (result != MMSYSERR_NOERROR) {
1002 DLOG(ERROR) << "Failed to initialize input buffer: "
1003 << GetInErrorMessage(result);
1004 return;
1006 result = midiInAddBuffer(state->midi_handle, state->midi_header.get(),
1007 sizeof(*state->midi_header));
1008 if (result != MMSYSERR_NOERROR) {
1009 DLOG(ERROR) << "Failed to attach input buffer: "
1010 << GetInErrorMessage(result);
1011 return;
1013 result = midiInStart(state->midi_handle);
1014 if (result != MMSYSERR_NOERROR) {
1015 DLOG(ERROR) << "Failed to start input port: "
1016 << GetInErrorMessage(result);
1017 return;
1019 state->start_time = base::TimeTicks::Now();
1020 state->start_time_initialized = true;
1023 void CompleteInitializationOnTaskThread(MidiResult result) {
1024 AssertOnTaskThread();
1025 delegate_->OnCompleteInitialization(result);
1028 void ReceiveMidiDataOnTaskThread(uint32 port_index,
1029 std::vector<uint8> data,
1030 base::TimeTicks time) {
1031 AssertOnTaskThread();
1032 delegate_->OnReceiveMidiData(port_index, data, time);
1035 void AddInputPortOnTaskThread(MidiPortInfo info) {
1036 AssertOnTaskThread();
1037 delegate_->OnAddInputPort(info);
1040 void AddOutputPortOnTaskThread(MidiPortInfo info) {
1041 AssertOnTaskThread();
1042 delegate_->OnAddOutputPort(info);
1045 void SetInputPortStateOnTaskThread(uint32 port_index, MidiPortState state) {
1046 AssertOnTaskThread();
1047 delegate_->OnSetInputPortState(port_index, state);
1050 void SetOutputPortStateOnTaskThread(uint32 port_index, MidiPortState state) {
1051 AssertOnTaskThread();
1052 delegate_->OnSetOutputPortState(port_index, state);
1055 /////////////////////////////////////////////////////////////////////////////
1056 // Fields:
1057 /////////////////////////////////////////////////////////////////////////////
1059 // Does not take ownership.
1060 MidiServiceWinDelegate* delegate_;
1062 base::ThreadChecker thread_checker_;
1064 base::Thread sender_thread_;
1065 base::Thread task_thread_;
1067 base::Lock input_ports_lock_;
1068 base::hash_map<HMIDIIN, scoped_refptr<MidiInputDeviceState>>
1069 input_device_map_; // GUARDED_BY(input_ports_lock_)
1070 PortNumberCache unused_input_ports_; // GUARDED_BY(input_ports_lock_)
1071 std::vector<scoped_refptr<MidiInputDeviceState>>
1072 input_ports_; // GUARDED_BY(input_ports_lock_)
1073 std::vector<uint64> input_ports_ages_; // GUARDED_BY(input_ports_lock_)
1075 base::Lock output_ports_lock_;
1076 base::hash_map<HMIDIOUT, scoped_refptr<MidiOutputDeviceState>>
1077 output_device_map_; // GUARDED_BY(output_ports_lock_)
1078 PortNumberCache unused_output_ports_; // GUARDED_BY(output_ports_lock_)
1079 std::vector<scoped_refptr<MidiOutputDeviceState>>
1080 output_ports_; // GUARDED_BY(output_ports_lock_)
1081 std::vector<uint64> output_ports_ages_; // GUARDED_BY(output_ports_lock_)
1083 // True if one thread reached MidiServiceWinImpl::~MidiServiceWinImpl(). Note
1084 // that MidiServiceWinImpl::~MidiServiceWinImpl() is blocked until
1085 // |sender_thread_|, and |task_thread_| are stopped.
1086 // This flag can be used as the signal that when background tasks must be
1087 // interrupted.
1088 // TODO(toyoshim): Use std::atomic<bool> when it is allowed.
1089 volatile bool destructor_started;
1091 DISALLOW_COPY_AND_ASSIGN(MidiServiceWinImpl);
1094 } // namespace
1096 void MidiManagerWin::StartInitialization() {
1097 midi_service_.reset(new MidiServiceWinImpl);
1098 // Note that |CompleteInitialization()| will be called from the callback.
1099 midi_service_->InitializeAsync(this);
1102 void MidiManagerWin::DispatchSendMidiData(MidiManagerClient* client,
1103 uint32 port_index,
1104 const std::vector<uint8>& data,
1105 double timestamp) {
1106 if (!midi_service_)
1107 return;
1109 base::TimeTicks time_to_send = base::TimeTicks::Now();
1110 if (timestamp != 0.0) {
1111 time_to_send =
1112 base::TimeTicks() + base::TimeDelta::FromMicroseconds(
1113 timestamp * base::Time::kMicrosecondsPerSecond);
1115 midi_service_->SendMidiDataAsync(port_index, data, time_to_send);
1117 // TOOD(toyoshim): This calculation should be done when the date is actually
1118 // sent.
1119 client->AccumulateMidiBytesSent(data.size());
1122 MidiManager* MidiManager::Create() {
1123 return new MidiManagerWin();
1126 } // namespace media