blob: 50c616700a29ede4e6efbe6e07a25c17213f4fb7 [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
Sebastien Marchand2912d9f2019-01-25 16:49:37 +000017#include "base/bind.h"
toyoshim8a5ad422017-02-28 21:16:18 -080018#include "base/bind_helpers.h"
toyoshimd28b59c2017-02-20 11:07:37 -080019#include "base/callback.h"
20#include "base/logging.h"
Gabriel Charette78f94a02017-05-16 14:03:45 -040021#include "base/single_thread_task_runner.h"
Avi Drissman2c637192018-12-25 20:26:39 +000022#include "base/stl_util.h"
toyoshimd28b59c2017-02-20 11:07:37 -080023#include "base/strings/string16.h"
24#include "base/strings/stringprintf.h"
25#include "base/strings/utf_string_conversions.h"
26#include "base/synchronization/lock.h"
toyoshim15385b32017-04-24 23:52:01 -070027#include "base/win/windows_version.h"
toyoshim6ebf1182017-03-01 00:40:31 -080028#include "device/usb/usb_ids.h"
toyoshim8a5ad422017-02-28 21:16:18 -080029#include "media/midi/message_util.h"
toyoshim15385b32017-04-24 23:52:01 -070030#include "media/midi/midi_manager_winrt.h"
toyoshimd28b59c2017-02-20 11:07:37 -080031#include "media/midi/midi_service.h"
Adithya Srinivasan33252732018-10-17 15:59:40 +000032#include "media/midi/midi_service.mojom.h"
toyoshim15385b32017-04-24 23:52:01 -070033#include "media/midi/midi_switches.h"
toyoshimd28b59c2017-02-20 11:07:37 -080034
35namespace midi {
36
toyoshim8a5ad422017-02-28 21:16:18 -080037// Forward declaration of PortManager for anonymous functions and internal
38// classes to use it.
toyoshim63e32a52017-04-25 07:20:10 -070039class MidiManagerWin::PortManager {
toyoshim8a5ad422017-02-28 21:16:18 -080040 public:
41 // Calculates event time from elapsed time that system provides.
42 base::TimeTicks CalculateInEventTime(size_t index, uint32_t elapsed_ms) const;
43
44 // Registers HMIDIIN handle to resolve port index.
45 void RegisterInHandle(HMIDIIN handle, size_t index);
46
47 // Unregisters HMIDIIN handle.
48 void UnregisterInHandle(HMIDIIN handle);
49
50 // Finds HMIDIIN handle and fullfil |out_index| with the port index.
toyoshim6d87aaa2017-02-28 22:36:44 -080051 bool FindInHandle(HMIDIIN hmi, size_t* out_index);
toyoshim8a5ad422017-02-28 21:16:18 -080052
53 // Restores used input buffer for the next data receive.
54 void RestoreInBuffer(size_t index);
55
56 // Ports accessors.
57 std::vector<std::unique_ptr<InPort>>* inputs() { return &input_ports_; }
58 std::vector<std::unique_ptr<OutPort>>* outputs() { return &output_ports_; }
59
60 // Handles MIDI input port callbacks that runs on a system provided thread.
61 static void CALLBACK HandleMidiInCallback(HMIDIIN hmi,
62 UINT msg,
63 DWORD_PTR instance,
64 DWORD_PTR param1,
65 DWORD_PTR param2);
66
toyoshim6d87aaa2017-02-28 22:36:44 -080067 // Handles MIDI output port callbacks that runs on a system provided thread.
68 static void CALLBACK HandleMidiOutCallback(HMIDIOUT hmo,
69 UINT msg,
70 DWORD_PTR instance,
71 DWORD_PTR param1,
72 DWORD_PTR param2);
73
toyoshim8a5ad422017-02-28 21:16:18 -080074 private:
75 // Holds all MIDI input or output ports connected once.
76 std::vector<std::unique_ptr<InPort>> input_ports_;
77 std::vector<std::unique_ptr<OutPort>> output_ports_;
78
79 // Map to resolve MIDI input port index from HMIDIIN.
80 std::map<HMIDIIN, size_t> hmidiin_to_index_map_;
81};
82
toyoshimd28b59c2017-02-20 11:07:37 -080083namespace {
84
toyoshimc32dd892017-02-24 02:13:14 -080085// Assumes that nullptr represents an invalid MIDI handle.
86constexpr HMIDIIN kInvalidInHandle = nullptr;
87constexpr HMIDIOUT kInvalidOutHandle = nullptr;
88
toyoshim6d87aaa2017-02-28 22:36:44 -080089// Defines SysEx message size limit.
90// TODO(crbug.com/383578): This restriction should be removed once Web MIDI
91// defines a standardized way to handle large sysex messages.
92// Note for built-in USB-MIDI driver:
93// From an observation on Windows 7/8.1 with a USB-MIDI keyboard,
94// midiOutLongMsg() will be always blocked. Sending 64 bytes or less data takes
95// roughly 300 usecs. Sending 2048 bytes or more data takes roughly
96// |message.size() / (75 * 1024)| secs in practice. Here we put 256 KB size
97// limit on SysEx message, with hoping that midiOutLongMsg will be blocked at
98// most 4 sec or so with a typical USB-MIDI device.
99// TODO(toyoshim): Consider to use linked small buffers so that midiOutReset()
100// can abort sending unhandled following buffers.
101constexpr size_t kSysExSizeLimit = 256 * 1024;
102
toyoshim8a5ad422017-02-28 21:16:18 -0800103// Defines input buffer size.
104constexpr size_t kBufferLength = 32 * 1024;
105
toyoshimd28b59c2017-02-20 11:07:37 -0800106// Global variables to identify MidiManager instance.
107constexpr int kInvalidInstanceId = -1;
108int g_active_instance_id = kInvalidInstanceId;
toyoshim63e32a52017-04-25 07:20:10 -0700109MidiManagerWin* g_manager_instance = nullptr;
toyoshimd28b59c2017-02-20 11:07:37 -0800110
111// Obtains base::Lock instance pointer to lock instance_id.
112base::Lock* GetInstanceIdLock() {
113 static base::Lock* lock = new base::Lock;
114 return lock;
115}
116
117// Issues unique MidiManager instance ID.
118int IssueNextInstanceId() {
119 static int id = kInvalidInstanceId;
120 return ++id;
121}
122
123// Use single TaskRunner for all tasks running outside the I/O thread.
124constexpr int kTaskRunner = 0;
125
126// Obtains base::Lock instance pointer to ensure tasks run safely on TaskRunner.
toyoshimc32dd892017-02-24 02:13:14 -0800127// Since all tasks on TaskRunner run behind a lock of *GetTaskLock(), we can
128// access all members even on the I/O thread if a lock of *GetTaskLock() is
129// obtained.
toyoshimd28b59c2017-02-20 11:07:37 -0800130base::Lock* GetTaskLock() {
131 static base::Lock* lock = new base::Lock;
132 return lock;
133}
134
135// Helper function to run a posted task on TaskRunner safely.
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000136void RunTask(int instance_id, base::OnceClosure task) {
toyoshimd28b59c2017-02-20 11:07:37 -0800137 // Obtains task lock to ensure that the instance should not complete
138 // Finalize() while running the |task|.
139 base::AutoLock task_lock(*GetTaskLock());
140 {
Takashi Toyoshimae8240ab2018-10-03 09:30:11 +0000141 // If destructor finished before the lock avobe, do nothing.
toyoshimd28b59c2017-02-20 11:07:37 -0800142 base::AutoLock lock(*GetInstanceIdLock());
143 if (instance_id != g_active_instance_id)
144 return;
145 }
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000146 std::move(task).Run();
toyoshimd28b59c2017-02-20 11:07:37 -0800147}
148
Takashi Toyoshimae8240ab2018-10-03 09:30:11 +0000149// TODO(toyoshim): Use midi::TaskService and deprecate its prototype
150// implementation above that is still used in this MidiManagerWin class.
toyoshimd28b59c2017-02-20 11:07:37 -0800151
Takashi Toyoshimaa09cbc72017-06-01 18:49:34 +0900152// Obtains base::Lock instance pointer to protect
153// |g_midi_in_get_num_devs_thread_id|.
154base::Lock* GetMidiInGetNumDevsThreadIdLock() {
155 static base::Lock* lock = new base::Lock;
156 return lock;
157}
158
159// Holds a thread id that calls midiInGetNumDevs() now. We use a platform
160// primitive to identify the thread because the following functions can be
161// called on a thread that Windows allocates internally, and Chrome or //base
162// library does not know.
163base::PlatformThreadId g_midi_in_get_num_devs_thread_id;
164
165// Prepares to call midiInGetNumDevs().
166void EnterMidiInGetNumDevs() {
167 base::AutoLock lock(*GetMidiInGetNumDevsThreadIdLock());
168 g_midi_in_get_num_devs_thread_id = base::PlatformThread::CurrentId();
169}
170
171// Finalizes to call midiInGetNumDevs().
172void LeaveMidiInGetNumDevs() {
173 base::AutoLock lock(*GetMidiInGetNumDevsThreadIdLock());
174 g_midi_in_get_num_devs_thread_id = base::PlatformThreadId();
175}
176
177// Checks if the current thread is running midiInGetNumDevs(), that means
178// current code is invoked inside midiInGetNumDevs().
179bool IsRunningInsideMidiInGetNumDevs() {
180 base::AutoLock lock(*GetMidiInGetNumDevsThreadIdLock());
181 return base::PlatformThread::CurrentId() == g_midi_in_get_num_devs_thread_id;
182}
183
toyoshim8a5ad422017-02-28 21:16:18 -0800184// Utility class to handle MIDIHDR struct safely.
185class MIDIHDRDeleter {
186 public:
187 void operator()(LPMIDIHDR header) {
188 if (!header)
189 return;
190 delete[] static_cast<char*>(header->lpData);
191 delete header;
192 }
193};
194
195using ScopedMIDIHDR = std::unique_ptr<MIDIHDR, MIDIHDRDeleter>;
196
197ScopedMIDIHDR CreateMIDIHDR(size_t size) {
198 ScopedMIDIHDR hdr(new MIDIHDR);
199 ZeroMemory(hdr.get(), sizeof(*hdr));
200 hdr->lpData = new char[size];
201 hdr->dwBufferLength = static_cast<DWORD>(size);
202 return hdr;
203}
204
toyoshim6d87aaa2017-02-28 22:36:44 -0800205ScopedMIDIHDR CreateMIDIHDR(const std::vector<uint8_t>& data) {
206 ScopedMIDIHDR hdr(CreateMIDIHDR(data.size()));
207 std::copy(data.begin(), data.end(), hdr->lpData);
208 return hdr;
209}
210
toyoshimc32dd892017-02-24 02:13:14 -0800211// Helper functions to close MIDI device handles on TaskRunner asynchronously.
toyoshim8a5ad422017-02-28 21:16:18 -0800212void FinalizeInPort(HMIDIIN handle, ScopedMIDIHDR hdr) {
213 // Resets the device. This stops receiving messages, and allows to release
214 // registered buffer headers. Otherwise, midiInUnprepareHeader() and
215 // midiInClose() will fail with MIDIERR_STILLPLAYING.
216 midiInReset(handle);
217
218 if (hdr)
219 midiInUnprepareHeader(handle, hdr.get(), sizeof(*hdr));
toyoshimc32dd892017-02-24 02:13:14 -0800220 midiInClose(handle);
221}
222
223void FinalizeOutPort(HMIDIOUT handle) {
toyoshim6d87aaa2017-02-28 22:36:44 -0800224 // Resets inflight buffers. This will cancel sending data that system
225 // holds and were not sent yet.
226 midiOutReset(handle);
toyoshimc32dd892017-02-24 02:13:14 -0800227 midiOutClose(handle);
228}
229
toyoshim6ebf1182017-03-01 00:40:31 -0800230// Gets manufacturer name in string from identifiers.
231std::string GetManufacturerName(uint16_t id, const GUID& guid) {
232 if (IS_COMPATIBLE_USBAUDIO_MID(&guid)) {
233 const char* name =
234 device::UsbIds::GetVendorName(EXTRACT_USBAUDIO_MID(&guid));
235 if (name)
236 return std::string(name);
237 }
238 if (id == MM_MICROSOFT)
239 return "Microsoft Corporation";
240
241 // TODO(crbug.com/472341): Support other manufacture IDs.
242 return "";
243}
244
toyoshimc32dd892017-02-24 02:13:14 -0800245// All instances of Port subclasses are always accessed behind a lock of
246// *GetTaskLock(). Port and subclasses implementation do not need to
247// consider thread safety.
toyoshimd28b59c2017-02-20 11:07:37 -0800248class Port {
249 public:
250 Port(const std::string& type,
251 uint32_t device_id,
252 uint16_t manufacturer_id,
253 uint16_t product_id,
254 uint32_t driver_version,
toyoshim6ebf1182017-03-01 00:40:31 -0800255 const std::string& product_name,
256 const GUID& manufacturer_guid)
toyoshimd28b59c2017-02-20 11:07:37 -0800257 : index_(0u),
258 type_(type),
259 device_id_(device_id),
260 manufacturer_id_(manufacturer_id),
261 product_id_(product_id),
262 driver_version_(driver_version),
263 product_name_(product_name) {
toyoshim6ebf1182017-03-01 00:40:31 -0800264 info_.manufacturer =
265 GetManufacturerName(manufacturer_id, manufacturer_guid);
toyoshimd28b59c2017-02-20 11:07:37 -0800266 info_.name = product_name_;
267 info_.version = base::StringPrintf("%d.%d", HIBYTE(driver_version_),
268 LOBYTE(driver_version_));
269 info_.state = mojom::PortState::DISCONNECTED;
270 }
271
272 virtual ~Port() {}
273
274 bool operator==(const Port& other) const {
275 // Should not use |device_id| for comparison because it can be changed on
276 // each enumeration.
277 // Since the GUID will be changed on each enumeration for Microsoft GS
278 // Wavetable synth and might be done for others, do not use it for device
279 // comparison.
280 return manufacturer_id_ == other.manufacturer_id_ &&
281 product_id_ == other.product_id_ &&
282 driver_version_ == other.driver_version_ &&
283 product_name_ == other.product_name_;
284 }
285
286 bool IsConnected() const {
287 return info_.state != mojom::PortState::DISCONNECTED;
288 }
289
290 void set_index(size_t index) {
291 index_ = index;
292 // TODO(toyoshim): Use hashed ID.
Bruce Dawson66ae7db2017-08-04 17:57:45 +0000293 info_.id = base::StringPrintf("%s-%zd", type_.c_str(), index_);
toyoshimd28b59c2017-02-20 11:07:37 -0800294 }
295 size_t index() { return index_; }
296 void set_device_id(uint32_t device_id) { device_id_ = device_id; }
297 uint32_t device_id() { return device_id_; }
Adithya Srinivasan33252732018-10-17 15:59:40 +0000298 const mojom::PortInfo& info() { return info_; }
toyoshimd28b59c2017-02-20 11:07:37 -0800299
300 virtual bool Connect() {
301 if (info_.state != mojom::PortState::DISCONNECTED)
302 return false;
303
304 info_.state = mojom::PortState::CONNECTED;
305 // TODO(toyoshim) Until open() / close() are supported, open each device on
306 // connected.
307 Open();
308 return true;
309 }
310
311 virtual bool Disconnect() {
312 if (info_.state == mojom::PortState::DISCONNECTED)
313 return false;
314 info_.state = mojom::PortState::DISCONNECTED;
315 return true;
316 }
317
318 virtual void Open() { info_.state = mojom::PortState::OPENED; }
319
320 protected:
321 size_t index_;
322 std::string type_;
323 uint32_t device_id_;
324 const uint16_t manufacturer_id_;
325 const uint16_t product_id_;
326 const uint32_t driver_version_;
327 const std::string product_name_;
Adithya Srinivasan33252732018-10-17 15:59:40 +0000328 mojom::PortInfo info_;
toyoshimd28b59c2017-02-20 11:07:37 -0800329}; // class Port
330
331} // namespace
332
toyoshim63e32a52017-04-25 07:20:10 -0700333class MidiManagerWin::InPort final : public Port {
toyoshimd28b59c2017-02-20 11:07:37 -0800334 public:
toyoshim63e32a52017-04-25 07:20:10 -0700335 InPort(MidiManagerWin* manager,
toyoshim8a5ad422017-02-28 21:16:18 -0800336 int instance_id,
337 UINT device_id,
338 const MIDIINCAPS2W& caps)
toyoshimd28b59c2017-02-20 11:07:37 -0800339 : Port("input",
340 device_id,
341 caps.wMid,
342 caps.wPid,
343 caps.vDriverVersion,
344 base::WideToUTF8(
toyoshim6ebf1182017-03-01 00:40:31 -0800345 base::string16(caps.szPname, wcslen(caps.szPname))),
346 caps.ManufacturerGuid),
toyoshim8a5ad422017-02-28 21:16:18 -0800347 manager_(manager),
348 in_handle_(kInvalidInHandle),
349 instance_id_(instance_id) {}
toyoshimd28b59c2017-02-20 11:07:37 -0800350
toyoshim8a5ad422017-02-28 21:16:18 -0800351 static std::vector<std::unique_ptr<InPort>> EnumerateActivePorts(
toyoshim63e32a52017-04-25 07:20:10 -0700352 MidiManagerWin* manager,
toyoshim8a5ad422017-02-28 21:16:18 -0800353 int instance_id) {
toyoshimd28b59c2017-02-20 11:07:37 -0800354 std::vector<std::unique_ptr<InPort>> ports;
Takashi Toyoshimaa09cbc72017-06-01 18:49:34 +0900355
356 // Allow callback invocations indie midiInGetNumDevs().
357 EnterMidiInGetNumDevs();
toyoshimd28b59c2017-02-20 11:07:37 -0800358 const UINT num_devices = midiInGetNumDevs();
Takashi Toyoshimaa09cbc72017-06-01 18:49:34 +0900359 LeaveMidiInGetNumDevs();
360
toyoshimd28b59c2017-02-20 11:07:37 -0800361 for (UINT device_id = 0; device_id < num_devices; ++device_id) {
362 MIDIINCAPS2W caps;
363 MMRESULT result = midiInGetDevCaps(
364 device_id, reinterpret_cast<LPMIDIINCAPSW>(&caps), sizeof(caps));
365 if (result != MMSYSERR_NOERROR) {
366 LOG(ERROR) << "midiInGetDevCaps fails on device " << device_id;
367 continue;
368 }
toyoshim8a5ad422017-02-28 21:16:18 -0800369 ports.push_back(
Gyuyoung Kim34e191a2018-01-10 09:48:42 +0000370 std::make_unique<InPort>(manager, instance_id, device_id, caps));
toyoshimd28b59c2017-02-20 11:07:37 -0800371 }
372 return ports;
373 }
374
toyoshimc32dd892017-02-24 02:13:14 -0800375 void Finalize(scoped_refptr<base::SingleThreadTaskRunner> runner) {
376 if (in_handle_ != kInvalidInHandle) {
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000377 runner->PostTask(FROM_HERE, base::BindOnce(&FinalizeInPort, in_handle_,
378 base::Passed(&hdr_)));
toyoshim8a5ad422017-02-28 21:16:18 -0800379 manager_->port_manager()->UnregisterInHandle(in_handle_);
toyoshimc32dd892017-02-24 02:13:14 -0800380 in_handle_ = kInvalidInHandle;
381 }
382 }
383
toyoshim8a5ad422017-02-28 21:16:18 -0800384 base::TimeTicks CalculateInEventTime(uint32_t elapsed_ms) const {
385 return start_time_ + base::TimeDelta::FromMilliseconds(elapsed_ms);
386 }
387
388 void RestoreBuffer() {
389 if (in_handle_ == kInvalidInHandle || !hdr_)
390 return;
391 midiInAddBuffer(in_handle_, hdr_.get(), sizeof(*hdr_));
392 }
393
toyoshim63e32a52017-04-25 07:20:10 -0700394 void NotifyPortStateSet(MidiManagerWin* manager) {
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000395 manager->PostReplyTask(base::BindOnce(
Bruce Dawson7fd26de2017-08-01 19:33:12 +0000396 &MidiManagerWin::SetInputPortState, base::Unretained(manager),
397 static_cast<uint32_t>(index_), info_.state));
toyoshimd28b59c2017-02-20 11:07:37 -0800398 }
399
toyoshim63e32a52017-04-25 07:20:10 -0700400 void NotifyPortAdded(MidiManagerWin* manager) {
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000401 manager->PostReplyTask(base::BindOnce(&MidiManagerWin::AddInputPort,
402 base::Unretained(manager), info_));
toyoshimd28b59c2017-02-20 11:07:37 -0800403 }
toyoshimc32dd892017-02-24 02:13:14 -0800404
405 // Port overrides:
406 bool Disconnect() override {
407 if (in_handle_ != kInvalidInHandle) {
408 // Following API call may fail because device was already disconnected.
409 // But just in case.
410 midiInClose(in_handle_);
toyoshim8a5ad422017-02-28 21:16:18 -0800411 manager_->port_manager()->UnregisterInHandle(in_handle_);
toyoshimc32dd892017-02-24 02:13:14 -0800412 in_handle_ = kInvalidInHandle;
413 }
414 return Port::Disconnect();
415 }
416
417 void Open() override {
toyoshim8a5ad422017-02-28 21:16:18 -0800418 MMRESULT result = midiInOpen(
419 &in_handle_, device_id_,
420 reinterpret_cast<DWORD_PTR>(&PortManager::HandleMidiInCallback),
421 instance_id_, CALLBACK_FUNCTION);
toyoshimc32dd892017-02-24 02:13:14 -0800422 if (result == MMSYSERR_NOERROR) {
toyoshim8a5ad422017-02-28 21:16:18 -0800423 hdr_ = CreateMIDIHDR(kBufferLength);
424 result = midiInPrepareHeader(in_handle_, hdr_.get(), sizeof(*hdr_));
425 }
426 if (result != MMSYSERR_NOERROR)
427 in_handle_ = kInvalidInHandle;
428 if (result == MMSYSERR_NOERROR)
429 result = midiInAddBuffer(in_handle_, hdr_.get(), sizeof(*hdr_));
430 if (result == MMSYSERR_NOERROR)
431 result = midiInStart(in_handle_);
432 if (result == MMSYSERR_NOERROR) {
433 start_time_ = base::TimeTicks::Now();
434 manager_->port_manager()->RegisterInHandle(in_handle_, index_);
toyoshimc32dd892017-02-24 02:13:14 -0800435 Port::Open();
436 } else {
toyoshim8a5ad422017-02-28 21:16:18 -0800437 if (in_handle_ != kInvalidInHandle) {
438 midiInUnprepareHeader(in_handle_, hdr_.get(), sizeof(*hdr_));
439 hdr_.reset();
440 midiInClose(in_handle_);
441 in_handle_ = kInvalidInHandle;
442 }
toyoshimc32dd892017-02-24 02:13:14 -0800443 Disconnect();
444 }
445 }
446
447 private:
toyoshim63e32a52017-04-25 07:20:10 -0700448 MidiManagerWin* manager_;
toyoshimc32dd892017-02-24 02:13:14 -0800449 HMIDIIN in_handle_;
toyoshim8a5ad422017-02-28 21:16:18 -0800450 ScopedMIDIHDR hdr_;
451 base::TimeTicks start_time_;
452 const int instance_id_;
toyoshimd28b59c2017-02-20 11:07:37 -0800453};
454
toyoshim63e32a52017-04-25 07:20:10 -0700455class MidiManagerWin::OutPort final : public Port {
toyoshimd28b59c2017-02-20 11:07:37 -0800456 public:
457 OutPort(UINT device_id, const MIDIOUTCAPS2W& caps)
458 : Port("output",
459 device_id,
460 caps.wMid,
461 caps.wPid,
462 caps.vDriverVersion,
463 base::WideToUTF8(
toyoshim6ebf1182017-03-01 00:40:31 -0800464 base::string16(caps.szPname, wcslen(caps.szPname))),
465 caps.ManufacturerGuid),
toyoshimc32dd892017-02-24 02:13:14 -0800466 software_(caps.wTechnology == MOD_SWSYNTH),
467 out_handle_(kInvalidOutHandle) {}
toyoshimd28b59c2017-02-20 11:07:37 -0800468
469 static std::vector<std::unique_ptr<OutPort>> EnumerateActivePorts() {
470 std::vector<std::unique_ptr<OutPort>> ports;
471 const UINT num_devices = midiOutGetNumDevs();
472 for (UINT device_id = 0; device_id < num_devices; ++device_id) {
473 MIDIOUTCAPS2W caps;
474 MMRESULT result = midiOutGetDevCaps(
475 device_id, reinterpret_cast<LPMIDIOUTCAPSW>(&caps), sizeof(caps));
476 if (result != MMSYSERR_NOERROR) {
477 LOG(ERROR) << "midiOutGetDevCaps fails on device " << device_id;
478 continue;
479 }
Gyuyoung Kim34e191a2018-01-10 09:48:42 +0000480 ports.push_back(std::make_unique<OutPort>(device_id, caps));
toyoshimd28b59c2017-02-20 11:07:37 -0800481 }
482 return ports;
483 }
484
toyoshimc32dd892017-02-24 02:13:14 -0800485 void Finalize(scoped_refptr<base::SingleThreadTaskRunner> runner) {
486 if (out_handle_ != kInvalidOutHandle) {
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000487 runner->PostTask(FROM_HERE,
488 base::BindOnce(&FinalizeOutPort, out_handle_));
toyoshimc32dd892017-02-24 02:13:14 -0800489 out_handle_ = kInvalidOutHandle;
toyoshimd28b59c2017-02-20 11:07:37 -0800490 }
toyoshimd28b59c2017-02-20 11:07:37 -0800491 }
492
toyoshim63e32a52017-04-25 07:20:10 -0700493 void NotifyPortStateSet(MidiManagerWin* manager) {
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000494 manager->PostReplyTask(base::BindOnce(
Bruce Dawson7fd26de2017-08-01 19:33:12 +0000495 &MidiManagerWin::SetOutputPortState, base::Unretained(manager),
496 static_cast<uint32_t>(index_), info_.state));
toyoshimd28b59c2017-02-20 11:07:37 -0800497 }
498
toyoshim63e32a52017-04-25 07:20:10 -0700499 void NotifyPortAdded(MidiManagerWin* manager) {
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000500 manager->PostReplyTask(base::BindOnce(&MidiManagerWin::AddOutputPort,
501 base::Unretained(manager), info_));
toyoshimd28b59c2017-02-20 11:07:37 -0800502 }
503
toyoshim6d87aaa2017-02-28 22:36:44 -0800504 void Send(const std::vector<uint8_t>& data) {
505 if (out_handle_ == kInvalidOutHandle)
506 return;
507
508 if (data.size() <= 3) {
509 uint32_t message = 0;
510 for (size_t i = 0; i < data.size(); ++i)
511 message |= (static_cast<uint32_t>(data[i]) << (i * 8));
512 midiOutShortMsg(out_handle_, message);
513 } else {
514 if (data.size() > kSysExSizeLimit) {
515 LOG(ERROR) << "Ignoring SysEx message due to the size limit"
516 << ", size = " << data.size();
517 // TODO(toyoshim): Consider to report metrics here.
518 return;
519 }
520 ScopedMIDIHDR hdr(CreateMIDIHDR(data));
521 MMRESULT result =
522 midiOutPrepareHeader(out_handle_, hdr.get(), sizeof(*hdr));
523 if (result != MMSYSERR_NOERROR)
524 return;
525 result = midiOutLongMsg(out_handle_, hdr.get(), sizeof(*hdr));
526 if (result != MMSYSERR_NOERROR) {
527 midiOutUnprepareHeader(out_handle_, hdr.get(), sizeof(*hdr));
528 } else {
529 // MIDIHDR will be released on MOM_DONE.
530 ignore_result(hdr.release());
531 }
532 }
533 }
534
toyoshimc32dd892017-02-24 02:13:14 -0800535 // Port overrides:
536 bool Connect() override {
537 // Until |software| option is supported, disable Microsoft GS Wavetable
538 // Synth that has a known security issue.
539 if (software_ && manufacturer_id_ == MM_MICROSOFT &&
540 (product_id_ == MM_MSFT_WDMAUDIO_MIDIOUT ||
541 product_id_ == MM_MSFT_GENERIC_MIDISYNTH)) {
542 return false;
543 }
544 return Port::Connect();
545 }
546
547 bool Disconnect() override {
548 if (out_handle_ != kInvalidOutHandle) {
549 // Following API call may fail because device was already disconnected.
550 // But just in case.
551 midiOutClose(out_handle_);
552 out_handle_ = kInvalidOutHandle;
553 }
554 return Port::Disconnect();
555 }
556
557 void Open() override {
toyoshim6d87aaa2017-02-28 22:36:44 -0800558 MMRESULT result = midiOutOpen(
559 &out_handle_, device_id_,
560 reinterpret_cast<DWORD_PTR>(&PortManager::HandleMidiOutCallback), 0,
561 CALLBACK_FUNCTION);
toyoshimc32dd892017-02-24 02:13:14 -0800562 if (result == MMSYSERR_NOERROR) {
563 Port::Open();
564 } else {
565 out_handle_ = kInvalidOutHandle;
566 Disconnect();
567 }
568 }
569
toyoshimd28b59c2017-02-20 11:07:37 -0800570 const bool software_;
toyoshimc32dd892017-02-24 02:13:14 -0800571 HMIDIOUT out_handle_;
toyoshimd28b59c2017-02-20 11:07:37 -0800572};
573
toyoshim63e32a52017-04-25 07:20:10 -0700574base::TimeTicks MidiManagerWin::PortManager::CalculateInEventTime(
toyoshim8a5ad422017-02-28 21:16:18 -0800575 size_t index,
576 uint32_t elapsed_ms) const {
577 GetTaskLock()->AssertAcquired();
578 CHECK_GT(input_ports_.size(), index);
579 return input_ports_[index]->CalculateInEventTime(elapsed_ms);
580}
581
toyoshim63e32a52017-04-25 07:20:10 -0700582void MidiManagerWin::PortManager::RegisterInHandle(HMIDIIN handle,
583 size_t index) {
toyoshim8a5ad422017-02-28 21:16:18 -0800584 GetTaskLock()->AssertAcquired();
585 hmidiin_to_index_map_[handle] = index;
586}
587
toyoshim63e32a52017-04-25 07:20:10 -0700588void MidiManagerWin::PortManager::UnregisterInHandle(HMIDIIN handle) {
toyoshim8a5ad422017-02-28 21:16:18 -0800589 GetTaskLock()->AssertAcquired();
590 hmidiin_to_index_map_.erase(handle);
591}
592
toyoshim63e32a52017-04-25 07:20:10 -0700593bool MidiManagerWin::PortManager::FindInHandle(HMIDIIN hmi, size_t* out_index) {
toyoshim8a5ad422017-02-28 21:16:18 -0800594 GetTaskLock()->AssertAcquired();
595 auto found = hmidiin_to_index_map_.find(hmi);
596 if (found == hmidiin_to_index_map_.end())
597 return false;
598 *out_index = found->second;
599 return true;
600}
601
toyoshim63e32a52017-04-25 07:20:10 -0700602void MidiManagerWin::PortManager::RestoreInBuffer(size_t index) {
toyoshim8a5ad422017-02-28 21:16:18 -0800603 GetTaskLock()->AssertAcquired();
604 CHECK_GT(input_ports_.size(), index);
605 input_ports_[index]->RestoreBuffer();
606}
607
608void CALLBACK
toyoshim63e32a52017-04-25 07:20:10 -0700609MidiManagerWin::PortManager::HandleMidiInCallback(HMIDIIN hmi,
610 UINT msg,
611 DWORD_PTR instance,
612 DWORD_PTR param1,
613 DWORD_PTR param2) {
toyoshim8a5ad422017-02-28 21:16:18 -0800614 if (msg != MIM_DATA && msg != MIM_LONGDATA)
615 return;
616 int instance_id = static_cast<int>(instance);
toyoshim63e32a52017-04-25 07:20:10 -0700617 MidiManagerWin* manager = nullptr;
toyoshim8a5ad422017-02-28 21:16:18 -0800618
619 // Use |g_task_lock| so to ensure the instance can keep alive while running,
620 // and to access member variables that are used on TaskRunner.
Takashi Toyoshimaa09cbc72017-06-01 18:49:34 +0900621 // Exceptionally, we do not take the lock when this callback is invoked inside
622 // midiInGetNumDevs() on the caller thread because the lock is already
623 // obtained by the current caller thread.
624 std::unique_ptr<base::AutoLock> task_lock;
625 if (IsRunningInsideMidiInGetNumDevs())
626 GetTaskLock()->AssertAcquired();
627 else
628 task_lock.reset(new base::AutoLock(*GetTaskLock()));
toyoshim8a5ad422017-02-28 21:16:18 -0800629 {
630 base::AutoLock lock(*GetInstanceIdLock());
631 if (instance_id != g_active_instance_id)
632 return;
633 manager = g_manager_instance;
634 }
635
636 size_t index;
toyoshim6d87aaa2017-02-28 22:36:44 -0800637 if (!manager->port_manager()->FindInHandle(hmi, &index))
toyoshim8a5ad422017-02-28 21:16:18 -0800638 return;
639
640 DCHECK(msg == MIM_DATA || msg == MIM_LONGDATA);
641 if (msg == MIM_DATA) {
642 const uint8_t status_byte = static_cast<uint8_t>(param1 & 0xff);
643 const uint8_t first_data_byte = static_cast<uint8_t>((param1 >> 8) & 0xff);
644 const uint8_t second_data_byte =
645 static_cast<uint8_t>((param1 >> 16) & 0xff);
646 const uint8_t kData[] = {status_byte, first_data_byte, second_data_byte};
647 const size_t len = GetMessageLength(status_byte);
Avi Drissman2c637192018-12-25 20:26:39 +0000648 DCHECK_LE(len, base::size(kData));
toyoshim8a5ad422017-02-28 21:16:18 -0800649 std::vector<uint8_t> data;
650 data.assign(kData, kData + len);
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000651 manager->PostReplyTask(base::BindOnce(
Yutaka Hirano138dd962017-08-01 08:14:15 +0000652 &MidiManagerWin::ReceiveMidiData, base::Unretained(manager),
653 static_cast<uint32_t>(index), data,
654 manager->port_manager()->CalculateInEventTime(index, param2)));
toyoshim8a5ad422017-02-28 21:16:18 -0800655 } else {
656 DCHECK_EQ(static_cast<UINT>(MIM_LONGDATA), msg);
657 LPMIDIHDR hdr = reinterpret_cast<LPMIDIHDR>(param1);
658 if (hdr->dwBytesRecorded > 0) {
659 const uint8_t* src = reinterpret_cast<const uint8_t*>(hdr->lpData);
660 std::vector<uint8_t> data;
661 data.assign(src, src + hdr->dwBytesRecorded);
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000662 manager->PostReplyTask(base::BindOnce(
Yutaka Hirano138dd962017-08-01 08:14:15 +0000663 &MidiManagerWin::ReceiveMidiData, base::Unretained(manager),
664 static_cast<uint32_t>(index), data,
665 manager->port_manager()->CalculateInEventTime(index, param2)));
toyoshim8a5ad422017-02-28 21:16:18 -0800666 }
toyoshim824deed2017-06-07 16:45:43 -0700667 manager->port_manager()->RestoreInBuffer(index);
toyoshim8a5ad422017-02-28 21:16:18 -0800668 }
669}
670
toyoshim6d87aaa2017-02-28 22:36:44 -0800671void CALLBACK
toyoshim63e32a52017-04-25 07:20:10 -0700672MidiManagerWin::PortManager::HandleMidiOutCallback(HMIDIOUT hmo,
673 UINT msg,
674 DWORD_PTR instance,
675 DWORD_PTR param1,
676 DWORD_PTR param2) {
toyoshim6d87aaa2017-02-28 22:36:44 -0800677 if (msg == MOM_DONE) {
678 ScopedMIDIHDR hdr(reinterpret_cast<LPMIDIHDR>(param1));
679 if (!hdr)
680 return;
681 // TODO(toyoshim): Call midiOutUnprepareHeader outside the callback.
682 // Since this callback may be invoked after the manager is destructed,
683 // and can not send a task to the TaskRunner in such case, we need to
684 // consider to track MIDIHDR per port, and clean it in port finalization
685 // steps, too.
686 midiOutUnprepareHeader(hmo, hdr.get(), sizeof(*hdr));
687 }
688}
689
toyoshim63e32a52017-04-25 07:20:10 -0700690MidiManagerWin::MidiManagerWin(MidiService* service)
toyoshim8a5ad422017-02-28 21:16:18 -0800691 : MidiManager(service),
692 instance_id_(IssueNextInstanceId()),
Gyuyoung Kim34e191a2018-01-10 09:48:42 +0000693 port_manager_(std::make_unique<PortManager>()) {
toyoshimd28b59c2017-02-20 11:07:37 -0800694 base::AutoLock lock(*GetInstanceIdLock());
695 CHECK_EQ(kInvalidInstanceId, g_active_instance_id);
696
697 // Obtains the task runner for the current thread that hosts this instnace.
698 thread_runner_ = base::ThreadTaskRunnerHandle::Get();
699}
700
toyoshim63e32a52017-04-25 07:20:10 -0700701MidiManagerWin::~MidiManagerWin() {
Takashi Toyoshimae8240ab2018-10-03 09:30:11 +0000702 // Unregisters on the I/O thread. OnDevicesChanged() won't be called any more.
toyoshimd28b59c2017-02-20 11:07:37 -0800703 CHECK(thread_runner_->BelongsToCurrentThread());
Takashi Toyoshimae8240ab2018-10-03 09:30:11 +0000704 base::SystemMonitor::Get()->RemoveDevicesChangedObserver(this);
705
706 // Posts tasks that finalize each device port without MidiManager instance
707 // on TaskRunner. If another MidiManager instance is created, its
708 // initialization runs on the same task runner after all tasks posted here
709 // finish.
710 for (const auto& port : *port_manager_->inputs())
711 port->Finalize(service()->GetTaskRunner(kTaskRunner));
712 for (const auto& port : *port_manager_->outputs())
713 port->Finalize(service()->GetTaskRunner(kTaskRunner));
714
715 // Invalidate instance bound tasks.
716 {
717 base::AutoLock lock(*GetInstanceIdLock());
718 CHECK_EQ(instance_id_, g_active_instance_id);
719 g_active_instance_id = kInvalidInstanceId;
720 CHECK_EQ(this, g_manager_instance);
721 g_manager_instance = nullptr;
722 }
723
724 // Ensures that no bound task runs on TaskRunner so to destruct the instance
725 // safely.
726 // Tasks that did not started yet will do nothing after invalidate the
727 // instance ID above.
728 // Behind the lock below, we can safely access all members for finalization
729 // even on the I/O thread.
730 base::AutoLock lock(*GetTaskLock());
toyoshimd28b59c2017-02-20 11:07:37 -0800731}
732
toyoshim63e32a52017-04-25 07:20:10 -0700733void MidiManagerWin::StartInitialization() {
toyoshimd28b59c2017-02-20 11:07:37 -0800734 {
735 base::AutoLock lock(*GetInstanceIdLock());
736 CHECK_EQ(kInvalidInstanceId, g_active_instance_id);
737 g_active_instance_id = instance_id_;
738 CHECK_EQ(nullptr, g_manager_instance);
739 g_manager_instance = this;
740 }
741 // Registers on the I/O thread to be notified on the I/O thread.
742 CHECK(thread_runner_->BelongsToCurrentThread());
743 base::SystemMonitor::Get()->AddDevicesChangedObserver(this);
744
745 // Starts asynchronous initialization on TaskRunner.
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000746 PostTask(base::BindOnce(&MidiManagerWin::InitializeOnTaskRunner,
747 base::Unretained(this)));
toyoshimd28b59c2017-02-20 11:07:37 -0800748}
749
toyoshim63e32a52017-04-25 07:20:10 -0700750void MidiManagerWin::DispatchSendMidiData(MidiManagerClient* client,
751 uint32_t port_index,
752 const std::vector<uint8_t>& data,
tzik925e2c62018-02-02 07:39:45 +0000753 base::TimeTicks timestamp) {
Takashi Toyoshimaafb27d52017-09-13 11:50:41 +0000754 PostDelayedTask(
755 base::BindOnce(&MidiManagerWin::SendOnTaskRunner, base::Unretained(this),
756 client, port_index, data),
757 MidiService::TimestampToTimeDeltaDelay(timestamp));
toyoshimd28b59c2017-02-20 11:07:37 -0800758}
759
toyoshim63e32a52017-04-25 07:20:10 -0700760void MidiManagerWin::OnDevicesChanged(
toyoshimd28b59c2017-02-20 11:07:37 -0800761 base::SystemMonitor::DeviceType device_type) {
762 // Notified on the I/O thread.
763 CHECK(thread_runner_->BelongsToCurrentThread());
764
765 switch (device_type) {
766 case base::SystemMonitor::DEVTYPE_AUDIO:
767 case base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE:
768 // Add case of other unrelated device types here.
769 return;
770 case base::SystemMonitor::DEVTYPE_UNKNOWN: {
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000771 PostTask(base::BindOnce(&MidiManagerWin::UpdateDeviceListOnTaskRunner,
772 base::Unretained(this)));
toyoshimd28b59c2017-02-20 11:07:37 -0800773 break;
774 }
775 }
776}
777
toyoshim63e32a52017-04-25 07:20:10 -0700778void MidiManagerWin::ReceiveMidiData(uint32_t index,
779 const std::vector<uint8_t>& data,
780 base::TimeTicks time) {
toyoshim8a5ad422017-02-28 21:16:18 -0800781 MidiManager::ReceiveMidiData(index, data.data(), data.size(), time);
782}
783
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000784void MidiManagerWin::PostTask(base::OnceClosure task) {
toyoshimd28b59c2017-02-20 11:07:37 -0800785 service()
786 ->GetTaskRunner(kTaskRunner)
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000787 ->PostTask(FROM_HERE,
788 base::BindOnce(&RunTask, instance_id_, std::move(task)));
toyoshimd28b59c2017-02-20 11:07:37 -0800789}
790
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000791void MidiManagerWin::PostDelayedTask(base::OnceClosure task,
toyoshim63e32a52017-04-25 07:20:10 -0700792 base::TimeDelta delay) {
toyoshim6d87aaa2017-02-28 22:36:44 -0800793 service()
794 ->GetTaskRunner(kTaskRunner)
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000795 ->PostDelayedTask(FROM_HERE,
796 base::BindOnce(&RunTask, instance_id_, std::move(task)),
toyoshim6d87aaa2017-02-28 22:36:44 -0800797 delay);
798}
799
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000800void MidiManagerWin::PostReplyTask(base::OnceClosure task) {
801 thread_runner_->PostTask(
802 FROM_HERE, base::BindOnce(&RunTask, instance_id_, std::move(task)));
toyoshim8a5ad422017-02-28 21:16:18 -0800803}
804
toyoshim63e32a52017-04-25 07:20:10 -0700805void MidiManagerWin::InitializeOnTaskRunner() {
toyoshimd28b59c2017-02-20 11:07:37 -0800806 UpdateDeviceListOnTaskRunner();
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000807 PostReplyTask(base::BindOnce(&MidiManagerWin::CompleteInitialization,
808 base::Unretained(this), mojom::Result::OK));
toyoshimd28b59c2017-02-20 11:07:37 -0800809}
810
toyoshim63e32a52017-04-25 07:20:10 -0700811void MidiManagerWin::UpdateDeviceListOnTaskRunner() {
toyoshimd28b59c2017-02-20 11:07:37 -0800812 std::vector<std::unique_ptr<InPort>> active_input_ports =
toyoshim8a5ad422017-02-28 21:16:18 -0800813 InPort::EnumerateActivePorts(this, instance_id_);
814 ReflectActiveDeviceList(this, port_manager_->inputs(), &active_input_ports);
toyoshimd28b59c2017-02-20 11:07:37 -0800815
816 std::vector<std::unique_ptr<OutPort>> active_output_ports =
817 OutPort::EnumerateActivePorts();
toyoshim8a5ad422017-02-28 21:16:18 -0800818 ReflectActiveDeviceList(this, port_manager_->outputs(), &active_output_ports);
toyoshimd28b59c2017-02-20 11:07:37 -0800819
820 // TODO(toyoshim): This method may run before internal MIDI device lists that
821 // Windows manages were updated. This may be because MIDI driver may be loaded
822 // after the raw device list was updated. To avoid this problem, we may want
823 // to retry device check later if no changes are detected here.
824}
825
826template <typename T>
toyoshim63e32a52017-04-25 07:20:10 -0700827void MidiManagerWin::ReflectActiveDeviceList(MidiManagerWin* manager,
828 std::vector<T>* known_ports,
829 std::vector<T>* active_ports) {
toyoshimd28b59c2017-02-20 11:07:37 -0800830 // Update existing port states.
831 for (const auto& port : *known_ports) {
832 const auto& it = std::find_if(
833 active_ports->begin(), active_ports->end(),
834 [&port](const auto& candidate) { return *candidate == *port; });
835 if (it == active_ports->end()) {
836 if (port->Disconnect())
837 port->NotifyPortStateSet(this);
838 } else {
839 port->set_device_id((*it)->device_id());
840 if (port->Connect())
841 port->NotifyPortStateSet(this);
842 }
843 }
844
845 // Find new ports from active ports and append them to known ports.
846 for (auto& port : *active_ports) {
847 if (std::find_if(known_ports->begin(), known_ports->end(),
848 [&port](const auto& candidate) {
849 return *candidate == *port;
850 }) == known_ports->end()) {
851 size_t index = known_ports->size();
852 port->set_index(index);
853 known_ports->push_back(std::move(port));
854 (*known_ports)[index]->Connect();
855 (*known_ports)[index]->NotifyPortAdded(this);
856 }
857 }
858}
859
toyoshim63e32a52017-04-25 07:20:10 -0700860void MidiManagerWin::SendOnTaskRunner(MidiManagerClient* client,
861 uint32_t port_index,
862 const std::vector<uint8_t>& data) {
toyoshim6d87aaa2017-02-28 22:36:44 -0800863 CHECK_GT(port_manager_->outputs()->size(), port_index);
864 (*port_manager_->outputs())[port_index]->Send(data);
865 // |client| will be checked inside MidiManager::AccumulateMidiBytesSent.
Takashi Toyoshimad2bdc592017-09-13 10:02:54 +0000866 PostReplyTask(base::BindOnce(&MidiManagerWin::AccumulateMidiBytesSent,
867 base::Unretained(this), client, data.size()));
toyoshim6d87aaa2017-02-28 22:36:44 -0800868}
869
toyoshim15385b32017-04-24 23:52:01 -0700870MidiManager* MidiManager::Create(MidiService* service) {
871 if (base::FeatureList::IsEnabled(features::kMidiManagerWinrt) &&
toyoshim63e32a52017-04-25 07:20:10 -0700872 base::win::GetVersion() >= base::win::VERSION_WIN10) {
toyoshim15385b32017-04-24 23:52:01 -0700873 return new MidiManagerWinrt(service);
toyoshim63e32a52017-04-25 07:20:10 -0700874 }
875 return new MidiManagerWin(service);
toyoshim15385b32017-04-24 23:52:01 -0700876}
877
toyoshimd28b59c2017-02-20 11:07:37 -0800878} // namespace midi