Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(276)

Side by Side Diff: media/midi/dynamically_initialized_midi_manager_win.cc

Issue 2834423002: Web MIDI: rename DynamicallyInitializedMidiManagerWin (Closed)
Patch Set: Created 3 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « media/midi/dynamically_initialized_midi_manager_win.h ('k') | media/midi/midi_manager_win.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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
OLDNEW
« no previous file with comments | « media/midi/dynamically_initialized_midi_manager_win.h ('k') | media/midi/midi_manager_win.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698