blob: fcfc4e48831bb9a32a380ef394f485ae0fc6b754 [file] [log] [blame]
toyoshimd28b59c2017-02-20 11:07:37 -08001// 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
toyoshim63e32a52017-04-25 07:20:10 -07005#include "media/midi/midi_manager_win.h"
toyoshimd28b59c2017-02-20 11:07:37 -08006
7#include <windows.h>
8
toyoshim6ebf1182017-03-01 00:40:31 -08009#include <ks.h>
10#include <ksmedia.h>
toyoshimd28b59c2017-02-20 11:07:37 -080011#include <mmreg.h>
12#include <mmsystem.h>
13
14#include <algorithm>
15#include <string>
16
toyoshim8a5ad422017-02-28 21:16:18 -080017#include "base/bind_helpers.h"
toyoshimd28b59c2017-02-20 11:07:37 -080018#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"
toyoshim15385b32017-04-24 23:52:01 -070025#include "base/win/windows_version.h"
toyoshim6ebf1182017-03-01 00:40:31 -080026#include "device/usb/usb_ids.h"
toyoshim8a5ad422017-02-28 21:16:18 -080027#include "media/midi/message_util.h"
toyoshim15385b32017-04-24 23:52:01 -070028#include "media/midi/midi_manager_winrt.h"
toyoshimd28b59c2017-02-20 11:07:37 -080029#include "media/midi/midi_port_info.h"
30#include "media/midi/midi_service.h"
toyoshim15385b32017-04-24 23:52:01 -070031#include "media/midi/midi_switches.h"
toyoshimd28b59c2017-02-20 11:07:37 -080032
33namespace midi {
34
toyoshim8a5ad422017-02-28 21:16:18 -080035// Forward declaration of PortManager for anonymous functions and internal
36// classes to use it.
toyoshim63e32a52017-04-25 07:20:10 -070037class MidiManagerWin::PortManager {
toyoshim8a5ad422017-02-28 21:16:18 -080038 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.
toyoshim6d87aaa2017-02-28 22:36:44 -080049 bool FindInHandle(HMIDIIN hmi, size_t* out_index);
toyoshim8a5ad422017-02-28 21:16:18 -080050
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
toyoshim6d87aaa2017-02-28 22:36:44 -080065 // 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
toyoshim8a5ad422017-02-28 21:16:18 -080072 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
toyoshimd28b59c2017-02-20 11:07:37 -080081namespace {
82
toyoshimc32dd892017-02-24 02:13:14 -080083// Assumes that nullptr represents an invalid MIDI handle.
84constexpr HMIDIIN kInvalidInHandle = nullptr;
85constexpr HMIDIOUT kInvalidOutHandle = nullptr;
86
toyoshim6d87aaa2017-02-28 22:36:44 -080087// 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.
99constexpr size_t kSysExSizeLimit = 256 * 1024;
100
toyoshim8a5ad422017-02-28 21:16:18 -0800101// Defines input buffer size.
102constexpr size_t kBufferLength = 32 * 1024;
103
toyoshimd28b59c2017-02-20 11:07:37 -0800104// Global variables to identify MidiManager instance.
105constexpr int kInvalidInstanceId = -1;
106int g_active_instance_id = kInvalidInstanceId;
toyoshim63e32a52017-04-25 07:20:10 -0700107MidiManagerWin* g_manager_instance = nullptr;
toyoshimd28b59c2017-02-20 11:07:37 -0800108
109// Obtains base::Lock instance pointer to lock instance_id.
110base::Lock* GetInstanceIdLock() {
111 static base::Lock* lock = new base::Lock;
112 return lock;
113}
114
115// Issues unique MidiManager instance ID.
116int IssueNextInstanceId() {
117 static int id = kInvalidInstanceId;
118 return ++id;
119}
120
121// Use single TaskRunner for all tasks running outside the I/O thread.
122constexpr int kTaskRunner = 0;
123
124// Obtains base::Lock instance pointer to ensure tasks run safely on TaskRunner.
toyoshimc32dd892017-02-24 02:13:14 -0800125// 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.
toyoshimd28b59c2017-02-20 11:07:37 -0800128base::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.
134void 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
toyoshim8a5ad422017-02-28 21:16:18 -0800150// Utility class to handle MIDIHDR struct safely.
151class 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
161using ScopedMIDIHDR = std::unique_ptr<MIDIHDR, MIDIHDRDeleter>;
162
163ScopedMIDIHDR 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
toyoshim6d87aaa2017-02-28 22:36:44 -0800171ScopedMIDIHDR 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
toyoshimc32dd892017-02-24 02:13:14 -0800177// Helper functions to close MIDI device handles on TaskRunner asynchronously.
toyoshim8a5ad422017-02-28 21:16:18 -0800178void 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));
toyoshimc32dd892017-02-24 02:13:14 -0800186 midiInClose(handle);
187}
188
189void FinalizeOutPort(HMIDIOUT handle) {
toyoshim6d87aaa2017-02-28 22:36:44 -0800190 // Resets inflight buffers. This will cancel sending data that system
191 // holds and were not sent yet.
192 midiOutReset(handle);
toyoshimc32dd892017-02-24 02:13:14 -0800193 midiOutClose(handle);
194}
195
toyoshim6ebf1182017-03-01 00:40:31 -0800196// Gets manufacturer name in string from identifiers.
197std::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
toyoshimc32dd892017-02-24 02:13:14 -0800211// 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.
toyoshimd28b59c2017-02-20 11:07:37 -0800214class 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,
toyoshim6ebf1182017-03-01 00:40:31 -0800221 const std::string& product_name,
222 const GUID& manufacturer_guid)
toyoshimd28b59c2017-02-20 11:07:37 -0800223 : 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) {
toyoshim6ebf1182017-03-01 00:40:31 -0800230 info_.manufacturer =
231 GetManufacturerName(manufacturer_id, manufacturer_guid);
toyoshimd28b59c2017-02-20 11:07:37 -0800232 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
toyoshim63e32a52017-04-25 07:20:10 -0700299class MidiManagerWin::InPort final : public Port {
toyoshimd28b59c2017-02-20 11:07:37 -0800300 public:
toyoshim63e32a52017-04-25 07:20:10 -0700301 InPort(MidiManagerWin* manager,
toyoshim8a5ad422017-02-28 21:16:18 -0800302 int instance_id,
303 UINT device_id,
304 const MIDIINCAPS2W& caps)
toyoshimd28b59c2017-02-20 11:07:37 -0800305 : Port("input",
306 device_id,
307 caps.wMid,
308 caps.wPid,
309 caps.vDriverVersion,
310 base::WideToUTF8(
toyoshim6ebf1182017-03-01 00:40:31 -0800311 base::string16(caps.szPname, wcslen(caps.szPname))),
312 caps.ManufacturerGuid),
toyoshim8a5ad422017-02-28 21:16:18 -0800313 manager_(manager),
314 in_handle_(kInvalidInHandle),
315 instance_id_(instance_id) {}
toyoshimd28b59c2017-02-20 11:07:37 -0800316
toyoshim8a5ad422017-02-28 21:16:18 -0800317 static std::vector<std::unique_ptr<InPort>> EnumerateActivePorts(
toyoshim63e32a52017-04-25 07:20:10 -0700318 MidiManagerWin* manager,
toyoshim8a5ad422017-02-28 21:16:18 -0800319 int instance_id) {
toyoshimd28b59c2017-02-20 11:07:37 -0800320 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 }
toyoshim8a5ad422017-02-28 21:16:18 -0800330 ports.push_back(
331 base::MakeUnique<InPort>(manager, instance_id, device_id, caps));
toyoshimd28b59c2017-02-20 11:07:37 -0800332 }
333 return ports;
334 }
335
toyoshimc32dd892017-02-24 02:13:14 -0800336 void Finalize(scoped_refptr<base::SingleThreadTaskRunner> runner) {
337 if (in_handle_ != kInvalidInHandle) {
toyoshim63e32a52017-04-25 07:20:10 -0700338 runner->PostTask(FROM_HERE, base::Bind(&FinalizeInPort, in_handle_,
339 base::Passed(&hdr_)));
toyoshim8a5ad422017-02-28 21:16:18 -0800340 manager_->port_manager()->UnregisterInHandle(in_handle_);
toyoshimc32dd892017-02-24 02:13:14 -0800341 in_handle_ = kInvalidInHandle;
342 }
343 }
344
toyoshim8a5ad422017-02-28 21:16:18 -0800345 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
toyoshim63e32a52017-04-25 07:20:10 -0700355 void NotifyPortStateSet(MidiManagerWin* manager) {
356 manager->PostReplyTask(base::Bind(&MidiManagerWin::SetInputPortState,
357 base::Unretained(manager), index_,
358 info_.state));
toyoshimd28b59c2017-02-20 11:07:37 -0800359 }
360
toyoshim63e32a52017-04-25 07:20:10 -0700361 void NotifyPortAdded(MidiManagerWin* manager) {
362 manager->PostReplyTask(base::Bind(&MidiManagerWin::AddInputPort,
363 base::Unretained(manager), info_));
toyoshimd28b59c2017-02-20 11:07:37 -0800364 }
toyoshimc32dd892017-02-24 02:13:14 -0800365
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_);
toyoshim8a5ad422017-02-28 21:16:18 -0800372 manager_->port_manager()->UnregisterInHandle(in_handle_);
toyoshimc32dd892017-02-24 02:13:14 -0800373 in_handle_ = kInvalidInHandle;
374 }
375 return Port::Disconnect();
376 }
377
378 void Open() override {
toyoshim8a5ad422017-02-28 21:16:18 -0800379 MMRESULT result = midiInOpen(
380 &in_handle_, device_id_,
381 reinterpret_cast<DWORD_PTR>(&PortManager::HandleMidiInCallback),
382 instance_id_, CALLBACK_FUNCTION);
toyoshimc32dd892017-02-24 02:13:14 -0800383 if (result == MMSYSERR_NOERROR) {
toyoshim8a5ad422017-02-28 21:16:18 -0800384 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_);
toyoshimc32dd892017-02-24 02:13:14 -0800396 Port::Open();
397 } else {
toyoshim8a5ad422017-02-28 21:16:18 -0800398 if (in_handle_ != kInvalidInHandle) {
399 midiInUnprepareHeader(in_handle_, hdr_.get(), sizeof(*hdr_));
400 hdr_.reset();
401 midiInClose(in_handle_);
402 in_handle_ = kInvalidInHandle;
403 }
toyoshimc32dd892017-02-24 02:13:14 -0800404 Disconnect();
405 }
406 }
407
408 private:
toyoshim63e32a52017-04-25 07:20:10 -0700409 MidiManagerWin* manager_;
toyoshimc32dd892017-02-24 02:13:14 -0800410 HMIDIIN in_handle_;
toyoshim8a5ad422017-02-28 21:16:18 -0800411 ScopedMIDIHDR hdr_;
412 base::TimeTicks start_time_;
413 const int instance_id_;
toyoshimd28b59c2017-02-20 11:07:37 -0800414};
415
toyoshim63e32a52017-04-25 07:20:10 -0700416class MidiManagerWin::OutPort final : public Port {
toyoshimd28b59c2017-02-20 11:07:37 -0800417 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(
toyoshim6ebf1182017-03-01 00:40:31 -0800425 base::string16(caps.szPname, wcslen(caps.szPname))),
426 caps.ManufacturerGuid),
toyoshimc32dd892017-02-24 02:13:14 -0800427 software_(caps.wTechnology == MOD_SWSYNTH),
428 out_handle_(kInvalidOutHandle) {}
toyoshimd28b59c2017-02-20 11:07:37 -0800429
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
toyoshimc32dd892017-02-24 02:13:14 -0800446 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;
toyoshimd28b59c2017-02-20 11:07:37 -0800450 }
toyoshimd28b59c2017-02-20 11:07:37 -0800451 }
452
toyoshim63e32a52017-04-25 07:20:10 -0700453 void NotifyPortStateSet(MidiManagerWin* manager) {
454 manager->PostReplyTask(base::Bind(&MidiManagerWin::SetOutputPortState,
455 base::Unretained(manager), index_,
456 info_.state));
toyoshimd28b59c2017-02-20 11:07:37 -0800457 }
458
toyoshim63e32a52017-04-25 07:20:10 -0700459 void NotifyPortAdded(MidiManagerWin* manager) {
460 manager->PostReplyTask(base::Bind(&MidiManagerWin::AddOutputPort,
461 base::Unretained(manager), info_));
toyoshimd28b59c2017-02-20 11:07:37 -0800462 }
463
toyoshim6d87aaa2017-02-28 22:36:44 -0800464 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
toyoshimc32dd892017-02-24 02:13:14 -0800495 // 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 {
toyoshim6d87aaa2017-02-28 22:36:44 -0800518 MMRESULT result = midiOutOpen(
519 &out_handle_, device_id_,
520 reinterpret_cast<DWORD_PTR>(&PortManager::HandleMidiOutCallback), 0,
521 CALLBACK_FUNCTION);
toyoshimc32dd892017-02-24 02:13:14 -0800522 if (result == MMSYSERR_NOERROR) {
523 Port::Open();
524 } else {
525 out_handle_ = kInvalidOutHandle;
526 Disconnect();
527 }
528 }
529
toyoshimd28b59c2017-02-20 11:07:37 -0800530 const bool software_;
toyoshimc32dd892017-02-24 02:13:14 -0800531 HMIDIOUT out_handle_;
toyoshimd28b59c2017-02-20 11:07:37 -0800532};
533
toyoshim63e32a52017-04-25 07:20:10 -0700534base::TimeTicks MidiManagerWin::PortManager::CalculateInEventTime(
toyoshim8a5ad422017-02-28 21:16:18 -0800535 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
toyoshim63e32a52017-04-25 07:20:10 -0700542void MidiManagerWin::PortManager::RegisterInHandle(HMIDIIN handle,
543 size_t index) {
toyoshim8a5ad422017-02-28 21:16:18 -0800544 GetTaskLock()->AssertAcquired();
545 hmidiin_to_index_map_[handle] = index;
546}
547
toyoshim63e32a52017-04-25 07:20:10 -0700548void MidiManagerWin::PortManager::UnregisterInHandle(HMIDIIN handle) {
toyoshim8a5ad422017-02-28 21:16:18 -0800549 GetTaskLock()->AssertAcquired();
550 hmidiin_to_index_map_.erase(handle);
551}
552
toyoshim63e32a52017-04-25 07:20:10 -0700553bool MidiManagerWin::PortManager::FindInHandle(HMIDIIN hmi, size_t* out_index) {
toyoshim8a5ad422017-02-28 21:16:18 -0800554 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
toyoshim63e32a52017-04-25 07:20:10 -0700562void MidiManagerWin::PortManager::RestoreInBuffer(size_t index) {
toyoshim8a5ad422017-02-28 21:16:18 -0800563 GetTaskLock()->AssertAcquired();
564 CHECK_GT(input_ports_.size(), index);
565 input_ports_[index]->RestoreBuffer();
566}
567
568void CALLBACK
toyoshim63e32a52017-04-25 07:20:10 -0700569MidiManagerWin::PortManager::HandleMidiInCallback(HMIDIIN hmi,
570 UINT msg,
571 DWORD_PTR instance,
572 DWORD_PTR param1,
573 DWORD_PTR param2) {
toyoshim8a5ad422017-02-28 21:16:18 -0800574 if (msg != MIM_DATA && msg != MIM_LONGDATA)
575 return;
576 int instance_id = static_cast<int>(instance);
toyoshim63e32a52017-04-25 07:20:10 -0700577 MidiManagerWin* manager = nullptr;
toyoshim8a5ad422017-02-28 21:16:18 -0800578
579 // Use |g_task_lock| so to ensure the instance can keep alive while running,
580 // and to access member variables that are used on TaskRunner.
581 base::AutoLock task_lock(*GetTaskLock());
582 {
583 base::AutoLock lock(*GetInstanceIdLock());
584 if (instance_id != g_active_instance_id)
585 return;
586 manager = g_manager_instance;
587 }
588
589 size_t index;
toyoshim6d87aaa2017-02-28 22:36:44 -0800590 if (!manager->port_manager()->FindInHandle(hmi, &index))
toyoshim8a5ad422017-02-28 21:16:18 -0800591 return;
592
593 DCHECK(msg == MIM_DATA || msg == MIM_LONGDATA);
594 if (msg == MIM_DATA) {
595 const uint8_t status_byte = static_cast<uint8_t>(param1 & 0xff);
596 const uint8_t first_data_byte = static_cast<uint8_t>((param1 >> 8) & 0xff);
597 const uint8_t second_data_byte =
598 static_cast<uint8_t>((param1 >> 16) & 0xff);
599 const uint8_t kData[] = {status_byte, first_data_byte, second_data_byte};
600 const size_t len = GetMessageLength(status_byte);
601 DCHECK_LE(len, arraysize(kData));
602 std::vector<uint8_t> data;
603 data.assign(kData, kData + len);
604 manager->PostReplyTask(base::Bind(
toyoshim63e32a52017-04-25 07:20:10 -0700605 &MidiManagerWin::ReceiveMidiData, base::Unretained(manager), index,
606 data, manager->port_manager()->CalculateInEventTime(index, param2)));
toyoshim8a5ad422017-02-28 21:16:18 -0800607 } else {
608 DCHECK_EQ(static_cast<UINT>(MIM_LONGDATA), msg);
609 LPMIDIHDR hdr = reinterpret_cast<LPMIDIHDR>(param1);
610 if (hdr->dwBytesRecorded > 0) {
611 const uint8_t* src = reinterpret_cast<const uint8_t*>(hdr->lpData);
612 std::vector<uint8_t> data;
613 data.assign(src, src + hdr->dwBytesRecorded);
614 manager->PostReplyTask(base::Bind(
toyoshim63e32a52017-04-25 07:20:10 -0700615 &MidiManagerWin::ReceiveMidiData, base::Unretained(manager), index,
616 data, manager->port_manager()->CalculateInEventTime(index, param2)));
toyoshim8a5ad422017-02-28 21:16:18 -0800617 }
toyoshim63e32a52017-04-25 07:20:10 -0700618 manager->PostTask(base::Bind(&MidiManagerWin::PortManager::RestoreInBuffer,
619 base::Unretained(manager->port_manager()),
620 index));
toyoshim8a5ad422017-02-28 21:16:18 -0800621 }
622}
623
toyoshim6d87aaa2017-02-28 22:36:44 -0800624void CALLBACK
toyoshim63e32a52017-04-25 07:20:10 -0700625MidiManagerWin::PortManager::HandleMidiOutCallback(HMIDIOUT hmo,
626 UINT msg,
627 DWORD_PTR instance,
628 DWORD_PTR param1,
629 DWORD_PTR param2) {
toyoshim6d87aaa2017-02-28 22:36:44 -0800630 if (msg == MOM_DONE) {
631 ScopedMIDIHDR hdr(reinterpret_cast<LPMIDIHDR>(param1));
632 if (!hdr)
633 return;
634 // TODO(toyoshim): Call midiOutUnprepareHeader outside the callback.
635 // Since this callback may be invoked after the manager is destructed,
636 // and can not send a task to the TaskRunner in such case, we need to
637 // consider to track MIDIHDR per port, and clean it in port finalization
638 // steps, too.
639 midiOutUnprepareHeader(hmo, hdr.get(), sizeof(*hdr));
640 }
641}
642
toyoshim63e32a52017-04-25 07:20:10 -0700643MidiManagerWin::MidiManagerWin(MidiService* service)
toyoshim8a5ad422017-02-28 21:16:18 -0800644 : MidiManager(service),
645 instance_id_(IssueNextInstanceId()),
646 port_manager_(base::MakeUnique<PortManager>()) {
toyoshimd28b59c2017-02-20 11:07:37 -0800647 base::AutoLock lock(*GetInstanceIdLock());
648 CHECK_EQ(kInvalidInstanceId, g_active_instance_id);
649
650 // Obtains the task runner for the current thread that hosts this instnace.
651 thread_runner_ = base::ThreadTaskRunnerHandle::Get();
652}
653
toyoshim63e32a52017-04-25 07:20:10 -0700654MidiManagerWin::~MidiManagerWin() {
toyoshimd28b59c2017-02-20 11:07:37 -0800655 base::AutoLock lock(*GetInstanceIdLock());
656 CHECK_EQ(kInvalidInstanceId, g_active_instance_id);
657 CHECK(thread_runner_->BelongsToCurrentThread());
658}
659
toyoshim63e32a52017-04-25 07:20:10 -0700660void MidiManagerWin::StartInitialization() {
toyoshimd28b59c2017-02-20 11:07:37 -0800661 {
662 base::AutoLock lock(*GetInstanceIdLock());
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.
toyoshim63e32a52017-04-25 07:20:10 -0700673 PostTask(base::Bind(&MidiManagerWin::InitializeOnTaskRunner,
674 base::Unretained(this)));
toyoshimd28b59c2017-02-20 11:07:37 -0800675}
676
toyoshim63e32a52017-04-25 07:20:10 -0700677void MidiManagerWin::Finalize() {
toyoshimd28b59c2017-02-20 11:07:37 -0800678 // 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.
toyoshimc32dd892017-02-24 02:13:14 -0800692 // Behind the lock below, we can safely access all members for finalization
693 // even on the I/O thread.
toyoshimd28b59c2017-02-20 11:07:37 -0800694 base::AutoLock lock(*GetTaskLock());
toyoshimc32dd892017-02-24 02:13:14 -0800695
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.
toyoshim8a5ad422017-02-28 21:16:18 -0800700 for (const auto& port : *port_manager_->inputs())
toyoshimc32dd892017-02-24 02:13:14 -0800701 port->Finalize(service()->GetTaskRunner(kTaskRunner));
toyoshim8a5ad422017-02-28 21:16:18 -0800702 for (const auto& port : *port_manager_->outputs())
toyoshimc32dd892017-02-24 02:13:14 -0800703 port->Finalize(service()->GetTaskRunner(kTaskRunner));
toyoshimd28b59c2017-02-20 11:07:37 -0800704}
705
toyoshim63e32a52017-04-25 07:20:10 -0700706void MidiManagerWin::DispatchSendMidiData(MidiManagerClient* client,
707 uint32_t port_index,
708 const std::vector<uint8_t>& data,
709 double timestamp) {
toyoshim6d87aaa2017-02-28 22:36:44 -0800710 if (timestamp != 0.0) {
toyoshim63e32a52017-04-25 07:20:10 -0700711 base::TimeTicks time =
712 base::TimeTicks() + base::TimeDelta::FromMicroseconds(
713 timestamp * base::Time::kMicrosecondsPerSecond);
toyoshim6d87aaa2017-02-28 22:36:44 -0800714 base::TimeTicks now = base::TimeTicks::Now();
715 if (now < time) {
716 PostDelayedTask(
toyoshim63e32a52017-04-25 07:20:10 -0700717 base::Bind(&MidiManagerWin::SendOnTaskRunner, base::Unretained(this),
718 client, port_index, data),
toyoshim6d87aaa2017-02-28 22:36:44 -0800719 time - now);
720 return;
721 }
722 }
toyoshim63e32a52017-04-25 07:20:10 -0700723 PostTask(base::Bind(&MidiManagerWin::SendOnTaskRunner, base::Unretained(this),
724 client, port_index, data));
toyoshimd28b59c2017-02-20 11:07:37 -0800725}
726
toyoshim63e32a52017-04-25 07:20:10 -0700727void MidiManagerWin::OnDevicesChanged(
toyoshimd28b59c2017-02-20 11:07:37 -0800728 base::SystemMonitor::DeviceType device_type) {
729 // Notified on the I/O thread.
730 CHECK(thread_runner_->BelongsToCurrentThread());
731
732 switch (device_type) {
733 case base::SystemMonitor::DEVTYPE_AUDIO:
734 case base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE:
735 // Add case of other unrelated device types here.
736 return;
737 case base::SystemMonitor::DEVTYPE_UNKNOWN: {
toyoshim63e32a52017-04-25 07:20:10 -0700738 PostTask(base::Bind(&MidiManagerWin::UpdateDeviceListOnTaskRunner,
739 base::Unretained(this)));
toyoshimd28b59c2017-02-20 11:07:37 -0800740 break;
741 }
742 }
743}
744
toyoshim63e32a52017-04-25 07:20:10 -0700745void MidiManagerWin::ReceiveMidiData(uint32_t index,
746 const std::vector<uint8_t>& data,
747 base::TimeTicks time) {
toyoshim8a5ad422017-02-28 21:16:18 -0800748 MidiManager::ReceiveMidiData(index, data.data(), data.size(), time);
749}
750
toyoshim63e32a52017-04-25 07:20:10 -0700751void MidiManagerWin::PostTask(const base::Closure& task) {
toyoshimd28b59c2017-02-20 11:07:37 -0800752 service()
753 ->GetTaskRunner(kTaskRunner)
754 ->PostTask(FROM_HERE, base::Bind(&RunTask, instance_id_, task));
755}
756
toyoshim63e32a52017-04-25 07:20:10 -0700757void MidiManagerWin::PostDelayedTask(const base::Closure& task,
758 base::TimeDelta delay) {
toyoshim6d87aaa2017-02-28 22:36:44 -0800759 service()
760 ->GetTaskRunner(kTaskRunner)
761 ->PostDelayedTask(FROM_HERE, base::Bind(&RunTask, instance_id_, task),
762 delay);
763}
764
toyoshim63e32a52017-04-25 07:20:10 -0700765void MidiManagerWin::PostReplyTask(const base::Closure& task) {
toyoshim8a5ad422017-02-28 21:16:18 -0800766 thread_runner_->PostTask(FROM_HERE, base::Bind(&RunTask, instance_id_, task));
767}
768
toyoshim63e32a52017-04-25 07:20:10 -0700769void MidiManagerWin::InitializeOnTaskRunner() {
toyoshimd28b59c2017-02-20 11:07:37 -0800770 UpdateDeviceListOnTaskRunner();
toyoshim63e32a52017-04-25 07:20:10 -0700771 PostReplyTask(base::Bind(&MidiManagerWin::CompleteInitialization,
772 base::Unretained(this), mojom::Result::OK));
toyoshimd28b59c2017-02-20 11:07:37 -0800773}
774
toyoshim63e32a52017-04-25 07:20:10 -0700775void MidiManagerWin::UpdateDeviceListOnTaskRunner() {
toyoshimd28b59c2017-02-20 11:07:37 -0800776 std::vector<std::unique_ptr<InPort>> active_input_ports =
toyoshim8a5ad422017-02-28 21:16:18 -0800777 InPort::EnumerateActivePorts(this, instance_id_);
778 ReflectActiveDeviceList(this, port_manager_->inputs(), &active_input_ports);
toyoshimd28b59c2017-02-20 11:07:37 -0800779
780 std::vector<std::unique_ptr<OutPort>> active_output_ports =
781 OutPort::EnumerateActivePorts();
toyoshim8a5ad422017-02-28 21:16:18 -0800782 ReflectActiveDeviceList(this, port_manager_->outputs(), &active_output_ports);
toyoshimd28b59c2017-02-20 11:07:37 -0800783
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
790template <typename T>
toyoshim63e32a52017-04-25 07:20:10 -0700791void MidiManagerWin::ReflectActiveDeviceList(MidiManagerWin* manager,
792 std::vector<T>* known_ports,
793 std::vector<T>* active_ports) {
toyoshimd28b59c2017-02-20 11:07:37 -0800794 // 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
toyoshim63e32a52017-04-25 07:20:10 -0700824void MidiManagerWin::SendOnTaskRunner(MidiManagerClient* client,
825 uint32_t port_index,
826 const std::vector<uint8_t>& data) {
toyoshim6d87aaa2017-02-28 22:36:44 -0800827 CHECK_GT(port_manager_->outputs()->size(), port_index);
828 (*port_manager_->outputs())[port_index]->Send(data);
829 // |client| will be checked inside MidiManager::AccumulateMidiBytesSent.
toyoshim63e32a52017-04-25 07:20:10 -0700830 PostReplyTask(base::Bind(&MidiManagerWin::AccumulateMidiBytesSent,
831 base::Unretained(this), client, data.size()));
toyoshim6d87aaa2017-02-28 22:36:44 -0800832}
833
toyoshim15385b32017-04-24 23:52:01 -0700834MidiManager* MidiManager::Create(MidiService* service) {
835 if (base::FeatureList::IsEnabled(features::kMidiManagerWinrt) &&
toyoshim63e32a52017-04-25 07:20:10 -0700836 base::win::GetVersion() >= base::win::VERSION_WIN10) {
toyoshim15385b32017-04-24 23:52:01 -0700837 return new MidiManagerWinrt(service);
toyoshim63e32a52017-04-25 07:20:10 -0700838 }
839 return new MidiManagerWin(service);
toyoshim15385b32017-04-24 23:52:01 -0700840}
841
toyoshimd28b59c2017-02-20 11:07:37 -0800842} // namespace midi