| OLD | NEW |
| (Empty) |
| 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. | |
| 4 | |
| 5 #include "media/midi/midi_manager_win.h" | |
| 6 | |
| 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> | |
| 22 #include <stddef.h> | |
| 23 | |
| 24 #include <algorithm> | |
| 25 #include <functional> | |
| 26 #include <queue> | |
| 27 #include <string> | |
| 28 | |
| 29 #include "base/bind.h" | |
| 30 #include "base/containers/hash_tables.h" | |
| 31 #include "base/feature_list.h" | |
| 32 #include "base/macros.h" | |
| 33 #include "base/message_loop/message_loop.h" | |
| 34 #include "base/single_thread_task_runner.h" | |
| 35 #include "base/strings/string16.h" | |
| 36 #include "base/strings/string_number_conversions.h" | |
| 37 #include "base/strings/string_piece.h" | |
| 38 #include "base/strings/stringprintf.h" | |
| 39 #include "base/strings/utf_string_conversions.h" | |
| 40 #include "base/system_monitor/system_monitor.h" | |
| 41 #include "base/threading/thread_checker.h" | |
| 42 #include "base/timer/timer.h" | |
| 43 #include "base/win/message_window.h" | |
| 44 #include "base/win/windows_version.h" | |
| 45 #include "device/usb/usb_ids.h" | |
| 46 #include "media/midi/dynamically_initialized_midi_manager_win.h" | |
| 47 #include "media/midi/message_util.h" | |
| 48 #include "media/midi/midi_manager_winrt.h" | |
| 49 #include "media/midi/midi_message_queue.h" | |
| 50 #include "media/midi/midi_port_info.h" | |
| 51 #include "media/midi/midi_switches.h" | |
| 52 | |
| 53 namespace midi { | |
| 54 namespace { | |
| 55 | |
| 56 using mojom::PortState; | |
| 57 using mojom::Result; | |
| 58 | |
| 59 static const size_t kBufferLength = 32 * 1024; | |
| 60 | |
| 61 // We assume that nullpter represents an invalid MIDI handle. | |
| 62 const HMIDIIN kInvalidMidiInHandle = nullptr; | |
| 63 const HMIDIOUT kInvalidMidiOutHandle = nullptr; | |
| 64 | |
| 65 std::string GetInErrorMessage(MMRESULT result) { | |
| 66 wchar_t text[MAXERRORLENGTH]; | |
| 67 MMRESULT get_result = midiInGetErrorText(result, text, arraysize(text)); | |
| 68 if (get_result != MMSYSERR_NOERROR) { | |
| 69 DLOG(ERROR) << "Failed to get error message." | |
| 70 << " original error: " << result | |
| 71 << " midiInGetErrorText error: " << get_result; | |
| 72 return std::string(); | |
| 73 } | |
| 74 return base::WideToUTF8(text); | |
| 75 } | |
| 76 | |
| 77 std::string GetOutErrorMessage(MMRESULT result) { | |
| 78 wchar_t text[MAXERRORLENGTH]; | |
| 79 MMRESULT get_result = midiOutGetErrorText(result, text, arraysize(text)); | |
| 80 if (get_result != MMSYSERR_NOERROR) { | |
| 81 DLOG(ERROR) << "Failed to get error message." | |
| 82 << " original error: " << result | |
| 83 << " midiOutGetErrorText error: " << get_result; | |
| 84 return std::string(); | |
| 85 } | |
| 86 return base::WideToUTF8(text); | |
| 87 } | |
| 88 | |
| 89 std::string MmversionToString(MMVERSION version) { | |
| 90 return base::StringPrintf("%d.%d", HIBYTE(version), LOBYTE(version)); | |
| 91 } | |
| 92 | |
| 93 void CloseOutputPortOnTaskThread(HMIDIOUT midi_out_handle) { | |
| 94 midiOutClose(midi_out_handle); | |
| 95 } | |
| 96 | |
| 97 class MIDIHDRDeleter { | |
| 98 public: | |
| 99 void operator()(MIDIHDR* header) { | |
| 100 if (!header) | |
| 101 return; | |
| 102 delete[] static_cast<char*>(header->lpData); | |
| 103 header->lpData = NULL; | |
| 104 header->dwBufferLength = 0; | |
| 105 delete header; | |
| 106 } | |
| 107 }; | |
| 108 | |
| 109 using ScopedMIDIHDR = std::unique_ptr<MIDIHDR, MIDIHDRDeleter>; | |
| 110 | |
| 111 ScopedMIDIHDR CreateMIDIHDR(size_t size) { | |
| 112 ScopedMIDIHDR header(new MIDIHDR); | |
| 113 ZeroMemory(header.get(), sizeof(*header)); | |
| 114 header->lpData = new char[size]; | |
| 115 header->dwBufferLength = static_cast<DWORD>(size); | |
| 116 return header; | |
| 117 } | |
| 118 | |
| 119 void SendShortMidiMessageInternal(HMIDIOUT midi_out_handle, | |
| 120 const std::vector<uint8_t>& message) { | |
| 121 DCHECK_LE(message.size(), static_cast<size_t>(3)) | |
| 122 << "A short MIDI message should be up to 3 bytes."; | |
| 123 | |
| 124 DWORD packed_message = 0; | |
| 125 for (size_t i = 0; i < message.size(); ++i) | |
| 126 packed_message |= (static_cast<uint32_t>(message[i]) << (i * 8)); | |
| 127 MMRESULT result = midiOutShortMsg(midi_out_handle, packed_message); | |
| 128 DLOG_IF(ERROR, result != MMSYSERR_NOERROR) | |
| 129 << "Failed to output short message: " << GetOutErrorMessage(result); | |
| 130 } | |
| 131 | |
| 132 void SendLongMidiMessageInternal(HMIDIOUT midi_out_handle, | |
| 133 const std::vector<uint8_t>& message) { | |
| 134 // Implementation note: | |
| 135 // Sending a long MIDI message can be performed synchronously or | |
| 136 // asynchronously depending on the driver. There are 2 options to support both | |
| 137 // cases: | |
| 138 // 1) Call midiOutLongMsg() API and wait for its completion within this | |
| 139 // function. In this approach, we can avoid memory copy by directly pointing | |
| 140 // |message| as the data buffer to be sent. | |
| 141 // 2) Allocate a buffer and copy |message| to it, then call midiOutLongMsg() | |
| 142 // API. The buffer will be freed in the MOM_DONE event hander, which tells | |
| 143 // us that the task of midiOutLongMsg() API is completed. | |
| 144 // Here we choose option 2) in favor of asynchronous design. | |
| 145 | |
| 146 // Note for built-in USB-MIDI driver: | |
| 147 // From an observation on Windows 7/8.1 with a USB-MIDI keyboard, | |
| 148 // midiOutLongMsg() will be always blocked. Sending 64 bytes or less data | |
| 149 // takes roughly 300 usecs. Sending 2048 bytes or more data takes roughly | |
| 150 // |message.size() / (75 * 1024)| secs in practice. Here we put 256 KB size | |
| 151 // limit on SysEx message, with hoping that midiOutLongMsg will be blocked at | |
| 152 // most 4 sec or so with a typical USB-MIDI device. | |
| 153 // TODO(crbug.com/383578): This restriction should be removed once Web MIDI | |
| 154 // defines a standardized way to handle large sysex messages. | |
| 155 const size_t kSysExSizeLimit = 256 * 1024; | |
| 156 if (message.size() >= kSysExSizeLimit) { | |
| 157 DVLOG(1) << "Ingnoreing SysEx message due to the size limit" | |
| 158 << ", size = " << message.size(); | |
| 159 return; | |
| 160 } | |
| 161 | |
| 162 ScopedMIDIHDR midi_header(CreateMIDIHDR(message.size())); | |
| 163 std::copy(message.begin(), message.end(), midi_header->lpData); | |
| 164 | |
| 165 MMRESULT result = midiOutPrepareHeader(midi_out_handle, midi_header.get(), | |
| 166 sizeof(*midi_header)); | |
| 167 if (result != MMSYSERR_NOERROR) { | |
| 168 DLOG(ERROR) << "Failed to prepare output buffer: " | |
| 169 << GetOutErrorMessage(result); | |
| 170 return; | |
| 171 } | |
| 172 | |
| 173 result = | |
| 174 midiOutLongMsg(midi_out_handle, midi_header.get(), sizeof(*midi_header)); | |
| 175 if (result != MMSYSERR_NOERROR) { | |
| 176 DLOG(ERROR) << "Failed to output long message: " | |
| 177 << GetOutErrorMessage(result); | |
| 178 result = midiOutUnprepareHeader(midi_out_handle, midi_header.get(), | |
| 179 sizeof(*midi_header)); | |
| 180 DLOG_IF(ERROR, result != MMSYSERR_NOERROR) | |
| 181 << "Failed to uninitialize output buffer: " | |
| 182 << GetOutErrorMessage(result); | |
| 183 return; | |
| 184 } | |
| 185 | |
| 186 // The ownership of |midi_header| is moved to MOM_DONE event handler. | |
| 187 ignore_result(midi_header.release()); | |
| 188 } | |
| 189 | |
| 190 template <size_t array_size> | |
| 191 base::string16 AsString16(const wchar_t(&buffer)[array_size]) { | |
| 192 size_t len = 0; | |
| 193 for (len = 0; len < array_size; ++len) { | |
| 194 if (buffer[len] == L'\0') | |
| 195 break; | |
| 196 } | |
| 197 return base::string16(buffer, len); | |
| 198 } | |
| 199 | |
| 200 struct MidiDeviceInfo final { | |
| 201 explicit MidiDeviceInfo(const MIDIINCAPS2W& caps) | |
| 202 : manufacturer_id(caps.wMid), | |
| 203 product_id(caps.wPid), | |
| 204 driver_version(caps.vDriverVersion), | |
| 205 product_name(AsString16(caps.szPname)), | |
| 206 usb_vendor_id(ExtractUsbVendorIdIfExists(caps)), | |
| 207 usb_product_id(ExtractUsbProductIdIfExists(caps)), | |
| 208 is_usb_device(IsUsbDevice(caps)), | |
| 209 is_software_synth(false) {} | |
| 210 explicit MidiDeviceInfo(const MIDIOUTCAPS2W& caps) | |
| 211 : manufacturer_id(caps.wMid), | |
| 212 product_id(caps.wPid), | |
| 213 driver_version(caps.vDriverVersion), | |
| 214 product_name(AsString16(caps.szPname)), | |
| 215 usb_vendor_id(ExtractUsbVendorIdIfExists(caps)), | |
| 216 usb_product_id(ExtractUsbProductIdIfExists(caps)), | |
| 217 is_usb_device(IsUsbDevice(caps)), | |
| 218 is_software_synth(IsSoftwareSynth(caps)) {} | |
| 219 explicit MidiDeviceInfo(const MidiDeviceInfo& info) | |
| 220 : manufacturer_id(info.manufacturer_id), | |
| 221 product_id(info.product_id), | |
| 222 driver_version(info.driver_version), | |
| 223 product_name(info.product_name), | |
| 224 usb_vendor_id(info.usb_vendor_id), | |
| 225 usb_product_id(info.usb_product_id), | |
| 226 is_usb_device(info.is_usb_device), | |
| 227 is_software_synth(info.is_software_synth) {} | |
| 228 // Currently only following entities are considered when testing the equality | |
| 229 // of two MIDI devices. | |
| 230 // TODO(toyoshim): Consider to calculate MIDIPort.id here and use it as the | |
| 231 // key. See crbug.com/467448. Then optimize the data for |MidiPortInfo|. | |
| 232 const uint16_t manufacturer_id; | |
| 233 const uint16_t product_id; | |
| 234 const uint32_t driver_version; | |
| 235 const base::string16 product_name; | |
| 236 const uint16_t usb_vendor_id; | |
| 237 const uint16_t usb_product_id; | |
| 238 const bool is_usb_device; | |
| 239 const bool is_software_synth; | |
| 240 | |
| 241 // Required to be used as the key of base::hash_map. | |
| 242 bool operator==(const MidiDeviceInfo& that) const { | |
| 243 return manufacturer_id == that.manufacturer_id && | |
| 244 product_id == that.product_id && | |
| 245 driver_version == that.driver_version && | |
| 246 product_name == that.product_name && | |
| 247 is_usb_device == that.is_usb_device && | |
| 248 (is_usb_device && usb_vendor_id == that.usb_vendor_id && | |
| 249 usb_product_id == that.usb_product_id); | |
| 250 } | |
| 251 | |
| 252 // Hash function to be used in base::hash_map. | |
| 253 struct Hasher { | |
| 254 size_t operator()(const MidiDeviceInfo& info) const { | |
| 255 size_t hash = info.manufacturer_id; | |
| 256 hash *= 131; | |
| 257 hash += info.product_id; | |
| 258 hash *= 131; | |
| 259 hash += info.driver_version; | |
| 260 hash *= 131; | |
| 261 hash += info.product_name.size(); | |
| 262 hash *= 131; | |
| 263 if (!info.product_name.empty()) { | |
| 264 hash += info.product_name[0]; | |
| 265 } | |
| 266 hash *= 131; | |
| 267 hash += info.usb_vendor_id; | |
| 268 hash *= 131; | |
| 269 hash += info.usb_product_id; | |
| 270 return hash; | |
| 271 } | |
| 272 }; | |
| 273 | |
| 274 private: | |
| 275 static bool IsUsbDevice(const MIDIINCAPS2W& caps) { | |
| 276 return IS_COMPATIBLE_USBAUDIO_MID(&caps.ManufacturerGuid) && | |
| 277 IS_COMPATIBLE_USBAUDIO_PID(&caps.ProductGuid); | |
| 278 } | |
| 279 static bool IsUsbDevice(const MIDIOUTCAPS2W& caps) { | |
| 280 return IS_COMPATIBLE_USBAUDIO_MID(&caps.ManufacturerGuid) && | |
| 281 IS_COMPATIBLE_USBAUDIO_PID(&caps.ProductGuid); | |
| 282 } | |
| 283 static bool IsSoftwareSynth(const MIDIOUTCAPS2W& caps) { | |
| 284 return caps.wTechnology == MOD_SWSYNTH; | |
| 285 } | |
| 286 static uint16_t ExtractUsbVendorIdIfExists(const MIDIINCAPS2W& caps) { | |
| 287 if (!IS_COMPATIBLE_USBAUDIO_MID(&caps.ManufacturerGuid)) | |
| 288 return 0; | |
| 289 return EXTRACT_USBAUDIO_MID(&caps.ManufacturerGuid); | |
| 290 } | |
| 291 static uint16_t ExtractUsbVendorIdIfExists(const MIDIOUTCAPS2W& caps) { | |
| 292 if (!IS_COMPATIBLE_USBAUDIO_MID(&caps.ManufacturerGuid)) | |
| 293 return 0; | |
| 294 return EXTRACT_USBAUDIO_MID(&caps.ManufacturerGuid); | |
| 295 } | |
| 296 static uint16_t ExtractUsbProductIdIfExists(const MIDIINCAPS2W& caps) { | |
| 297 if (!IS_COMPATIBLE_USBAUDIO_PID(&caps.ProductGuid)) | |
| 298 return 0; | |
| 299 return EXTRACT_USBAUDIO_PID(&caps.ProductGuid); | |
| 300 } | |
| 301 static uint16_t ExtractUsbProductIdIfExists(const MIDIOUTCAPS2W& caps) { | |
| 302 if (!IS_COMPATIBLE_USBAUDIO_PID(&caps.ProductGuid)) | |
| 303 return 0; | |
| 304 return EXTRACT_USBAUDIO_PID(&caps.ProductGuid); | |
| 305 } | |
| 306 }; | |
| 307 | |
| 308 std::string GetManufacturerName(const MidiDeviceInfo& info) { | |
| 309 if (info.is_usb_device) { | |
| 310 const char* name = device::UsbIds::GetVendorName(info.usb_vendor_id); | |
| 311 return std::string(name ? name : ""); | |
| 312 } | |
| 313 | |
| 314 switch (info.manufacturer_id) { | |
| 315 case MM_MICROSOFT: | |
| 316 return "Microsoft Corporation"; | |
| 317 default: | |
| 318 // TODO(toyoshim): Support other manufacture IDs. crbug.com/472341. | |
| 319 return ""; | |
| 320 } | |
| 321 } | |
| 322 | |
| 323 bool IsUnsupportedDevice(const MidiDeviceInfo& info) { | |
| 324 return info.is_software_synth && info.manufacturer_id == MM_MICROSOFT && | |
| 325 (info.product_id == MM_MSFT_WDMAUDIO_MIDIOUT || | |
| 326 info.product_id == MM_MSFT_GENERIC_MIDISYNTH); | |
| 327 } | |
| 328 | |
| 329 using PortNumberCache = | |
| 330 base::hash_map<MidiDeviceInfo, | |
| 331 std::priority_queue<uint32_t, | |
| 332 std::vector<uint32_t>, | |
| 333 std::greater<uint32_t>>, | |
| 334 MidiDeviceInfo::Hasher>; | |
| 335 | |
| 336 struct MidiInputDeviceState final | |
| 337 : base::RefCountedThreadSafe<MidiInputDeviceState> { | |
| 338 explicit MidiInputDeviceState(const MidiDeviceInfo& device_info) | |
| 339 : device_info(device_info), | |
| 340 midi_handle(kInvalidMidiInHandle), | |
| 341 port_index(0), | |
| 342 port_age(0), | |
| 343 start_time_initialized(false) {} | |
| 344 | |
| 345 const MidiDeviceInfo device_info; | |
| 346 HMIDIIN midi_handle; | |
| 347 ScopedMIDIHDR midi_header; | |
| 348 // Since Win32 multimedia system uses a relative time offset from when | |
| 349 // |midiInStart| API is called, we need to record when it is called. | |
| 350 base::TimeTicks start_time; | |
| 351 // 0-based port index. We will try to reuse the previous port index when the | |
| 352 // MIDI device is closed then reopened. | |
| 353 uint32_t port_index; | |
| 354 // A sequence number which represents how many times |port_index| is reused. | |
| 355 // We can remove this field if we decide not to clear unsent events | |
| 356 // when the device is disconnected. | |
| 357 // See https://github.com/WebAudio/web-midi-api/issues/133 | |
| 358 uint64_t port_age; | |
| 359 // True if |start_time| is initialized. This field is not used so far, but | |
| 360 // kept for the debugging purpose. | |
| 361 bool start_time_initialized; | |
| 362 | |
| 363 private: | |
| 364 friend class base::RefCountedThreadSafe<MidiInputDeviceState>; | |
| 365 ~MidiInputDeviceState() {} | |
| 366 }; | |
| 367 | |
| 368 struct MidiOutputDeviceState final | |
| 369 : base::RefCountedThreadSafe<MidiOutputDeviceState> { | |
| 370 explicit MidiOutputDeviceState(const MidiDeviceInfo& device_info) | |
| 371 : device_info(device_info), | |
| 372 midi_handle(kInvalidMidiOutHandle), | |
| 373 port_index(0), | |
| 374 port_age(0), | |
| 375 closed(false) {} | |
| 376 | |
| 377 const MidiDeviceInfo device_info; | |
| 378 HMIDIOUT midi_handle; | |
| 379 // 0-based port index. We will try to reuse the previous port index when the | |
| 380 // MIDI device is closed then reopened. | |
| 381 uint32_t port_index; | |
| 382 // A sequence number which represents how many times |port_index| is reused. | |
| 383 // We can remove this field if we decide not to clear unsent events | |
| 384 // when the device is disconnected. | |
| 385 // See https://github.com/WebAudio/web-midi-api/issues/133 | |
| 386 uint64_t port_age; | |
| 387 // True if the device is already closed and |midi_handle| is considered to be | |
| 388 // invalid. | |
| 389 // TODO(toyoshim): Use std::atomic<bool> when it is allowed in Chromium | |
| 390 // project. | |
| 391 volatile bool closed; | |
| 392 | |
| 393 private: | |
| 394 friend class base::RefCountedThreadSafe<MidiOutputDeviceState>; | |
| 395 ~MidiOutputDeviceState() {} | |
| 396 }; | |
| 397 | |
| 398 // The core logic of MIDI device handling for Windows. Basically this class is | |
| 399 // shared among following 4 threads: | |
| 400 // 1. Chrome IO Thread | |
| 401 // 2. OS Multimedia Thread | |
| 402 // 3. Task Thread | |
| 403 // 4. Sender Thread | |
| 404 // | |
| 405 // Chrome IO Thread: | |
| 406 // MidiManager runs on Chrome IO thread. Device change notification is | |
| 407 // delivered to the thread through the SystemMonitor service. | |
| 408 // OnDevicesChanged() callback is invoked to update the MIDI device list. | |
| 409 // Note that in the current implementation we will try to open all the | |
| 410 // existing devices in practice. This is OK because trying to reopen a MIDI | |
| 411 // device that is already opened would simply fail, and there is no unwilling | |
| 412 // side effect. | |
| 413 // | |
| 414 // OS Multimedia Thread: | |
| 415 // This thread is maintained by the OS as a part of MIDI runtime, and | |
| 416 // responsible for receiving all the system initiated events such as device | |
| 417 // close, and receiving data. For performance reasons, most of potentially | |
| 418 // blocking operations will be dispatched into Task Thread. | |
| 419 // | |
| 420 // Task Thread: | |
| 421 // This thread will be used to call back following methods of MidiManager. | |
| 422 // - MidiManager::CompleteInitialization | |
| 423 // - MidiManager::AddInputPort | |
| 424 // - MidiManager::AddOutputPort | |
| 425 // - MidiManager::SetInputPortState | |
| 426 // - MidiManager::SetOutputPortState | |
| 427 // - MidiManager::ReceiveMidiData | |
| 428 // | |
| 429 // Sender Thread: | |
| 430 // This thread will be used to call Win32 APIs to send MIDI message at the | |
| 431 // specified time. We don't want to call MIDI send APIs on Task Thread | |
| 432 // because those APIs could be performed synchronously, hence they could block | |
| 433 // the caller thread for a while. See the comment in | |
| 434 // SendLongMidiMessageInternal for details. Currently we expect that the | |
| 435 // blocking time would be less than 1 second. | |
| 436 class MidiServiceWinImpl : public MidiServiceWin, | |
| 437 public base::SystemMonitor::DevicesChangedObserver { | |
| 438 public: | |
| 439 MidiServiceWinImpl() | |
| 440 : delegate_(nullptr), | |
| 441 sender_thread_("Windows MIDI sender thread"), | |
| 442 task_thread_("Windows MIDI task thread"), | |
| 443 destructor_started(false) {} | |
| 444 | |
| 445 ~MidiServiceWinImpl() final { | |
| 446 // Start() and Stop() of the threads, and AddDevicesChangeObserver() and | |
| 447 // RemoveDevicesChangeObserver() should be called on the same thread. | |
| 448 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 449 | |
| 450 destructor_started = true; | |
| 451 base::SystemMonitor::Get()->RemoveDevicesChangedObserver(this); | |
| 452 { | |
| 453 std::vector<HMIDIIN> input_devices; | |
| 454 { | |
| 455 base::AutoLock auto_lock(input_ports_lock_); | |
| 456 for (auto it : input_device_map_) | |
| 457 input_devices.push_back(it.first); | |
| 458 } | |
| 459 { | |
| 460 for (auto* handle : input_devices) { | |
| 461 MMRESULT result = midiInClose(handle); | |
| 462 if (result == MIDIERR_STILLPLAYING) { | |
| 463 result = midiInReset(handle); | |
| 464 DLOG_IF(ERROR, result != MMSYSERR_NOERROR) | |
| 465 << "midiInReset failed: " << GetInErrorMessage(result); | |
| 466 result = midiInClose(handle); | |
| 467 } | |
| 468 DLOG_IF(ERROR, result != MMSYSERR_NOERROR) | |
| 469 << "midiInClose failed: " << GetInErrorMessage(result); | |
| 470 } | |
| 471 } | |
| 472 } | |
| 473 { | |
| 474 std::vector<HMIDIOUT> output_devices; | |
| 475 { | |
| 476 base::AutoLock auto_lock(output_ports_lock_); | |
| 477 for (auto it : output_device_map_) | |
| 478 output_devices.push_back(it.first); | |
| 479 } | |
| 480 { | |
| 481 for (auto* handle : output_devices) { | |
| 482 MMRESULT result = midiOutClose(handle); | |
| 483 if (result == MIDIERR_STILLPLAYING) { | |
| 484 result = midiOutReset(handle); | |
| 485 DLOG_IF(ERROR, result != MMSYSERR_NOERROR) | |
| 486 << "midiOutReset failed: " << GetOutErrorMessage(result); | |
| 487 result = midiOutClose(handle); | |
| 488 } | |
| 489 DLOG_IF(ERROR, result != MMSYSERR_NOERROR) | |
| 490 << "midiOutClose failed: " << GetOutErrorMessage(result); | |
| 491 } | |
| 492 } | |
| 493 } | |
| 494 sender_thread_.Stop(); | |
| 495 task_thread_.Stop(); | |
| 496 } | |
| 497 | |
| 498 // MidiServiceWin overrides: | |
| 499 void InitializeAsync(MidiServiceWinDelegate* delegate) final { | |
| 500 // Start() and Stop() of the threads, and AddDevicesChangeObserver() and | |
| 501 // RemoveDevicesChangeObserver() should be called on the same thread. | |
| 502 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 503 | |
| 504 delegate_ = delegate; | |
| 505 | |
| 506 sender_thread_.Start(); | |
| 507 task_thread_.Start(); | |
| 508 | |
| 509 // Start monitoring device changes. This should start before the | |
| 510 // following UpdateDeviceList() call not to miss the event happening | |
| 511 // between the call and the observer registration. | |
| 512 base::SystemMonitor::Get()->AddDevicesChangedObserver(this); | |
| 513 | |
| 514 UpdateDeviceList(); | |
| 515 | |
| 516 task_thread_.task_runner()->PostTask( | |
| 517 FROM_HERE, | |
| 518 base::Bind(&MidiServiceWinImpl::CompleteInitializationOnTaskThread, | |
| 519 base::Unretained(this), Result::OK)); | |
| 520 } | |
| 521 | |
| 522 void SendMidiDataAsync(uint32_t port_number, | |
| 523 const std::vector<uint8_t>& data, | |
| 524 base::TimeTicks time) final { | |
| 525 if (destructor_started) { | |
| 526 LOG(ERROR) << "ThreadSafeSendData failed because MidiServiceWinImpl is " | |
| 527 "being destructed. port: " << port_number; | |
| 528 return; | |
| 529 } | |
| 530 auto state = GetOutputDeviceFromPort(port_number); | |
| 531 if (!state) { | |
| 532 LOG(ERROR) << "ThreadSafeSendData failed due to an invalid port number. " | |
| 533 << "port: " << port_number; | |
| 534 return; | |
| 535 } | |
| 536 if (state->closed) { | |
| 537 LOG(ERROR) | |
| 538 << "ThreadSafeSendData failed because target port is already closed." | |
| 539 << "port: " << port_number; | |
| 540 return; | |
| 541 } | |
| 542 const auto now = base::TimeTicks::Now(); | |
| 543 if (now < time) { | |
| 544 sender_thread_.task_runner()->PostDelayedTask( | |
| 545 FROM_HERE, base::Bind(&MidiServiceWinImpl::SendOnSenderThread, | |
| 546 base::Unretained(this), port_number, | |
| 547 state->port_age, data, time), | |
| 548 time - now); | |
| 549 } else { | |
| 550 sender_thread_.task_runner()->PostTask( | |
| 551 FROM_HERE, base::Bind(&MidiServiceWinImpl::SendOnSenderThread, | |
| 552 base::Unretained(this), port_number, | |
| 553 state->port_age, data, time)); | |
| 554 } | |
| 555 } | |
| 556 | |
| 557 // base::SystemMonitor::DevicesChangedObserver overrides: | |
| 558 void OnDevicesChanged(base::SystemMonitor::DeviceType device_type) final { | |
| 559 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 560 if (destructor_started) | |
| 561 return; | |
| 562 | |
| 563 switch (device_type) { | |
| 564 case base::SystemMonitor::DEVTYPE_AUDIO: | |
| 565 case base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE: | |
| 566 // Add case of other unrelated device types here. | |
| 567 return; | |
| 568 case base::SystemMonitor::DEVTYPE_UNKNOWN: | |
| 569 // Interested in MIDI devices. Try updating the device list. | |
| 570 UpdateDeviceList(); | |
| 571 break; | |
| 572 // No default here to capture new DeviceType by compile time. | |
| 573 } | |
| 574 } | |
| 575 | |
| 576 private: | |
| 577 scoped_refptr<MidiInputDeviceState> GetInputDeviceFromHandle( | |
| 578 HMIDIIN midi_handle) { | |
| 579 base::AutoLock auto_lock(input_ports_lock_); | |
| 580 const auto it = input_device_map_.find(midi_handle); | |
| 581 return (it != input_device_map_.end() ? it->second : nullptr); | |
| 582 } | |
| 583 | |
| 584 scoped_refptr<MidiOutputDeviceState> GetOutputDeviceFromHandle( | |
| 585 HMIDIOUT midi_handle) { | |
| 586 base::AutoLock auto_lock(output_ports_lock_); | |
| 587 const auto it = output_device_map_.find(midi_handle); | |
| 588 return (it != output_device_map_.end() ? it->second : nullptr); | |
| 589 } | |
| 590 | |
| 591 scoped_refptr<MidiOutputDeviceState> GetOutputDeviceFromPort( | |
| 592 uint32_t port_number) { | |
| 593 base::AutoLock auto_lock(output_ports_lock_); | |
| 594 if (output_ports_.size() <= port_number) | |
| 595 return nullptr; | |
| 596 return output_ports_[port_number]; | |
| 597 } | |
| 598 | |
| 599 void UpdateDeviceList() { | |
| 600 task_thread_.task_runner()->PostTask( | |
| 601 FROM_HERE, base::Bind(&MidiServiceWinImpl::UpdateDeviceListOnTaskThread, | |
| 602 base::Unretained(this))); | |
| 603 } | |
| 604 | |
| 605 ///////////////////////////////////////////////////////////////////////////// | |
| 606 // Callbacks on the OS multimedia thread. | |
| 607 ///////////////////////////////////////////////////////////////////////////// | |
| 608 | |
| 609 static void CALLBACK | |
| 610 OnMidiInEventOnMainlyMultimediaThread(HMIDIIN midi_in_handle, | |
| 611 UINT message, | |
| 612 DWORD_PTR instance, | |
| 613 DWORD_PTR param1, | |
| 614 DWORD_PTR param2) { | |
| 615 MidiServiceWinImpl* self = reinterpret_cast<MidiServiceWinImpl*>(instance); | |
| 616 if (!self) | |
| 617 return; | |
| 618 switch (message) { | |
| 619 case MIM_OPEN: | |
| 620 self->OnMidiInOpen(midi_in_handle); | |
| 621 break; | |
| 622 case MIM_DATA: | |
| 623 self->OnMidiInDataOnMultimediaThread(midi_in_handle, param1, param2); | |
| 624 break; | |
| 625 case MIM_LONGDATA: | |
| 626 self->OnMidiInLongDataOnMultimediaThread(midi_in_handle, param1, | |
| 627 param2); | |
| 628 break; | |
| 629 case MIM_CLOSE: | |
| 630 self->OnMidiInCloseOnMultimediaThread(midi_in_handle); | |
| 631 break; | |
| 632 } | |
| 633 } | |
| 634 | |
| 635 void OnMidiInOpen(HMIDIIN midi_in_handle) { | |
| 636 UINT device_id = 0; | |
| 637 MMRESULT result = midiInGetID(midi_in_handle, &device_id); | |
| 638 if (result != MMSYSERR_NOERROR) { | |
| 639 DLOG(ERROR) << "midiInGetID failed: " << GetInErrorMessage(result); | |
| 640 return; | |
| 641 } | |
| 642 MIDIINCAPS2W caps = {}; | |
| 643 result = midiInGetDevCaps(device_id, reinterpret_cast<LPMIDIINCAPSW>(&caps), | |
| 644 sizeof(caps)); | |
| 645 if (result != MMSYSERR_NOERROR) { | |
| 646 DLOG(ERROR) << "midiInGetDevCaps failed: " << GetInErrorMessage(result); | |
| 647 return; | |
| 648 } | |
| 649 auto state = | |
| 650 make_scoped_refptr(new MidiInputDeviceState(MidiDeviceInfo(caps))); | |
| 651 state->midi_handle = midi_in_handle; | |
| 652 state->midi_header = CreateMIDIHDR(kBufferLength); | |
| 653 const auto& state_device_info = state->device_info; | |
| 654 bool add_new_port = false; | |
| 655 uint32_t port_number = 0; | |
| 656 { | |
| 657 base::AutoLock auto_lock(input_ports_lock_); | |
| 658 const auto it = unused_input_ports_.find(state_device_info); | |
| 659 if (it == unused_input_ports_.end()) { | |
| 660 port_number = static_cast<uint32_t>(input_ports_.size()); | |
| 661 add_new_port = true; | |
| 662 input_ports_.push_back(nullptr); | |
| 663 input_ports_ages_.push_back(0); | |
| 664 } else { | |
| 665 port_number = it->second.top(); | |
| 666 it->second.pop(); | |
| 667 if (it->second.empty()) { | |
| 668 unused_input_ports_.erase(it); | |
| 669 } | |
| 670 } | |
| 671 input_ports_[port_number] = state; | |
| 672 | |
| 673 input_ports_ages_[port_number] += 1; | |
| 674 input_device_map_[input_ports_[port_number]->midi_handle] = | |
| 675 input_ports_[port_number]; | |
| 676 input_ports_[port_number]->port_index = port_number; | |
| 677 input_ports_[port_number]->port_age = input_ports_ages_[port_number]; | |
| 678 } | |
| 679 // Several initial startup tasks cannot be done in MIM_OPEN handler. | |
| 680 task_thread_.task_runner()->PostTask( | |
| 681 FROM_HERE, base::Bind(&MidiServiceWinImpl::StartInputDeviceOnTaskThread, | |
| 682 base::Unretained(this), midi_in_handle)); | |
| 683 if (add_new_port) { | |
| 684 const MidiPortInfo port_info( | |
| 685 // TODO(toyoshim): Use a hash ID insted crbug.com/467448 | |
| 686 base::IntToString(static_cast<int>(port_number)), | |
| 687 GetManufacturerName(state_device_info), | |
| 688 base::WideToUTF8(state_device_info.product_name), | |
| 689 MmversionToString(state_device_info.driver_version), | |
| 690 PortState::OPENED); | |
| 691 task_thread_.task_runner()->PostTask( | |
| 692 FROM_HERE, base::Bind(&MidiServiceWinImpl::AddInputPortOnTaskThread, | |
| 693 base::Unretained(this), port_info)); | |
| 694 } else { | |
| 695 task_thread_.task_runner()->PostTask( | |
| 696 FROM_HERE, | |
| 697 base::Bind(&MidiServiceWinImpl::SetInputPortStateOnTaskThread, | |
| 698 base::Unretained(this), port_number, | |
| 699 PortState::CONNECTED)); | |
| 700 } | |
| 701 } | |
| 702 | |
| 703 void OnMidiInDataOnMultimediaThread(HMIDIIN midi_in_handle, | |
| 704 DWORD_PTR param1, | |
| 705 DWORD_PTR param2) { | |
| 706 auto state = GetInputDeviceFromHandle(midi_in_handle); | |
| 707 if (!state) | |
| 708 return; | |
| 709 const uint8_t status_byte = static_cast<uint8_t>(param1 & 0xff); | |
| 710 const uint8_t first_data_byte = static_cast<uint8_t>((param1 >> 8) & 0xff); | |
| 711 const uint8_t second_data_byte = | |
| 712 static_cast<uint8_t>((param1 >> 16) & 0xff); | |
| 713 const DWORD elapsed_ms = param2; | |
| 714 const size_t len = GetMessageLength(status_byte); | |
| 715 const uint8_t kData[] = {status_byte, first_data_byte, second_data_byte}; | |
| 716 std::vector<uint8_t> data; | |
| 717 data.assign(kData, kData + len); | |
| 718 DCHECK_LE(len, arraysize(kData)); | |
| 719 // MIM_DATA/MIM_LONGDATA message treats the time when midiInStart() is | |
| 720 // called as the origin of |elapsed_ms|. | |
| 721 // http://msdn.microsoft.com/en-us/library/windows/desktop/dd757284.aspx | |
| 722 // http://msdn.microsoft.com/en-us/library/windows/desktop/dd757286.aspx | |
| 723 const base::TimeTicks event_time = | |
| 724 state->start_time + base::TimeDelta::FromMilliseconds(elapsed_ms); | |
| 725 task_thread_.task_runner()->PostTask( | |
| 726 FROM_HERE, base::Bind(&MidiServiceWinImpl::ReceiveMidiDataOnTaskThread, | |
| 727 base::Unretained(this), state->port_index, data, | |
| 728 event_time)); | |
| 729 } | |
| 730 | |
| 731 void OnMidiInLongDataOnMultimediaThread(HMIDIIN midi_in_handle, | |
| 732 DWORD_PTR param1, | |
| 733 DWORD_PTR param2) { | |
| 734 auto state = GetInputDeviceFromHandle(midi_in_handle); | |
| 735 if (!state) | |
| 736 return; | |
| 737 MIDIHDR* header = reinterpret_cast<MIDIHDR*>(param1); | |
| 738 const DWORD elapsed_ms = param2; | |
| 739 MMRESULT result = MMSYSERR_NOERROR; | |
| 740 if (destructor_started) { | |
| 741 if (state->midi_header && | |
| 742 (state->midi_header->dwFlags & MHDR_PREPARED) == MHDR_PREPARED) { | |
| 743 result = | |
| 744 midiInUnprepareHeader(state->midi_handle, state->midi_header.get(), | |
| 745 sizeof(*state->midi_header)); | |
| 746 DLOG_IF(ERROR, result != MMSYSERR_NOERROR) | |
| 747 << "Failed to uninitialize input buffer: " | |
| 748 << GetInErrorMessage(result); | |
| 749 } | |
| 750 return; | |
| 751 } | |
| 752 if (header->dwBytesRecorded > 0) { | |
| 753 const uint8_t* src = reinterpret_cast<const uint8_t*>(header->lpData); | |
| 754 std::vector<uint8_t> data; | |
| 755 data.assign(src, src + header->dwBytesRecorded); | |
| 756 // MIM_DATA/MIM_LONGDATA message treats the time when midiInStart() is | |
| 757 // called as the origin of |elapsed_ms|. | |
| 758 // http://msdn.microsoft.com/en-us/library/windows/desktop/dd757284.aspx | |
| 759 // http://msdn.microsoft.com/en-us/library/windows/desktop/dd757286.aspx | |
| 760 const base::TimeTicks event_time = | |
| 761 state->start_time + base::TimeDelta::FromMilliseconds(elapsed_ms); | |
| 762 task_thread_.task_runner()->PostTask( | |
| 763 FROM_HERE, | |
| 764 base::Bind(&MidiServiceWinImpl::ReceiveMidiDataOnTaskThread, | |
| 765 base::Unretained(this), state->port_index, data, | |
| 766 event_time)); | |
| 767 } | |
| 768 result = midiInAddBuffer(state->midi_handle, header, sizeof(*header)); | |
| 769 DLOG_IF(ERROR, result != MMSYSERR_NOERROR) | |
| 770 << "Failed to attach input buffer: " << GetInErrorMessage(result) | |
| 771 << "port number:" << state->port_index; | |
| 772 } | |
| 773 | |
| 774 void OnMidiInCloseOnMultimediaThread(HMIDIIN midi_in_handle) { | |
| 775 auto state = GetInputDeviceFromHandle(midi_in_handle); | |
| 776 if (!state) | |
| 777 return; | |
| 778 const uint32_t port_number = state->port_index; | |
| 779 const auto device_info(state->device_info); | |
| 780 { | |
| 781 base::AutoLock auto_lock(input_ports_lock_); | |
| 782 input_device_map_.erase(state->midi_handle); | |
| 783 input_ports_[port_number] = nullptr; | |
| 784 input_ports_ages_[port_number] += 1; | |
| 785 unused_input_ports_[device_info].push(port_number); | |
| 786 } | |
| 787 task_thread_.task_runner()->PostTask( | |
| 788 FROM_HERE, | |
| 789 base::Bind(&MidiServiceWinImpl::SetInputPortStateOnTaskThread, | |
| 790 base::Unretained(this), port_number, | |
| 791 PortState::DISCONNECTED)); | |
| 792 } | |
| 793 | |
| 794 static void CALLBACK | |
| 795 OnMidiOutEventOnMainlyMultimediaThread(HMIDIOUT midi_out_handle, | |
| 796 UINT message, | |
| 797 DWORD_PTR instance, | |
| 798 DWORD_PTR param1, | |
| 799 DWORD_PTR param2) { | |
| 800 MidiServiceWinImpl* self = reinterpret_cast<MidiServiceWinImpl*>(instance); | |
| 801 if (!self) | |
| 802 return; | |
| 803 switch (message) { | |
| 804 case MOM_OPEN: | |
| 805 self->OnMidiOutOpen(midi_out_handle, param1, param2); | |
| 806 break; | |
| 807 case MOM_DONE: | |
| 808 self->OnMidiOutDoneOnMultimediaThread(midi_out_handle, param1); | |
| 809 break; | |
| 810 case MOM_CLOSE: | |
| 811 self->OnMidiOutCloseOnMultimediaThread(midi_out_handle); | |
| 812 break; | |
| 813 } | |
| 814 } | |
| 815 | |
| 816 void OnMidiOutOpen(HMIDIOUT midi_out_handle, | |
| 817 DWORD_PTR param1, | |
| 818 DWORD_PTR param2) { | |
| 819 UINT device_id = 0; | |
| 820 MMRESULT result = midiOutGetID(midi_out_handle, &device_id); | |
| 821 if (result != MMSYSERR_NOERROR) { | |
| 822 DLOG(ERROR) << "midiOutGetID failed: " << GetOutErrorMessage(result); | |
| 823 return; | |
| 824 } | |
| 825 MIDIOUTCAPS2W caps = {}; | |
| 826 result = midiOutGetDevCaps( | |
| 827 device_id, reinterpret_cast<LPMIDIOUTCAPSW>(&caps), sizeof(caps)); | |
| 828 if (result != MMSYSERR_NOERROR) { | |
| 829 DLOG(ERROR) << "midiInGetDevCaps failed: " << GetOutErrorMessage(result); | |
| 830 return; | |
| 831 } | |
| 832 auto state = | |
| 833 make_scoped_refptr(new MidiOutputDeviceState(MidiDeviceInfo(caps))); | |
| 834 state->midi_handle = midi_out_handle; | |
| 835 const auto& state_device_info = state->device_info; | |
| 836 if (IsUnsupportedDevice(state_device_info)) { | |
| 837 task_thread_.task_runner()->PostTask( | |
| 838 FROM_HERE, base::Bind(&CloseOutputPortOnTaskThread, midi_out_handle)); | |
| 839 return; | |
| 840 } | |
| 841 bool add_new_port = false; | |
| 842 uint32_t port_number = 0; | |
| 843 { | |
| 844 base::AutoLock auto_lock(output_ports_lock_); | |
| 845 const auto it = unused_output_ports_.find(state_device_info); | |
| 846 if (it == unused_output_ports_.end()) { | |
| 847 port_number = static_cast<uint32_t>(output_ports_.size()); | |
| 848 add_new_port = true; | |
| 849 output_ports_.push_back(nullptr); | |
| 850 output_ports_ages_.push_back(0); | |
| 851 } else { | |
| 852 port_number = it->second.top(); | |
| 853 it->second.pop(); | |
| 854 if (it->second.empty()) | |
| 855 unused_output_ports_.erase(it); | |
| 856 } | |
| 857 output_ports_[port_number] = state; | |
| 858 output_ports_ages_[port_number] += 1; | |
| 859 output_device_map_[output_ports_[port_number]->midi_handle] = | |
| 860 output_ports_[port_number]; | |
| 861 output_ports_[port_number]->port_index = port_number; | |
| 862 output_ports_[port_number]->port_age = output_ports_ages_[port_number]; | |
| 863 } | |
| 864 if (add_new_port) { | |
| 865 const MidiPortInfo port_info( | |
| 866 // TODO(toyoshim): Use a hash ID insted. crbug.com/467448 | |
| 867 base::IntToString(static_cast<int>(port_number)), | |
| 868 GetManufacturerName(state_device_info), | |
| 869 base::WideToUTF8(state_device_info.product_name), | |
| 870 MmversionToString(state_device_info.driver_version), | |
| 871 PortState::OPENED); | |
| 872 task_thread_.task_runner()->PostTask( | |
| 873 FROM_HERE, base::Bind(&MidiServiceWinImpl::AddOutputPortOnTaskThread, | |
| 874 base::Unretained(this), port_info)); | |
| 875 } else { | |
| 876 task_thread_.task_runner()->PostTask( | |
| 877 FROM_HERE, | |
| 878 base::Bind(&MidiServiceWinImpl::SetOutputPortStateOnTaskThread, | |
| 879 base::Unretained(this), port_number, | |
| 880 PortState::CONNECTED)); | |
| 881 } | |
| 882 } | |
| 883 | |
| 884 void OnMidiOutDoneOnMultimediaThread(HMIDIOUT midi_out_handle, | |
| 885 DWORD_PTR param1) { | |
| 886 auto state = GetOutputDeviceFromHandle(midi_out_handle); | |
| 887 if (!state) | |
| 888 return; | |
| 889 // Take ownership of the MIDIHDR object. | |
| 890 ScopedMIDIHDR header(reinterpret_cast<MIDIHDR*>(param1)); | |
| 891 if (!header) | |
| 892 return; | |
| 893 MMRESULT result = midiOutUnprepareHeader(state->midi_handle, header.get(), | |
| 894 sizeof(*header)); | |
| 895 DLOG_IF(ERROR, result != MMSYSERR_NOERROR) | |
| 896 << "Failed to uninitialize output buffer: " | |
| 897 << GetOutErrorMessage(result); | |
| 898 } | |
| 899 | |
| 900 void OnMidiOutCloseOnMultimediaThread(HMIDIOUT midi_out_handle) { | |
| 901 auto state = GetOutputDeviceFromHandle(midi_out_handle); | |
| 902 if (!state) | |
| 903 return; | |
| 904 const uint32_t port_number = state->port_index; | |
| 905 const auto device_info(state->device_info); | |
| 906 { | |
| 907 base::AutoLock auto_lock(output_ports_lock_); | |
| 908 output_device_map_.erase(state->midi_handle); | |
| 909 output_ports_[port_number] = nullptr; | |
| 910 output_ports_ages_[port_number] += 1; | |
| 911 unused_output_ports_[device_info].push(port_number); | |
| 912 state->closed = true; | |
| 913 } | |
| 914 task_thread_.task_runner()->PostTask( | |
| 915 FROM_HERE, | |
| 916 base::Bind(&MidiServiceWinImpl::SetOutputPortStateOnTaskThread, | |
| 917 base::Unretained(this), port_number, | |
| 918 PortState::DISCONNECTED)); | |
| 919 } | |
| 920 | |
| 921 ///////////////////////////////////////////////////////////////////////////// | |
| 922 // Callbacks on the sender thread. | |
| 923 ///////////////////////////////////////////////////////////////////////////// | |
| 924 | |
| 925 void AssertOnSenderThread() { | |
| 926 DCHECK_EQ(sender_thread_.GetThreadId(), base::PlatformThread::CurrentId()); | |
| 927 } | |
| 928 | |
| 929 void SendOnSenderThread(uint32_t port_number, | |
| 930 uint64_t port_age, | |
| 931 const std::vector<uint8_t>& data, | |
| 932 base::TimeTicks time) { | |
| 933 AssertOnSenderThread(); | |
| 934 if (destructor_started) { | |
| 935 LOG(ERROR) << "ThreadSafeSendData failed because MidiServiceWinImpl is " | |
| 936 "being destructed. port: " << port_number; | |
| 937 } | |
| 938 auto state = GetOutputDeviceFromPort(port_number); | |
| 939 if (!state) { | |
| 940 LOG(ERROR) << "ThreadSafeSendData failed due to an invalid port number. " | |
| 941 << "port: " << port_number; | |
| 942 return; | |
| 943 } | |
| 944 if (state->closed) { | |
| 945 LOG(ERROR) | |
| 946 << "ThreadSafeSendData failed because target port is already closed." | |
| 947 << "port: " << port_number; | |
| 948 return; | |
| 949 } | |
| 950 if (state->port_age != port_age) { | |
| 951 LOG(ERROR) | |
| 952 << "ThreadSafeSendData failed because target port is being closed." | |
| 953 << "port: " << port_number << "expected port age: " << port_age | |
| 954 << "actual port age: " << state->port_age; | |
| 955 } | |
| 956 | |
| 957 // MIDI Running status must be filtered out. | |
| 958 MidiMessageQueue message_queue(false); | |
| 959 message_queue.Add(data); | |
| 960 std::vector<uint8_t> message; | |
| 961 while (true) { | |
| 962 if (destructor_started) | |
| 963 break; | |
| 964 if (state->closed) | |
| 965 break; | |
| 966 message_queue.Get(&message); | |
| 967 if (message.empty()) | |
| 968 break; | |
| 969 // SendShortMidiMessageInternal can send a MIDI message up to 3 bytes. | |
| 970 if (message.size() <= 3) | |
| 971 SendShortMidiMessageInternal(state->midi_handle, message); | |
| 972 else | |
| 973 SendLongMidiMessageInternal(state->midi_handle, message); | |
| 974 } | |
| 975 } | |
| 976 | |
| 977 ///////////////////////////////////////////////////////////////////////////// | |
| 978 // Callbacks on the task thread. | |
| 979 ///////////////////////////////////////////////////////////////////////////// | |
| 980 | |
| 981 void AssertOnTaskThread() { | |
| 982 DCHECK_EQ(task_thread_.GetThreadId(), base::PlatformThread::CurrentId()); | |
| 983 } | |
| 984 | |
| 985 void UpdateDeviceListOnTaskThread() { | |
| 986 AssertOnTaskThread(); | |
| 987 const UINT num_in_devices = midiInGetNumDevs(); | |
| 988 for (UINT device_id = 0; device_id < num_in_devices; ++device_id) { | |
| 989 // Here we use |CALLBACK_FUNCTION| to subscribe MIM_DATA, MIM_LONGDATA, | |
| 990 // MIM_OPEN, and MIM_CLOSE events. | |
| 991 // - MIM_DATA: This is the only way to get a short MIDI message with | |
| 992 // timestamp information. | |
| 993 // - MIM_LONGDATA: This is the only way to get a long MIDI message with | |
| 994 // timestamp information. | |
| 995 // - MIM_OPEN: This event is sent the input device is opened. Note that | |
| 996 // this message is called on the caller thread. | |
| 997 // - MIM_CLOSE: This event is sent when 1) midiInClose() is called, or 2) | |
| 998 // the MIDI device becomes unavailable for some reasons, e.g., the | |
| 999 // cable is disconnected. As for the former case, HMIDIOUT will be | |
| 1000 // invalidated soon after the callback is finished. As for the later | |
| 1001 // case, however, HMIDIOUT continues to be valid until midiInClose() | |
| 1002 // is called. | |
| 1003 HMIDIIN midi_handle = kInvalidMidiInHandle; | |
| 1004 const MMRESULT result = midiInOpen( | |
| 1005 &midi_handle, device_id, | |
| 1006 reinterpret_cast<DWORD_PTR>(&OnMidiInEventOnMainlyMultimediaThread), | |
| 1007 reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION); | |
| 1008 DLOG_IF(ERROR, result != MMSYSERR_NOERROR && result != MMSYSERR_ALLOCATED) | |
| 1009 << "Failed to open output device. " | |
| 1010 << " id: " << device_id << " message: " << GetInErrorMessage(result); | |
| 1011 } | |
| 1012 | |
| 1013 const UINT num_out_devices = midiOutGetNumDevs(); | |
| 1014 for (UINT device_id = 0; device_id < num_out_devices; ++device_id) { | |
| 1015 // Here we use |CALLBACK_FUNCTION| to subscribe MOM_DONE, MOM_OPEN, and | |
| 1016 // MOM_CLOSE events. | |
| 1017 // - MOM_DONE: SendLongMidiMessageInternal() relies on this event to clean | |
| 1018 // up the backing store where a long MIDI message is stored. | |
| 1019 // - MOM_OPEN: This event is sent the output device is opened. Note that | |
| 1020 // this message is called on the caller thread. | |
| 1021 // - MOM_CLOSE: This event is sent when 1) midiOutClose() is called, or 2) | |
| 1022 // the MIDI device becomes unavailable for some reasons, e.g., the | |
| 1023 // cable is disconnected. As for the former case, HMIDIOUT will be | |
| 1024 // invalidated soon after the callback is finished. As for the later | |
| 1025 // case, however, HMIDIOUT continues to be valid until midiOutClose() | |
| 1026 // is called. | |
| 1027 HMIDIOUT midi_handle = kInvalidMidiOutHandle; | |
| 1028 const MMRESULT result = midiOutOpen( | |
| 1029 &midi_handle, device_id, | |
| 1030 reinterpret_cast<DWORD_PTR>(&OnMidiOutEventOnMainlyMultimediaThread), | |
| 1031 reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION); | |
| 1032 DLOG_IF(ERROR, result != MMSYSERR_NOERROR && result != MMSYSERR_ALLOCATED) | |
| 1033 << "Failed to open output device. " | |
| 1034 << " id: " << device_id << " message: " << GetOutErrorMessage(result); | |
| 1035 } | |
| 1036 } | |
| 1037 | |
| 1038 void StartInputDeviceOnTaskThread(HMIDIIN midi_in_handle) { | |
| 1039 AssertOnTaskThread(); | |
| 1040 auto state = GetInputDeviceFromHandle(midi_in_handle); | |
| 1041 if (!state) | |
| 1042 return; | |
| 1043 MMRESULT result = | |
| 1044 midiInPrepareHeader(state->midi_handle, state->midi_header.get(), | |
| 1045 sizeof(*state->midi_header)); | |
| 1046 if (result != MMSYSERR_NOERROR) { | |
| 1047 DLOG(ERROR) << "Failed to initialize input buffer: " | |
| 1048 << GetInErrorMessage(result); | |
| 1049 return; | |
| 1050 } | |
| 1051 result = midiInAddBuffer(state->midi_handle, state->midi_header.get(), | |
| 1052 sizeof(*state->midi_header)); | |
| 1053 if (result != MMSYSERR_NOERROR) { | |
| 1054 DLOG(ERROR) << "Failed to attach input buffer: " | |
| 1055 << GetInErrorMessage(result); | |
| 1056 return; | |
| 1057 } | |
| 1058 result = midiInStart(state->midi_handle); | |
| 1059 if (result != MMSYSERR_NOERROR) { | |
| 1060 DLOG(ERROR) << "Failed to start input port: " | |
| 1061 << GetInErrorMessage(result); | |
| 1062 return; | |
| 1063 } | |
| 1064 state->start_time = base::TimeTicks::Now(); | |
| 1065 state->start_time_initialized = true; | |
| 1066 } | |
| 1067 | |
| 1068 void CompleteInitializationOnTaskThread(Result result) { | |
| 1069 AssertOnTaskThread(); | |
| 1070 delegate_->OnCompleteInitialization(result); | |
| 1071 } | |
| 1072 | |
| 1073 void ReceiveMidiDataOnTaskThread(uint32_t port_index, | |
| 1074 std::vector<uint8_t> data, | |
| 1075 base::TimeTicks time) { | |
| 1076 AssertOnTaskThread(); | |
| 1077 delegate_->OnReceiveMidiData(port_index, data, time); | |
| 1078 } | |
| 1079 | |
| 1080 void AddInputPortOnTaskThread(MidiPortInfo info) { | |
| 1081 AssertOnTaskThread(); | |
| 1082 delegate_->OnAddInputPort(info); | |
| 1083 } | |
| 1084 | |
| 1085 void AddOutputPortOnTaskThread(MidiPortInfo info) { | |
| 1086 AssertOnTaskThread(); | |
| 1087 delegate_->OnAddOutputPort(info); | |
| 1088 } | |
| 1089 | |
| 1090 void SetInputPortStateOnTaskThread(uint32_t port_index, PortState state) { | |
| 1091 AssertOnTaskThread(); | |
| 1092 delegate_->OnSetInputPortState(port_index, state); | |
| 1093 } | |
| 1094 | |
| 1095 void SetOutputPortStateOnTaskThread(uint32_t port_index, PortState state) { | |
| 1096 AssertOnTaskThread(); | |
| 1097 delegate_->OnSetOutputPortState(port_index, state); | |
| 1098 } | |
| 1099 | |
| 1100 ///////////////////////////////////////////////////////////////////////////// | |
| 1101 // Fields: | |
| 1102 ///////////////////////////////////////////////////////////////////////////// | |
| 1103 | |
| 1104 // Does not take ownership. | |
| 1105 MidiServiceWinDelegate* delegate_; | |
| 1106 | |
| 1107 base::ThreadChecker thread_checker_; | |
| 1108 | |
| 1109 base::Thread sender_thread_; | |
| 1110 base::Thread task_thread_; | |
| 1111 | |
| 1112 base::Lock input_ports_lock_; | |
| 1113 base::hash_map<HMIDIIN, scoped_refptr<MidiInputDeviceState>> | |
| 1114 input_device_map_; // GUARDED_BY(input_ports_lock_) | |
| 1115 PortNumberCache unused_input_ports_; // GUARDED_BY(input_ports_lock_) | |
| 1116 std::vector<scoped_refptr<MidiInputDeviceState>> | |
| 1117 input_ports_; // GUARDED_BY(input_ports_lock_) | |
| 1118 std::vector<uint64_t> input_ports_ages_; // GUARDED_BY(input_ports_lock_) | |
| 1119 | |
| 1120 base::Lock output_ports_lock_; | |
| 1121 base::hash_map<HMIDIOUT, scoped_refptr<MidiOutputDeviceState>> | |
| 1122 output_device_map_; // GUARDED_BY(output_ports_lock_) | |
| 1123 PortNumberCache unused_output_ports_; // GUARDED_BY(output_ports_lock_) | |
| 1124 std::vector<scoped_refptr<MidiOutputDeviceState>> | |
| 1125 output_ports_; // GUARDED_BY(output_ports_lock_) | |
| 1126 std::vector<uint64_t> output_ports_ages_; // GUARDED_BY(output_ports_lock_) | |
| 1127 | |
| 1128 // True if one thread reached MidiServiceWinImpl::~MidiServiceWinImpl(). Note | |
| 1129 // that MidiServiceWinImpl::~MidiServiceWinImpl() is blocked until | |
| 1130 // |sender_thread_|, and |task_thread_| are stopped. | |
| 1131 // This flag can be used as the signal that when background tasks must be | |
| 1132 // interrupted. | |
| 1133 // TODO(toyoshim): Use std::atomic<bool> when it is allowed. | |
| 1134 volatile bool destructor_started; | |
| 1135 | |
| 1136 DISALLOW_COPY_AND_ASSIGN(MidiServiceWinImpl); | |
| 1137 }; | |
| 1138 | |
| 1139 } // namespace | |
| 1140 | |
| 1141 MidiManagerWin::MidiManagerWin(MidiService* service) : MidiManager(service) {} | |
| 1142 | |
| 1143 MidiManagerWin::~MidiManagerWin() { | |
| 1144 } | |
| 1145 | |
| 1146 void MidiManagerWin::StartInitialization() { | |
| 1147 midi_service_.reset(new MidiServiceWinImpl); | |
| 1148 // Note that |CompleteInitialization()| will be called from the callback. | |
| 1149 midi_service_->InitializeAsync(this); | |
| 1150 } | |
| 1151 | |
| 1152 void MidiManagerWin::Finalize() { | |
| 1153 midi_service_.reset(); | |
| 1154 } | |
| 1155 | |
| 1156 void MidiManagerWin::DispatchSendMidiData(MidiManagerClient* client, | |
| 1157 uint32_t port_index, | |
| 1158 const std::vector<uint8_t>& data, | |
| 1159 double timestamp) { | |
| 1160 if (!midi_service_) | |
| 1161 return; | |
| 1162 | |
| 1163 base::TimeTicks time_to_send = base::TimeTicks::Now(); | |
| 1164 if (timestamp != 0.0) { | |
| 1165 time_to_send = | |
| 1166 base::TimeTicks() + base::TimeDelta::FromMicroseconds( | |
| 1167 timestamp * base::Time::kMicrosecondsPerSecond); | |
| 1168 } | |
| 1169 midi_service_->SendMidiDataAsync(port_index, data, time_to_send); | |
| 1170 | |
| 1171 // TOOD(toyoshim): This calculation should be done when the date is actually | |
| 1172 // sent. | |
| 1173 client->AccumulateMidiBytesSent(data.size()); | |
| 1174 } | |
| 1175 | |
| 1176 void MidiManagerWin::OnCompleteInitialization(Result result) { | |
| 1177 CompleteInitialization(result); | |
| 1178 } | |
| 1179 | |
| 1180 void MidiManagerWin::OnAddInputPort(MidiPortInfo info) { | |
| 1181 AddInputPort(info); | |
| 1182 } | |
| 1183 | |
| 1184 void MidiManagerWin::OnAddOutputPort(MidiPortInfo info) { | |
| 1185 AddOutputPort(info); | |
| 1186 } | |
| 1187 | |
| 1188 void MidiManagerWin::OnSetInputPortState(uint32_t port_index, PortState state) { | |
| 1189 SetInputPortState(port_index, state); | |
| 1190 } | |
| 1191 | |
| 1192 void MidiManagerWin::OnSetOutputPortState(uint32_t port_index, | |
| 1193 PortState state) { | |
| 1194 SetOutputPortState(port_index, state); | |
| 1195 } | |
| 1196 | |
| 1197 void MidiManagerWin::OnReceiveMidiData(uint32_t port_index, | |
| 1198 const std::vector<uint8_t>& data, | |
| 1199 base::TimeTicks time) { | |
| 1200 ReceiveMidiData(port_index, &data[0], data.size(), time); | |
| 1201 } | |
| 1202 | |
| 1203 MidiManager* MidiManager::Create(MidiService* service) { | |
| 1204 if (base::FeatureList::IsEnabled(features::kMidiManagerWinrt) && | |
| 1205 base::win::GetVersion() >= base::win::VERSION_WIN10) | |
| 1206 return new MidiManagerWinrt(service); | |
| 1207 if (base::FeatureList::IsEnabled(features::kMidiManagerDynamicInstantiation)) | |
| 1208 return new DynamicallyInitializedMidiManagerWin(service); | |
| 1209 return new MidiManagerWin(service); | |
| 1210 } | |
| 1211 | |
| 1212 } // namespace midi | |
| OLD | NEW |