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

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

Issue 2841493003: Web MIDI: remove old Windows backend (Closed)
Patch Set: owners update Created 3 years, 8 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
« media/midi/OWNERS ('K') | « media/midi/midi_manager_win.h ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
OLDNEW
« media/midi/OWNERS ('K') | « media/midi/midi_manager_win.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698