Index: media/midi/midi_manager_win.cc |
diff --git a/media/midi/midi_manager_win.cc b/media/midi/midi_manager_win.cc |
index 2a7265ced4c68bd0219614b8b661bd50c97274ed..fcfc4e48831bb9a32a380ef394f485ae0fc6b754 100644 |
--- a/media/midi/midi_manager_win.cc |
+++ b/media/midi/midi_manager_win.cc |
@@ -1,107 +1,159 @@ |
-// Copyright 2013 The Chromium Authors. All rights reserved. |
+// Copyright 2017 The Chromium Authors. All rights reserved. |
// Use of this source code is governed by a BSD-style license that can be |
// found in the LICENSE file. |
#include "media/midi/midi_manager_win.h" |
#include <windows.h> |
+ |
#include <ks.h> |
#include <ksmedia.h> |
#include <mmreg.h> |
-// Prevent unnecessary functions from being included from <mmsystem.h> |
-#define MMNODRV |
-#define MMNOSOUND |
-#define MMNOWAVE |
-#define MMNOAUX |
-#define MMNOMIXER |
-#define MMNOTIMER |
-#define MMNOJOY |
-#define MMNOMCI |
-#define MMNOMMIO |
#include <mmsystem.h> |
-#include <stddef.h> |
#include <algorithm> |
-#include <functional> |
-#include <queue> |
#include <string> |
-#include "base/bind.h" |
-#include "base/containers/hash_tables.h" |
-#include "base/feature_list.h" |
-#include "base/macros.h" |
-#include "base/message_loop/message_loop.h" |
-#include "base/single_thread_task_runner.h" |
+#include "base/bind_helpers.h" |
+#include "base/callback.h" |
+#include "base/logging.h" |
+#include "base/memory/ptr_util.h" |
#include "base/strings/string16.h" |
-#include "base/strings/string_number_conversions.h" |
-#include "base/strings/string_piece.h" |
#include "base/strings/stringprintf.h" |
#include "base/strings/utf_string_conversions.h" |
-#include "base/system_monitor/system_monitor.h" |
-#include "base/threading/thread_checker.h" |
-#include "base/timer/timer.h" |
-#include "base/win/message_window.h" |
+#include "base/synchronization/lock.h" |
#include "base/win/windows_version.h" |
#include "device/usb/usb_ids.h" |
-#include "media/midi/dynamically_initialized_midi_manager_win.h" |
#include "media/midi/message_util.h" |
#include "media/midi/midi_manager_winrt.h" |
-#include "media/midi/midi_message_queue.h" |
#include "media/midi/midi_port_info.h" |
+#include "media/midi/midi_service.h" |
#include "media/midi/midi_switches.h" |
namespace midi { |
-namespace { |
-using mojom::PortState; |
-using mojom::Result; |
+// Forward declaration of PortManager for anonymous functions and internal |
+// classes to use it. |
+class MidiManagerWin::PortManager { |
+ public: |
+ // Calculates event time from elapsed time that system provides. |
+ base::TimeTicks CalculateInEventTime(size_t index, uint32_t elapsed_ms) const; |
-static const size_t kBufferLength = 32 * 1024; |
+ // Registers HMIDIIN handle to resolve port index. |
+ void RegisterInHandle(HMIDIIN handle, size_t index); |
-// We assume that nullpter represents an invalid MIDI handle. |
-const HMIDIIN kInvalidMidiInHandle = nullptr; |
-const HMIDIOUT kInvalidMidiOutHandle = nullptr; |
+ // Unregisters HMIDIIN handle. |
+ void UnregisterInHandle(HMIDIIN handle); |
-std::string GetInErrorMessage(MMRESULT result) { |
- wchar_t text[MAXERRORLENGTH]; |
- MMRESULT get_result = midiInGetErrorText(result, text, arraysize(text)); |
- if (get_result != MMSYSERR_NOERROR) { |
- DLOG(ERROR) << "Failed to get error message." |
- << " original error: " << result |
- << " midiInGetErrorText error: " << get_result; |
- return std::string(); |
- } |
- return base::WideToUTF8(text); |
+ // Finds HMIDIIN handle and fullfil |out_index| with the port index. |
+ bool FindInHandle(HMIDIIN hmi, size_t* out_index); |
+ |
+ // Restores used input buffer for the next data receive. |
+ void RestoreInBuffer(size_t index); |
+ |
+ // Ports accessors. |
+ std::vector<std::unique_ptr<InPort>>* inputs() { return &input_ports_; } |
+ std::vector<std::unique_ptr<OutPort>>* outputs() { return &output_ports_; } |
+ |
+ // Handles MIDI input port callbacks that runs on a system provided thread. |
+ static void CALLBACK HandleMidiInCallback(HMIDIIN hmi, |
+ UINT msg, |
+ DWORD_PTR instance, |
+ DWORD_PTR param1, |
+ DWORD_PTR param2); |
+ |
+ // Handles MIDI output port callbacks that runs on a system provided thread. |
+ static void CALLBACK HandleMidiOutCallback(HMIDIOUT hmo, |
+ UINT msg, |
+ DWORD_PTR instance, |
+ DWORD_PTR param1, |
+ DWORD_PTR param2); |
+ |
+ private: |
+ // Holds all MIDI input or output ports connected once. |
+ std::vector<std::unique_ptr<InPort>> input_ports_; |
+ std::vector<std::unique_ptr<OutPort>> output_ports_; |
+ |
+ // Map to resolve MIDI input port index from HMIDIIN. |
+ std::map<HMIDIIN, size_t> hmidiin_to_index_map_; |
+}; |
+ |
+namespace { |
+ |
+// Assumes that nullptr represents an invalid MIDI handle. |
+constexpr HMIDIIN kInvalidInHandle = nullptr; |
+constexpr HMIDIOUT kInvalidOutHandle = nullptr; |
+ |
+// Defines SysEx message size limit. |
+// TODO(crbug.com/383578): This restriction should be removed once Web MIDI |
+// defines a standardized way to handle large sysex messages. |
+// Note for built-in USB-MIDI driver: |
+// From an observation on Windows 7/8.1 with a USB-MIDI keyboard, |
+// midiOutLongMsg() will be always blocked. Sending 64 bytes or less data takes |
+// roughly 300 usecs. Sending 2048 bytes or more data takes roughly |
+// |message.size() / (75 * 1024)| secs in practice. Here we put 256 KB size |
+// limit on SysEx message, with hoping that midiOutLongMsg will be blocked at |
+// most 4 sec or so with a typical USB-MIDI device. |
+// TODO(toyoshim): Consider to use linked small buffers so that midiOutReset() |
+// can abort sending unhandled following buffers. |
+constexpr size_t kSysExSizeLimit = 256 * 1024; |
+ |
+// Defines input buffer size. |
+constexpr size_t kBufferLength = 32 * 1024; |
+ |
+// Global variables to identify MidiManager instance. |
+constexpr int kInvalidInstanceId = -1; |
+int g_active_instance_id = kInvalidInstanceId; |
+MidiManagerWin* g_manager_instance = nullptr; |
+ |
+// Obtains base::Lock instance pointer to lock instance_id. |
+base::Lock* GetInstanceIdLock() { |
+ static base::Lock* lock = new base::Lock; |
+ return lock; |
} |
-std::string GetOutErrorMessage(MMRESULT result) { |
- wchar_t text[MAXERRORLENGTH]; |
- MMRESULT get_result = midiOutGetErrorText(result, text, arraysize(text)); |
- if (get_result != MMSYSERR_NOERROR) { |
- DLOG(ERROR) << "Failed to get error message." |
- << " original error: " << result |
- << " midiOutGetErrorText error: " << get_result; |
- return std::string(); |
- } |
- return base::WideToUTF8(text); |
+// Issues unique MidiManager instance ID. |
+int IssueNextInstanceId() { |
+ static int id = kInvalidInstanceId; |
+ return ++id; |
} |
-std::string MmversionToString(MMVERSION version) { |
- return base::StringPrintf("%d.%d", HIBYTE(version), LOBYTE(version)); |
+// Use single TaskRunner for all tasks running outside the I/O thread. |
+constexpr int kTaskRunner = 0; |
+ |
+// Obtains base::Lock instance pointer to ensure tasks run safely on TaskRunner. |
+// Since all tasks on TaskRunner run behind a lock of *GetTaskLock(), we can |
+// access all members even on the I/O thread if a lock of *GetTaskLock() is |
+// obtained. |
+base::Lock* GetTaskLock() { |
+ static base::Lock* lock = new base::Lock; |
+ return lock; |
} |
-void CloseOutputPortOnTaskThread(HMIDIOUT midi_out_handle) { |
- midiOutClose(midi_out_handle); |
+// Helper function to run a posted task on TaskRunner safely. |
+void RunTask(int instance_id, const base::Closure& task) { |
+ // Obtains task lock to ensure that the instance should not complete |
+ // Finalize() while running the |task|. |
+ base::AutoLock task_lock(*GetTaskLock()); |
+ { |
+ // If Finalize() finished before the lock avobe, do nothing. |
+ base::AutoLock lock(*GetInstanceIdLock()); |
+ if (instance_id != g_active_instance_id) |
+ return; |
+ } |
+ task.Run(); |
} |
+// TODO(toyoshim): Factor out TaskRunner related functionaliries above, and |
+// deprecate MidiScheduler. It should be available via MidiManager::scheduler(). |
+ |
+// Utility class to handle MIDIHDR struct safely. |
class MIDIHDRDeleter { |
public: |
- void operator()(MIDIHDR* header) { |
+ void operator()(LPMIDIHDR header) { |
if (!header) |
return; |
delete[] static_cast<char*>(header->lpData); |
- header->lpData = NULL; |
- header->dwBufferLength = 0; |
delete header; |
} |
}; |
@@ -109,1103 +161,681 @@ class MIDIHDRDeleter { |
using ScopedMIDIHDR = std::unique_ptr<MIDIHDR, MIDIHDRDeleter>; |
ScopedMIDIHDR CreateMIDIHDR(size_t size) { |
- ScopedMIDIHDR header(new MIDIHDR); |
- ZeroMemory(header.get(), sizeof(*header)); |
- header->lpData = new char[size]; |
- header->dwBufferLength = static_cast<DWORD>(size); |
- return header; |
+ ScopedMIDIHDR hdr(new MIDIHDR); |
+ ZeroMemory(hdr.get(), sizeof(*hdr)); |
+ hdr->lpData = new char[size]; |
+ hdr->dwBufferLength = static_cast<DWORD>(size); |
+ return hdr; |
} |
-void SendShortMidiMessageInternal(HMIDIOUT midi_out_handle, |
- const std::vector<uint8_t>& message) { |
- DCHECK_LE(message.size(), static_cast<size_t>(3)) |
- << "A short MIDI message should be up to 3 bytes."; |
- |
- DWORD packed_message = 0; |
- for (size_t i = 0; i < message.size(); ++i) |
- packed_message |= (static_cast<uint32_t>(message[i]) << (i * 8)); |
- MMRESULT result = midiOutShortMsg(midi_out_handle, packed_message); |
- DLOG_IF(ERROR, result != MMSYSERR_NOERROR) |
- << "Failed to output short message: " << GetOutErrorMessage(result); |
+ScopedMIDIHDR CreateMIDIHDR(const std::vector<uint8_t>& data) { |
+ ScopedMIDIHDR hdr(CreateMIDIHDR(data.size())); |
+ std::copy(data.begin(), data.end(), hdr->lpData); |
+ return hdr; |
} |
-void SendLongMidiMessageInternal(HMIDIOUT midi_out_handle, |
- const std::vector<uint8_t>& message) { |
- // Implementation note: |
- // Sending a long MIDI message can be performed synchronously or |
- // asynchronously depending on the driver. There are 2 options to support both |
- // cases: |
- // 1) Call midiOutLongMsg() API and wait for its completion within this |
- // function. In this approach, we can avoid memory copy by directly pointing |
- // |message| as the data buffer to be sent. |
- // 2) Allocate a buffer and copy |message| to it, then call midiOutLongMsg() |
- // API. The buffer will be freed in the MOM_DONE event hander, which tells |
- // us that the task of midiOutLongMsg() API is completed. |
- // Here we choose option 2) in favor of asynchronous design. |
- |
- // Note for built-in USB-MIDI driver: |
- // From an observation on Windows 7/8.1 with a USB-MIDI keyboard, |
- // midiOutLongMsg() will be always blocked. Sending 64 bytes or less data |
- // takes roughly 300 usecs. Sending 2048 bytes or more data takes roughly |
- // |message.size() / (75 * 1024)| secs in practice. Here we put 256 KB size |
- // limit on SysEx message, with hoping that midiOutLongMsg will be blocked at |
- // most 4 sec or so with a typical USB-MIDI device. |
- // TODO(crbug.com/383578): This restriction should be removed once Web MIDI |
- // defines a standardized way to handle large sysex messages. |
- const size_t kSysExSizeLimit = 256 * 1024; |
- if (message.size() >= kSysExSizeLimit) { |
- DVLOG(1) << "Ingnoreing SysEx message due to the size limit" |
- << ", size = " << message.size(); |
- return; |
- } |
- |
- ScopedMIDIHDR midi_header(CreateMIDIHDR(message.size())); |
- std::copy(message.begin(), message.end(), midi_header->lpData); |
- |
- MMRESULT result = midiOutPrepareHeader(midi_out_handle, midi_header.get(), |
- sizeof(*midi_header)); |
- if (result != MMSYSERR_NOERROR) { |
- DLOG(ERROR) << "Failed to prepare output buffer: " |
- << GetOutErrorMessage(result); |
- return; |
- } |
- |
- result = |
- midiOutLongMsg(midi_out_handle, midi_header.get(), sizeof(*midi_header)); |
- if (result != MMSYSERR_NOERROR) { |
- DLOG(ERROR) << "Failed to output long message: " |
- << GetOutErrorMessage(result); |
- result = midiOutUnprepareHeader(midi_out_handle, midi_header.get(), |
- sizeof(*midi_header)); |
- DLOG_IF(ERROR, result != MMSYSERR_NOERROR) |
- << "Failed to uninitialize output buffer: " |
- << GetOutErrorMessage(result); |
- return; |
- } |
+// Helper functions to close MIDI device handles on TaskRunner asynchronously. |
+void FinalizeInPort(HMIDIIN handle, ScopedMIDIHDR hdr) { |
+ // Resets the device. This stops receiving messages, and allows to release |
+ // registered buffer headers. Otherwise, midiInUnprepareHeader() and |
+ // midiInClose() will fail with MIDIERR_STILLPLAYING. |
+ midiInReset(handle); |
- // The ownership of |midi_header| is moved to MOM_DONE event handler. |
- ignore_result(midi_header.release()); |
+ if (hdr) |
+ midiInUnprepareHeader(handle, hdr.get(), sizeof(*hdr)); |
+ midiInClose(handle); |
} |
-template <size_t array_size> |
-base::string16 AsString16(const wchar_t(&buffer)[array_size]) { |
- size_t len = 0; |
- for (len = 0; len < array_size; ++len) { |
- if (buffer[len] == L'\0') |
- break; |
- } |
- return base::string16(buffer, len); |
+void FinalizeOutPort(HMIDIOUT handle) { |
+ // Resets inflight buffers. This will cancel sending data that system |
+ // holds and were not sent yet. |
+ midiOutReset(handle); |
+ midiOutClose(handle); |
} |
-struct MidiDeviceInfo final { |
- explicit MidiDeviceInfo(const MIDIINCAPS2W& caps) |
- : manufacturer_id(caps.wMid), |
- product_id(caps.wPid), |
- driver_version(caps.vDriverVersion), |
- product_name(AsString16(caps.szPname)), |
- usb_vendor_id(ExtractUsbVendorIdIfExists(caps)), |
- usb_product_id(ExtractUsbProductIdIfExists(caps)), |
- is_usb_device(IsUsbDevice(caps)), |
- is_software_synth(false) {} |
- explicit MidiDeviceInfo(const MIDIOUTCAPS2W& caps) |
- : manufacturer_id(caps.wMid), |
- product_id(caps.wPid), |
- driver_version(caps.vDriverVersion), |
- product_name(AsString16(caps.szPname)), |
- usb_vendor_id(ExtractUsbVendorIdIfExists(caps)), |
- usb_product_id(ExtractUsbProductIdIfExists(caps)), |
- is_usb_device(IsUsbDevice(caps)), |
- is_software_synth(IsSoftwareSynth(caps)) {} |
- explicit MidiDeviceInfo(const MidiDeviceInfo& info) |
- : manufacturer_id(info.manufacturer_id), |
- product_id(info.product_id), |
- driver_version(info.driver_version), |
- product_name(info.product_name), |
- usb_vendor_id(info.usb_vendor_id), |
- usb_product_id(info.usb_product_id), |
- is_usb_device(info.is_usb_device), |
- is_software_synth(info.is_software_synth) {} |
- // Currently only following entities are considered when testing the equality |
- // of two MIDI devices. |
- // TODO(toyoshim): Consider to calculate MIDIPort.id here and use it as the |
- // key. See crbug.com/467448. Then optimize the data for |MidiPortInfo|. |
- const uint16_t manufacturer_id; |
- const uint16_t product_id; |
- const uint32_t driver_version; |
- const base::string16 product_name; |
- const uint16_t usb_vendor_id; |
- const uint16_t usb_product_id; |
- const bool is_usb_device; |
- const bool is_software_synth; |
- |
- // Required to be used as the key of base::hash_map. |
- bool operator==(const MidiDeviceInfo& that) const { |
- return manufacturer_id == that.manufacturer_id && |
- product_id == that.product_id && |
- driver_version == that.driver_version && |
- product_name == that.product_name && |
- is_usb_device == that.is_usb_device && |
- (is_usb_device && usb_vendor_id == that.usb_vendor_id && |
- usb_product_id == that.usb_product_id); |
+// Gets manufacturer name in string from identifiers. |
+std::string GetManufacturerName(uint16_t id, const GUID& guid) { |
+ if (IS_COMPATIBLE_USBAUDIO_MID(&guid)) { |
+ const char* name = |
+ device::UsbIds::GetVendorName(EXTRACT_USBAUDIO_MID(&guid)); |
+ if (name) |
+ return std::string(name); |
} |
+ if (id == MM_MICROSOFT) |
+ return "Microsoft Corporation"; |
- // Hash function to be used in base::hash_map. |
- struct Hasher { |
- size_t operator()(const MidiDeviceInfo& info) const { |
- size_t hash = info.manufacturer_id; |
- hash *= 131; |
- hash += info.product_id; |
- hash *= 131; |
- hash += info.driver_version; |
- hash *= 131; |
- hash += info.product_name.size(); |
- hash *= 131; |
- if (!info.product_name.empty()) { |
- hash += info.product_name[0]; |
- } |
- hash *= 131; |
- hash += info.usb_vendor_id; |
- hash *= 131; |
- hash += info.usb_product_id; |
- return hash; |
- } |
- }; |
- |
- private: |
- static bool IsUsbDevice(const MIDIINCAPS2W& caps) { |
- return IS_COMPATIBLE_USBAUDIO_MID(&caps.ManufacturerGuid) && |
- IS_COMPATIBLE_USBAUDIO_PID(&caps.ProductGuid); |
- } |
- static bool IsUsbDevice(const MIDIOUTCAPS2W& caps) { |
- return IS_COMPATIBLE_USBAUDIO_MID(&caps.ManufacturerGuid) && |
- IS_COMPATIBLE_USBAUDIO_PID(&caps.ProductGuid); |
- } |
- static bool IsSoftwareSynth(const MIDIOUTCAPS2W& caps) { |
- return caps.wTechnology == MOD_SWSYNTH; |
- } |
- static uint16_t ExtractUsbVendorIdIfExists(const MIDIINCAPS2W& caps) { |
- if (!IS_COMPATIBLE_USBAUDIO_MID(&caps.ManufacturerGuid)) |
- return 0; |
- return EXTRACT_USBAUDIO_MID(&caps.ManufacturerGuid); |
- } |
- static uint16_t ExtractUsbVendorIdIfExists(const MIDIOUTCAPS2W& caps) { |
- if (!IS_COMPATIBLE_USBAUDIO_MID(&caps.ManufacturerGuid)) |
- return 0; |
- return EXTRACT_USBAUDIO_MID(&caps.ManufacturerGuid); |
- } |
- static uint16_t ExtractUsbProductIdIfExists(const MIDIINCAPS2W& caps) { |
- if (!IS_COMPATIBLE_USBAUDIO_PID(&caps.ProductGuid)) |
- return 0; |
- return EXTRACT_USBAUDIO_PID(&caps.ProductGuid); |
- } |
- static uint16_t ExtractUsbProductIdIfExists(const MIDIOUTCAPS2W& caps) { |
- if (!IS_COMPATIBLE_USBAUDIO_PID(&caps.ProductGuid)) |
- return 0; |
- return EXTRACT_USBAUDIO_PID(&caps.ProductGuid); |
- } |
-}; |
- |
-std::string GetManufacturerName(const MidiDeviceInfo& info) { |
- if (info.is_usb_device) { |
- const char* name = device::UsbIds::GetVendorName(info.usb_vendor_id); |
- return std::string(name ? name : ""); |
- } |
- |
- switch (info.manufacturer_id) { |
- case MM_MICROSOFT: |
- return "Microsoft Corporation"; |
- default: |
- // TODO(toyoshim): Support other manufacture IDs. crbug.com/472341. |
- return ""; |
- } |
+ // TODO(crbug.com/472341): Support other manufacture IDs. |
+ return ""; |
} |
-bool IsUnsupportedDevice(const MidiDeviceInfo& info) { |
- return info.is_software_synth && info.manufacturer_id == MM_MICROSOFT && |
- (info.product_id == MM_MSFT_WDMAUDIO_MIDIOUT || |
- info.product_id == MM_MSFT_GENERIC_MIDISYNTH); |
-} |
- |
-using PortNumberCache = |
- base::hash_map<MidiDeviceInfo, |
- std::priority_queue<uint32_t, |
- std::vector<uint32_t>, |
- std::greater<uint32_t>>, |
- MidiDeviceInfo::Hasher>; |
- |
-struct MidiInputDeviceState final |
- : base::RefCountedThreadSafe<MidiInputDeviceState> { |
- explicit MidiInputDeviceState(const MidiDeviceInfo& device_info) |
- : device_info(device_info), |
- midi_handle(kInvalidMidiInHandle), |
- port_index(0), |
- port_age(0), |
- start_time_initialized(false) {} |
- |
- const MidiDeviceInfo device_info; |
- HMIDIIN midi_handle; |
- ScopedMIDIHDR midi_header; |
- // Since Win32 multimedia system uses a relative time offset from when |
- // |midiInStart| API is called, we need to record when it is called. |
- base::TimeTicks start_time; |
- // 0-based port index. We will try to reuse the previous port index when the |
- // MIDI device is closed then reopened. |
- uint32_t port_index; |
- // A sequence number which represents how many times |port_index| is reused. |
- // We can remove this field if we decide not to clear unsent events |
- // when the device is disconnected. |
- // See https://github.com/WebAudio/web-midi-api/issues/133 |
- uint64_t port_age; |
- // True if |start_time| is initialized. This field is not used so far, but |
- // kept for the debugging purpose. |
- bool start_time_initialized; |
- |
- private: |
- friend class base::RefCountedThreadSafe<MidiInputDeviceState>; |
- ~MidiInputDeviceState() {} |
-}; |
- |
-struct MidiOutputDeviceState final |
- : base::RefCountedThreadSafe<MidiOutputDeviceState> { |
- explicit MidiOutputDeviceState(const MidiDeviceInfo& device_info) |
- : device_info(device_info), |
- midi_handle(kInvalidMidiOutHandle), |
- port_index(0), |
- port_age(0), |
- closed(false) {} |
- |
- const MidiDeviceInfo device_info; |
- HMIDIOUT midi_handle; |
- // 0-based port index. We will try to reuse the previous port index when the |
- // MIDI device is closed then reopened. |
- uint32_t port_index; |
- // A sequence number which represents how many times |port_index| is reused. |
- // We can remove this field if we decide not to clear unsent events |
- // when the device is disconnected. |
- // See https://github.com/WebAudio/web-midi-api/issues/133 |
- uint64_t port_age; |
- // True if the device is already closed and |midi_handle| is considered to be |
- // invalid. |
- // TODO(toyoshim): Use std::atomic<bool> when it is allowed in Chromium |
- // project. |
- volatile bool closed; |
+// All instances of Port subclasses are always accessed behind a lock of |
+// *GetTaskLock(). Port and subclasses implementation do not need to |
+// consider thread safety. |
+class Port { |
+ public: |
+ Port(const std::string& type, |
+ uint32_t device_id, |
+ uint16_t manufacturer_id, |
+ uint16_t product_id, |
+ uint32_t driver_version, |
+ const std::string& product_name, |
+ const GUID& manufacturer_guid) |
+ : index_(0u), |
+ type_(type), |
+ device_id_(device_id), |
+ manufacturer_id_(manufacturer_id), |
+ product_id_(product_id), |
+ driver_version_(driver_version), |
+ product_name_(product_name) { |
+ info_.manufacturer = |
+ GetManufacturerName(manufacturer_id, manufacturer_guid); |
+ info_.name = product_name_; |
+ info_.version = base::StringPrintf("%d.%d", HIBYTE(driver_version_), |
+ LOBYTE(driver_version_)); |
+ info_.state = mojom::PortState::DISCONNECTED; |
+ } |
+ |
+ virtual ~Port() {} |
+ |
+ bool operator==(const Port& other) const { |
+ // Should not use |device_id| for comparison because it can be changed on |
+ // each enumeration. |
+ // Since the GUID will be changed on each enumeration for Microsoft GS |
+ // Wavetable synth and might be done for others, do not use it for device |
+ // comparison. |
+ return manufacturer_id_ == other.manufacturer_id_ && |
+ product_id_ == other.product_id_ && |
+ driver_version_ == other.driver_version_ && |
+ product_name_ == other.product_name_; |
+ } |
+ |
+ bool IsConnected() const { |
+ return info_.state != mojom::PortState::DISCONNECTED; |
+ } |
+ |
+ void set_index(size_t index) { |
+ index_ = index; |
+ // TODO(toyoshim): Use hashed ID. |
+ info_.id = base::StringPrintf("%s-%d", type_.c_str(), index_); |
+ } |
+ size_t index() { return index_; } |
+ void set_device_id(uint32_t device_id) { device_id_ = device_id; } |
+ uint32_t device_id() { return device_id_; } |
+ const MidiPortInfo& info() { return info_; } |
+ |
+ virtual bool Connect() { |
+ if (info_.state != mojom::PortState::DISCONNECTED) |
+ return false; |
+ |
+ info_.state = mojom::PortState::CONNECTED; |
+ // TODO(toyoshim) Until open() / close() are supported, open each device on |
+ // connected. |
+ Open(); |
+ return true; |
+ } |
+ |
+ virtual bool Disconnect() { |
+ if (info_.state == mojom::PortState::DISCONNECTED) |
+ return false; |
+ info_.state = mojom::PortState::DISCONNECTED; |
+ return true; |
+ } |
+ |
+ virtual void Open() { info_.state = mojom::PortState::OPENED; } |
+ |
+ protected: |
+ size_t index_; |
+ std::string type_; |
+ uint32_t device_id_; |
+ const uint16_t manufacturer_id_; |
+ const uint16_t product_id_; |
+ const uint32_t driver_version_; |
+ const std::string product_name_; |
+ MidiPortInfo info_; |
+}; // class Port |
- private: |
- friend class base::RefCountedThreadSafe<MidiOutputDeviceState>; |
- ~MidiOutputDeviceState() {} |
-}; |
+} // namespace |
-// The core logic of MIDI device handling for Windows. Basically this class is |
-// shared among following 4 threads: |
-// 1. Chrome IO Thread |
-// 2. OS Multimedia Thread |
-// 3. Task Thread |
-// 4. Sender Thread |
-// |
-// Chrome IO Thread: |
-// MidiManager runs on Chrome IO thread. Device change notification is |
-// delivered to the thread through the SystemMonitor service. |
-// OnDevicesChanged() callback is invoked to update the MIDI device list. |
-// Note that in the current implementation we will try to open all the |
-// existing devices in practice. This is OK because trying to reopen a MIDI |
-// device that is already opened would simply fail, and there is no unwilling |
-// side effect. |
-// |
-// OS Multimedia Thread: |
-// This thread is maintained by the OS as a part of MIDI runtime, and |
-// responsible for receiving all the system initiated events such as device |
-// close, and receiving data. For performance reasons, most of potentially |
-// blocking operations will be dispatched into Task Thread. |
-// |
-// Task Thread: |
-// This thread will be used to call back following methods of MidiManager. |
-// - MidiManager::CompleteInitialization |
-// - MidiManager::AddInputPort |
-// - MidiManager::AddOutputPort |
-// - MidiManager::SetInputPortState |
-// - MidiManager::SetOutputPortState |
-// - MidiManager::ReceiveMidiData |
-// |
-// Sender Thread: |
-// This thread will be used to call Win32 APIs to send MIDI message at the |
-// specified time. We don't want to call MIDI send APIs on Task Thread |
-// because those APIs could be performed synchronously, hence they could block |
-// the caller thread for a while. See the comment in |
-// SendLongMidiMessageInternal for details. Currently we expect that the |
-// blocking time would be less than 1 second. |
-class MidiServiceWinImpl : public MidiServiceWin, |
- public base::SystemMonitor::DevicesChangedObserver { |
+class MidiManagerWin::InPort final : public Port { |
public: |
- MidiServiceWinImpl() |
- : delegate_(nullptr), |
- sender_thread_("Windows MIDI sender thread"), |
- task_thread_("Windows MIDI task thread"), |
- destructor_started(false) {} |
- |
- ~MidiServiceWinImpl() final { |
- // Start() and Stop() of the threads, and AddDevicesChangeObserver() and |
- // RemoveDevicesChangeObserver() should be called on the same thread. |
- DCHECK(thread_checker_.CalledOnValidThread()); |
- |
- destructor_started = true; |
- base::SystemMonitor::Get()->RemoveDevicesChangedObserver(this); |
- { |
- std::vector<HMIDIIN> input_devices; |
- { |
- base::AutoLock auto_lock(input_ports_lock_); |
- for (auto it : input_device_map_) |
- input_devices.push_back(it.first); |
- } |
- { |
- for (auto* handle : input_devices) { |
- MMRESULT result = midiInClose(handle); |
- if (result == MIDIERR_STILLPLAYING) { |
- result = midiInReset(handle); |
- DLOG_IF(ERROR, result != MMSYSERR_NOERROR) |
- << "midiInReset failed: " << GetInErrorMessage(result); |
- result = midiInClose(handle); |
- } |
- DLOG_IF(ERROR, result != MMSYSERR_NOERROR) |
- << "midiInClose failed: " << GetInErrorMessage(result); |
- } |
- } |
- } |
- { |
- std::vector<HMIDIOUT> output_devices; |
- { |
- base::AutoLock auto_lock(output_ports_lock_); |
- for (auto it : output_device_map_) |
- output_devices.push_back(it.first); |
- } |
- { |
- for (auto* handle : output_devices) { |
- MMRESULT result = midiOutClose(handle); |
- if (result == MIDIERR_STILLPLAYING) { |
- result = midiOutReset(handle); |
- DLOG_IF(ERROR, result != MMSYSERR_NOERROR) |
- << "midiOutReset failed: " << GetOutErrorMessage(result); |
- result = midiOutClose(handle); |
- } |
- DLOG_IF(ERROR, result != MMSYSERR_NOERROR) |
- << "midiOutClose failed: " << GetOutErrorMessage(result); |
- } |
+ InPort(MidiManagerWin* manager, |
+ int instance_id, |
+ UINT device_id, |
+ const MIDIINCAPS2W& caps) |
+ : Port("input", |
+ device_id, |
+ caps.wMid, |
+ caps.wPid, |
+ caps.vDriverVersion, |
+ base::WideToUTF8( |
+ base::string16(caps.szPname, wcslen(caps.szPname))), |
+ caps.ManufacturerGuid), |
+ manager_(manager), |
+ in_handle_(kInvalidInHandle), |
+ instance_id_(instance_id) {} |
+ |
+ static std::vector<std::unique_ptr<InPort>> EnumerateActivePorts( |
+ MidiManagerWin* manager, |
+ int instance_id) { |
+ std::vector<std::unique_ptr<InPort>> ports; |
+ const UINT num_devices = midiInGetNumDevs(); |
+ for (UINT device_id = 0; device_id < num_devices; ++device_id) { |
+ MIDIINCAPS2W caps; |
+ MMRESULT result = midiInGetDevCaps( |
+ device_id, reinterpret_cast<LPMIDIINCAPSW>(&caps), sizeof(caps)); |
+ if (result != MMSYSERR_NOERROR) { |
+ LOG(ERROR) << "midiInGetDevCaps fails on device " << device_id; |
+ continue; |
} |
+ ports.push_back( |
+ base::MakeUnique<InPort>(manager, instance_id, device_id, caps)); |
} |
- sender_thread_.Stop(); |
- task_thread_.Stop(); |
+ return ports; |
} |
- // MidiServiceWin overrides: |
- void InitializeAsync(MidiServiceWinDelegate* delegate) final { |
- // Start() and Stop() of the threads, and AddDevicesChangeObserver() and |
- // RemoveDevicesChangeObserver() should be called on the same thread. |
- DCHECK(thread_checker_.CalledOnValidThread()); |
- |
- delegate_ = delegate; |
- |
- sender_thread_.Start(); |
- task_thread_.Start(); |
- |
- // Start monitoring device changes. This should start before the |
- // following UpdateDeviceList() call not to miss the event happening |
- // between the call and the observer registration. |
- base::SystemMonitor::Get()->AddDevicesChangedObserver(this); |
- |
- UpdateDeviceList(); |
- |
- task_thread_.task_runner()->PostTask( |
- FROM_HERE, |
- base::Bind(&MidiServiceWinImpl::CompleteInitializationOnTaskThread, |
- base::Unretained(this), Result::OK)); |
+ void Finalize(scoped_refptr<base::SingleThreadTaskRunner> runner) { |
+ if (in_handle_ != kInvalidInHandle) { |
+ runner->PostTask(FROM_HERE, base::Bind(&FinalizeInPort, in_handle_, |
+ base::Passed(&hdr_))); |
+ manager_->port_manager()->UnregisterInHandle(in_handle_); |
+ in_handle_ = kInvalidInHandle; |
+ } |
} |
- void SendMidiDataAsync(uint32_t port_number, |
- const std::vector<uint8_t>& data, |
- base::TimeTicks time) final { |
- if (destructor_started) { |
- LOG(ERROR) << "ThreadSafeSendData failed because MidiServiceWinImpl is " |
- "being destructed. port: " << port_number; |
- return; |
- } |
- auto state = GetOutputDeviceFromPort(port_number); |
- if (!state) { |
- LOG(ERROR) << "ThreadSafeSendData failed due to an invalid port number. " |
- << "port: " << port_number; |
- return; |
- } |
- if (state->closed) { |
- LOG(ERROR) |
- << "ThreadSafeSendData failed because target port is already closed." |
- << "port: " << port_number; |
- return; |
- } |
- const auto now = base::TimeTicks::Now(); |
- if (now < time) { |
- sender_thread_.task_runner()->PostDelayedTask( |
- FROM_HERE, base::Bind(&MidiServiceWinImpl::SendOnSenderThread, |
- base::Unretained(this), port_number, |
- state->port_age, data, time), |
- time - now); |
- } else { |
- sender_thread_.task_runner()->PostTask( |
- FROM_HERE, base::Bind(&MidiServiceWinImpl::SendOnSenderThread, |
- base::Unretained(this), port_number, |
- state->port_age, data, time)); |
- } |
+ base::TimeTicks CalculateInEventTime(uint32_t elapsed_ms) const { |
+ return start_time_ + base::TimeDelta::FromMilliseconds(elapsed_ms); |
} |
- // base::SystemMonitor::DevicesChangedObserver overrides: |
- void OnDevicesChanged(base::SystemMonitor::DeviceType device_type) final { |
- DCHECK(thread_checker_.CalledOnValidThread()); |
- if (destructor_started) |
+ void RestoreBuffer() { |
+ if (in_handle_ == kInvalidInHandle || !hdr_) |
return; |
- |
- switch (device_type) { |
- case base::SystemMonitor::DEVTYPE_AUDIO: |
- case base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE: |
- // Add case of other unrelated device types here. |
- return; |
- case base::SystemMonitor::DEVTYPE_UNKNOWN: |
- // Interested in MIDI devices. Try updating the device list. |
- UpdateDeviceList(); |
- break; |
- // No default here to capture new DeviceType by compile time. |
+ midiInAddBuffer(in_handle_, hdr_.get(), sizeof(*hdr_)); |
+ } |
+ |
+ void NotifyPortStateSet(MidiManagerWin* manager) { |
+ manager->PostReplyTask(base::Bind(&MidiManagerWin::SetInputPortState, |
+ base::Unretained(manager), index_, |
+ info_.state)); |
+ } |
+ |
+ void NotifyPortAdded(MidiManagerWin* manager) { |
+ manager->PostReplyTask(base::Bind(&MidiManagerWin::AddInputPort, |
+ base::Unretained(manager), info_)); |
+ } |
+ |
+ // Port overrides: |
+ bool Disconnect() override { |
+ if (in_handle_ != kInvalidInHandle) { |
+ // Following API call may fail because device was already disconnected. |
+ // But just in case. |
+ midiInClose(in_handle_); |
+ manager_->port_manager()->UnregisterInHandle(in_handle_); |
+ in_handle_ = kInvalidInHandle; |
+ } |
+ return Port::Disconnect(); |
+ } |
+ |
+ void Open() override { |
+ MMRESULT result = midiInOpen( |
+ &in_handle_, device_id_, |
+ reinterpret_cast<DWORD_PTR>(&PortManager::HandleMidiInCallback), |
+ instance_id_, CALLBACK_FUNCTION); |
+ if (result == MMSYSERR_NOERROR) { |
+ hdr_ = CreateMIDIHDR(kBufferLength); |
+ result = midiInPrepareHeader(in_handle_, hdr_.get(), sizeof(*hdr_)); |
+ } |
+ if (result != MMSYSERR_NOERROR) |
+ in_handle_ = kInvalidInHandle; |
+ if (result == MMSYSERR_NOERROR) |
+ result = midiInAddBuffer(in_handle_, hdr_.get(), sizeof(*hdr_)); |
+ if (result == MMSYSERR_NOERROR) |
+ result = midiInStart(in_handle_); |
+ if (result == MMSYSERR_NOERROR) { |
+ start_time_ = base::TimeTicks::Now(); |
+ manager_->port_manager()->RegisterInHandle(in_handle_, index_); |
+ Port::Open(); |
+ } else { |
+ if (in_handle_ != kInvalidInHandle) { |
+ midiInUnprepareHeader(in_handle_, hdr_.get(), sizeof(*hdr_)); |
+ hdr_.reset(); |
+ midiInClose(in_handle_); |
+ in_handle_ = kInvalidInHandle; |
+ } |
+ Disconnect(); |
} |
} |
private: |
- scoped_refptr<MidiInputDeviceState> GetInputDeviceFromHandle( |
- HMIDIIN midi_handle) { |
- base::AutoLock auto_lock(input_ports_lock_); |
- const auto it = input_device_map_.find(midi_handle); |
- return (it != input_device_map_.end() ? it->second : nullptr); |
- } |
+ MidiManagerWin* manager_; |
+ HMIDIIN in_handle_; |
+ ScopedMIDIHDR hdr_; |
+ base::TimeTicks start_time_; |
+ const int instance_id_; |
+}; |
- scoped_refptr<MidiOutputDeviceState> GetOutputDeviceFromHandle( |
- HMIDIOUT midi_handle) { |
- base::AutoLock auto_lock(output_ports_lock_); |
- const auto it = output_device_map_.find(midi_handle); |
- return (it != output_device_map_.end() ? it->second : nullptr); |
+class MidiManagerWin::OutPort final : public Port { |
+ public: |
+ OutPort(UINT device_id, const MIDIOUTCAPS2W& caps) |
+ : Port("output", |
+ device_id, |
+ caps.wMid, |
+ caps.wPid, |
+ caps.vDriverVersion, |
+ base::WideToUTF8( |
+ base::string16(caps.szPname, wcslen(caps.szPname))), |
+ caps.ManufacturerGuid), |
+ software_(caps.wTechnology == MOD_SWSYNTH), |
+ out_handle_(kInvalidOutHandle) {} |
+ |
+ static std::vector<std::unique_ptr<OutPort>> EnumerateActivePorts() { |
+ std::vector<std::unique_ptr<OutPort>> ports; |
+ const UINT num_devices = midiOutGetNumDevs(); |
+ for (UINT device_id = 0; device_id < num_devices; ++device_id) { |
+ MIDIOUTCAPS2W caps; |
+ MMRESULT result = midiOutGetDevCaps( |
+ device_id, reinterpret_cast<LPMIDIOUTCAPSW>(&caps), sizeof(caps)); |
+ if (result != MMSYSERR_NOERROR) { |
+ LOG(ERROR) << "midiOutGetDevCaps fails on device " << device_id; |
+ continue; |
+ } |
+ ports.push_back(base::MakeUnique<OutPort>(device_id, caps)); |
+ } |
+ return ports; |
} |
- scoped_refptr<MidiOutputDeviceState> GetOutputDeviceFromPort( |
- uint32_t port_number) { |
- base::AutoLock auto_lock(output_ports_lock_); |
- if (output_ports_.size() <= port_number) |
- return nullptr; |
- return output_ports_[port_number]; |
+ void Finalize(scoped_refptr<base::SingleThreadTaskRunner> runner) { |
+ if (out_handle_ != kInvalidOutHandle) { |
+ runner->PostTask(FROM_HERE, base::Bind(&FinalizeOutPort, out_handle_)); |
+ out_handle_ = kInvalidOutHandle; |
+ } |
} |
- void UpdateDeviceList() { |
- task_thread_.task_runner()->PostTask( |
- FROM_HERE, base::Bind(&MidiServiceWinImpl::UpdateDeviceListOnTaskThread, |
- base::Unretained(this))); |
+ void NotifyPortStateSet(MidiManagerWin* manager) { |
+ manager->PostReplyTask(base::Bind(&MidiManagerWin::SetOutputPortState, |
+ base::Unretained(manager), index_, |
+ info_.state)); |
} |
- ///////////////////////////////////////////////////////////////////////////// |
- // Callbacks on the OS multimedia thread. |
- ///////////////////////////////////////////////////////////////////////////// |
- |
- static void CALLBACK |
- OnMidiInEventOnMainlyMultimediaThread(HMIDIIN midi_in_handle, |
- UINT message, |
- DWORD_PTR instance, |
- DWORD_PTR param1, |
- DWORD_PTR param2) { |
- MidiServiceWinImpl* self = reinterpret_cast<MidiServiceWinImpl*>(instance); |
- if (!self) |
- return; |
- switch (message) { |
- case MIM_OPEN: |
- self->OnMidiInOpen(midi_in_handle); |
- break; |
- case MIM_DATA: |
- self->OnMidiInDataOnMultimediaThread(midi_in_handle, param1, param2); |
- break; |
- case MIM_LONGDATA: |
- self->OnMidiInLongDataOnMultimediaThread(midi_in_handle, param1, |
- param2); |
- break; |
- case MIM_CLOSE: |
- self->OnMidiInCloseOnMultimediaThread(midi_in_handle); |
- break; |
- } |
+ void NotifyPortAdded(MidiManagerWin* manager) { |
+ manager->PostReplyTask(base::Bind(&MidiManagerWin::AddOutputPort, |
+ base::Unretained(manager), info_)); |
} |
- void OnMidiInOpen(HMIDIIN midi_in_handle) { |
- UINT device_id = 0; |
- MMRESULT result = midiInGetID(midi_in_handle, &device_id); |
- if (result != MMSYSERR_NOERROR) { |
- DLOG(ERROR) << "midiInGetID failed: " << GetInErrorMessage(result); |
- return; |
- } |
- MIDIINCAPS2W caps = {}; |
- result = midiInGetDevCaps(device_id, reinterpret_cast<LPMIDIINCAPSW>(&caps), |
- sizeof(caps)); |
- if (result != MMSYSERR_NOERROR) { |
- DLOG(ERROR) << "midiInGetDevCaps failed: " << GetInErrorMessage(result); |
+ void Send(const std::vector<uint8_t>& data) { |
+ if (out_handle_ == kInvalidOutHandle) |
return; |
- } |
- auto state = |
- make_scoped_refptr(new MidiInputDeviceState(MidiDeviceInfo(caps))); |
- state->midi_handle = midi_in_handle; |
- state->midi_header = CreateMIDIHDR(kBufferLength); |
- const auto& state_device_info = state->device_info; |
- bool add_new_port = false; |
- uint32_t port_number = 0; |
- { |
- base::AutoLock auto_lock(input_ports_lock_); |
- const auto it = unused_input_ports_.find(state_device_info); |
- if (it == unused_input_ports_.end()) { |
- port_number = static_cast<uint32_t>(input_ports_.size()); |
- add_new_port = true; |
- input_ports_.push_back(nullptr); |
- input_ports_ages_.push_back(0); |
- } else { |
- port_number = it->second.top(); |
- it->second.pop(); |
- if (it->second.empty()) { |
- unused_input_ports_.erase(it); |
- } |
- } |
- input_ports_[port_number] = state; |
- input_ports_ages_[port_number] += 1; |
- input_device_map_[input_ports_[port_number]->midi_handle] = |
- input_ports_[port_number]; |
- input_ports_[port_number]->port_index = port_number; |
- input_ports_[port_number]->port_age = input_ports_ages_[port_number]; |
- } |
- // Several initial startup tasks cannot be done in MIM_OPEN handler. |
- task_thread_.task_runner()->PostTask( |
- FROM_HERE, base::Bind(&MidiServiceWinImpl::StartInputDeviceOnTaskThread, |
- base::Unretained(this), midi_in_handle)); |
- if (add_new_port) { |
- const MidiPortInfo port_info( |
- // TODO(toyoshim): Use a hash ID insted crbug.com/467448 |
- base::IntToString(static_cast<int>(port_number)), |
- GetManufacturerName(state_device_info), |
- base::WideToUTF8(state_device_info.product_name), |
- MmversionToString(state_device_info.driver_version), |
- PortState::OPENED); |
- task_thread_.task_runner()->PostTask( |
- FROM_HERE, base::Bind(&MidiServiceWinImpl::AddInputPortOnTaskThread, |
- base::Unretained(this), port_info)); |
+ if (data.size() <= 3) { |
+ uint32_t message = 0; |
+ for (size_t i = 0; i < data.size(); ++i) |
+ message |= (static_cast<uint32_t>(data[i]) << (i * 8)); |
+ midiOutShortMsg(out_handle_, message); |
} else { |
- task_thread_.task_runner()->PostTask( |
- FROM_HERE, |
- base::Bind(&MidiServiceWinImpl::SetInputPortStateOnTaskThread, |
- base::Unretained(this), port_number, |
- PortState::CONNECTED)); |
- } |
- } |
- |
- void OnMidiInDataOnMultimediaThread(HMIDIIN midi_in_handle, |
- DWORD_PTR param1, |
- DWORD_PTR param2) { |
- auto state = GetInputDeviceFromHandle(midi_in_handle); |
- if (!state) |
- return; |
- const uint8_t status_byte = static_cast<uint8_t>(param1 & 0xff); |
- const uint8_t first_data_byte = static_cast<uint8_t>((param1 >> 8) & 0xff); |
- const uint8_t second_data_byte = |
- static_cast<uint8_t>((param1 >> 16) & 0xff); |
- const DWORD elapsed_ms = param2; |
- const size_t len = GetMessageLength(status_byte); |
- const uint8_t kData[] = {status_byte, first_data_byte, second_data_byte}; |
- std::vector<uint8_t> data; |
- data.assign(kData, kData + len); |
- DCHECK_LE(len, arraysize(kData)); |
- // MIM_DATA/MIM_LONGDATA message treats the time when midiInStart() is |
- // called as the origin of |elapsed_ms|. |
- // http://msdn.microsoft.com/en-us/library/windows/desktop/dd757284.aspx |
- // http://msdn.microsoft.com/en-us/library/windows/desktop/dd757286.aspx |
- const base::TimeTicks event_time = |
- state->start_time + base::TimeDelta::FromMilliseconds(elapsed_ms); |
- task_thread_.task_runner()->PostTask( |
- FROM_HERE, base::Bind(&MidiServiceWinImpl::ReceiveMidiDataOnTaskThread, |
- base::Unretained(this), state->port_index, data, |
- event_time)); |
- } |
- |
- void OnMidiInLongDataOnMultimediaThread(HMIDIIN midi_in_handle, |
- DWORD_PTR param1, |
- DWORD_PTR param2) { |
- auto state = GetInputDeviceFromHandle(midi_in_handle); |
- if (!state) |
- return; |
- MIDIHDR* header = reinterpret_cast<MIDIHDR*>(param1); |
- const DWORD elapsed_ms = param2; |
- MMRESULT result = MMSYSERR_NOERROR; |
- if (destructor_started) { |
- if (state->midi_header && |
- (state->midi_header->dwFlags & MHDR_PREPARED) == MHDR_PREPARED) { |
- result = |
- midiInUnprepareHeader(state->midi_handle, state->midi_header.get(), |
- sizeof(*state->midi_header)); |
- DLOG_IF(ERROR, result != MMSYSERR_NOERROR) |
- << "Failed to uninitialize input buffer: " |
- << GetInErrorMessage(result); |
+ if (data.size() > kSysExSizeLimit) { |
+ LOG(ERROR) << "Ignoring SysEx message due to the size limit" |
+ << ", size = " << data.size(); |
+ // TODO(toyoshim): Consider to report metrics here. |
+ return; |
+ } |
+ ScopedMIDIHDR hdr(CreateMIDIHDR(data)); |
+ MMRESULT result = |
+ midiOutPrepareHeader(out_handle_, hdr.get(), sizeof(*hdr)); |
+ if (result != MMSYSERR_NOERROR) |
+ return; |
+ result = midiOutLongMsg(out_handle_, hdr.get(), sizeof(*hdr)); |
+ if (result != MMSYSERR_NOERROR) { |
+ midiOutUnprepareHeader(out_handle_, hdr.get(), sizeof(*hdr)); |
+ } else { |
+ // MIDIHDR will be released on MOM_DONE. |
+ ignore_result(hdr.release()); |
} |
- return; |
- } |
- if (header->dwBytesRecorded > 0) { |
- const uint8_t* src = reinterpret_cast<const uint8_t*>(header->lpData); |
- std::vector<uint8_t> data; |
- data.assign(src, src + header->dwBytesRecorded); |
- // MIM_DATA/MIM_LONGDATA message treats the time when midiInStart() is |
- // called as the origin of |elapsed_ms|. |
- // http://msdn.microsoft.com/en-us/library/windows/desktop/dd757284.aspx |
- // http://msdn.microsoft.com/en-us/library/windows/desktop/dd757286.aspx |
- const base::TimeTicks event_time = |
- state->start_time + base::TimeDelta::FromMilliseconds(elapsed_ms); |
- task_thread_.task_runner()->PostTask( |
- FROM_HERE, |
- base::Bind(&MidiServiceWinImpl::ReceiveMidiDataOnTaskThread, |
- base::Unretained(this), state->port_index, data, |
- event_time)); |
} |
- result = midiInAddBuffer(state->midi_handle, header, sizeof(*header)); |
- DLOG_IF(ERROR, result != MMSYSERR_NOERROR) |
- << "Failed to attach input buffer: " << GetInErrorMessage(result) |
- << "port number:" << state->port_index; |
} |
- void OnMidiInCloseOnMultimediaThread(HMIDIIN midi_in_handle) { |
- auto state = GetInputDeviceFromHandle(midi_in_handle); |
- if (!state) |
- return; |
- const uint32_t port_number = state->port_index; |
- const auto device_info(state->device_info); |
- { |
- base::AutoLock auto_lock(input_ports_lock_); |
- input_device_map_.erase(state->midi_handle); |
- input_ports_[port_number] = nullptr; |
- input_ports_ages_[port_number] += 1; |
- unused_input_ports_[device_info].push(port_number); |
+ // Port overrides: |
+ bool Connect() override { |
+ // Until |software| option is supported, disable Microsoft GS Wavetable |
+ // Synth that has a known security issue. |
+ if (software_ && manufacturer_id_ == MM_MICROSOFT && |
+ (product_id_ == MM_MSFT_WDMAUDIO_MIDIOUT || |
+ product_id_ == MM_MSFT_GENERIC_MIDISYNTH)) { |
+ return false; |
} |
- task_thread_.task_runner()->PostTask( |
- FROM_HERE, |
- base::Bind(&MidiServiceWinImpl::SetInputPortStateOnTaskThread, |
- base::Unretained(this), port_number, |
- PortState::DISCONNECTED)); |
+ return Port::Connect(); |
} |
- static void CALLBACK |
- OnMidiOutEventOnMainlyMultimediaThread(HMIDIOUT midi_out_handle, |
- UINT message, |
- DWORD_PTR instance, |
- DWORD_PTR param1, |
- DWORD_PTR param2) { |
- MidiServiceWinImpl* self = reinterpret_cast<MidiServiceWinImpl*>(instance); |
- if (!self) |
- return; |
- switch (message) { |
- case MOM_OPEN: |
- self->OnMidiOutOpen(midi_out_handle, param1, param2); |
- break; |
- case MOM_DONE: |
- self->OnMidiOutDoneOnMultimediaThread(midi_out_handle, param1); |
- break; |
- case MOM_CLOSE: |
- self->OnMidiOutCloseOnMultimediaThread(midi_out_handle); |
- break; |
+ bool Disconnect() override { |
+ if (out_handle_ != kInvalidOutHandle) { |
+ // Following API call may fail because device was already disconnected. |
+ // But just in case. |
+ midiOutClose(out_handle_); |
+ out_handle_ = kInvalidOutHandle; |
} |
+ return Port::Disconnect(); |
} |
- void OnMidiOutOpen(HMIDIOUT midi_out_handle, |
- DWORD_PTR param1, |
- DWORD_PTR param2) { |
- UINT device_id = 0; |
- MMRESULT result = midiOutGetID(midi_out_handle, &device_id); |
- if (result != MMSYSERR_NOERROR) { |
- DLOG(ERROR) << "midiOutGetID failed: " << GetOutErrorMessage(result); |
- return; |
- } |
- MIDIOUTCAPS2W caps = {}; |
- result = midiOutGetDevCaps( |
- device_id, reinterpret_cast<LPMIDIOUTCAPSW>(&caps), sizeof(caps)); |
- if (result != MMSYSERR_NOERROR) { |
- DLOG(ERROR) << "midiInGetDevCaps failed: " << GetOutErrorMessage(result); |
- return; |
- } |
- auto state = |
- make_scoped_refptr(new MidiOutputDeviceState(MidiDeviceInfo(caps))); |
- state->midi_handle = midi_out_handle; |
- const auto& state_device_info = state->device_info; |
- if (IsUnsupportedDevice(state_device_info)) { |
- task_thread_.task_runner()->PostTask( |
- FROM_HERE, base::Bind(&CloseOutputPortOnTaskThread, midi_out_handle)); |
- return; |
- } |
- bool add_new_port = false; |
- uint32_t port_number = 0; |
- { |
- base::AutoLock auto_lock(output_ports_lock_); |
- const auto it = unused_output_ports_.find(state_device_info); |
- if (it == unused_output_ports_.end()) { |
- port_number = static_cast<uint32_t>(output_ports_.size()); |
- add_new_port = true; |
- output_ports_.push_back(nullptr); |
- output_ports_ages_.push_back(0); |
- } else { |
- port_number = it->second.top(); |
- it->second.pop(); |
- if (it->second.empty()) |
- unused_output_ports_.erase(it); |
- } |
- output_ports_[port_number] = state; |
- output_ports_ages_[port_number] += 1; |
- output_device_map_[output_ports_[port_number]->midi_handle] = |
- output_ports_[port_number]; |
- output_ports_[port_number]->port_index = port_number; |
- output_ports_[port_number]->port_age = output_ports_ages_[port_number]; |
- } |
- if (add_new_port) { |
- const MidiPortInfo port_info( |
- // TODO(toyoshim): Use a hash ID insted. crbug.com/467448 |
- base::IntToString(static_cast<int>(port_number)), |
- GetManufacturerName(state_device_info), |
- base::WideToUTF8(state_device_info.product_name), |
- MmversionToString(state_device_info.driver_version), |
- PortState::OPENED); |
- task_thread_.task_runner()->PostTask( |
- FROM_HERE, base::Bind(&MidiServiceWinImpl::AddOutputPortOnTaskThread, |
- base::Unretained(this), port_info)); |
+ void Open() override { |
+ MMRESULT result = midiOutOpen( |
+ &out_handle_, device_id_, |
+ reinterpret_cast<DWORD_PTR>(&PortManager::HandleMidiOutCallback), 0, |
+ CALLBACK_FUNCTION); |
+ if (result == MMSYSERR_NOERROR) { |
+ Port::Open(); |
} else { |
- task_thread_.task_runner()->PostTask( |
- FROM_HERE, |
- base::Bind(&MidiServiceWinImpl::SetOutputPortStateOnTaskThread, |
- base::Unretained(this), port_number, |
- PortState::CONNECTED)); |
+ out_handle_ = kInvalidOutHandle; |
+ Disconnect(); |
} |
} |
- void OnMidiOutDoneOnMultimediaThread(HMIDIOUT midi_out_handle, |
- DWORD_PTR param1) { |
- auto state = GetOutputDeviceFromHandle(midi_out_handle); |
- if (!state) |
- return; |
- // Take ownership of the MIDIHDR object. |
- ScopedMIDIHDR header(reinterpret_cast<MIDIHDR*>(param1)); |
- if (!header) |
- return; |
- MMRESULT result = midiOutUnprepareHeader(state->midi_handle, header.get(), |
- sizeof(*header)); |
- DLOG_IF(ERROR, result != MMSYSERR_NOERROR) |
- << "Failed to uninitialize output buffer: " |
- << GetOutErrorMessage(result); |
- } |
- |
- void OnMidiOutCloseOnMultimediaThread(HMIDIOUT midi_out_handle) { |
- auto state = GetOutputDeviceFromHandle(midi_out_handle); |
- if (!state) |
- return; |
- const uint32_t port_number = state->port_index; |
- const auto device_info(state->device_info); |
- { |
- base::AutoLock auto_lock(output_ports_lock_); |
- output_device_map_.erase(state->midi_handle); |
- output_ports_[port_number] = nullptr; |
- output_ports_ages_[port_number] += 1; |
- unused_output_ports_[device_info].push(port_number); |
- state->closed = true; |
- } |
- task_thread_.task_runner()->PostTask( |
- FROM_HERE, |
- base::Bind(&MidiServiceWinImpl::SetOutputPortStateOnTaskThread, |
- base::Unretained(this), port_number, |
- PortState::DISCONNECTED)); |
- } |
+ const bool software_; |
+ HMIDIOUT out_handle_; |
+}; |
- ///////////////////////////////////////////////////////////////////////////// |
- // Callbacks on the sender thread. |
- ///////////////////////////////////////////////////////////////////////////// |
+base::TimeTicks MidiManagerWin::PortManager::CalculateInEventTime( |
+ size_t index, |
+ uint32_t elapsed_ms) const { |
+ GetTaskLock()->AssertAcquired(); |
+ CHECK_GT(input_ports_.size(), index); |
+ return input_ports_[index]->CalculateInEventTime(elapsed_ms); |
+} |
- void AssertOnSenderThread() { |
- DCHECK_EQ(sender_thread_.GetThreadId(), base::PlatformThread::CurrentId()); |
- } |
+void MidiManagerWin::PortManager::RegisterInHandle(HMIDIIN handle, |
+ size_t index) { |
+ GetTaskLock()->AssertAcquired(); |
+ hmidiin_to_index_map_[handle] = index; |
+} |
- void SendOnSenderThread(uint32_t port_number, |
- uint64_t port_age, |
- const std::vector<uint8_t>& data, |
- base::TimeTicks time) { |
- AssertOnSenderThread(); |
- if (destructor_started) { |
- LOG(ERROR) << "ThreadSafeSendData failed because MidiServiceWinImpl is " |
- "being destructed. port: " << port_number; |
- } |
- auto state = GetOutputDeviceFromPort(port_number); |
- if (!state) { |
- LOG(ERROR) << "ThreadSafeSendData failed due to an invalid port number. " |
- << "port: " << port_number; |
- return; |
- } |
- if (state->closed) { |
- LOG(ERROR) |
- << "ThreadSafeSendData failed because target port is already closed." |
- << "port: " << port_number; |
- return; |
- } |
- if (state->port_age != port_age) { |
- LOG(ERROR) |
- << "ThreadSafeSendData failed because target port is being closed." |
- << "port: " << port_number << "expected port age: " << port_age |
- << "actual port age: " << state->port_age; |
- } |
+void MidiManagerWin::PortManager::UnregisterInHandle(HMIDIIN handle) { |
+ GetTaskLock()->AssertAcquired(); |
+ hmidiin_to_index_map_.erase(handle); |
+} |
- // MIDI Running status must be filtered out. |
- MidiMessageQueue message_queue(false); |
- message_queue.Add(data); |
- std::vector<uint8_t> message; |
- while (true) { |
- if (destructor_started) |
- break; |
- if (state->closed) |
- break; |
- message_queue.Get(&message); |
- if (message.empty()) |
- break; |
- // SendShortMidiMessageInternal can send a MIDI message up to 3 bytes. |
- if (message.size() <= 3) |
- SendShortMidiMessageInternal(state->midi_handle, message); |
- else |
- SendLongMidiMessageInternal(state->midi_handle, message); |
- } |
- } |
+bool MidiManagerWin::PortManager::FindInHandle(HMIDIIN hmi, size_t* out_index) { |
+ GetTaskLock()->AssertAcquired(); |
+ auto found = hmidiin_to_index_map_.find(hmi); |
+ if (found == hmidiin_to_index_map_.end()) |
+ return false; |
+ *out_index = found->second; |
+ return true; |
+} |
- ///////////////////////////////////////////////////////////////////////////// |
- // Callbacks on the task thread. |
- ///////////////////////////////////////////////////////////////////////////// |
+void MidiManagerWin::PortManager::RestoreInBuffer(size_t index) { |
+ GetTaskLock()->AssertAcquired(); |
+ CHECK_GT(input_ports_.size(), index); |
+ input_ports_[index]->RestoreBuffer(); |
+} |
- void AssertOnTaskThread() { |
- DCHECK_EQ(task_thread_.GetThreadId(), base::PlatformThread::CurrentId()); |
+void CALLBACK |
+MidiManagerWin::PortManager::HandleMidiInCallback(HMIDIIN hmi, |
+ UINT msg, |
+ DWORD_PTR instance, |
+ DWORD_PTR param1, |
+ DWORD_PTR param2) { |
+ if (msg != MIM_DATA && msg != MIM_LONGDATA) |
+ return; |
+ int instance_id = static_cast<int>(instance); |
+ MidiManagerWin* manager = nullptr; |
+ |
+ // Use |g_task_lock| so to ensure the instance can keep alive while running, |
+ // and to access member variables that are used on TaskRunner. |
+ base::AutoLock task_lock(*GetTaskLock()); |
+ { |
+ base::AutoLock lock(*GetInstanceIdLock()); |
+ if (instance_id != g_active_instance_id) |
+ return; |
+ manager = g_manager_instance; |
} |
- void UpdateDeviceListOnTaskThread() { |
- AssertOnTaskThread(); |
- const UINT num_in_devices = midiInGetNumDevs(); |
- for (UINT device_id = 0; device_id < num_in_devices; ++device_id) { |
- // Here we use |CALLBACK_FUNCTION| to subscribe MIM_DATA, MIM_LONGDATA, |
- // MIM_OPEN, and MIM_CLOSE events. |
- // - MIM_DATA: This is the only way to get a short MIDI message with |
- // timestamp information. |
- // - MIM_LONGDATA: This is the only way to get a long MIDI message with |
- // timestamp information. |
- // - MIM_OPEN: This event is sent the input device is opened. Note that |
- // this message is called on the caller thread. |
- // - MIM_CLOSE: This event is sent when 1) midiInClose() is called, or 2) |
- // the MIDI device becomes unavailable for some reasons, e.g., the |
- // cable is disconnected. As for the former case, HMIDIOUT will be |
- // invalidated soon after the callback is finished. As for the later |
- // case, however, HMIDIOUT continues to be valid until midiInClose() |
- // is called. |
- HMIDIIN midi_handle = kInvalidMidiInHandle; |
- const MMRESULT result = midiInOpen( |
- &midi_handle, device_id, |
- reinterpret_cast<DWORD_PTR>(&OnMidiInEventOnMainlyMultimediaThread), |
- reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION); |
- DLOG_IF(ERROR, result != MMSYSERR_NOERROR && result != MMSYSERR_ALLOCATED) |
- << "Failed to open output device. " |
- << " id: " << device_id << " message: " << GetInErrorMessage(result); |
- } |
+ size_t index; |
+ if (!manager->port_manager()->FindInHandle(hmi, &index)) |
+ return; |
- const UINT num_out_devices = midiOutGetNumDevs(); |
- for (UINT device_id = 0; device_id < num_out_devices; ++device_id) { |
- // Here we use |CALLBACK_FUNCTION| to subscribe MOM_DONE, MOM_OPEN, and |
- // MOM_CLOSE events. |
- // - MOM_DONE: SendLongMidiMessageInternal() relies on this event to clean |
- // up the backing store where a long MIDI message is stored. |
- // - MOM_OPEN: This event is sent the output device is opened. Note that |
- // this message is called on the caller thread. |
- // - MOM_CLOSE: This event is sent when 1) midiOutClose() is called, or 2) |
- // the MIDI device becomes unavailable for some reasons, e.g., the |
- // cable is disconnected. As for the former case, HMIDIOUT will be |
- // invalidated soon after the callback is finished. As for the later |
- // case, however, HMIDIOUT continues to be valid until midiOutClose() |
- // is called. |
- HMIDIOUT midi_handle = kInvalidMidiOutHandle; |
- const MMRESULT result = midiOutOpen( |
- &midi_handle, device_id, |
- reinterpret_cast<DWORD_PTR>(&OnMidiOutEventOnMainlyMultimediaThread), |
- reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION); |
- DLOG_IF(ERROR, result != MMSYSERR_NOERROR && result != MMSYSERR_ALLOCATED) |
- << "Failed to open output device. " |
- << " id: " << device_id << " message: " << GetOutErrorMessage(result); |
+ DCHECK(msg == MIM_DATA || msg == MIM_LONGDATA); |
+ if (msg == MIM_DATA) { |
+ const uint8_t status_byte = static_cast<uint8_t>(param1 & 0xff); |
+ const uint8_t first_data_byte = static_cast<uint8_t>((param1 >> 8) & 0xff); |
+ const uint8_t second_data_byte = |
+ static_cast<uint8_t>((param1 >> 16) & 0xff); |
+ const uint8_t kData[] = {status_byte, first_data_byte, second_data_byte}; |
+ const size_t len = GetMessageLength(status_byte); |
+ DCHECK_LE(len, arraysize(kData)); |
+ std::vector<uint8_t> data; |
+ data.assign(kData, kData + len); |
+ manager->PostReplyTask(base::Bind( |
+ &MidiManagerWin::ReceiveMidiData, base::Unretained(manager), index, |
+ data, manager->port_manager()->CalculateInEventTime(index, param2))); |
+ } else { |
+ DCHECK_EQ(static_cast<UINT>(MIM_LONGDATA), msg); |
+ LPMIDIHDR hdr = reinterpret_cast<LPMIDIHDR>(param1); |
+ if (hdr->dwBytesRecorded > 0) { |
+ const uint8_t* src = reinterpret_cast<const uint8_t*>(hdr->lpData); |
+ std::vector<uint8_t> data; |
+ data.assign(src, src + hdr->dwBytesRecorded); |
+ manager->PostReplyTask(base::Bind( |
+ &MidiManagerWin::ReceiveMidiData, base::Unretained(manager), index, |
+ data, manager->port_manager()->CalculateInEventTime(index, param2))); |
} |
+ manager->PostTask(base::Bind(&MidiManagerWin::PortManager::RestoreInBuffer, |
+ base::Unretained(manager->port_manager()), |
+ index)); |
} |
+} |
- void StartInputDeviceOnTaskThread(HMIDIIN midi_in_handle) { |
- AssertOnTaskThread(); |
- auto state = GetInputDeviceFromHandle(midi_in_handle); |
- if (!state) |
- return; |
- MMRESULT result = |
- midiInPrepareHeader(state->midi_handle, state->midi_header.get(), |
- sizeof(*state->midi_header)); |
- if (result != MMSYSERR_NOERROR) { |
- DLOG(ERROR) << "Failed to initialize input buffer: " |
- << GetInErrorMessage(result); |
- return; |
- } |
- result = midiInAddBuffer(state->midi_handle, state->midi_header.get(), |
- sizeof(*state->midi_header)); |
- if (result != MMSYSERR_NOERROR) { |
- DLOG(ERROR) << "Failed to attach input buffer: " |
- << GetInErrorMessage(result); |
- return; |
- } |
- result = midiInStart(state->midi_handle); |
- if (result != MMSYSERR_NOERROR) { |
- DLOG(ERROR) << "Failed to start input port: " |
- << GetInErrorMessage(result); |
+void CALLBACK |
+MidiManagerWin::PortManager::HandleMidiOutCallback(HMIDIOUT hmo, |
+ UINT msg, |
+ DWORD_PTR instance, |
+ DWORD_PTR param1, |
+ DWORD_PTR param2) { |
+ if (msg == MOM_DONE) { |
+ ScopedMIDIHDR hdr(reinterpret_cast<LPMIDIHDR>(param1)); |
+ if (!hdr) |
return; |
- } |
- state->start_time = base::TimeTicks::Now(); |
- state->start_time_initialized = true; |
+ // TODO(toyoshim): Call midiOutUnprepareHeader outside the callback. |
+ // Since this callback may be invoked after the manager is destructed, |
+ // and can not send a task to the TaskRunner in such case, we need to |
+ // consider to track MIDIHDR per port, and clean it in port finalization |
+ // steps, too. |
+ midiOutUnprepareHeader(hmo, hdr.get(), sizeof(*hdr)); |
} |
+} |
- void CompleteInitializationOnTaskThread(Result result) { |
- AssertOnTaskThread(); |
- delegate_->OnCompleteInitialization(result); |
- } |
- |
- void ReceiveMidiDataOnTaskThread(uint32_t port_index, |
- std::vector<uint8_t> data, |
- base::TimeTicks time) { |
- AssertOnTaskThread(); |
- delegate_->OnReceiveMidiData(port_index, data, time); |
- } |
- |
- void AddInputPortOnTaskThread(MidiPortInfo info) { |
- AssertOnTaskThread(); |
- delegate_->OnAddInputPort(info); |
- } |
- |
- void AddOutputPortOnTaskThread(MidiPortInfo info) { |
- AssertOnTaskThread(); |
- delegate_->OnAddOutputPort(info); |
- } |
- |
- void SetInputPortStateOnTaskThread(uint32_t port_index, PortState state) { |
- AssertOnTaskThread(); |
- delegate_->OnSetInputPortState(port_index, state); |
- } |
- |
- void SetOutputPortStateOnTaskThread(uint32_t port_index, PortState state) { |
- AssertOnTaskThread(); |
- delegate_->OnSetOutputPortState(port_index, state); |
- } |
- |
- ///////////////////////////////////////////////////////////////////////////// |
- // Fields: |
- ///////////////////////////////////////////////////////////////////////////// |
- |
- // Does not take ownership. |
- MidiServiceWinDelegate* delegate_; |
- |
- base::ThreadChecker thread_checker_; |
- |
- base::Thread sender_thread_; |
- base::Thread task_thread_; |
- |
- base::Lock input_ports_lock_; |
- base::hash_map<HMIDIIN, scoped_refptr<MidiInputDeviceState>> |
- input_device_map_; // GUARDED_BY(input_ports_lock_) |
- PortNumberCache unused_input_ports_; // GUARDED_BY(input_ports_lock_) |
- std::vector<scoped_refptr<MidiInputDeviceState>> |
- input_ports_; // GUARDED_BY(input_ports_lock_) |
- std::vector<uint64_t> input_ports_ages_; // GUARDED_BY(input_ports_lock_) |
- |
- base::Lock output_ports_lock_; |
- base::hash_map<HMIDIOUT, scoped_refptr<MidiOutputDeviceState>> |
- output_device_map_; // GUARDED_BY(output_ports_lock_) |
- PortNumberCache unused_output_ports_; // GUARDED_BY(output_ports_lock_) |
- std::vector<scoped_refptr<MidiOutputDeviceState>> |
- output_ports_; // GUARDED_BY(output_ports_lock_) |
- std::vector<uint64_t> output_ports_ages_; // GUARDED_BY(output_ports_lock_) |
- |
- // True if one thread reached MidiServiceWinImpl::~MidiServiceWinImpl(). Note |
- // that MidiServiceWinImpl::~MidiServiceWinImpl() is blocked until |
- // |sender_thread_|, and |task_thread_| are stopped. |
- // This flag can be used as the signal that when background tasks must be |
- // interrupted. |
- // TODO(toyoshim): Use std::atomic<bool> when it is allowed. |
- volatile bool destructor_started; |
- |
- DISALLOW_COPY_AND_ASSIGN(MidiServiceWinImpl); |
-}; |
- |
-} // namespace |
+MidiManagerWin::MidiManagerWin(MidiService* service) |
+ : MidiManager(service), |
+ instance_id_(IssueNextInstanceId()), |
+ port_manager_(base::MakeUnique<PortManager>()) { |
+ base::AutoLock lock(*GetInstanceIdLock()); |
+ CHECK_EQ(kInvalidInstanceId, g_active_instance_id); |
-MidiManagerWin::MidiManagerWin(MidiService* service) : MidiManager(service) {} |
+ // Obtains the task runner for the current thread that hosts this instnace. |
+ thread_runner_ = base::ThreadTaskRunnerHandle::Get(); |
+} |
MidiManagerWin::~MidiManagerWin() { |
+ base::AutoLock lock(*GetInstanceIdLock()); |
+ CHECK_EQ(kInvalidInstanceId, g_active_instance_id); |
+ CHECK(thread_runner_->BelongsToCurrentThread()); |
} |
void MidiManagerWin::StartInitialization() { |
- midi_service_.reset(new MidiServiceWinImpl); |
- // Note that |CompleteInitialization()| will be called from the callback. |
- midi_service_->InitializeAsync(this); |
+ { |
+ base::AutoLock lock(*GetInstanceIdLock()); |
+ CHECK_EQ(kInvalidInstanceId, g_active_instance_id); |
+ g_active_instance_id = instance_id_; |
+ CHECK_EQ(nullptr, g_manager_instance); |
+ g_manager_instance = this; |
+ } |
+ // Registers on the I/O thread to be notified on the I/O thread. |
+ CHECK(thread_runner_->BelongsToCurrentThread()); |
+ base::SystemMonitor::Get()->AddDevicesChangedObserver(this); |
+ |
+ // Starts asynchronous initialization on TaskRunner. |
+ PostTask(base::Bind(&MidiManagerWin::InitializeOnTaskRunner, |
+ base::Unretained(this))); |
} |
void MidiManagerWin::Finalize() { |
- midi_service_.reset(); |
+ // Unregisters on the I/O thread. OnDevicesChanged() won't be called any more. |
+ CHECK(thread_runner_->BelongsToCurrentThread()); |
+ base::SystemMonitor::Get()->RemoveDevicesChangedObserver(this); |
+ { |
+ base::AutoLock lock(*GetInstanceIdLock()); |
+ CHECK_EQ(instance_id_, g_active_instance_id); |
+ g_active_instance_id = kInvalidInstanceId; |
+ CHECK_EQ(this, g_manager_instance); |
+ g_manager_instance = nullptr; |
+ } |
+ |
+ // Ensures that no task runs on TaskRunner so to destruct the instance safely. |
+ // Tasks that did not started yet will do nothing after invalidate the |
+ // instance ID above. |
+ // Behind the lock below, we can safely access all members for finalization |
+ // even on the I/O thread. |
+ base::AutoLock lock(*GetTaskLock()); |
+ |
+ // Posts tasks that finalize each device port without MidiManager instance |
+ // on TaskRunner. If another MidiManager instance is created, its |
+ // initialization runs on the same task runner after all tasks posted here |
+ // finish. |
+ for (const auto& port : *port_manager_->inputs()) |
+ port->Finalize(service()->GetTaskRunner(kTaskRunner)); |
+ for (const auto& port : *port_manager_->outputs()) |
+ port->Finalize(service()->GetTaskRunner(kTaskRunner)); |
} |
void MidiManagerWin::DispatchSendMidiData(MidiManagerClient* client, |
uint32_t port_index, |
const std::vector<uint8_t>& data, |
double timestamp) { |
- if (!midi_service_) |
- return; |
- |
- base::TimeTicks time_to_send = base::TimeTicks::Now(); |
if (timestamp != 0.0) { |
- time_to_send = |
+ base::TimeTicks time = |
base::TimeTicks() + base::TimeDelta::FromMicroseconds( |
timestamp * base::Time::kMicrosecondsPerSecond); |
+ base::TimeTicks now = base::TimeTicks::Now(); |
+ if (now < time) { |
+ PostDelayedTask( |
+ base::Bind(&MidiManagerWin::SendOnTaskRunner, base::Unretained(this), |
+ client, port_index, data), |
+ time - now); |
+ return; |
+ } |
+ } |
+ PostTask(base::Bind(&MidiManagerWin::SendOnTaskRunner, base::Unretained(this), |
+ client, port_index, data)); |
+} |
+ |
+void MidiManagerWin::OnDevicesChanged( |
+ base::SystemMonitor::DeviceType device_type) { |
+ // Notified on the I/O thread. |
+ CHECK(thread_runner_->BelongsToCurrentThread()); |
+ |
+ switch (device_type) { |
+ case base::SystemMonitor::DEVTYPE_AUDIO: |
+ case base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE: |
+ // Add case of other unrelated device types here. |
+ return; |
+ case base::SystemMonitor::DEVTYPE_UNKNOWN: { |
+ PostTask(base::Bind(&MidiManagerWin::UpdateDeviceListOnTaskRunner, |
+ base::Unretained(this))); |
+ break; |
+ } |
} |
- midi_service_->SendMidiDataAsync(port_index, data, time_to_send); |
+} |
- // TOOD(toyoshim): This calculation should be done when the date is actually |
- // sent. |
- client->AccumulateMidiBytesSent(data.size()); |
+void MidiManagerWin::ReceiveMidiData(uint32_t index, |
+ const std::vector<uint8_t>& data, |
+ base::TimeTicks time) { |
+ MidiManager::ReceiveMidiData(index, data.data(), data.size(), time); |
} |
-void MidiManagerWin::OnCompleteInitialization(Result result) { |
- CompleteInitialization(result); |
+void MidiManagerWin::PostTask(const base::Closure& task) { |
+ service() |
+ ->GetTaskRunner(kTaskRunner) |
+ ->PostTask(FROM_HERE, base::Bind(&RunTask, instance_id_, task)); |
} |
-void MidiManagerWin::OnAddInputPort(MidiPortInfo info) { |
- AddInputPort(info); |
+void MidiManagerWin::PostDelayedTask(const base::Closure& task, |
+ base::TimeDelta delay) { |
+ service() |
+ ->GetTaskRunner(kTaskRunner) |
+ ->PostDelayedTask(FROM_HERE, base::Bind(&RunTask, instance_id_, task), |
+ delay); |
} |
-void MidiManagerWin::OnAddOutputPort(MidiPortInfo info) { |
- AddOutputPort(info); |
+void MidiManagerWin::PostReplyTask(const base::Closure& task) { |
+ thread_runner_->PostTask(FROM_HERE, base::Bind(&RunTask, instance_id_, task)); |
} |
-void MidiManagerWin::OnSetInputPortState(uint32_t port_index, PortState state) { |
- SetInputPortState(port_index, state); |
+void MidiManagerWin::InitializeOnTaskRunner() { |
+ UpdateDeviceListOnTaskRunner(); |
+ PostReplyTask(base::Bind(&MidiManagerWin::CompleteInitialization, |
+ base::Unretained(this), mojom::Result::OK)); |
} |
-void MidiManagerWin::OnSetOutputPortState(uint32_t port_index, |
- PortState state) { |
- SetOutputPortState(port_index, state); |
+void MidiManagerWin::UpdateDeviceListOnTaskRunner() { |
+ std::vector<std::unique_ptr<InPort>> active_input_ports = |
+ InPort::EnumerateActivePorts(this, instance_id_); |
+ ReflectActiveDeviceList(this, port_manager_->inputs(), &active_input_ports); |
+ |
+ std::vector<std::unique_ptr<OutPort>> active_output_ports = |
+ OutPort::EnumerateActivePorts(); |
+ ReflectActiveDeviceList(this, port_manager_->outputs(), &active_output_ports); |
+ |
+ // TODO(toyoshim): This method may run before internal MIDI device lists that |
+ // Windows manages were updated. This may be because MIDI driver may be loaded |
+ // after the raw device list was updated. To avoid this problem, we may want |
+ // to retry device check later if no changes are detected here. |
} |
-void MidiManagerWin::OnReceiveMidiData(uint32_t port_index, |
- const std::vector<uint8_t>& data, |
- base::TimeTicks time) { |
- ReceiveMidiData(port_index, &data[0], data.size(), time); |
+template <typename T> |
+void MidiManagerWin::ReflectActiveDeviceList(MidiManagerWin* manager, |
+ std::vector<T>* known_ports, |
+ std::vector<T>* active_ports) { |
+ // Update existing port states. |
+ for (const auto& port : *known_ports) { |
+ const auto& it = std::find_if( |
+ active_ports->begin(), active_ports->end(), |
+ [&port](const auto& candidate) { return *candidate == *port; }); |
+ if (it == active_ports->end()) { |
+ if (port->Disconnect()) |
+ port->NotifyPortStateSet(this); |
+ } else { |
+ port->set_device_id((*it)->device_id()); |
+ if (port->Connect()) |
+ port->NotifyPortStateSet(this); |
+ } |
+ } |
+ |
+ // Find new ports from active ports and append them to known ports. |
+ for (auto& port : *active_ports) { |
+ if (std::find_if(known_ports->begin(), known_ports->end(), |
+ [&port](const auto& candidate) { |
+ return *candidate == *port; |
+ }) == known_ports->end()) { |
+ size_t index = known_ports->size(); |
+ port->set_index(index); |
+ known_ports->push_back(std::move(port)); |
+ (*known_ports)[index]->Connect(); |
+ (*known_ports)[index]->NotifyPortAdded(this); |
+ } |
+ } |
+} |
+ |
+void MidiManagerWin::SendOnTaskRunner(MidiManagerClient* client, |
+ uint32_t port_index, |
+ const std::vector<uint8_t>& data) { |
+ CHECK_GT(port_manager_->outputs()->size(), port_index); |
+ (*port_manager_->outputs())[port_index]->Send(data); |
+ // |client| will be checked inside MidiManager::AccumulateMidiBytesSent. |
+ PostReplyTask(base::Bind(&MidiManagerWin::AccumulateMidiBytesSent, |
+ base::Unretained(this), client, data.size())); |
} |
MidiManager* MidiManager::Create(MidiService* service) { |
Takashi Toyoshima
2017/04/24 09:30:23
crreview shows some complicated differences on _wi
|
if (base::FeatureList::IsEnabled(features::kMidiManagerWinrt) && |
- base::win::GetVersion() >= base::win::VERSION_WIN10) |
+ base::win::GetVersion() >= base::win::VERSION_WIN10) { |
return new MidiManagerWinrt(service); |
- if (base::FeatureList::IsEnabled(features::kMidiManagerDynamicInstantiation)) |
- return new DynamicallyInitializedMidiManagerWin(service); |
+ } |
return new MidiManagerWin(service); |
} |