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

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

Issue 29793006: Enable WebMIDI for Windows behind the flag (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Leave a TODO comment crbug.com/325810. Created 7 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « media/midi/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
(Empty)
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "media/midi/midi_manager_win.h"
6
7 #include <windows.h>
8
9 // Prevent unnecessary functions from being included from <mmsystem.h>
10 #define MMNODRV
11 #define MMNOSOUND
12 #define MMNOWAVE
13 #define MMNOAUX
14 #define MMNOMIXER
15 #define MMNOTIMER
16 #define MMNOJOY
17 #define MMNOMCI
18 #define MMNOMMIO
19 #include <mmsystem.h>
20
21 #include "base/bind.h"
22 #include "base/message_loop/message_loop.h"
23 #include "base/strings/string_number_conversions.h"
24 #include "base/strings/utf_string_conversions.h"
25 #include "base/threading/thread.h"
26 #include "media/midi/midi_message_queue.h"
27 #include "media/midi/midi_message_util.h"
28 #include "media/midi/midi_port_info.h"
29
30 namespace media {
31 namespace {
32
33 std::string GetInErrorMessage(MMRESULT result) {
34 wchar_t text[MAXERRORLENGTH];
35 MMRESULT get_result = midiInGetErrorText(result, text, arraysize(text));
36 if (get_result != MMSYSERR_NOERROR) {
37 DLOG(ERROR) << "Failed to get error message."
38 << " original error: " << result
39 << " midiInGetErrorText error: " << get_result;
40 return std::string();
41 }
42 return WideToUTF8(text);
43 }
44
45 std::string GetOutErrorMessage(MMRESULT result) {
46 wchar_t text[MAXERRORLENGTH];
47 MMRESULT get_result = midiOutGetErrorText(result, text, arraysize(text));
48 if (get_result != MMSYSERR_NOERROR) {
49 DLOG(ERROR) << "Failed to get error message."
50 << " original error: " << result
51 << " midiOutGetErrorText error: " << get_result;
52 return std::string();
53 }
54 return WideToUTF8(text);
55 }
56
57 class MIDIHDRDeleter {
58 public:
59 void operator()(MIDIHDR* header) {
60 if (!header)
61 return;
62 delete[] static_cast<char*>(header->lpData);
63 header->lpData = NULL;
64 header->dwBufferLength = 0;
65 delete header;
66 }
67 };
68
69 typedef scoped_ptr<MIDIHDR, MIDIHDRDeleter> ScopedMIDIHDR;
70
71 ScopedMIDIHDR CreateMIDIHDR(size_t size) {
72 ScopedMIDIHDR header(new MIDIHDR);
73 ZeroMemory(header.get(), sizeof(*header));
74 header->lpData = new char[size];
75 header->dwBufferLength = size;
76 return header.Pass();
77 }
78
79 void SendShortMIDIMessageInternal(HMIDIOUT midi_out_handle,
80 const std::vector<uint8>& message) {
81 if (message.size() >= 4)
82 return;
83
84 DWORD packed_message = 0;
85 for (size_t i = 0; i < message.size(); ++i)
86 packed_message |= (static_cast<uint32>(message[i]) << (i * 8));
87 MMRESULT result = midiOutShortMsg(midi_out_handle, packed_message);
88 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
89 << "Failed to output short message: " << GetOutErrorMessage(result);
90 }
91
92 void SendLongMIDIMessageInternal(HMIDIOUT midi_out_handle,
93 const std::vector<uint8>& message) {
94 // Implementation note:
95 // Sending long MIDI message can be performed synchronously or asynchronously
96 // depending on the driver. There are 2 options to support both cases:
97 // 1) Call midiOutLongMsg() API and wait for its completion within this
98 // function. In this approach, we can avoid memory copy by directly pointing
99 // |message| as the data buffer to be sent.
100 // 2) Allocate a buffer and copy |message| to it, then call midiOutLongMsg()
101 // API. The buffer will be freed in the MOM_DONE event hander, which tells
102 // us that the task of midiOutLongMsg() API is completed.
103 // Here we choose option 2) in favor of asynchronous design.
104
105 // Note for built-in USB-MIDI driver:
106 // From an observation on Windows 7/8.1 with a USB-MIDI keyboard,
107 // midiOutLongMsg() will be always blocked. Sending 64 bytes or less data
108 // takes roughly 300 usecs. Sending 2048 bytes or more data takes roughly
109 // |message.size() / (75 * 1024)| secs in practice. Here we put 60 KB size
110 // limit on SysEx message, with hoping that midiOutLongMsg will be blocked at
111 // most 1 sec or so with a typical USB-MIDI device.
112 const size_t kSysExSizeLimit = 60 * 1024;
113 if (message.size() >= kSysExSizeLimit) {
114 DVLOG(1) << "Ingnoreing SysEx message due to the size limit"
115 << ", size = " << message.size();
116 return;
117 }
118
119 ScopedMIDIHDR midi_header(CreateMIDIHDR(message.size()));
120 for (size_t i = 0; i < message.size(); ++i)
121 midi_header->lpData[i] = static_cast<char>(message[i]);
122
123 MMRESULT result = midiOutPrepareHeader(
124 midi_out_handle, midi_header.get(), sizeof(*midi_header));
125 if (result != MMSYSERR_NOERROR) {
126 DLOG(ERROR) << "Failed to prepare output buffer: "
127 << GetOutErrorMessage(result);
128 return;
129 }
130
131 result = midiOutLongMsg(
132 midi_out_handle, midi_header.get(), sizeof(*midi_header));
133 if (result != MMSYSERR_NOERROR) {
134 DLOG(ERROR) << "Failed to output long message: "
135 << GetOutErrorMessage(result);
136 result = midiOutUnprepareHeader(
137 midi_out_handle, midi_header.get(), sizeof(*midi_header));
138 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
139 << "Failed to uninitialize output buffer: "
140 << GetOutErrorMessage(result);
141 return;
142 }
143
144 // The ownership of |midi_header| is moved to MOM_DONE event handler.
145 midi_header.release();
146 }
147
148 } // namespace
149
150 class MIDIManagerWin::InDeviceInfo {
151 public:
152 ~InDeviceInfo() {
153 Uninitialize();
154 }
155 void set_port_index(int index) {
156 port_index_ = index;
157 }
158 int port_index() const {
159 return port_index_;
160 }
161 bool device_to_be_closed() const {
162 return device_to_be_closed_;
163 }
164 HMIDIIN midi_handle() const {
165 return midi_handle_;
166 }
167 const base::TimeDelta& start_time_offset() const {
168 return start_time_offset_;
169 }
170
171 static scoped_ptr<InDeviceInfo> Create(MIDIManagerWin* manager,
172 UINT device_id) {
173 scoped_ptr<InDeviceInfo> obj(new InDeviceInfo(manager));
174 if (!obj->Initialize(device_id))
175 obj.reset();
176 return obj.Pass();
177 }
178
179 private:
180 static const int kInvalidPortIndex = -1;
181 static const size_t kBufferLength = 32 * 1024;
182
183 explicit InDeviceInfo(MIDIManagerWin* manager)
184 : manager_(manager),
185 port_index_(kInvalidPortIndex),
186 midi_handle_(NULL),
187 started_(false),
188 device_to_be_closed_(false) {
189 }
190
191 bool Initialize(DWORD device_id) {
192 Uninitialize();
193 midi_header_ = CreateMIDIHDR(kBufferLength);
194
195 // Here we use |CALLBACK_FUNCTION| to subscribe MIM_DATA, MIM_LONGDATA, and
196 // MIM_CLOSE events.
197 // - MIM_DATA: This is the only way to get a short MIDI message with
198 // timestamp information.
199 // - MIM_LONGDATA: This is the only way to get a long MIDI message with
200 // timestamp information.
201 // - MIM_CLOSE: This event is sent when 1) midiInClose() is called, or 2)
202 // the MIDI device becomes unavailable for some reasons, e.g., the cable
203 // is disconnected. As for the former case, HMIDIOUT will be invalidated
204 // soon after the callback is finished. As for the later case, however,
205 // HMIDIOUT continues to be valid until midiInClose() is called.
206 MMRESULT result = midiInOpen(&midi_handle_,
207 device_id,
208 reinterpret_cast<DWORD_PTR>(&HandleMessage),
209 reinterpret_cast<DWORD_PTR>(this),
210 CALLBACK_FUNCTION);
211 if (result != MMSYSERR_NOERROR) {
212 DLOG(ERROR) << "Failed to open output device. "
213 << " id: " << device_id
214 << " message: " << GetInErrorMessage(result);
215 return false;
216 }
217 result = midiInPrepareHeader(
218 midi_handle_, midi_header_.get(), sizeof(*midi_header_));
219 if (result != MMSYSERR_NOERROR) {
220 DLOG(ERROR) << "Failed to initialize input buffer: "
221 << GetInErrorMessage(result);
222 return false;
223 }
224 result = midiInAddBuffer(
225 midi_handle_, midi_header_.get(), sizeof(*midi_header_));
226 if (result != MMSYSERR_NOERROR) {
227 DLOG(ERROR) << "Failed to attach input buffer: "
228 << GetInErrorMessage(result);
229 return false;
230 }
231 result = midiInStart(midi_handle_);
232 if (result != MMSYSERR_NOERROR) {
233 DLOG(ERROR) << "Failed to start input port: "
234 << GetInErrorMessage(result);
235 return false;
236 }
237 started_ = true;
238 start_time_offset_ = base::TimeTicks::Now() - base::TimeTicks();
239 return true;
240 }
241
242 void Uninitialize() {
243 MMRESULT result = MMSYSERR_NOERROR;
244 if (midi_handle_ && started_) {
245 result = midiInStop(midi_handle_);
246 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
247 << "Failed to stop input port: " << GetInErrorMessage(result);
248 started_ = false;
249 start_time_offset_ = base::TimeDelta();
250 }
251 if (midi_handle_) {
252 // midiInReset flushes pending messages. We ignore these messages.
253 device_to_be_closed_ = true;
254 result = midiInReset(midi_handle_);
255 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
256 << "Failed to reset input port: " << GetInErrorMessage(result);
257 result = midiInClose(midi_handle_);
258 device_to_be_closed_ = false;
259 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
260 << "Failed to close input port: " << GetInErrorMessage(result);
261 midi_header_.reset();
262 midi_handle_ = NULL;
263 port_index_ = kInvalidPortIndex;
264 }
265 }
266
267 static void CALLBACK HandleMessage(HMIDIIN midi_in_handle,
268 UINT message,
269 DWORD_PTR instance,
270 DWORD_PTR param1,
271 DWORD_PTR param2) {
272 // This method can be called back on any thread depending on Windows
273 // multimedia subsystem and underlying MIDI drivers.
274 InDeviceInfo* self = reinterpret_cast<InDeviceInfo*>(instance);
275 if (!self)
276 return;
277 if (self->midi_handle() != midi_in_handle)
278 return;
279
280 switch (message) {
281 case MIM_DATA:
282 self->OnShortMessageReceived(static_cast<uint8>(param1 & 0xff),
283 static_cast<uint8>((param1 >> 8) & 0xff),
284 static_cast<uint8>((param1 >> 16) & 0xff),
285 self->TickToTimeDelta(param2));
286 return;
287 case MIM_LONGDATA:
288 self->OnLongMessageReceived(reinterpret_cast<MIDIHDR*>(param1),
289 self->TickToTimeDelta(param2));
290 return;
291 case MIM_CLOSE:
292 // TODO(yukawa): Implement crbug.com/279097.
293 return;
294 }
295 }
296
297 void OnShortMessageReceived(uint8 status_byte,
298 uint8 first_data_byte,
299 uint8 second_data_byte,
300 base::TimeDelta timestamp) {
301 if (device_to_be_closed())
302 return;
303 const size_t len = GetMIDIMessageLength(status_byte);
304 if (len == 0 || port_index() == kInvalidPortIndex)
305 return;
306 const uint8 kData[] = { status_byte, first_data_byte, second_data_byte };
307 DCHECK_LE(len, arraysize(kData));
308 manager_->ReceiveMIDIData(port_index(), kData, len, timestamp.InSecondsF());
309 }
310
311 void OnLongMessageReceived(MIDIHDR* header, base::TimeDelta timestamp) {
312 if (header != midi_header_.get())
313 return;
314 MMRESULT result = MMSYSERR_NOERROR;
315 if (device_to_be_closed()) {
316 if (midi_header_ &&
317 (midi_header_->dwFlags & MHDR_PREPARED) == MHDR_PREPARED) {
318 result = midiInUnprepareHeader(
319 midi_handle_, midi_header_.get(), sizeof(*midi_header_));
320 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
321 << "Failed to uninitialize input buffer: "
322 << GetInErrorMessage(result);
323 }
324 return;
325 }
326 if (header->dwBytesRecorded > 0 && port_index() != kInvalidPortIndex) {
327 manager_->ReceiveMIDIData(port_index_,
328 reinterpret_cast<const uint8*>(header->lpData),
329 header->dwBytesRecorded,
330 timestamp.InSecondsF());
331 }
332 result = midiInAddBuffer(midi_handle(), header, sizeof(*header));
333 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
334 << "Failed to attach input port: " << GetInErrorMessage(result);
335 }
336
337 base::TimeDelta TickToTimeDelta(DWORD tick) const {
338 const base::TimeDelta delta =
339 base::TimeDelta::FromMicroseconds(static_cast<uint32>(tick));
340 return start_time_offset_ + delta;
341 }
342
343 MIDIManagerWin* manager_;
344 int port_index_;
345 HMIDIIN midi_handle_;
346 ScopedMIDIHDR midi_header_;
347 base::TimeDelta start_time_offset_;
348 bool started_;
349 bool device_to_be_closed_;
350 DISALLOW_COPY_AND_ASSIGN(MIDIManagerWin::InDeviceInfo);
351 };
352
353 class MIDIManagerWin::OutDeviceInfo {
354 public:
355 ~OutDeviceInfo() {
356 Uninitialize();
357 }
358
359 static scoped_ptr<OutDeviceInfo> Create(UINT device_id) {
360 scoped_ptr<OutDeviceInfo> obj(new OutDeviceInfo);
361 if (!obj->Initialize(device_id))
362 obj.reset();
363 return obj.Pass();
364 }
365
366 HMIDIOUT midi_handle() const {
367 return midi_handle_;
368 }
369
370 void Quit() {
371 quitting_ = true;
372 }
373
374 void Send(const std::vector<uint8>& data) {
375 // Check if the attached device is still available or not.
376 if (!midi_handle_)
377 return;
378
379 // Give up sending MIDI messages here if the device is already closed.
380 // Note that this check is optional. Regardless of that we check |closed_|
381 // or not, nothing harmful happens as long as |midi_handle_| is still valid.
382 if (closed_)
383 return;
384
385 // MIDI Running status must be filtered out.
386 MIDIMessageQueue message_queue(false);
387 message_queue.Add(data);
388 std::vector<uint8> message;
389 while (!quitting_) {
390 message_queue.Get(&message);
391 if (message.empty())
392 break;
393 // SendShortMIDIMessageInternal can send a MIDI message up to 3 bytes.
394 if (message.size() <= 3)
395 SendShortMIDIMessageInternal(midi_handle_, message);
396 else
397 SendLongMIDIMessageInternal(midi_handle_, message);
398 }
399 }
400
401 private:
402 OutDeviceInfo()
403 : midi_handle_(NULL),
404 closed_(false),
405 quitting_(false) {}
406
407 bool Initialize(DWORD device_id) {
408 Uninitialize();
409 // Here we use |CALLBACK_FUNCTION| to subscribe MOM_DONE and MOM_CLOSE
410 // events.
411 // - MOM_DONE: SendLongMIDIMessageInternal() relies on this event to clean
412 // up the backing store where a long MIDI message is stored.
413 // - MOM_CLOSE: This event is sent when 1) midiOutClose() is called, or 2)
414 // the MIDI device becomes unavailable for some reasons, e.g., the cable
415 // is disconnected. As for the former case, HMIDIOUT will be invalidated
416 // soon after the callback is finished. As for the later case, however,
417 // HMIDIOUT continues to be valid until midiOutClose() is called.
418 MMRESULT result = midiOutOpen(&midi_handle_,
419 device_id,
420 reinterpret_cast<DWORD_PTR>(&HandleMessage),
421 reinterpret_cast<DWORD_PTR>(this),
422 CALLBACK_FUNCTION);
423 if (result != MMSYSERR_NOERROR) {
424 DLOG(ERROR) << "Failed to open output device. "
425 << " id: " << device_id
426 << " message: "<< GetOutErrorMessage(result);
427 midi_handle_ = NULL;
428 return false;
429 }
430 return true;
431 }
432
433 void Uninitialize() {
434 if (!midi_handle_)
435 return;
436
437 MMRESULT result = midiOutReset(midi_handle_);
438 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
439 << "Failed to reset output port: " << GetOutErrorMessage(result);
440 result = midiOutClose(midi_handle_);
441 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
442 << "Failed to close output port: " << GetOutErrorMessage(result);
443 midi_handle_ = NULL;
444 closed_ = true;
445 }
446
447 static void CALLBACK HandleMessage(HMIDIOUT midi_out_handle,
448 UINT message,
449 DWORD_PTR instance,
450 DWORD_PTR param1,
451 DWORD_PTR param2) {
452 // This method can be called back on any thread depending on Windows
453 // multimedia subsystem and underlying MIDI drivers.
454
455 OutDeviceInfo* self = reinterpret_cast<OutDeviceInfo*>(instance);
456 if (!self)
457 return;
458 if (self->midi_handle() != midi_out_handle)
459 return;
460 switch (message) {
461 case MOM_DONE: {
462 // Take ownership of the MIDIHDR object.
463 ScopedMIDIHDR header(reinterpret_cast<MIDIHDR*>(param1));
464 if (!header)
465 return;
466 MMRESULT result = midiOutUnprepareHeader(
467 self->midi_handle(), header.get(), sizeof(*header));
468 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
469 << "Failed to uninitialize output buffer: "
470 << GetOutErrorMessage(result);
471 return;
472 }
473 case MOM_CLOSE:
474 // No lock is required since this flag is just a hint to avoid
475 // unnecessary API calls that will result in failure anyway.
476 self->closed_ = true;
477 // TODO(yukawa): Implement crbug.com/279097.
478 return;
479 }
480 }
481
482 HMIDIOUT midi_handle_;
483
484 // True if the device is already closed.
485 volatile bool closed_;
486
487 // True if the MIDIManagerWin is trying to stop the sender thread.
488 volatile bool quitting_;
489
490 DISALLOW_COPY_AND_ASSIGN(MIDIManagerWin::OutDeviceInfo);
491 };
492
493 MIDIManagerWin::MIDIManagerWin()
494 : send_thread_("MIDISendThread") {
495 }
496
497 bool MIDIManagerWin::Initialize() {
498 const UINT num_in_devices = midiInGetNumDevs();
499 in_devices_.reserve(num_in_devices);
500 for (UINT device_id = 0; device_id < num_in_devices; ++device_id) {
501 MIDIINCAPS caps = {};
502 MMRESULT result = midiInGetDevCaps(device_id, &caps, sizeof(caps));
503 if (result != MMSYSERR_NOERROR) {
504 DLOG(ERROR) << "Failed to obtain input device info: "
505 << GetInErrorMessage(result);
506 continue;
507 }
508 scoped_ptr<InDeviceInfo> in_device(InDeviceInfo::Create(this, device_id));
509 if (!in_device)
510 continue;
511 MIDIPortInfo info(
512 base::IntToString(static_cast<int>(device_id)),
513 "",
514 base::WideToUTF8(caps.szPname),
515 base::IntToString(static_cast<int>(caps.vDriverVersion)));
516 AddInputPort(info);
517 in_device->set_port_index(input_ports_.size() - 1);
518 in_devices_.push_back(in_device.Pass());
519 }
520
521 const UINT num_out_devices = midiOutGetNumDevs();
522 out_devices_.reserve(num_out_devices);
523 for (UINT device_id = 0; device_id < num_out_devices; ++device_id) {
524 MIDIOUTCAPS caps = {};
525 MMRESULT result = midiOutGetDevCaps(device_id, &caps, sizeof(caps));
526 if (result != MMSYSERR_NOERROR) {
527 DLOG(ERROR) << "Failed to obtain output device info: "
528 << GetOutErrorMessage(result);
529 continue;
530 }
531 scoped_ptr<OutDeviceInfo> out_port(OutDeviceInfo::Create(device_id));
532 if (!out_port)
533 continue;
534 MIDIPortInfo info(
535 base::IntToString(static_cast<int>(device_id)),
536 "",
537 base::WideToUTF8(caps.szPname),
538 base::IntToString(static_cast<int>(caps.vDriverVersion)));
539 AddOutputPort(info);
540 out_devices_.push_back(out_port.Pass());
541 }
542
543 return true;
544 }
545
546 MIDIManagerWin::~MIDIManagerWin() {
547 // Cleanup order is important. |send_thread_| must be stopped before
548 // |out_devices_| is cleared.
549 for (size_t i = 0; i < output_ports_.size(); ++i)
550 out_devices_[i]->Quit();
551 send_thread_.Stop();
552
553 out_devices_.clear();
554 output_ports_.clear();
555 in_devices_.clear();
556 input_ports_.clear();
557 }
558
559 void MIDIManagerWin::DispatchSendMIDIData(MIDIManagerClient* client,
560 uint32 port_index,
561 const std::vector<uint8>& data,
562 double timestamp) {
563 if (out_devices_.size() <= port_index)
564 return;
565
566 base::TimeDelta delay;
567 if (timestamp != 0.0) {
568 base::TimeTicks time_to_send =
569 base::TimeTicks() + base::TimeDelta::FromMicroseconds(
570 timestamp * base::Time::kMicrosecondsPerSecond);
571 delay = std::max(time_to_send - base::TimeTicks::Now(), base::TimeDelta());
572 }
573
574 if (!send_thread_.IsRunning())
575 send_thread_.Start();
576
577 OutDeviceInfo* out_port = out_devices_[port_index].get();
578 send_thread_.message_loop()->PostDelayedTask(
579 FROM_HERE,
580 base::Bind(&OutDeviceInfo::Send, base::Unretained(out_port), data),
581 delay);
582
583 // Call back AccumulateMIDIBytesSent() on |send_thread_| to emulate the
584 // behavior of MIDIManagerMac::SendMIDIData.
585 // TODO(yukawa): Do this task in a platform-independent way if possible.
586 // See crbug.com/325810.
587 send_thread_.message_loop()->PostTask(
588 FROM_HERE,
589 base::Bind(&MIDIManagerClient::AccumulateMIDIBytesSent,
590 base::Unretained(client), data.size()));
591 }
592
593 MIDIManager* MIDIManager::Create() {
594 return new MIDIManagerWin();
595 }
596
597 } // namespace media
OLDNEW
« no previous file with comments | « media/midi/midi_manager_win.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698