OLD | NEW |
| (Empty) |
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 | |
3 // found in the LICENSE file. | |
4 | |
5 #include "media/midi/dynamically_initialized_midi_manager_win.h" | |
6 | |
7 #include <windows.h> | |
8 | |
9 #include <ks.h> | |
10 #include <ksmedia.h> | |
11 #include <mmreg.h> | |
12 #include <mmsystem.h> | |
13 | |
14 #include <algorithm> | |
15 #include <string> | |
16 | |
17 #include "base/bind_helpers.h" | |
18 #include "base/callback.h" | |
19 #include "base/logging.h" | |
20 #include "base/memory/ptr_util.h" | |
21 #include "base/strings/string16.h" | |
22 #include "base/strings/stringprintf.h" | |
23 #include "base/strings/utf_string_conversions.h" | |
24 #include "base/synchronization/lock.h" | |
25 #include "base/win/windows_version.h" | |
26 #include "device/usb/usb_ids.h" | |
27 #include "media/midi/message_util.h" | |
28 #include "media/midi/midi_manager_winrt.h" | |
29 #include "media/midi/midi_port_info.h" | |
30 #include "media/midi/midi_service.h" | |
31 #include "media/midi/midi_switches.h" | |
32 | |
33 namespace midi { | |
34 | |
35 // Forward declaration of PortManager for anonymous functions and internal | |
36 // classes to use it. | |
37 class DynamicallyInitializedMidiManagerWin::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 | |
81 namespace { | |
82 | |
83 // Assumes that nullptr represents an invalid MIDI handle. | |
84 constexpr HMIDIIN kInvalidInHandle = nullptr; | |
85 constexpr HMIDIOUT kInvalidOutHandle = nullptr; | |
86 | |
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; | |
100 | |
101 // Defines input buffer size. | |
102 constexpr size_t kBufferLength = 32 * 1024; | |
103 | |
104 // Global variables to identify MidiManager instance. | |
105 constexpr int kInvalidInstanceId = -1; | |
106 int g_active_instance_id = kInvalidInstanceId; | |
107 DynamicallyInitializedMidiManagerWin* g_manager_instance = nullptr; | |
108 | |
109 // Obtains base::Lock instance pointer to lock instance_id. | |
110 base::Lock* GetInstanceIdLock() { | |
111 static base::Lock* lock = new base::Lock; | |
112 return lock; | |
113 } | |
114 | |
115 // Issues unique MidiManager instance ID. | |
116 int IssueNextInstanceId() { | |
117 static int id = kInvalidInstanceId; | |
118 return ++id; | |
119 } | |
120 | |
121 // Use single TaskRunner for all tasks running outside the I/O thread. | |
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; | |
131 } | |
132 | |
133 // Helper function to run a posted task on TaskRunner safely. | |
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(); | |
145 } | |
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. | |
151 class MIDIHDRDeleter { | |
152 public: | |
153 void operator()(LPMIDIHDR header) { | |
154 if (!header) | |
155 return; | |
156 delete[] static_cast<char*>(header->lpData); | |
157 delete header; | |
158 } | |
159 }; | |
160 | |
161 using ScopedMIDIHDR = std::unique_ptr<MIDIHDR, MIDIHDRDeleter>; | |
162 | |
163 ScopedMIDIHDR CreateMIDIHDR(size_t size) { | |
164 ScopedMIDIHDR hdr(new MIDIHDR); | |
165 ZeroMemory(hdr.get(), sizeof(*hdr)); | |
166 hdr->lpData = new char[size]; | |
167 hdr->dwBufferLength = static_cast<DWORD>(size); | |
168 return hdr; | |
169 } | |
170 | |
171 ScopedMIDIHDR CreateMIDIHDR(const std::vector<uint8_t>& data) { | |
172 ScopedMIDIHDR hdr(CreateMIDIHDR(data.size())); | |
173 std::copy(data.begin(), data.end(), hdr->lpData); | |
174 return hdr; | |
175 } | |
176 | |
177 // Helper functions to close MIDI device handles on TaskRunner asynchronously. | |
178 void FinalizeInPort(HMIDIIN handle, ScopedMIDIHDR hdr) { | |
179 // Resets the device. This stops receiving messages, and allows to release | |
180 // registered buffer headers. Otherwise, midiInUnprepareHeader() and | |
181 // midiInClose() will fail with MIDIERR_STILLPLAYING. | |
182 midiInReset(handle); | |
183 | |
184 if (hdr) | |
185 midiInUnprepareHeader(handle, hdr.get(), sizeof(*hdr)); | |
186 midiInClose(handle); | |
187 } | |
188 | |
189 void FinalizeOutPort(HMIDIOUT handle) { | |
190 // Resets inflight buffers. This will cancel sending data that system | |
191 // holds and were not sent yet. | |
192 midiOutReset(handle); | |
193 midiOutClose(handle); | |
194 } | |
195 | |
196 // Gets manufacturer name in string from identifiers. | |
197 std::string GetManufacturerName(uint16_t id, const GUID& guid) { | |
198 if (IS_COMPATIBLE_USBAUDIO_MID(&guid)) { | |
199 const char* name = | |
200 device::UsbIds::GetVendorName(EXTRACT_USBAUDIO_MID(&guid)); | |
201 if (name) | |
202 return std::string(name); | |
203 } | |
204 if (id == MM_MICROSOFT) | |
205 return "Microsoft Corporation"; | |
206 | |
207 // TODO(crbug.com/472341): Support other manufacture IDs. | |
208 return ""; | |
209 } | |
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 DynamicallyInitializedMidiManagerWin::InPort final : public Port { | |
300 public: | |
301 InPort(DynamicallyInitializedMidiManagerWin* 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 DynamicallyInitializedMidiManagerWin* 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( | |
339 FROM_HERE, | |
340 base::Bind(&FinalizeInPort, in_handle_, base::Passed(&hdr_))); | |
341 manager_->port_manager()->UnregisterInHandle(in_handle_); | |
342 in_handle_ = kInvalidInHandle; | |
343 } | |
344 } | |
345 | |
346 base::TimeTicks CalculateInEventTime(uint32_t elapsed_ms) const { | |
347 return start_time_ + base::TimeDelta::FromMilliseconds(elapsed_ms); | |
348 } | |
349 | |
350 void RestoreBuffer() { | |
351 if (in_handle_ == kInvalidInHandle || !hdr_) | |
352 return; | |
353 midiInAddBuffer(in_handle_, hdr_.get(), sizeof(*hdr_)); | |
354 } | |
355 | |
356 void NotifyPortStateSet(DynamicallyInitializedMidiManagerWin* manager) { | |
357 manager->PostReplyTask( | |
358 base::Bind(&DynamicallyInitializedMidiManagerWin::SetInputPortState, | |
359 base::Unretained(manager), index_, info_.state)); | |
360 } | |
361 | |
362 void NotifyPortAdded(DynamicallyInitializedMidiManagerWin* manager) { | |
363 manager->PostReplyTask( | |
364 base::Bind(&DynamicallyInitializedMidiManagerWin::AddInputPort, | |
365 base::Unretained(manager), info_)); | |
366 } | |
367 | |
368 // Port overrides: | |
369 bool Disconnect() override { | |
370 if (in_handle_ != kInvalidInHandle) { | |
371 // Following API call may fail because device was already disconnected. | |
372 // But just in case. | |
373 midiInClose(in_handle_); | |
374 manager_->port_manager()->UnregisterInHandle(in_handle_); | |
375 in_handle_ = kInvalidInHandle; | |
376 } | |
377 return Port::Disconnect(); | |
378 } | |
379 | |
380 void Open() override { | |
381 MMRESULT result = midiInOpen( | |
382 &in_handle_, device_id_, | |
383 reinterpret_cast<DWORD_PTR>(&PortManager::HandleMidiInCallback), | |
384 instance_id_, CALLBACK_FUNCTION); | |
385 if (result == MMSYSERR_NOERROR) { | |
386 hdr_ = CreateMIDIHDR(kBufferLength); | |
387 result = midiInPrepareHeader(in_handle_, hdr_.get(), sizeof(*hdr_)); | |
388 } | |
389 if (result != MMSYSERR_NOERROR) | |
390 in_handle_ = kInvalidInHandle; | |
391 if (result == MMSYSERR_NOERROR) | |
392 result = midiInAddBuffer(in_handle_, hdr_.get(), sizeof(*hdr_)); | |
393 if (result == MMSYSERR_NOERROR) | |
394 result = midiInStart(in_handle_); | |
395 if (result == MMSYSERR_NOERROR) { | |
396 start_time_ = base::TimeTicks::Now(); | |
397 manager_->port_manager()->RegisterInHandle(in_handle_, index_); | |
398 Port::Open(); | |
399 } else { | |
400 if (in_handle_ != kInvalidInHandle) { | |
401 midiInUnprepareHeader(in_handle_, hdr_.get(), sizeof(*hdr_)); | |
402 hdr_.reset(); | |
403 midiInClose(in_handle_); | |
404 in_handle_ = kInvalidInHandle; | |
405 } | |
406 Disconnect(); | |
407 } | |
408 } | |
409 | |
410 private: | |
411 DynamicallyInitializedMidiManagerWin* manager_; | |
412 HMIDIIN in_handle_; | |
413 ScopedMIDIHDR hdr_; | |
414 base::TimeTicks start_time_; | |
415 const int instance_id_; | |
416 }; | |
417 | |
418 class DynamicallyInitializedMidiManagerWin::OutPort final : public Port { | |
419 public: | |
420 OutPort(UINT device_id, const MIDIOUTCAPS2W& caps) | |
421 : Port("output", | |
422 device_id, | |
423 caps.wMid, | |
424 caps.wPid, | |
425 caps.vDriverVersion, | |
426 base::WideToUTF8( | |
427 base::string16(caps.szPname, wcslen(caps.szPname))), | |
428 caps.ManufacturerGuid), | |
429 software_(caps.wTechnology == MOD_SWSYNTH), | |
430 out_handle_(kInvalidOutHandle) {} | |
431 | |
432 static std::vector<std::unique_ptr<OutPort>> EnumerateActivePorts() { | |
433 std::vector<std::unique_ptr<OutPort>> ports; | |
434 const UINT num_devices = midiOutGetNumDevs(); | |
435 for (UINT device_id = 0; device_id < num_devices; ++device_id) { | |
436 MIDIOUTCAPS2W caps; | |
437 MMRESULT result = midiOutGetDevCaps( | |
438 device_id, reinterpret_cast<LPMIDIOUTCAPSW>(&caps), sizeof(caps)); | |
439 if (result != MMSYSERR_NOERROR) { | |
440 LOG(ERROR) << "midiOutGetDevCaps fails on device " << device_id; | |
441 continue; | |
442 } | |
443 ports.push_back(base::MakeUnique<OutPort>(device_id, caps)); | |
444 } | |
445 return ports; | |
446 } | |
447 | |
448 void Finalize(scoped_refptr<base::SingleThreadTaskRunner> runner) { | |
449 if (out_handle_ != kInvalidOutHandle) { | |
450 runner->PostTask(FROM_HERE, base::Bind(&FinalizeOutPort, out_handle_)); | |
451 out_handle_ = kInvalidOutHandle; | |
452 } | |
453 } | |
454 | |
455 void NotifyPortStateSet(DynamicallyInitializedMidiManagerWin* manager) { | |
456 manager->PostReplyTask( | |
457 base::Bind(&DynamicallyInitializedMidiManagerWin::SetOutputPortState, | |
458 base::Unretained(manager), index_, info_.state)); | |
459 } | |
460 | |
461 void NotifyPortAdded(DynamicallyInitializedMidiManagerWin* manager) { | |
462 manager->PostReplyTask( | |
463 base::Bind(&DynamicallyInitializedMidiManagerWin::AddOutputPort, | |
464 base::Unretained(manager), info_)); | |
465 } | |
466 | |
467 void Send(const std::vector<uint8_t>& data) { | |
468 if (out_handle_ == kInvalidOutHandle) | |
469 return; | |
470 | |
471 if (data.size() <= 3) { | |
472 uint32_t message = 0; | |
473 for (size_t i = 0; i < data.size(); ++i) | |
474 message |= (static_cast<uint32_t>(data[i]) << (i * 8)); | |
475 midiOutShortMsg(out_handle_, message); | |
476 } else { | |
477 if (data.size() > kSysExSizeLimit) { | |
478 LOG(ERROR) << "Ignoring SysEx message due to the size limit" | |
479 << ", size = " << data.size(); | |
480 // TODO(toyoshim): Consider to report metrics here. | |
481 return; | |
482 } | |
483 ScopedMIDIHDR hdr(CreateMIDIHDR(data)); | |
484 MMRESULT result = | |
485 midiOutPrepareHeader(out_handle_, hdr.get(), sizeof(*hdr)); | |
486 if (result != MMSYSERR_NOERROR) | |
487 return; | |
488 result = midiOutLongMsg(out_handle_, hdr.get(), sizeof(*hdr)); | |
489 if (result != MMSYSERR_NOERROR) { | |
490 midiOutUnprepareHeader(out_handle_, hdr.get(), sizeof(*hdr)); | |
491 } else { | |
492 // MIDIHDR will be released on MOM_DONE. | |
493 ignore_result(hdr.release()); | |
494 } | |
495 } | |
496 } | |
497 | |
498 // Port overrides: | |
499 bool Connect() override { | |
500 // Until |software| option is supported, disable Microsoft GS Wavetable | |
501 // Synth that has a known security issue. | |
502 if (software_ && manufacturer_id_ == MM_MICROSOFT && | |
503 (product_id_ == MM_MSFT_WDMAUDIO_MIDIOUT || | |
504 product_id_ == MM_MSFT_GENERIC_MIDISYNTH)) { | |
505 return false; | |
506 } | |
507 return Port::Connect(); | |
508 } | |
509 | |
510 bool Disconnect() override { | |
511 if (out_handle_ != kInvalidOutHandle) { | |
512 // Following API call may fail because device was already disconnected. | |
513 // But just in case. | |
514 midiOutClose(out_handle_); | |
515 out_handle_ = kInvalidOutHandle; | |
516 } | |
517 return Port::Disconnect(); | |
518 } | |
519 | |
520 void Open() override { | |
521 MMRESULT result = midiOutOpen( | |
522 &out_handle_, device_id_, | |
523 reinterpret_cast<DWORD_PTR>(&PortManager::HandleMidiOutCallback), 0, | |
524 CALLBACK_FUNCTION); | |
525 if (result == MMSYSERR_NOERROR) { | |
526 Port::Open(); | |
527 } else { | |
528 out_handle_ = kInvalidOutHandle; | |
529 Disconnect(); | |
530 } | |
531 } | |
532 | |
533 const bool software_; | |
534 HMIDIOUT out_handle_; | |
535 }; | |
536 | |
537 base::TimeTicks | |
538 DynamicallyInitializedMidiManagerWin::PortManager::CalculateInEventTime( | |
539 size_t index, | |
540 uint32_t elapsed_ms) const { | |
541 GetTaskLock()->AssertAcquired(); | |
542 CHECK_GT(input_ports_.size(), index); | |
543 return input_ports_[index]->CalculateInEventTime(elapsed_ms); | |
544 } | |
545 | |
546 void DynamicallyInitializedMidiManagerWin::PortManager::RegisterInHandle( | |
547 HMIDIIN handle, | |
548 size_t index) { | |
549 GetTaskLock()->AssertAcquired(); | |
550 hmidiin_to_index_map_[handle] = index; | |
551 } | |
552 | |
553 void DynamicallyInitializedMidiManagerWin::PortManager::UnregisterInHandle( | |
554 HMIDIIN handle) { | |
555 GetTaskLock()->AssertAcquired(); | |
556 hmidiin_to_index_map_.erase(handle); | |
557 } | |
558 | |
559 bool DynamicallyInitializedMidiManagerWin::PortManager::FindInHandle( | |
560 HMIDIIN hmi, | |
561 size_t* out_index) { | |
562 GetTaskLock()->AssertAcquired(); | |
563 auto found = hmidiin_to_index_map_.find(hmi); | |
564 if (found == hmidiin_to_index_map_.end()) | |
565 return false; | |
566 *out_index = found->second; | |
567 return true; | |
568 } | |
569 | |
570 void DynamicallyInitializedMidiManagerWin::PortManager::RestoreInBuffer( | |
571 size_t index) { | |
572 GetTaskLock()->AssertAcquired(); | |
573 CHECK_GT(input_ports_.size(), index); | |
574 input_ports_[index]->RestoreBuffer(); | |
575 } | |
576 | |
577 void CALLBACK | |
578 DynamicallyInitializedMidiManagerWin::PortManager::HandleMidiInCallback( | |
579 HMIDIIN hmi, | |
580 UINT msg, | |
581 DWORD_PTR instance, | |
582 DWORD_PTR param1, | |
583 DWORD_PTR param2) { | |
584 if (msg != MIM_DATA && msg != MIM_LONGDATA) | |
585 return; | |
586 int instance_id = static_cast<int>(instance); | |
587 DynamicallyInitializedMidiManagerWin* manager = nullptr; | |
588 | |
589 // Use |g_task_lock| so to ensure the instance can keep alive while running, | |
590 // and to access member variables that are used on TaskRunner. | |
591 base::AutoLock task_lock(*GetTaskLock()); | |
592 { | |
593 base::AutoLock lock(*GetInstanceIdLock()); | |
594 if (instance_id != g_active_instance_id) | |
595 return; | |
596 manager = g_manager_instance; | |
597 } | |
598 | |
599 size_t index; | |
600 if (!manager->port_manager()->FindInHandle(hmi, &index)) | |
601 return; | |
602 | |
603 DCHECK(msg == MIM_DATA || msg == MIM_LONGDATA); | |
604 if (msg == MIM_DATA) { | |
605 const uint8_t status_byte = static_cast<uint8_t>(param1 & 0xff); | |
606 const uint8_t first_data_byte = static_cast<uint8_t>((param1 >> 8) & 0xff); | |
607 const uint8_t second_data_byte = | |
608 static_cast<uint8_t>((param1 >> 16) & 0xff); | |
609 const uint8_t kData[] = {status_byte, first_data_byte, second_data_byte}; | |
610 const size_t len = GetMessageLength(status_byte); | |
611 DCHECK_LE(len, arraysize(kData)); | |
612 std::vector<uint8_t> data; | |
613 data.assign(kData, kData + len); | |
614 manager->PostReplyTask(base::Bind( | |
615 &DynamicallyInitializedMidiManagerWin::ReceiveMidiData, | |
616 base::Unretained(manager), index, data, | |
617 manager->port_manager()->CalculateInEventTime(index, param2))); | |
618 } else { | |
619 DCHECK_EQ(static_cast<UINT>(MIM_LONGDATA), msg); | |
620 LPMIDIHDR hdr = reinterpret_cast<LPMIDIHDR>(param1); | |
621 if (hdr->dwBytesRecorded > 0) { | |
622 const uint8_t* src = reinterpret_cast<const uint8_t*>(hdr->lpData); | |
623 std::vector<uint8_t> data; | |
624 data.assign(src, src + hdr->dwBytesRecorded); | |
625 manager->PostReplyTask(base::Bind( | |
626 &DynamicallyInitializedMidiManagerWin::ReceiveMidiData, | |
627 base::Unretained(manager), index, data, | |
628 manager->port_manager()->CalculateInEventTime(index, param2))); | |
629 } | |
630 manager->PostTask(base::Bind( | |
631 &DynamicallyInitializedMidiManagerWin::PortManager::RestoreInBuffer, | |
632 base::Unretained(manager->port_manager()), index)); | |
633 } | |
634 } | |
635 | |
636 void CALLBACK | |
637 DynamicallyInitializedMidiManagerWin::PortManager::HandleMidiOutCallback( | |
638 HMIDIOUT hmo, | |
639 UINT msg, | |
640 DWORD_PTR instance, | |
641 DWORD_PTR param1, | |
642 DWORD_PTR param2) { | |
643 if (msg == MOM_DONE) { | |
644 ScopedMIDIHDR hdr(reinterpret_cast<LPMIDIHDR>(param1)); | |
645 if (!hdr) | |
646 return; | |
647 // TODO(toyoshim): Call midiOutUnprepareHeader outside the callback. | |
648 // Since this callback may be invoked after the manager is destructed, | |
649 // and can not send a task to the TaskRunner in such case, we need to | |
650 // consider to track MIDIHDR per port, and clean it in port finalization | |
651 // steps, too. | |
652 midiOutUnprepareHeader(hmo, hdr.get(), sizeof(*hdr)); | |
653 } | |
654 } | |
655 | |
656 DynamicallyInitializedMidiManagerWin::DynamicallyInitializedMidiManagerWin( | |
657 MidiService* service) | |
658 : MidiManager(service), | |
659 instance_id_(IssueNextInstanceId()), | |
660 port_manager_(base::MakeUnique<PortManager>()) { | |
661 base::AutoLock lock(*GetInstanceIdLock()); | |
662 CHECK_EQ(kInvalidInstanceId, g_active_instance_id); | |
663 | |
664 // Obtains the task runner for the current thread that hosts this instnace. | |
665 thread_runner_ = base::ThreadTaskRunnerHandle::Get(); | |
666 } | |
667 | |
668 DynamicallyInitializedMidiManagerWin::~DynamicallyInitializedMidiManagerWin() { | |
669 base::AutoLock lock(*GetInstanceIdLock()); | |
670 CHECK_EQ(kInvalidInstanceId, g_active_instance_id); | |
671 CHECK(thread_runner_->BelongsToCurrentThread()); | |
672 } | |
673 | |
674 void DynamicallyInitializedMidiManagerWin::StartInitialization() { | |
675 { | |
676 base::AutoLock lock(*GetInstanceIdLock()); | |
677 CHECK_EQ(kInvalidInstanceId, g_active_instance_id); | |
678 g_active_instance_id = instance_id_; | |
679 CHECK_EQ(nullptr, g_manager_instance); | |
680 g_manager_instance = this; | |
681 } | |
682 // Registers on the I/O thread to be notified on the I/O thread. | |
683 CHECK(thread_runner_->BelongsToCurrentThread()); | |
684 base::SystemMonitor::Get()->AddDevicesChangedObserver(this); | |
685 | |
686 // Starts asynchronous initialization on TaskRunner. | |
687 PostTask( | |
688 base::Bind(&DynamicallyInitializedMidiManagerWin::InitializeOnTaskRunner, | |
689 base::Unretained(this))); | |
690 } | |
691 | |
692 void DynamicallyInitializedMidiManagerWin::Finalize() { | |
693 // Unregisters on the I/O thread. OnDevicesChanged() won't be called any more. | |
694 CHECK(thread_runner_->BelongsToCurrentThread()); | |
695 base::SystemMonitor::Get()->RemoveDevicesChangedObserver(this); | |
696 { | |
697 base::AutoLock lock(*GetInstanceIdLock()); | |
698 CHECK_EQ(instance_id_, g_active_instance_id); | |
699 g_active_instance_id = kInvalidInstanceId; | |
700 CHECK_EQ(this, g_manager_instance); | |
701 g_manager_instance = nullptr; | |
702 } | |
703 | |
704 // Ensures that no task runs on TaskRunner so to destruct the instance safely. | |
705 // Tasks that did not started yet will do nothing after invalidate the | |
706 // instance ID above. | |
707 // Behind the lock below, we can safely access all members for finalization | |
708 // even on the I/O thread. | |
709 base::AutoLock lock(*GetTaskLock()); | |
710 | |
711 // Posts tasks that finalize each device port without MidiManager instance | |
712 // on TaskRunner. If another MidiManager instance is created, its | |
713 // initialization runs on the same task runner after all tasks posted here | |
714 // finish. | |
715 for (const auto& port : *port_manager_->inputs()) | |
716 port->Finalize(service()->GetTaskRunner(kTaskRunner)); | |
717 for (const auto& port : *port_manager_->outputs()) | |
718 port->Finalize(service()->GetTaskRunner(kTaskRunner)); | |
719 } | |
720 | |
721 void DynamicallyInitializedMidiManagerWin::DispatchSendMidiData( | |
722 MidiManagerClient* client, | |
723 uint32_t port_index, | |
724 const std::vector<uint8_t>& data, | |
725 double timestamp) { | |
726 if (timestamp != 0.0) { | |
727 base::TimeTicks time = base::TimeTicks() + | |
728 base::TimeDelta::FromMicroseconds( | |
729 timestamp * base::Time::kMicrosecondsPerSecond); | |
730 base::TimeTicks now = base::TimeTicks::Now(); | |
731 if (now < time) { | |
732 PostDelayedTask( | |
733 base::Bind(&DynamicallyInitializedMidiManagerWin::SendOnTaskRunner, | |
734 base::Unretained(this), client, port_index, data), | |
735 time - now); | |
736 return; | |
737 } | |
738 } | |
739 PostTask(base::Bind(&DynamicallyInitializedMidiManagerWin::SendOnTaskRunner, | |
740 base::Unretained(this), client, port_index, data)); | |
741 } | |
742 | |
743 void DynamicallyInitializedMidiManagerWin::OnDevicesChanged( | |
744 base::SystemMonitor::DeviceType device_type) { | |
745 // Notified on the I/O thread. | |
746 CHECK(thread_runner_->BelongsToCurrentThread()); | |
747 | |
748 switch (device_type) { | |
749 case base::SystemMonitor::DEVTYPE_AUDIO: | |
750 case base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE: | |
751 // Add case of other unrelated device types here. | |
752 return; | |
753 case base::SystemMonitor::DEVTYPE_UNKNOWN: { | |
754 PostTask(base::Bind( | |
755 &DynamicallyInitializedMidiManagerWin::UpdateDeviceListOnTaskRunner, | |
756 base::Unretained(this))); | |
757 break; | |
758 } | |
759 } | |
760 } | |
761 | |
762 void DynamicallyInitializedMidiManagerWin::ReceiveMidiData( | |
763 uint32_t index, | |
764 const std::vector<uint8_t>& data, | |
765 base::TimeTicks time) { | |
766 MidiManager::ReceiveMidiData(index, data.data(), data.size(), time); | |
767 } | |
768 | |
769 void DynamicallyInitializedMidiManagerWin::PostTask(const base::Closure& task) { | |
770 service() | |
771 ->GetTaskRunner(kTaskRunner) | |
772 ->PostTask(FROM_HERE, base::Bind(&RunTask, instance_id_, task)); | |
773 } | |
774 | |
775 void DynamicallyInitializedMidiManagerWin::PostDelayedTask( | |
776 const base::Closure& task, | |
777 base::TimeDelta delay) { | |
778 service() | |
779 ->GetTaskRunner(kTaskRunner) | |
780 ->PostDelayedTask(FROM_HERE, base::Bind(&RunTask, instance_id_, task), | |
781 delay); | |
782 } | |
783 | |
784 void DynamicallyInitializedMidiManagerWin::PostReplyTask( | |
785 const base::Closure& task) { | |
786 thread_runner_->PostTask(FROM_HERE, base::Bind(&RunTask, instance_id_, task)); | |
787 } | |
788 | |
789 void DynamicallyInitializedMidiManagerWin::InitializeOnTaskRunner() { | |
790 UpdateDeviceListOnTaskRunner(); | |
791 PostReplyTask( | |
792 base::Bind(&DynamicallyInitializedMidiManagerWin::CompleteInitialization, | |
793 base::Unretained(this), mojom::Result::OK)); | |
794 } | |
795 | |
796 void DynamicallyInitializedMidiManagerWin::UpdateDeviceListOnTaskRunner() { | |
797 std::vector<std::unique_ptr<InPort>> active_input_ports = | |
798 InPort::EnumerateActivePorts(this, instance_id_); | |
799 ReflectActiveDeviceList(this, port_manager_->inputs(), &active_input_ports); | |
800 | |
801 std::vector<std::unique_ptr<OutPort>> active_output_ports = | |
802 OutPort::EnumerateActivePorts(); | |
803 ReflectActiveDeviceList(this, port_manager_->outputs(), &active_output_ports); | |
804 | |
805 // TODO(toyoshim): This method may run before internal MIDI device lists that | |
806 // Windows manages were updated. This may be because MIDI driver may be loaded | |
807 // after the raw device list was updated. To avoid this problem, we may want | |
808 // to retry device check later if no changes are detected here. | |
809 } | |
810 | |
811 template <typename T> | |
812 void DynamicallyInitializedMidiManagerWin::ReflectActiveDeviceList( | |
813 DynamicallyInitializedMidiManagerWin* manager, | |
814 std::vector<T>* known_ports, | |
815 std::vector<T>* active_ports) { | |
816 // Update existing port states. | |
817 for (const auto& port : *known_ports) { | |
818 const auto& it = std::find_if( | |
819 active_ports->begin(), active_ports->end(), | |
820 [&port](const auto& candidate) { return *candidate == *port; }); | |
821 if (it == active_ports->end()) { | |
822 if (port->Disconnect()) | |
823 port->NotifyPortStateSet(this); | |
824 } else { | |
825 port->set_device_id((*it)->device_id()); | |
826 if (port->Connect()) | |
827 port->NotifyPortStateSet(this); | |
828 } | |
829 } | |
830 | |
831 // Find new ports from active ports and append them to known ports. | |
832 for (auto& port : *active_ports) { | |
833 if (std::find_if(known_ports->begin(), known_ports->end(), | |
834 [&port](const auto& candidate) { | |
835 return *candidate == *port; | |
836 }) == known_ports->end()) { | |
837 size_t index = known_ports->size(); | |
838 port->set_index(index); | |
839 known_ports->push_back(std::move(port)); | |
840 (*known_ports)[index]->Connect(); | |
841 (*known_ports)[index]->NotifyPortAdded(this); | |
842 } | |
843 } | |
844 } | |
845 | |
846 void DynamicallyInitializedMidiManagerWin::SendOnTaskRunner( | |
847 MidiManagerClient* client, | |
848 uint32_t port_index, | |
849 const std::vector<uint8_t>& data) { | |
850 CHECK_GT(port_manager_->outputs()->size(), port_index); | |
851 (*port_manager_->outputs())[port_index]->Send(data); | |
852 // |client| will be checked inside MidiManager::AccumulateMidiBytesSent. | |
853 PostReplyTask( | |
854 base::Bind(&DynamicallyInitializedMidiManagerWin::AccumulateMidiBytesSent, | |
855 base::Unretained(this), client, data.size())); | |
856 } | |
857 | |
858 MidiManager* MidiManager::Create(MidiService* service) { | |
859 if (base::FeatureList::IsEnabled(features::kMidiManagerWinrt) && | |
860 base::win::GetVersion() >= base::win::VERSION_WIN10) | |
861 return new MidiManagerWinrt(service); | |
862 return new DynamicallyInitializedMidiManagerWin(service); | |
863 } | |
864 | |
865 } // namespace midi | |
OLD | NEW |