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