OLD | NEW |
---|---|
1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2017 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "media/midi/midi_manager_win.h" | 5 #include "media/midi/midi_manager_win.h" |
6 | 6 |
7 #include <windows.h> | 7 #include <windows.h> |
8 | |
8 #include <ks.h> | 9 #include <ks.h> |
9 #include <ksmedia.h> | 10 #include <ksmedia.h> |
10 #include <mmreg.h> | 11 #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> | 12 #include <mmsystem.h> |
22 #include <stddef.h> | |
23 | 13 |
24 #include <algorithm> | 14 #include <algorithm> |
25 #include <functional> | |
26 #include <queue> | |
27 #include <string> | 15 #include <string> |
28 | 16 |
29 #include "base/bind.h" | 17 #include "base/bind_helpers.h" |
30 #include "base/containers/hash_tables.h" | 18 #include "base/callback.h" |
31 #include "base/feature_list.h" | 19 #include "base/logging.h" |
32 #include "base/macros.h" | 20 #include "base/memory/ptr_util.h" |
33 #include "base/message_loop/message_loop.h" | |
34 #include "base/single_thread_task_runner.h" | |
35 #include "base/strings/string16.h" | 21 #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" | 22 #include "base/strings/stringprintf.h" |
39 #include "base/strings/utf_string_conversions.h" | 23 #include "base/strings/utf_string_conversions.h" |
40 #include "base/system_monitor/system_monitor.h" | 24 #include "base/synchronization/lock.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" | 25 #include "base/win/windows_version.h" |
45 #include "device/usb/usb_ids.h" | 26 #include "device/usb/usb_ids.h" |
46 #include "media/midi/dynamically_initialized_midi_manager_win.h" | |
47 #include "media/midi/message_util.h" | 27 #include "media/midi/message_util.h" |
48 #include "media/midi/midi_manager_winrt.h" | 28 #include "media/midi/midi_manager_winrt.h" |
49 #include "media/midi/midi_message_queue.h" | |
50 #include "media/midi/midi_port_info.h" | 29 #include "media/midi/midi_port_info.h" |
30 #include "media/midi/midi_service.h" | |
51 #include "media/midi/midi_switches.h" | 31 #include "media/midi/midi_switches.h" |
52 | 32 |
53 namespace midi { | 33 namespace midi { |
34 | |
35 // Forward declaration of PortManager for anonymous functions and internal | |
36 // classes to use it. | |
37 class MidiManagerWin::PortManager { | |
38 public: | |
39 // Calculates event time from elapsed time that system provides. | |
40 base::TimeTicks CalculateInEventTime(size_t index, uint32_t elapsed_ms) const; | |
41 | |
42 // Registers HMIDIIN handle to resolve port index. | |
43 void RegisterInHandle(HMIDIIN handle, size_t index); | |
44 | |
45 // Unregisters HMIDIIN handle. | |
46 void UnregisterInHandle(HMIDIIN handle); | |
47 | |
48 // Finds HMIDIIN handle and fullfil |out_index| with the port index. | |
49 bool FindInHandle(HMIDIIN hmi, size_t* out_index); | |
50 | |
51 // Restores used input buffer for the next data receive. | |
52 void RestoreInBuffer(size_t index); | |
53 | |
54 // Ports accessors. | |
55 std::vector<std::unique_ptr<InPort>>* inputs() { return &input_ports_; } | |
56 std::vector<std::unique_ptr<OutPort>>* outputs() { return &output_ports_; } | |
57 | |
58 // Handles MIDI input port callbacks that runs on a system provided thread. | |
59 static void CALLBACK HandleMidiInCallback(HMIDIIN hmi, | |
60 UINT msg, | |
61 DWORD_PTR instance, | |
62 DWORD_PTR param1, | |
63 DWORD_PTR param2); | |
64 | |
65 // Handles MIDI output port callbacks that runs on a system provided thread. | |
66 static void CALLBACK HandleMidiOutCallback(HMIDIOUT hmo, | |
67 UINT msg, | |
68 DWORD_PTR instance, | |
69 DWORD_PTR param1, | |
70 DWORD_PTR param2); | |
71 | |
72 private: | |
73 // Holds all MIDI input or output ports connected once. | |
74 std::vector<std::unique_ptr<InPort>> input_ports_; | |
75 std::vector<std::unique_ptr<OutPort>> output_ports_; | |
76 | |
77 // Map to resolve MIDI input port index from HMIDIIN. | |
78 std::map<HMIDIIN, size_t> hmidiin_to_index_map_; | |
79 }; | |
80 | |
54 namespace { | 81 namespace { |
55 | 82 |
56 using mojom::PortState; | 83 // Assumes that nullptr represents an invalid MIDI handle. |
57 using mojom::Result; | 84 constexpr HMIDIIN kInvalidInHandle = nullptr; |
85 constexpr HMIDIOUT kInvalidOutHandle = nullptr; | |
58 | 86 |
59 static const size_t kBufferLength = 32 * 1024; | 87 // Defines SysEx message size limit. |
88 // TODO(crbug.com/383578): This restriction should be removed once Web MIDI | |
89 // defines a standardized way to handle large sysex messages. | |
90 // Note for built-in USB-MIDI driver: | |
91 // From an observation on Windows 7/8.1 with a USB-MIDI keyboard, | |
92 // midiOutLongMsg() will be always blocked. Sending 64 bytes or less data takes | |
93 // roughly 300 usecs. Sending 2048 bytes or more data takes roughly | |
94 // |message.size() / (75 * 1024)| secs in practice. Here we put 256 KB size | |
95 // limit on SysEx message, with hoping that midiOutLongMsg will be blocked at | |
96 // most 4 sec or so with a typical USB-MIDI device. | |
97 // TODO(toyoshim): Consider to use linked small buffers so that midiOutReset() | |
98 // can abort sending unhandled following buffers. | |
99 constexpr size_t kSysExSizeLimit = 256 * 1024; | |
60 | 100 |
61 // We assume that nullpter represents an invalid MIDI handle. | 101 // Defines input buffer size. |
62 const HMIDIIN kInvalidMidiInHandle = nullptr; | 102 constexpr size_t kBufferLength = 32 * 1024; |
63 const HMIDIOUT kInvalidMidiOutHandle = nullptr; | |
64 | 103 |
65 std::string GetInErrorMessage(MMRESULT result) { | 104 // Global variables to identify MidiManager instance. |
66 wchar_t text[MAXERRORLENGTH]; | 105 constexpr int kInvalidInstanceId = -1; |
67 MMRESULT get_result = midiInGetErrorText(result, text, arraysize(text)); | 106 int g_active_instance_id = kInvalidInstanceId; |
68 if (get_result != MMSYSERR_NOERROR) { | 107 MidiManagerWin* g_manager_instance = nullptr; |
69 DLOG(ERROR) << "Failed to get error message." | 108 |
70 << " original error: " << result | 109 // Obtains base::Lock instance pointer to lock instance_id. |
71 << " midiInGetErrorText error: " << get_result; | 110 base::Lock* GetInstanceIdLock() { |
72 return std::string(); | 111 static base::Lock* lock = new base::Lock; |
73 } | 112 return lock; |
74 return base::WideToUTF8(text); | |
75 } | 113 } |
76 | 114 |
77 std::string GetOutErrorMessage(MMRESULT result) { | 115 // Issues unique MidiManager instance ID. |
78 wchar_t text[MAXERRORLENGTH]; | 116 int IssueNextInstanceId() { |
79 MMRESULT get_result = midiOutGetErrorText(result, text, arraysize(text)); | 117 static int id = kInvalidInstanceId; |
80 if (get_result != MMSYSERR_NOERROR) { | 118 return ++id; |
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 } | 119 } |
88 | 120 |
89 std::string MmversionToString(MMVERSION version) { | 121 // Use single TaskRunner for all tasks running outside the I/O thread. |
90 return base::StringPrintf("%d.%d", HIBYTE(version), LOBYTE(version)); | 122 constexpr int kTaskRunner = 0; |
123 | |
124 // Obtains base::Lock instance pointer to ensure tasks run safely on TaskRunner. | |
125 // Since all tasks on TaskRunner run behind a lock of *GetTaskLock(), we can | |
126 // access all members even on the I/O thread if a lock of *GetTaskLock() is | |
127 // obtained. | |
128 base::Lock* GetTaskLock() { | |
129 static base::Lock* lock = new base::Lock; | |
130 return lock; | |
91 } | 131 } |
92 | 132 |
93 void CloseOutputPortOnTaskThread(HMIDIOUT midi_out_handle) { | 133 // Helper function to run a posted task on TaskRunner safely. |
94 midiOutClose(midi_out_handle); | 134 void RunTask(int instance_id, const base::Closure& task) { |
135 // Obtains task lock to ensure that the instance should not complete | |
136 // Finalize() while running the |task|. | |
137 base::AutoLock task_lock(*GetTaskLock()); | |
138 { | |
139 // If Finalize() finished before the lock avobe, do nothing. | |
140 base::AutoLock lock(*GetInstanceIdLock()); | |
141 if (instance_id != g_active_instance_id) | |
142 return; | |
143 } | |
144 task.Run(); | |
95 } | 145 } |
96 | 146 |
147 // TODO(toyoshim): Factor out TaskRunner related functionaliries above, and | |
148 // deprecate MidiScheduler. It should be available via MidiManager::scheduler(). | |
149 | |
150 // Utility class to handle MIDIHDR struct safely. | |
97 class MIDIHDRDeleter { | 151 class MIDIHDRDeleter { |
98 public: | 152 public: |
99 void operator()(MIDIHDR* header) { | 153 void operator()(LPMIDIHDR header) { |
100 if (!header) | 154 if (!header) |
101 return; | 155 return; |
102 delete[] static_cast<char*>(header->lpData); | 156 delete[] static_cast<char*>(header->lpData); |
103 header->lpData = NULL; | |
104 header->dwBufferLength = 0; | |
105 delete header; | 157 delete header; |
106 } | 158 } |
107 }; | 159 }; |
108 | 160 |
109 using ScopedMIDIHDR = std::unique_ptr<MIDIHDR, MIDIHDRDeleter>; | 161 using ScopedMIDIHDR = std::unique_ptr<MIDIHDR, MIDIHDRDeleter>; |
110 | 162 |
111 ScopedMIDIHDR CreateMIDIHDR(size_t size) { | 163 ScopedMIDIHDR CreateMIDIHDR(size_t size) { |
112 ScopedMIDIHDR header(new MIDIHDR); | 164 ScopedMIDIHDR hdr(new MIDIHDR); |
113 ZeroMemory(header.get(), sizeof(*header)); | 165 ZeroMemory(hdr.get(), sizeof(*hdr)); |
114 header->lpData = new char[size]; | 166 hdr->lpData = new char[size]; |
115 header->dwBufferLength = static_cast<DWORD>(size); | 167 hdr->dwBufferLength = static_cast<DWORD>(size); |
116 return header; | 168 return hdr; |
117 } | 169 } |
118 | 170 |
119 void SendShortMidiMessageInternal(HMIDIOUT midi_out_handle, | 171 ScopedMIDIHDR CreateMIDIHDR(const std::vector<uint8_t>& data) { |
120 const std::vector<uint8_t>& message) { | 172 ScopedMIDIHDR hdr(CreateMIDIHDR(data.size())); |
121 DCHECK_LE(message.size(), static_cast<size_t>(3)) | 173 std::copy(data.begin(), data.end(), hdr->lpData); |
122 << "A short MIDI message should be up to 3 bytes."; | 174 return hdr; |
123 | 175 } |
124 DWORD packed_message = 0; | 176 |
125 for (size_t i = 0; i < message.size(); ++i) | 177 // Helper functions to close MIDI device handles on TaskRunner asynchronously. |
126 packed_message |= (static_cast<uint32_t>(message[i]) << (i * 8)); | 178 void FinalizeInPort(HMIDIIN handle, ScopedMIDIHDR hdr) { |
127 MMRESULT result = midiOutShortMsg(midi_out_handle, packed_message); | 179 // Resets the device. This stops receiving messages, and allows to release |
128 DLOG_IF(ERROR, result != MMSYSERR_NOERROR) | 180 // registered buffer headers. Otherwise, midiInUnprepareHeader() and |
129 << "Failed to output short message: " << GetOutErrorMessage(result); | 181 // midiInClose() will fail with MIDIERR_STILLPLAYING. |
130 } | 182 midiInReset(handle); |
131 | 183 |
132 void SendLongMidiMessageInternal(HMIDIOUT midi_out_handle, | 184 if (hdr) |
133 const std::vector<uint8_t>& message) { | 185 midiInUnprepareHeader(handle, hdr.get(), sizeof(*hdr)); |
134 // Implementation note: | 186 midiInClose(handle); |
135 // Sending a long MIDI message can be performed synchronously or | 187 } |
136 // asynchronously depending on the driver. There are 2 options to support both | 188 |
137 // cases: | 189 void FinalizeOutPort(HMIDIOUT handle) { |
138 // 1) Call midiOutLongMsg() API and wait for its completion within this | 190 // Resets inflight buffers. This will cancel sending data that system |
139 // function. In this approach, we can avoid memory copy by directly pointing | 191 // holds and were not sent yet. |
140 // |message| as the data buffer to be sent. | 192 midiOutReset(handle); |
141 // 2) Allocate a buffer and copy |message| to it, then call midiOutLongMsg() | 193 midiOutClose(handle); |
142 // API. The buffer will be freed in the MOM_DONE event hander, which tells | 194 } |
143 // us that the task of midiOutLongMsg() API is completed. | 195 |
144 // Here we choose option 2) in favor of asynchronous design. | 196 // Gets manufacturer name in string from identifiers. |
145 | 197 std::string GetManufacturerName(uint16_t id, const GUID& guid) { |
146 // Note for built-in USB-MIDI driver: | 198 if (IS_COMPATIBLE_USBAUDIO_MID(&guid)) { |
147 // From an observation on Windows 7/8.1 with a USB-MIDI keyboard, | 199 const char* name = |
148 // midiOutLongMsg() will be always blocked. Sending 64 bytes or less data | 200 device::UsbIds::GetVendorName(EXTRACT_USBAUDIO_MID(&guid)); |
149 // takes roughly 300 usecs. Sending 2048 bytes or more data takes roughly | 201 if (name) |
150 // |message.size() / (75 * 1024)| secs in practice. Here we put 256 KB size | 202 return std::string(name); |
151 // limit on SysEx message, with hoping that midiOutLongMsg will be blocked at | 203 } |
152 // most 4 sec or so with a typical USB-MIDI device. | 204 if (id == MM_MICROSOFT) |
153 // TODO(crbug.com/383578): This restriction should be removed once Web MIDI | 205 return "Microsoft Corporation"; |
154 // defines a standardized way to handle large sysex messages. | 206 |
155 const size_t kSysExSizeLimit = 256 * 1024; | 207 // TODO(crbug.com/472341): Support other manufacture IDs. |
156 if (message.size() >= kSysExSizeLimit) { | 208 return ""; |
157 DVLOG(1) << "Ingnoreing SysEx message due to the size limit" | 209 } |
158 << ", size = " << message.size(); | 210 |
211 // All instances of Port subclasses are always accessed behind a lock of | |
212 // *GetTaskLock(). Port and subclasses implementation do not need to | |
213 // consider thread safety. | |
214 class Port { | |
215 public: | |
216 Port(const std::string& type, | |
217 uint32_t device_id, | |
218 uint16_t manufacturer_id, | |
219 uint16_t product_id, | |
220 uint32_t driver_version, | |
221 const std::string& product_name, | |
222 const GUID& manufacturer_guid) | |
223 : index_(0u), | |
224 type_(type), | |
225 device_id_(device_id), | |
226 manufacturer_id_(manufacturer_id), | |
227 product_id_(product_id), | |
228 driver_version_(driver_version), | |
229 product_name_(product_name) { | |
230 info_.manufacturer = | |
231 GetManufacturerName(manufacturer_id, manufacturer_guid); | |
232 info_.name = product_name_; | |
233 info_.version = base::StringPrintf("%d.%d", HIBYTE(driver_version_), | |
234 LOBYTE(driver_version_)); | |
235 info_.state = mojom::PortState::DISCONNECTED; | |
236 } | |
237 | |
238 virtual ~Port() {} | |
239 | |
240 bool operator==(const Port& other) const { | |
241 // Should not use |device_id| for comparison because it can be changed on | |
242 // each enumeration. | |
243 // Since the GUID will be changed on each enumeration for Microsoft GS | |
244 // Wavetable synth and might be done for others, do not use it for device | |
245 // comparison. | |
246 return manufacturer_id_ == other.manufacturer_id_ && | |
247 product_id_ == other.product_id_ && | |
248 driver_version_ == other.driver_version_ && | |
249 product_name_ == other.product_name_; | |
250 } | |
251 | |
252 bool IsConnected() const { | |
253 return info_.state != mojom::PortState::DISCONNECTED; | |
254 } | |
255 | |
256 void set_index(size_t index) { | |
257 index_ = index; | |
258 // TODO(toyoshim): Use hashed ID. | |
259 info_.id = base::StringPrintf("%s-%d", type_.c_str(), index_); | |
260 } | |
261 size_t index() { return index_; } | |
262 void set_device_id(uint32_t device_id) { device_id_ = device_id; } | |
263 uint32_t device_id() { return device_id_; } | |
264 const MidiPortInfo& info() { return info_; } | |
265 | |
266 virtual bool Connect() { | |
267 if (info_.state != mojom::PortState::DISCONNECTED) | |
268 return false; | |
269 | |
270 info_.state = mojom::PortState::CONNECTED; | |
271 // TODO(toyoshim) Until open() / close() are supported, open each device on | |
272 // connected. | |
273 Open(); | |
274 return true; | |
275 } | |
276 | |
277 virtual bool Disconnect() { | |
278 if (info_.state == mojom::PortState::DISCONNECTED) | |
279 return false; | |
280 info_.state = mojom::PortState::DISCONNECTED; | |
281 return true; | |
282 } | |
283 | |
284 virtual void Open() { info_.state = mojom::PortState::OPENED; } | |
285 | |
286 protected: | |
287 size_t index_; | |
288 std::string type_; | |
289 uint32_t device_id_; | |
290 const uint16_t manufacturer_id_; | |
291 const uint16_t product_id_; | |
292 const uint32_t driver_version_; | |
293 const std::string product_name_; | |
294 MidiPortInfo info_; | |
295 }; // class Port | |
296 | |
297 } // namespace | |
298 | |
299 class MidiManagerWin::InPort final : public Port { | |
300 public: | |
301 InPort(MidiManagerWin* manager, | |
302 int instance_id, | |
303 UINT device_id, | |
304 const MIDIINCAPS2W& caps) | |
305 : Port("input", | |
306 device_id, | |
307 caps.wMid, | |
308 caps.wPid, | |
309 caps.vDriverVersion, | |
310 base::WideToUTF8( | |
311 base::string16(caps.szPname, wcslen(caps.szPname))), | |
312 caps.ManufacturerGuid), | |
313 manager_(manager), | |
314 in_handle_(kInvalidInHandle), | |
315 instance_id_(instance_id) {} | |
316 | |
317 static std::vector<std::unique_ptr<InPort>> EnumerateActivePorts( | |
318 MidiManagerWin* manager, | |
319 int instance_id) { | |
320 std::vector<std::unique_ptr<InPort>> ports; | |
321 const UINT num_devices = midiInGetNumDevs(); | |
322 for (UINT device_id = 0; device_id < num_devices; ++device_id) { | |
323 MIDIINCAPS2W caps; | |
324 MMRESULT result = midiInGetDevCaps( | |
325 device_id, reinterpret_cast<LPMIDIINCAPSW>(&caps), sizeof(caps)); | |
326 if (result != MMSYSERR_NOERROR) { | |
327 LOG(ERROR) << "midiInGetDevCaps fails on device " << device_id; | |
328 continue; | |
329 } | |
330 ports.push_back( | |
331 base::MakeUnique<InPort>(manager, instance_id, device_id, caps)); | |
332 } | |
333 return ports; | |
334 } | |
335 | |
336 void Finalize(scoped_refptr<base::SingleThreadTaskRunner> runner) { | |
337 if (in_handle_ != kInvalidInHandle) { | |
338 runner->PostTask(FROM_HERE, base::Bind(&FinalizeInPort, in_handle_, | |
339 base::Passed(&hdr_))); | |
340 manager_->port_manager()->UnregisterInHandle(in_handle_); | |
341 in_handle_ = kInvalidInHandle; | |
342 } | |
343 } | |
344 | |
345 base::TimeTicks CalculateInEventTime(uint32_t elapsed_ms) const { | |
346 return start_time_ + base::TimeDelta::FromMilliseconds(elapsed_ms); | |
347 } | |
348 | |
349 void RestoreBuffer() { | |
350 if (in_handle_ == kInvalidInHandle || !hdr_) | |
351 return; | |
352 midiInAddBuffer(in_handle_, hdr_.get(), sizeof(*hdr_)); | |
353 } | |
354 | |
355 void NotifyPortStateSet(MidiManagerWin* manager) { | |
356 manager->PostReplyTask(base::Bind(&MidiManagerWin::SetInputPortState, | |
357 base::Unretained(manager), index_, | |
358 info_.state)); | |
359 } | |
360 | |
361 void NotifyPortAdded(MidiManagerWin* manager) { | |
362 manager->PostReplyTask(base::Bind(&MidiManagerWin::AddInputPort, | |
363 base::Unretained(manager), info_)); | |
364 } | |
365 | |
366 // Port overrides: | |
367 bool Disconnect() override { | |
368 if (in_handle_ != kInvalidInHandle) { | |
369 // Following API call may fail because device was already disconnected. | |
370 // But just in case. | |
371 midiInClose(in_handle_); | |
372 manager_->port_manager()->UnregisterInHandle(in_handle_); | |
373 in_handle_ = kInvalidInHandle; | |
374 } | |
375 return Port::Disconnect(); | |
376 } | |
377 | |
378 void Open() override { | |
379 MMRESULT result = midiInOpen( | |
380 &in_handle_, device_id_, | |
381 reinterpret_cast<DWORD_PTR>(&PortManager::HandleMidiInCallback), | |
382 instance_id_, CALLBACK_FUNCTION); | |
383 if (result == MMSYSERR_NOERROR) { | |
384 hdr_ = CreateMIDIHDR(kBufferLength); | |
385 result = midiInPrepareHeader(in_handle_, hdr_.get(), sizeof(*hdr_)); | |
386 } | |
387 if (result != MMSYSERR_NOERROR) | |
388 in_handle_ = kInvalidInHandle; | |
389 if (result == MMSYSERR_NOERROR) | |
390 result = midiInAddBuffer(in_handle_, hdr_.get(), sizeof(*hdr_)); | |
391 if (result == MMSYSERR_NOERROR) | |
392 result = midiInStart(in_handle_); | |
393 if (result == MMSYSERR_NOERROR) { | |
394 start_time_ = base::TimeTicks::Now(); | |
395 manager_->port_manager()->RegisterInHandle(in_handle_, index_); | |
396 Port::Open(); | |
397 } else { | |
398 if (in_handle_ != kInvalidInHandle) { | |
399 midiInUnprepareHeader(in_handle_, hdr_.get(), sizeof(*hdr_)); | |
400 hdr_.reset(); | |
401 midiInClose(in_handle_); | |
402 in_handle_ = kInvalidInHandle; | |
403 } | |
404 Disconnect(); | |
405 } | |
406 } | |
407 | |
408 private: | |
409 MidiManagerWin* manager_; | |
410 HMIDIIN in_handle_; | |
411 ScopedMIDIHDR hdr_; | |
412 base::TimeTicks start_time_; | |
413 const int instance_id_; | |
414 }; | |
415 | |
416 class MidiManagerWin::OutPort final : public Port { | |
417 public: | |
418 OutPort(UINT device_id, const MIDIOUTCAPS2W& caps) | |
419 : Port("output", | |
420 device_id, | |
421 caps.wMid, | |
422 caps.wPid, | |
423 caps.vDriverVersion, | |
424 base::WideToUTF8( | |
425 base::string16(caps.szPname, wcslen(caps.szPname))), | |
426 caps.ManufacturerGuid), | |
427 software_(caps.wTechnology == MOD_SWSYNTH), | |
428 out_handle_(kInvalidOutHandle) {} | |
429 | |
430 static std::vector<std::unique_ptr<OutPort>> EnumerateActivePorts() { | |
431 std::vector<std::unique_ptr<OutPort>> ports; | |
432 const UINT num_devices = midiOutGetNumDevs(); | |
433 for (UINT device_id = 0; device_id < num_devices; ++device_id) { | |
434 MIDIOUTCAPS2W caps; | |
435 MMRESULT result = midiOutGetDevCaps( | |
436 device_id, reinterpret_cast<LPMIDIOUTCAPSW>(&caps), sizeof(caps)); | |
437 if (result != MMSYSERR_NOERROR) { | |
438 LOG(ERROR) << "midiOutGetDevCaps fails on device " << device_id; | |
439 continue; | |
440 } | |
441 ports.push_back(base::MakeUnique<OutPort>(device_id, caps)); | |
442 } | |
443 return ports; | |
444 } | |
445 | |
446 void Finalize(scoped_refptr<base::SingleThreadTaskRunner> runner) { | |
447 if (out_handle_ != kInvalidOutHandle) { | |
448 runner->PostTask(FROM_HERE, base::Bind(&FinalizeOutPort, out_handle_)); | |
449 out_handle_ = kInvalidOutHandle; | |
450 } | |
451 } | |
452 | |
453 void NotifyPortStateSet(MidiManagerWin* manager) { | |
454 manager->PostReplyTask(base::Bind(&MidiManagerWin::SetOutputPortState, | |
455 base::Unretained(manager), index_, | |
456 info_.state)); | |
457 } | |
458 | |
459 void NotifyPortAdded(MidiManagerWin* manager) { | |
460 manager->PostReplyTask(base::Bind(&MidiManagerWin::AddOutputPort, | |
461 base::Unretained(manager), info_)); | |
462 } | |
463 | |
464 void Send(const std::vector<uint8_t>& data) { | |
465 if (out_handle_ == kInvalidOutHandle) | |
466 return; | |
467 | |
468 if (data.size() <= 3) { | |
469 uint32_t message = 0; | |
470 for (size_t i = 0; i < data.size(); ++i) | |
471 message |= (static_cast<uint32_t>(data[i]) << (i * 8)); | |
472 midiOutShortMsg(out_handle_, message); | |
473 } else { | |
474 if (data.size() > kSysExSizeLimit) { | |
475 LOG(ERROR) << "Ignoring SysEx message due to the size limit" | |
476 << ", size = " << data.size(); | |
477 // TODO(toyoshim): Consider to report metrics here. | |
478 return; | |
479 } | |
480 ScopedMIDIHDR hdr(CreateMIDIHDR(data)); | |
481 MMRESULT result = | |
482 midiOutPrepareHeader(out_handle_, hdr.get(), sizeof(*hdr)); | |
483 if (result != MMSYSERR_NOERROR) | |
484 return; | |
485 result = midiOutLongMsg(out_handle_, hdr.get(), sizeof(*hdr)); | |
486 if (result != MMSYSERR_NOERROR) { | |
487 midiOutUnprepareHeader(out_handle_, hdr.get(), sizeof(*hdr)); | |
488 } else { | |
489 // MIDIHDR will be released on MOM_DONE. | |
490 ignore_result(hdr.release()); | |
491 } | |
492 } | |
493 } | |
494 | |
495 // Port overrides: | |
496 bool Connect() override { | |
497 // Until |software| option is supported, disable Microsoft GS Wavetable | |
498 // Synth that has a known security issue. | |
499 if (software_ && manufacturer_id_ == MM_MICROSOFT && | |
500 (product_id_ == MM_MSFT_WDMAUDIO_MIDIOUT || | |
501 product_id_ == MM_MSFT_GENERIC_MIDISYNTH)) { | |
502 return false; | |
503 } | |
504 return Port::Connect(); | |
505 } | |
506 | |
507 bool Disconnect() override { | |
508 if (out_handle_ != kInvalidOutHandle) { | |
509 // Following API call may fail because device was already disconnected. | |
510 // But just in case. | |
511 midiOutClose(out_handle_); | |
512 out_handle_ = kInvalidOutHandle; | |
513 } | |
514 return Port::Disconnect(); | |
515 } | |
516 | |
517 void Open() override { | |
518 MMRESULT result = midiOutOpen( | |
519 &out_handle_, device_id_, | |
520 reinterpret_cast<DWORD_PTR>(&PortManager::HandleMidiOutCallback), 0, | |
521 CALLBACK_FUNCTION); | |
522 if (result == MMSYSERR_NOERROR) { | |
523 Port::Open(); | |
524 } else { | |
525 out_handle_ = kInvalidOutHandle; | |
526 Disconnect(); | |
527 } | |
528 } | |
529 | |
530 const bool software_; | |
531 HMIDIOUT out_handle_; | |
532 }; | |
533 | |
534 base::TimeTicks MidiManagerWin::PortManager::CalculateInEventTime( | |
535 size_t index, | |
536 uint32_t elapsed_ms) const { | |
537 GetTaskLock()->AssertAcquired(); | |
538 CHECK_GT(input_ports_.size(), index); | |
539 return input_ports_[index]->CalculateInEventTime(elapsed_ms); | |
540 } | |
541 | |
542 void MidiManagerWin::PortManager::RegisterInHandle(HMIDIIN handle, | |
543 size_t index) { | |
544 GetTaskLock()->AssertAcquired(); | |
545 hmidiin_to_index_map_[handle] = index; | |
546 } | |
547 | |
548 void MidiManagerWin::PortManager::UnregisterInHandle(HMIDIIN handle) { | |
549 GetTaskLock()->AssertAcquired(); | |
550 hmidiin_to_index_map_.erase(handle); | |
551 } | |
552 | |
553 bool MidiManagerWin::PortManager::FindInHandle(HMIDIIN hmi, size_t* out_index) { | |
554 GetTaskLock()->AssertAcquired(); | |
555 auto found = hmidiin_to_index_map_.find(hmi); | |
556 if (found == hmidiin_to_index_map_.end()) | |
557 return false; | |
558 *out_index = found->second; | |
559 return true; | |
560 } | |
561 | |
562 void MidiManagerWin::PortManager::RestoreInBuffer(size_t index) { | |
563 GetTaskLock()->AssertAcquired(); | |
564 CHECK_GT(input_ports_.size(), index); | |
565 input_ports_[index]->RestoreBuffer(); | |
566 } | |
567 | |
568 void CALLBACK | |
569 MidiManagerWin::PortManager::HandleMidiInCallback(HMIDIIN hmi, | |
570 UINT msg, | |
571 DWORD_PTR instance, | |
572 DWORD_PTR param1, | |
573 DWORD_PTR param2) { | |
574 if (msg != MIM_DATA && msg != MIM_LONGDATA) | |
159 return; | 575 return; |
160 } | 576 int instance_id = static_cast<int>(instance); |
161 | 577 MidiManagerWin* manager = nullptr; |
162 ScopedMIDIHDR midi_header(CreateMIDIHDR(message.size())); | 578 |
163 std::copy(message.begin(), message.end(), midi_header->lpData); | 579 // Use |g_task_lock| so to ensure the instance can keep alive while running, |
164 | 580 // and to access member variables that are used on TaskRunner. |
165 MMRESULT result = midiOutPrepareHeader(midi_out_handle, midi_header.get(), | 581 base::AutoLock task_lock(*GetTaskLock()); |
166 sizeof(*midi_header)); | 582 { |
167 if (result != MMSYSERR_NOERROR) { | 583 base::AutoLock lock(*GetInstanceIdLock()); |
168 DLOG(ERROR) << "Failed to prepare output buffer: " | 584 if (instance_id != g_active_instance_id) |
169 << GetOutErrorMessage(result); | 585 return; |
586 manager = g_manager_instance; | |
587 } | |
588 | |
589 size_t index; | |
590 if (!manager->port_manager()->FindInHandle(hmi, &index)) | |
170 return; | 591 return; |
171 } | 592 |
172 | 593 DCHECK(msg == MIM_DATA || msg == MIM_LONGDATA); |
173 result = | 594 if (msg == MIM_DATA) { |
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); | 595 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); | 596 const uint8_t first_data_byte = static_cast<uint8_t>((param1 >> 8) & 0xff); |
711 const uint8_t second_data_byte = | 597 const uint8_t second_data_byte = |
712 static_cast<uint8_t>((param1 >> 16) & 0xff); | 598 static_cast<uint8_t>((param1 >> 16) & 0xff); |
713 const DWORD elapsed_ms = param2; | 599 const uint8_t kData[] = {status_byte, first_data_byte, second_data_byte}; |
714 const size_t len = GetMessageLength(status_byte); | 600 const size_t len = GetMessageLength(status_byte); |
715 const uint8_t kData[] = {status_byte, first_data_byte, second_data_byte}; | 601 DCHECK_LE(len, arraysize(kData)); |
716 std::vector<uint8_t> data; | 602 std::vector<uint8_t> data; |
717 data.assign(kData, kData + len); | 603 data.assign(kData, kData + len); |
718 DCHECK_LE(len, arraysize(kData)); | 604 manager->PostReplyTask(base::Bind( |
719 // MIM_DATA/MIM_LONGDATA message treats the time when midiInStart() is | 605 &MidiManagerWin::ReceiveMidiData, base::Unretained(manager), index, |
720 // called as the origin of |elapsed_ms|. | 606 data, manager->port_manager()->CalculateInEventTime(index, param2))); |
721 // http://msdn.microsoft.com/en-us/library/windows/desktop/dd757284.aspx | 607 } else { |
722 // http://msdn.microsoft.com/en-us/library/windows/desktop/dd757286.aspx | 608 DCHECK_EQ(static_cast<UINT>(MIM_LONGDATA), msg); |
723 const base::TimeTicks event_time = | 609 LPMIDIHDR hdr = reinterpret_cast<LPMIDIHDR>(param1); |
724 state->start_time + base::TimeDelta::FromMilliseconds(elapsed_ms); | 610 if (hdr->dwBytesRecorded > 0) { |
725 task_thread_.task_runner()->PostTask( | 611 const uint8_t* src = reinterpret_cast<const uint8_t*>(hdr->lpData); |
726 FROM_HERE, base::Bind(&MidiServiceWinImpl::ReceiveMidiDataOnTaskThread, | 612 std::vector<uint8_t> data; |
727 base::Unretained(this), state->port_index, data, | 613 data.assign(src, src + hdr->dwBytesRecorded); |
728 event_time)); | 614 manager->PostReplyTask(base::Bind( |
729 } | 615 &MidiManagerWin::ReceiveMidiData, base::Unretained(manager), index, |
730 | 616 data, manager->port_manager()->CalculateInEventTime(index, param2))); |
731 void OnMidiInLongDataOnMultimediaThread(HMIDIIN midi_in_handle, | 617 } |
732 DWORD_PTR param1, | 618 manager->PostTask(base::Bind(&MidiManagerWin::PortManager::RestoreInBuffer, |
733 DWORD_PTR param2) { | 619 base::Unretained(manager->port_manager()), |
734 auto state = GetInputDeviceFromHandle(midi_in_handle); | 620 index)); |
735 if (!state) | 621 } |
622 } | |
623 | |
624 void CALLBACK | |
625 MidiManagerWin::PortManager::HandleMidiOutCallback(HMIDIOUT hmo, | |
626 UINT msg, | |
627 DWORD_PTR instance, | |
628 DWORD_PTR param1, | |
629 DWORD_PTR param2) { | |
630 if (msg == MOM_DONE) { | |
631 ScopedMIDIHDR hdr(reinterpret_cast<LPMIDIHDR>(param1)); | |
632 if (!hdr) | |
736 return; | 633 return; |
737 MIDIHDR* header = reinterpret_cast<MIDIHDR*>(param1); | 634 // TODO(toyoshim): Call midiOutUnprepareHeader outside the callback. |
738 const DWORD elapsed_ms = param2; | 635 // Since this callback may be invoked after the manager is destructed, |
739 MMRESULT result = MMSYSERR_NOERROR; | 636 // and can not send a task to the TaskRunner in such case, we need to |
740 if (destructor_started) { | 637 // consider to track MIDIHDR per port, and clean it in port finalization |
741 if (state->midi_header && | 638 // steps, too. |
742 (state->midi_header->dwFlags & MHDR_PREPARED) == MHDR_PREPARED) { | 639 midiOutUnprepareHeader(hmo, hdr.get(), sizeof(*hdr)); |
743 result = | 640 } |
744 midiInUnprepareHeader(state->midi_handle, state->midi_header.get(), | 641 } |
745 sizeof(*state->midi_header)); | 642 |
746 DLOG_IF(ERROR, result != MMSYSERR_NOERROR) | 643 MidiManagerWin::MidiManagerWin(MidiService* service) |
747 << "Failed to uninitialize input buffer: " | 644 : MidiManager(service), |
748 << GetInErrorMessage(result); | 645 instance_id_(IssueNextInstanceId()), |
749 } | 646 port_manager_(base::MakeUnique<PortManager>()) { |
750 return; | 647 base::AutoLock lock(*GetInstanceIdLock()); |
751 } | 648 CHECK_EQ(kInvalidInstanceId, g_active_instance_id); |
752 if (header->dwBytesRecorded > 0) { | 649 |
753 const uint8_t* src = reinterpret_cast<const uint8_t*>(header->lpData); | 650 // Obtains the task runner for the current thread that hosts this instnace. |
754 std::vector<uint8_t> data; | 651 thread_runner_ = base::ThreadTaskRunnerHandle::Get(); |
755 data.assign(src, src + header->dwBytesRecorded); | 652 } |
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 | 653 |
1143 MidiManagerWin::~MidiManagerWin() { | 654 MidiManagerWin::~MidiManagerWin() { |
655 base::AutoLock lock(*GetInstanceIdLock()); | |
656 CHECK_EQ(kInvalidInstanceId, g_active_instance_id); | |
657 CHECK(thread_runner_->BelongsToCurrentThread()); | |
1144 } | 658 } |
1145 | 659 |
1146 void MidiManagerWin::StartInitialization() { | 660 void MidiManagerWin::StartInitialization() { |
1147 midi_service_.reset(new MidiServiceWinImpl); | 661 { |
1148 // Note that |CompleteInitialization()| will be called from the callback. | 662 base::AutoLock lock(*GetInstanceIdLock()); |
1149 midi_service_->InitializeAsync(this); | 663 CHECK_EQ(kInvalidInstanceId, g_active_instance_id); |
664 g_active_instance_id = instance_id_; | |
665 CHECK_EQ(nullptr, g_manager_instance); | |
666 g_manager_instance = this; | |
667 } | |
668 // Registers on the I/O thread to be notified on the I/O thread. | |
669 CHECK(thread_runner_->BelongsToCurrentThread()); | |
670 base::SystemMonitor::Get()->AddDevicesChangedObserver(this); | |
671 | |
672 // Starts asynchronous initialization on TaskRunner. | |
673 PostTask(base::Bind(&MidiManagerWin::InitializeOnTaskRunner, | |
674 base::Unretained(this))); | |
1150 } | 675 } |
1151 | 676 |
1152 void MidiManagerWin::Finalize() { | 677 void MidiManagerWin::Finalize() { |
1153 midi_service_.reset(); | 678 // Unregisters on the I/O thread. OnDevicesChanged() won't be called any more. |
679 CHECK(thread_runner_->BelongsToCurrentThread()); | |
680 base::SystemMonitor::Get()->RemoveDevicesChangedObserver(this); | |
681 { | |
682 base::AutoLock lock(*GetInstanceIdLock()); | |
683 CHECK_EQ(instance_id_, g_active_instance_id); | |
684 g_active_instance_id = kInvalidInstanceId; | |
685 CHECK_EQ(this, g_manager_instance); | |
686 g_manager_instance = nullptr; | |
687 } | |
688 | |
689 // Ensures that no task runs on TaskRunner so to destruct the instance safely. | |
690 // Tasks that did not started yet will do nothing after invalidate the | |
691 // instance ID above. | |
692 // Behind the lock below, we can safely access all members for finalization | |
693 // even on the I/O thread. | |
694 base::AutoLock lock(*GetTaskLock()); | |
695 | |
696 // Posts tasks that finalize each device port without MidiManager instance | |
697 // on TaskRunner. If another MidiManager instance is created, its | |
698 // initialization runs on the same task runner after all tasks posted here | |
699 // finish. | |
700 for (const auto& port : *port_manager_->inputs()) | |
701 port->Finalize(service()->GetTaskRunner(kTaskRunner)); | |
702 for (const auto& port : *port_manager_->outputs()) | |
703 port->Finalize(service()->GetTaskRunner(kTaskRunner)); | |
1154 } | 704 } |
1155 | 705 |
1156 void MidiManagerWin::DispatchSendMidiData(MidiManagerClient* client, | 706 void MidiManagerWin::DispatchSendMidiData(MidiManagerClient* client, |
1157 uint32_t port_index, | 707 uint32_t port_index, |
1158 const std::vector<uint8_t>& data, | 708 const std::vector<uint8_t>& data, |
1159 double timestamp) { | 709 double timestamp) { |
1160 if (!midi_service_) | |
1161 return; | |
1162 | |
1163 base::TimeTicks time_to_send = base::TimeTicks::Now(); | |
1164 if (timestamp != 0.0) { | 710 if (timestamp != 0.0) { |
1165 time_to_send = | 711 base::TimeTicks time = |
1166 base::TimeTicks() + base::TimeDelta::FromMicroseconds( | 712 base::TimeTicks() + base::TimeDelta::FromMicroseconds( |
1167 timestamp * base::Time::kMicrosecondsPerSecond); | 713 timestamp * base::Time::kMicrosecondsPerSecond); |
1168 } | 714 base::TimeTicks now = base::TimeTicks::Now(); |
1169 midi_service_->SendMidiDataAsync(port_index, data, time_to_send); | 715 if (now < time) { |
1170 | 716 PostDelayedTask( |
1171 // TOOD(toyoshim): This calculation should be done when the date is actually | 717 base::Bind(&MidiManagerWin::SendOnTaskRunner, base::Unretained(this), |
1172 // sent. | 718 client, port_index, data), |
1173 client->AccumulateMidiBytesSent(data.size()); | 719 time - now); |
1174 } | 720 return; |
1175 | 721 } |
1176 void MidiManagerWin::OnCompleteInitialization(Result result) { | 722 } |
1177 CompleteInitialization(result); | 723 PostTask(base::Bind(&MidiManagerWin::SendOnTaskRunner, base::Unretained(this), |
1178 } | 724 client, port_index, data)); |
1179 | 725 } |
1180 void MidiManagerWin::OnAddInputPort(MidiPortInfo info) { | 726 |
1181 AddInputPort(info); | 727 void MidiManagerWin::OnDevicesChanged( |
1182 } | 728 base::SystemMonitor::DeviceType device_type) { |
1183 | 729 // Notified on the I/O thread. |
1184 void MidiManagerWin::OnAddOutputPort(MidiPortInfo info) { | 730 CHECK(thread_runner_->BelongsToCurrentThread()); |
1185 AddOutputPort(info); | 731 |
1186 } | 732 switch (device_type) { |
1187 | 733 case base::SystemMonitor::DEVTYPE_AUDIO: |
1188 void MidiManagerWin::OnSetInputPortState(uint32_t port_index, PortState state) { | 734 case base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE: |
1189 SetInputPortState(port_index, state); | 735 // Add case of other unrelated device types here. |
1190 } | 736 return; |
1191 | 737 case base::SystemMonitor::DEVTYPE_UNKNOWN: { |
1192 void MidiManagerWin::OnSetOutputPortState(uint32_t port_index, | 738 PostTask(base::Bind(&MidiManagerWin::UpdateDeviceListOnTaskRunner, |
1193 PortState state) { | 739 base::Unretained(this))); |
1194 SetOutputPortState(port_index, state); | 740 break; |
1195 } | 741 } |
1196 | 742 } |
1197 void MidiManagerWin::OnReceiveMidiData(uint32_t port_index, | 743 } |
1198 const std::vector<uint8_t>& data, | 744 |
1199 base::TimeTicks time) { | 745 void MidiManagerWin::ReceiveMidiData(uint32_t index, |
1200 ReceiveMidiData(port_index, &data[0], data.size(), time); | 746 const std::vector<uint8_t>& data, |
747 base::TimeTicks time) { | |
748 MidiManager::ReceiveMidiData(index, data.data(), data.size(), time); | |
749 } | |
750 | |
751 void MidiManagerWin::PostTask(const base::Closure& task) { | |
752 service() | |
753 ->GetTaskRunner(kTaskRunner) | |
754 ->PostTask(FROM_HERE, base::Bind(&RunTask, instance_id_, task)); | |
755 } | |
756 | |
757 void MidiManagerWin::PostDelayedTask(const base::Closure& task, | |
758 base::TimeDelta delay) { | |
759 service() | |
760 ->GetTaskRunner(kTaskRunner) | |
761 ->PostDelayedTask(FROM_HERE, base::Bind(&RunTask, instance_id_, task), | |
762 delay); | |
763 } | |
764 | |
765 void MidiManagerWin::PostReplyTask(const base::Closure& task) { | |
766 thread_runner_->PostTask(FROM_HERE, base::Bind(&RunTask, instance_id_, task)); | |
767 } | |
768 | |
769 void MidiManagerWin::InitializeOnTaskRunner() { | |
770 UpdateDeviceListOnTaskRunner(); | |
771 PostReplyTask(base::Bind(&MidiManagerWin::CompleteInitialization, | |
772 base::Unretained(this), mojom::Result::OK)); | |
773 } | |
774 | |
775 void MidiManagerWin::UpdateDeviceListOnTaskRunner() { | |
776 std::vector<std::unique_ptr<InPort>> active_input_ports = | |
777 InPort::EnumerateActivePorts(this, instance_id_); | |
778 ReflectActiveDeviceList(this, port_manager_->inputs(), &active_input_ports); | |
779 | |
780 std::vector<std::unique_ptr<OutPort>> active_output_ports = | |
781 OutPort::EnumerateActivePorts(); | |
782 ReflectActiveDeviceList(this, port_manager_->outputs(), &active_output_ports); | |
783 | |
784 // TODO(toyoshim): This method may run before internal MIDI device lists that | |
785 // Windows manages were updated. This may be because MIDI driver may be loaded | |
786 // after the raw device list was updated. To avoid this problem, we may want | |
787 // to retry device check later if no changes are detected here. | |
788 } | |
789 | |
790 template <typename T> | |
791 void MidiManagerWin::ReflectActiveDeviceList(MidiManagerWin* manager, | |
792 std::vector<T>* known_ports, | |
793 std::vector<T>* active_ports) { | |
794 // Update existing port states. | |
795 for (const auto& port : *known_ports) { | |
796 const auto& it = std::find_if( | |
797 active_ports->begin(), active_ports->end(), | |
798 [&port](const auto& candidate) { return *candidate == *port; }); | |
799 if (it == active_ports->end()) { | |
800 if (port->Disconnect()) | |
801 port->NotifyPortStateSet(this); | |
802 } else { | |
803 port->set_device_id((*it)->device_id()); | |
804 if (port->Connect()) | |
805 port->NotifyPortStateSet(this); | |
806 } | |
807 } | |
808 | |
809 // Find new ports from active ports and append them to known ports. | |
810 for (auto& port : *active_ports) { | |
811 if (std::find_if(known_ports->begin(), known_ports->end(), | |
812 [&port](const auto& candidate) { | |
813 return *candidate == *port; | |
814 }) == known_ports->end()) { | |
815 size_t index = known_ports->size(); | |
816 port->set_index(index); | |
817 known_ports->push_back(std::move(port)); | |
818 (*known_ports)[index]->Connect(); | |
819 (*known_ports)[index]->NotifyPortAdded(this); | |
820 } | |
821 } | |
822 } | |
823 | |
824 void MidiManagerWin::SendOnTaskRunner(MidiManagerClient* client, | |
825 uint32_t port_index, | |
826 const std::vector<uint8_t>& data) { | |
827 CHECK_GT(port_manager_->outputs()->size(), port_index); | |
828 (*port_manager_->outputs())[port_index]->Send(data); | |
829 // |client| will be checked inside MidiManager::AccumulateMidiBytesSent. | |
830 PostReplyTask(base::Bind(&MidiManagerWin::AccumulateMidiBytesSent, | |
831 base::Unretained(this), client, data.size())); | |
1201 } | 832 } |
1202 | 833 |
1203 MidiManager* MidiManager::Create(MidiService* service) { | 834 MidiManager* MidiManager::Create(MidiService* service) { |
Takashi Toyoshima
2017/04/24 09:30:23
crreview shows some complicated differences on _wi
| |
1204 if (base::FeatureList::IsEnabled(features::kMidiManagerWinrt) && | 835 if (base::FeatureList::IsEnabled(features::kMidiManagerWinrt) && |
1205 base::win::GetVersion() >= base::win::VERSION_WIN10) | 836 base::win::GetVersion() >= base::win::VERSION_WIN10) { |
1206 return new MidiManagerWinrt(service); | 837 return new MidiManagerWinrt(service); |
1207 if (base::FeatureList::IsEnabled(features::kMidiManagerDynamicInstantiation)) | 838 } |
1208 return new DynamicallyInitializedMidiManagerWin(service); | |
1209 return new MidiManagerWin(service); | 839 return new MidiManagerWin(service); |
1210 } | 840 } |
1211 | 841 |
1212 } // namespace midi | 842 } // namespace midi |
OLD | NEW |