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

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: 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 use option 2) in favor of asynchronous design.
104 //
105 // Open question:
106 // In the era of USB-MIDI, OS built-in driver seems to always perform
107 // synchronously. Thus option 1) still might be a good option.
108
109 ScopedMIDIHDR midi_header(CreateMIDIHDR(message.size()));
110 for (size_t i = 0; i < message.size(); ++i)
111 midi_header->lpData[i] = static_cast<char>(message[i]);
112
113 MMRESULT result = midiOutPrepareHeader(
114 midi_out_handle, midi_header.get(), sizeof(*midi_header));
115 if (result != MMSYSERR_NOERROR) {
116 DLOG(ERROR) << "Failed to prepare output buffer: "
117 << GetOutErrorMessage(result);
118 return;
119 }
120
121 result = midiOutLongMsg(
122 midi_out_handle, midi_header.get(), sizeof(*midi_header));
123 if (result != MMSYSERR_NOERROR) {
124 DLOG(ERROR) << "Failed to output long message: "
125 << GetOutErrorMessage(result);
126 result = midiOutUnprepareHeader(
127 midi_out_handle, midi_header.get(), sizeof(*midi_header));
128 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
129 << "Failed to uninitialize output buffer: "
130 << GetOutErrorMessage(result);
131 return;
132 }
133
134 // The ownership of |midi_header| is moved to MOM_DONE event handler.
135 midi_header.release();
136 }
137
138 void SendMIDIDataInternal(HMIDIOUT midi_out_handle,
139 const std::vector<uint8>& data) {
140 // MIDI Running status must not be enabled for |data|.
141 MIDIMessageQueue message_queue(false);
142 message_queue.Add(data);
143 std::vector<uint8> message;
144 while (true) {
145 message_queue.Get(&message);
146 if (message.empty())
147 break;
148 // SendShortMIDIMessageInternal can send a MIDI message up to 3 bytes.
149 if (message.size() <= 3)
150 SendShortMIDIMessageInternal(midi_out_handle, message);
151 else
152 SendLongMIDIMessageInternal(midi_out_handle, message);
153 }
154 }
155
156 } // namespace
157
158 class MIDIManagerWin::InPortInfo {
159 public:
160 ~InPortInfo() {
161 Uninitialize();
162 }
163 void set_port_index(int index) {
164 port_index_ = index;
165 }
166 int port_index() const {
167 return port_index_;
168 }
169 bool device_to_be_closed() const {
170 return device_to_be_closed_;
171 }
172 HMIDIIN midi_handle() const {
173 return midi_handle_;
174 }
175 const base::TimeDelta& start_time_offset() const {
176 return start_time_offset_;
177 }
178
179 static scoped_ptr<InPortInfo> Create(MIDIManagerWin* manager,
180 UINT device_id) {
181 scoped_ptr<InPortInfo> obj(new InPortInfo(manager));
182 if (!obj->Initialize(device_id))
183 obj.reset(NULL);
184 return obj.Pass();
185 }
186
187 private:
188 static const int kInvalidPortIndex = -1;
189 static const size_t kBufferLength = 32 * 1024;
190
191 explicit InPortInfo(MIDIManagerWin* manager)
192 : manager_(manager),
193 port_index_(kInvalidPortIndex),
194 midi_handle_(NULL),
195 started_(false),
196 device_to_be_closed_(false) {
197 }
198
199 bool Initialize(DWORD device_id) {
200 Uninitialize();
201 midi_header_ = CreateMIDIHDR(kBufferLength);
202
203 // Here we use |CALLBACK_FUNCTION| to subscribe MIM_DATA, MIM_LONGDATA, and
204 // MIM_CLOSE events.
205 // - MIM_DATA: This is the only way to get a short MIDI message with
206 // timestamp information.
207 // - MIM_LONGDATA: This is the only way to get a long MIDI message with
208 // timestamp information.
209 // - MIN_CLOSE: This event is notified when the device is removed logically
210 // or physically.
211 MMRESULT result = midiInOpen(&midi_handle_,
212 device_id,
213 reinterpret_cast<DWORD_PTR>(&HandleMessage),
214 reinterpret_cast<DWORD_PTR>(this),
215 CALLBACK_FUNCTION);
216 if (result != MMSYSERR_NOERROR) {
217 DLOG(ERROR) << "Failed to open output device. "
218 << " id: " << device_id
219 << " message: " << GetInErrorMessage(result);
220 return false;
221 }
222 result = midiInPrepareHeader(
223 midi_handle_, midi_header_.get(), sizeof(*midi_header_));
224 if (result != MMSYSERR_NOERROR) {
225 DLOG(ERROR) << "Failed to initialize input buffer: "
226 << GetInErrorMessage(result);
227 return false;
228 }
229 result = midiInAddBuffer(
230 midi_handle_, midi_header_.get(), sizeof(*midi_header_));
231 if (result != MMSYSERR_NOERROR) {
232 DLOG(ERROR) << "Failed to attach input buffer: "
233 << GetInErrorMessage(result);
234 return false;
235 }
236 result = midiInStart(midi_handle_);
237 if (result != MMSYSERR_NOERROR) {
238 DLOG(ERROR) << "Failed to start input port: "
239 << GetInErrorMessage(result);
240 return false;
241 }
242 started_ = true;
243 start_time_offset_ = base::TimeTicks::Now() - base::TimeTicks();
244 return true;
245 }
246
247 void Uninitialize() {
248 MMRESULT result = MMSYSERR_NOERROR;
249 if (midi_handle_ && started_) {
250 result = midiInStop(midi_handle_);
251 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
252 << "Failed to stop input port: " << GetInErrorMessage(result);
253 started_ = false;
254 start_time_offset_ = base::TimeDelta();
255 }
256 if (midi_handle_) {
257 // midiInReset flushes pending messages. We ignore these messages.
258 device_to_be_closed_ = true;
259 result = midiInReset(midi_handle_);
260 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
261 << "Failed to reset input port: " << GetInErrorMessage(result);
262 result = midiInClose(midi_handle_);
263 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
264 << "Failed to close input port: " << GetInErrorMessage(result);
265 }
266 device_to_be_closed_ = false;
267 }
268
269 static void CALLBACK HandleMessage(HMIDIIN midi_in_handle,
270 UINT message,
271 DWORD_PTR instance,
272 DWORD_PTR param1,
273 DWORD_PTR param2) {
274 // This method is called back by Windows multimedia subsystem and not always
275 // in the UI thread.
276 InPortInfo* self = reinterpret_cast<InPortInfo*>(instance);
277 if (!self)
278 return;
279 if (self->midi_handle() != midi_in_handle)
280 return;
281 self->HandleMessageInternal(message, param1, param2);
282 }
283
284 void HandleMessageInternal(UINT message, DWORD_PTR param1, DWORD_PTR param2) {
285 // This method is called from HandleMessage method and not always in the UI
286 // thread.
287 switch (message) {
288 case MIM_DATA:
289 HandleShortMessageInternal(static_cast<uint8>(param1 & 0xff),
290 static_cast<uint8>((param1 >> 8) & 0xff),
291 static_cast<uint8>((param1 >> 16) & 0xff),
292 TickToTimeDelta(param2));
293 return;
294 case MIM_LONGDATA:
295 HandleLongMessageInternal(reinterpret_cast<MIDIHDR*>(param1),
296 TickToTimeDelta(param2));
297 return;
298 case MIM_CLOSE:
299 HandleCloseMessageInternal();
300 }
301 }
302
303 void HandleShortMessageInternal(uint8 status_byte,
304 uint8 first_data_byte,
305 uint8 second_data_byte,
306 base::TimeDelta timestamp) {
307 if (device_to_be_closed())
308 return;
309 const size_t len = GetMIDIMessageLength(status_byte);
310 if (len == 0 || port_index() == kInvalidPortIndex)
311 return;
312 const uint8 kData[] = { status_byte, first_data_byte, second_data_byte };
313 DCHECK_LE(len, arraysize(kData));
314 manager_->ReceiveMIDIData(port_index(), kData, len, timestamp.InSecondsF());
315 }
316
317 void HandleLongMessageInternal(MIDIHDR* header, base::TimeDelta timestamp) {
318 if (header != midi_header_.get())
319 return;
320 MMRESULT result = MMSYSERR_NOERROR;
321 if (device_to_be_closed()) {
322 if (midi_header_ &&
323 (midi_header_->dwFlags & MHDR_PREPARED) == MHDR_PREPARED) {
324 result = midiInUnprepareHeader(
325 midi_handle_, midi_header_.get(), sizeof(*midi_header_));
326 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
327 << "Failed to uninitialize input buffer: "
328 << GetInErrorMessage(result);
329 }
330 return;
331 }
332 if (header->dwBytesRecorded > 0 && port_index() != kInvalidPortIndex) {
333 manager_->ReceiveMIDIData(port_index_,
334 reinterpret_cast<const uint8*>(header->lpData),
335 header->dwBytesRecorded,
336 timestamp.InSecondsF());
337 }
338 result = midiInAddBuffer(midi_handle(), header, sizeof(*header));
339 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
340 << "Failed to attach input port: " << GetInErrorMessage(result);
341 }
342
343 void HandleCloseMessageInternal() {
344 if (midi_handle_ && midi_header_ &&
345 (midi_header_->dwFlags & MHDR_PREPARED) == MHDR_PREPARED) {
346 MMRESULT result = midiInUnprepareHeader(
347 midi_handle_, midi_header_.get(), sizeof(*midi_header_));
348 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
349 << "Failed to uninitialize input buffer: "
350 << GetInErrorMessage(result);
351 }
352 midi_header_.reset();
353 midi_handle_ = NULL;
354 port_index_ = kInvalidPortIndex;
355 return;
356 }
357
358 base::TimeDelta TickToTimeDelta(DWORD tick) const {
359 const base::TimeDelta delta =
360 base::TimeDelta::FromMicroseconds(static_cast<uint32>(tick));
361 return start_time_offset_ + delta;
362 }
363
364 MIDIManagerWin* manager_;
365 int port_index_;
366 HMIDIIN midi_handle_;
367 ScopedMIDIHDR midi_header_;
368 base::TimeDelta start_time_offset_;
369 bool started_;
370 bool device_to_be_closed_;
371 DISALLOW_COPY_AND_ASSIGN(MIDIManagerWin::InPortInfo);
372 };
373
374 class MIDIManagerWin::OutPortInfo {
375 public:
376 ~OutPortInfo() {
377 Uninitialize();
378 }
379
380 HMIDIOUT midi_handle() const {
381 return midi_handle_;
382 }
383
384 static scoped_ptr<OutPortInfo> Create(UINT device_id) {
385 scoped_ptr<OutPortInfo> obj(new OutPortInfo);
386 if (!obj->Initialize(device_id))
387 obj.reset(NULL);
388 return obj.Pass();
389 }
390
391 private:
392 OutPortInfo()
393 : midi_handle_(NULL) {}
394
395 bool Initialize(DWORD device_id) {
396 Uninitialize();
397 // Here we use |CALLBACK_FUNCTION| to subscribe MOM_DONE and MOM_CLOSE
398 // events.
399 // - MOM_DONE: SendLongMIDIMessageInternal relies on this event to clean up
400 // the backing store where a long MIDI message is stored.
401 // - MOM_CLOSE: This event is notified when the device is removed logically
402 // or physically.
403 MMRESULT result = midiOutOpen(&midi_handle_,
404 device_id,
405 reinterpret_cast<DWORD_PTR>(&HandleMessage),
406 reinterpret_cast<DWORD_PTR>(this),
407 CALLBACK_FUNCTION);
408 if (result != MMSYSERR_NOERROR) {
409 DLOG(ERROR) << "Failed to open output device. "
410 << " id: " << device_id
411 << " message: "<< GetOutErrorMessage(result);
412 midi_handle_ = NULL;
413 return false;
414 }
415 return true;
416 }
417
418 void Uninitialize() {
419 if (midi_handle_ == NULL)
420 return;
421 MMRESULT result = midiOutReset(midi_handle_);
422 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
423 << "Failed to reset output port: " << GetOutErrorMessage(result);
424 result = midiOutClose(midi_handle_);
425 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
426 << "Failed to close output port: " << GetOutErrorMessage(result);
427 midi_handle_ = NULL;
428 }
429
430 static void CALLBACK HandleMessage(HMIDIIN midi_in_handle,
431 UINT message,
432 DWORD_PTR instance,
433 DWORD_PTR param1,
434 DWORD_PTR param2) {
435 // This method is called back by Windows multimedia subsystem and not always
436 // in the UI thread.
437
438 OutPortInfo* self = reinterpret_cast<OutPortInfo*>(instance);
439 if (!self)
440 return;
441 switch (message) {
442 case MOM_DONE: {
443 // Take the ownership of the MIDIHDR object.
444 ScopedMIDIHDR header(reinterpret_cast<MIDIHDR*>(param1));
445 if (!header)
446 return;
447 MMRESULT result = midiOutUnprepareHeader(
448 self->midi_handle(), header.get(), sizeof(*header));
449 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
450 << "Failed to uninitialize output buffer: "
451 << GetOutErrorMessage(result);
452 return;
453 }
454 case MOM_CLOSE:
455 self->midi_handle_ = NULL;
456 return;
457 }
458 }
459
460 HMIDIOUT midi_handle_;
461 DISALLOW_COPY_AND_ASSIGN(MIDIManagerWin::OutPortInfo);
462 };
463
464 MIDIManagerWin::MIDIManagerWin() {
Takashi Toyoshima 2013/11/27 14:18:52 I don't think I understand this comment correctly,
yukawa 2013/11/27 15:23:37 Oops, this is just a garbage. Removed.
465 // Wait for |send_thread_| is finished before all the members and
466 // overriden methods of MIDIManagerWin are invalidated.
467 send_thread_.reset();
468 }
469
470 bool MIDIManagerWin::Initialize() {
471 {
472 const UINT num_in_devices = midiInGetNumDevs();
473 in_ports_.reserve(num_in_devices);
474 for (UINT device_id = 0; device_id < num_in_devices; ++device_id) {
475 MIDIINCAPS caps = {};
476 MMRESULT result = midiInGetDevCaps(device_id, &caps, sizeof(caps));
477 if (result != MMSYSERR_NOERROR) {
478 DLOG(ERROR) << "Failed to obtain input device info: "
479 << GetInErrorMessage(result);
480 continue;
481 }
482 scoped_ptr<InPortInfo> in_port(InPortInfo::Create(this, device_id));
483 if (!in_port)
484 continue;
485 MIDIPortInfo info(
486 base::IntToString(static_cast<int>(device_id)),
487 "",
488 base::WideToUTF8(caps.szPname),
489 base::IntToString(static_cast<int>(caps.vDriverVersion)));
490 AddInputPort(info);
491 in_port->set_port_index(input_ports_.size() - 1);
492 in_ports_.push_back(in_port.Pass());
493 }
494 }
495 {
496 const UINT num_out_devices = midiOutGetNumDevs();
497 out_ports_.reserve(num_out_devices);
498 for (UINT device_id = 0; device_id < num_out_devices; ++device_id) {
499 MIDIOUTCAPS caps = {};
500 MMRESULT result = midiOutGetDevCaps(device_id, &caps, sizeof(caps));
501 if (result != MMSYSERR_NOERROR) {
502 DLOG(ERROR) << "Failed to obtain output device info: "
503 << GetOutErrorMessage(result);
504 continue;
505 }
506 scoped_ptr<OutPortInfo> out_port(OutPortInfo::Create(device_id));
507 if (!out_port)
508 continue;
509 MIDIPortInfo info(
510 base::IntToString(static_cast<int>(device_id)),
511 "",
512 base::WideToUTF8(caps.szPname),
513 base::IntToString(static_cast<int>(caps.vDriverVersion)));
514 AddOutputPort(info);
515 out_ports_.push_back(out_port.Pass());
516 }
517 }
518 return true;
519 }
520
521 MIDIManagerWin::~MIDIManagerWin() {
522 // |send_thread_| must be terminated here to ensure that
523 // MIDIManagerWin::SendMIDIData will not be called any longer. A drawback
524 // of this approach is if |send_thread_| is somehow blocked forever,
525 // the UI thread is also blocked forever.
526 send_thread_.reset(NULL);
Takashi Toyoshima 2013/11/27 14:18:52 Hum... The same issue seems to happen in mac poten
yukawa 2013/11/27 15:23:37 Probably yes. I'm making a different CL to do it.
527 }
528
529 void MIDIManagerWin::SendMIDIData(MIDIManagerClient* client,
530 uint32 port_index,
531 const std::vector<uint8>& data,
532 double timestamp) {
533 DCHECK(CurrentlyOnMIDISendThread());
534 // Caveat: Whether |this| is still valid or not is non-trivial question
535 // because this method is called back in a background thread that is owned by
536 // MIDIManager rather than MIDIManagerWin. Currently |this| is considered to
537 // be always valid because ~MIDIManagerWin() internally waits for the
538 // termination of this background thread.
539
540 if (out_ports_.size() <= port_index)
541 return;
542
543 // Check if the target device is still available. Note that |handle| can be
544 // NULL once the device is logically or physically removed.
545 const HMIDIOUT handle = out_ports_[port_index]->midi_handle();
546 if (!handle)
547 return;
548
549 base::TimeDelta delay;
550 if (timestamp != 0.0) {
551 base::TimeTicks time_to_send =
552 base::TimeTicks() + base::TimeDelta::FromMicroseconds(
553 timestamp * base::Time::kMicrosecondsPerSecond);
554 delay = time_to_send - base::TimeTicks::Now();
555 }
556
557 if (delay.InMilliseconds() <= 0) {
558 SendMIDIDataInternal(handle, data);
559 } else {
560 base::MessageLoop::current()->PostDelayedTask(
561 FROM_HERE,
562 base::Bind(&SendMIDIDataInternal, handle, data),
563 delay);
564 }
565
566 client->AccumulateMIDIBytesSent(data.size());
567 }
568
569 MIDIManager* MIDIManager::Create() {
570 return new MIDIManagerWin();
571 }
572
573 } // 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