Upstream version 5.34.104.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 "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 base::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 base::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
168   static scoped_ptr<InDeviceInfo> Create(MidiManagerWin* manager,
169                                          UINT device_id) {
170     scoped_ptr<InDeviceInfo> obj(new InDeviceInfo(manager));
171     if (!obj->Initialize(device_id))
172       obj.reset();
173     return obj.Pass();
174   }
175
176  private:
177   static const int kInvalidPortIndex = -1;
178   static const size_t kBufferLength = 32 * 1024;
179
180   explicit InDeviceInfo(MidiManagerWin* manager)
181       : manager_(manager),
182         port_index_(kInvalidPortIndex),
183         midi_handle_(NULL),
184         started_(false),
185         device_to_be_closed_(false) {
186   }
187
188   bool Initialize(DWORD device_id) {
189     Uninitialize();
190     midi_header_ = CreateMIDIHDR(kBufferLength);
191
192     // Here we use |CALLBACK_FUNCTION| to subscribe MIM_DATA, MIM_LONGDATA, and
193     // MIM_CLOSE events.
194     // - MIM_DATA: This is the only way to get a short MIDI message with
195     //     timestamp information.
196     // - MIM_LONGDATA: This is the only way to get a long MIDI message with
197     //     timestamp information.
198     // - MIM_CLOSE: This event is sent when 1) midiInClose() is called, or 2)
199     //     the MIDI device becomes unavailable for some reasons, e.g., the cable
200     //     is disconnected. As for the former case, HMIDIOUT will be invalidated
201     //     soon after the callback is finished. As for the later case, however,
202     //     HMIDIOUT continues to be valid until midiInClose() is called.
203     MMRESULT result = midiInOpen(&midi_handle_,
204                                  device_id,
205                                  reinterpret_cast<DWORD_PTR>(&HandleMessage),
206                                  reinterpret_cast<DWORD_PTR>(this),
207                                  CALLBACK_FUNCTION);
208     if (result != MMSYSERR_NOERROR) {
209       DLOG(ERROR) << "Failed to open output device. "
210                   << " id: " << device_id
211                   << " message: " << GetInErrorMessage(result);
212       return false;
213     }
214     result = midiInPrepareHeader(
215         midi_handle_, midi_header_.get(), sizeof(*midi_header_));
216     if (result != MMSYSERR_NOERROR) {
217       DLOG(ERROR) << "Failed to initialize input buffer: "
218                   << GetInErrorMessage(result);
219       return false;
220     }
221     result = midiInAddBuffer(
222         midi_handle_, midi_header_.get(), sizeof(*midi_header_));
223     if (result != MMSYSERR_NOERROR) {
224       DLOG(ERROR) << "Failed to attach input buffer: "
225                   << GetInErrorMessage(result);
226       return false;
227     }
228     result = midiInStart(midi_handle_);
229     if (result != MMSYSERR_NOERROR) {
230       DLOG(ERROR) << "Failed to start input port: "
231                   << GetInErrorMessage(result);
232       return false;
233     }
234     started_ = true;
235     start_time_ = base::TimeTicks::Now();
236     return true;
237   }
238
239   void Uninitialize() {
240     MMRESULT result = MMSYSERR_NOERROR;
241     if (midi_handle_ && started_) {
242       result = midiInStop(midi_handle_);
243       DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
244           << "Failed to stop input port: " << GetInErrorMessage(result);
245       started_ = false;
246       start_time_ = base::TimeTicks();
247     }
248     if (midi_handle_) {
249       // midiInReset flushes pending messages. We ignore these messages.
250       device_to_be_closed_ = true;
251       result = midiInReset(midi_handle_);
252       DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
253           << "Failed to reset input port: " << GetInErrorMessage(result);
254       result = midiInClose(midi_handle_);
255       device_to_be_closed_ = false;
256       DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
257           << "Failed to close input port: " << GetInErrorMessage(result);
258       midi_header_.reset();
259       midi_handle_ = NULL;
260       port_index_ = kInvalidPortIndex;
261     }
262   }
263
264   static void CALLBACK HandleMessage(HMIDIIN midi_in_handle,
265                                      UINT message,
266                                      DWORD_PTR instance,
267                                      DWORD_PTR param1,
268                                      DWORD_PTR param2) {
269     // This method can be called back on any thread depending on Windows
270     // multimedia subsystem and underlying MIDI drivers.
271     InDeviceInfo* self = reinterpret_cast<InDeviceInfo*>(instance);
272     if (!self)
273       return;
274     if (self->midi_handle() != midi_in_handle)
275       return;
276
277     switch (message) {
278       case MIM_DATA:
279         self->OnShortMessageReceived(static_cast<uint8>(param1 & 0xff),
280                                      static_cast<uint8>((param1 >> 8) & 0xff),
281                                      static_cast<uint8>((param1 >> 16) & 0xff),
282                                      param2);
283         return;
284       case MIM_LONGDATA:
285         self->OnLongMessageReceived(reinterpret_cast<MIDIHDR*>(param1),
286                                     param2);
287         return;
288       case MIM_CLOSE:
289         // TODO(yukawa): Implement crbug.com/279097.
290         return;
291     }
292   }
293
294   void OnShortMessageReceived(uint8 status_byte,
295                               uint8 first_data_byte,
296                               uint8 second_data_byte,
297                               DWORD elapsed_ms) {
298     if (device_to_be_closed())
299       return;
300     const size_t len = GetMidiMessageLength(status_byte);
301     if (len == 0 || port_index() == kInvalidPortIndex)
302       return;
303     const uint8 kData[] = { status_byte, first_data_byte, second_data_byte };
304     DCHECK_LE(len, arraysize(kData));
305     OnMessageReceived(kData, len, elapsed_ms);
306   }
307
308   void OnLongMessageReceived(MIDIHDR* header, DWORD elapsed_ms) {
309     if (header != midi_header_.get())
310       return;
311     MMRESULT result = MMSYSERR_NOERROR;
312     if (device_to_be_closed()) {
313       if (midi_header_ &&
314           (midi_header_->dwFlags & MHDR_PREPARED) == MHDR_PREPARED) {
315         result = midiInUnprepareHeader(
316             midi_handle_, midi_header_.get(), sizeof(*midi_header_));
317         DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
318             << "Failed to uninitialize input buffer: "
319             << GetInErrorMessage(result);
320       }
321       return;
322     }
323     if (header->dwBytesRecorded > 0 && port_index() != kInvalidPortIndex) {
324       OnMessageReceived(reinterpret_cast<const uint8*>(header->lpData),
325                         header->dwBytesRecorded,
326                         elapsed_ms);
327     }
328     result = midiInAddBuffer(midi_handle_, header, sizeof(*header));
329     DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
330         << "Failed to attach input port: " << GetInErrorMessage(result);
331   }
332
333   void OnMessageReceived(const uint8* data, size_t length, DWORD elapsed_ms) {
334     // MIM_DATA/MIM_LONGDATA message treats the time when midiInStart() is
335     // called as the origin of |elapsed_ms|.
336     // http://msdn.microsoft.com/en-us/library/windows/desktop/dd757284.aspx
337     // http://msdn.microsoft.com/en-us/library/windows/desktop/dd757286.aspx
338     const base::TimeTicks event_time =
339         start_time_ + base::TimeDelta::FromMilliseconds(elapsed_ms);
340     // MidiManager::ReceiveMidiData() expects |timestamp| as the elapsed seconds
341     // from base::TimeTicks::Now().
342     // TODO(yukawa): Update MidiManager::ReceiveMidiData() so that it can
343     // receive |event_time| directly if the precision of base::TimeTicks is
344     // sufficient.
345     const double timestamp = (event_time - base::TimeTicks()).InSecondsF();
346     manager_->ReceiveMidiData(port_index_, data, length, timestamp);
347   }
348
349   MidiManagerWin* manager_;
350   int port_index_;
351   HMIDIIN midi_handle_;
352   ScopedMIDIHDR midi_header_;
353   base::TimeTicks start_time_;
354   bool started_;
355   bool device_to_be_closed_;
356   DISALLOW_COPY_AND_ASSIGN(MidiManagerWin::InDeviceInfo);
357 };
358
359 class MidiManagerWin::OutDeviceInfo {
360  public:
361   ~OutDeviceInfo() {
362     Uninitialize();
363   }
364
365   static scoped_ptr<OutDeviceInfo> Create(UINT device_id) {
366     scoped_ptr<OutDeviceInfo> obj(new OutDeviceInfo);
367     if (!obj->Initialize(device_id))
368       obj.reset();
369     return obj.Pass();
370   }
371
372   HMIDIOUT midi_handle() const {
373     return midi_handle_;
374   }
375
376   void Quit() {
377     quitting_ = true;
378   }
379
380   void Send(const std::vector<uint8>& data) {
381     // Check if the attached device is still available or not.
382     if (!midi_handle_)
383       return;
384
385     // Give up sending MIDI messages here if the device is already closed.
386     // Note that this check is optional. Regardless of that we check |closed_|
387     // or not, nothing harmful happens as long as |midi_handle_| is still valid.
388     if (closed_)
389       return;
390
391     // MIDI Running status must be filtered out.
392     MidiMessageQueue message_queue(false);
393     message_queue.Add(data);
394     std::vector<uint8> message;
395     while (!quitting_) {
396       message_queue.Get(&message);
397       if (message.empty())
398         break;
399       // SendShortMidiMessageInternal can send a MIDI message up to 3 bytes.
400       if (message.size() <= 3)
401         SendShortMidiMessageInternal(midi_handle_, message);
402       else
403         SendLongMidiMessageInternal(midi_handle_, message);
404     }
405   }
406
407  private:
408   OutDeviceInfo()
409       : midi_handle_(NULL),
410         closed_(false),
411         quitting_(false) {}
412
413   bool Initialize(DWORD device_id) {
414     Uninitialize();
415     // Here we use |CALLBACK_FUNCTION| to subscribe MOM_DONE and MOM_CLOSE
416     // events.
417     // - MOM_DONE: SendLongMidiMessageInternal() relies on this event to clean
418     //     up the backing store where a long MIDI message is stored.
419     // - MOM_CLOSE: This event is sent when 1) midiOutClose() is called, or 2)
420     //     the MIDI device becomes unavailable for some reasons, e.g., the cable
421     //     is disconnected. As for the former case, HMIDIOUT will be invalidated
422     //     soon after the callback is finished. As for the later case, however,
423     //     HMIDIOUT continues to be valid until midiOutClose() is called.
424     MMRESULT result = midiOutOpen(&midi_handle_,
425                                   device_id,
426                                   reinterpret_cast<DWORD_PTR>(&HandleMessage),
427                                   reinterpret_cast<DWORD_PTR>(this),
428                                   CALLBACK_FUNCTION);
429     if (result != MMSYSERR_NOERROR) {
430       DLOG(ERROR) << "Failed to open output device. "
431                   << " id: " << device_id
432                   << " message: "<< GetOutErrorMessage(result);
433       midi_handle_ = NULL;
434       return false;
435     }
436     return true;
437   }
438
439   void Uninitialize() {
440     if (!midi_handle_)
441       return;
442
443     MMRESULT result = midiOutReset(midi_handle_);
444     DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
445         << "Failed to reset output port: " << GetOutErrorMessage(result);
446     result = midiOutClose(midi_handle_);
447     DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
448         << "Failed to close output port: " << GetOutErrorMessage(result);
449     midi_handle_ = NULL;
450     closed_ = true;
451   }
452
453   static void CALLBACK HandleMessage(HMIDIOUT midi_out_handle,
454                                      UINT message,
455                                      DWORD_PTR instance,
456                                      DWORD_PTR param1,
457                                      DWORD_PTR param2) {
458     // This method can be called back on any thread depending on Windows
459     // multimedia subsystem and underlying MIDI drivers.
460
461     OutDeviceInfo* self = reinterpret_cast<OutDeviceInfo*>(instance);
462     if (!self)
463       return;
464     if (self->midi_handle() != midi_out_handle)
465       return;
466     switch (message) {
467       case MOM_DONE: {
468         // Take ownership of the MIDIHDR object.
469         ScopedMIDIHDR header(reinterpret_cast<MIDIHDR*>(param1));
470         if (!header)
471           return;
472         MMRESULT result = midiOutUnprepareHeader(
473             self->midi_handle(), header.get(), sizeof(*header));
474         DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
475             << "Failed to uninitialize output buffer: "
476             << GetOutErrorMessage(result);
477         return;
478       }
479       case MOM_CLOSE:
480         // No lock is required since this flag is just a hint to avoid
481         // unnecessary API calls that will result in failure anyway.
482         self->closed_ = true;
483         // TODO(yukawa): Implement crbug.com/279097.
484         return;
485     }
486   }
487
488   HMIDIOUT midi_handle_;
489
490   // True if the device is already closed.
491   volatile bool closed_;
492
493   // True if the MidiManagerWin is trying to stop the sender thread.
494   volatile bool quitting_;
495
496   DISALLOW_COPY_AND_ASSIGN(MidiManagerWin::OutDeviceInfo);
497 };
498
499 MidiManagerWin::MidiManagerWin()
500     : send_thread_("MidiSendThread") {
501 }
502
503 bool MidiManagerWin::Initialize() {
504   const UINT num_in_devices = midiInGetNumDevs();
505   in_devices_.reserve(num_in_devices);
506   for (UINT device_id = 0; device_id < num_in_devices; ++device_id) {
507     MIDIINCAPS caps = {};
508     MMRESULT result = midiInGetDevCaps(device_id, &caps, sizeof(caps));
509     if (result != MMSYSERR_NOERROR) {
510       DLOG(ERROR) << "Failed to obtain input device info: "
511                   << GetInErrorMessage(result);
512       continue;
513     }
514     scoped_ptr<InDeviceInfo> in_device(InDeviceInfo::Create(this, device_id));
515     if (!in_device)
516       continue;
517     MidiPortInfo info(
518         base::IntToString(static_cast<int>(device_id)),
519         "",
520         base::WideToUTF8(caps.szPname),
521         base::IntToString(static_cast<int>(caps.vDriverVersion)));
522     AddInputPort(info);
523     in_device->set_port_index(input_ports_.size() - 1);
524     in_devices_.push_back(in_device.Pass());
525   }
526
527   const UINT num_out_devices = midiOutGetNumDevs();
528   out_devices_.reserve(num_out_devices);
529   for (UINT device_id = 0; device_id < num_out_devices; ++device_id) {
530     MIDIOUTCAPS caps = {};
531     MMRESULT result = midiOutGetDevCaps(device_id, &caps, sizeof(caps));
532     if (result != MMSYSERR_NOERROR) {
533       DLOG(ERROR) << "Failed to obtain output device info: "
534                   << GetOutErrorMessage(result);
535       continue;
536     }
537     scoped_ptr<OutDeviceInfo> out_port(OutDeviceInfo::Create(device_id));
538     if (!out_port)
539       continue;
540     MidiPortInfo info(
541         base::IntToString(static_cast<int>(device_id)),
542         "",
543         base::WideToUTF8(caps.szPname),
544         base::IntToString(static_cast<int>(caps.vDriverVersion)));
545     AddOutputPort(info);
546     out_devices_.push_back(out_port.Pass());
547   }
548
549   return true;
550 }
551
552 MidiManagerWin::~MidiManagerWin() {
553   // Cleanup order is important. |send_thread_| must be stopped before
554   // |out_devices_| is cleared.
555   for (size_t i = 0; i < output_ports_.size(); ++i)
556     out_devices_[i]->Quit();
557   send_thread_.Stop();
558
559   out_devices_.clear();
560   output_ports_.clear();
561   in_devices_.clear();
562   input_ports_.clear();
563 }
564
565 void MidiManagerWin::DispatchSendMidiData(MidiManagerClient* client,
566                                           uint32 port_index,
567                                           const std::vector<uint8>& data,
568                                           double timestamp) {
569   if (out_devices_.size() <= port_index)
570     return;
571
572   base::TimeDelta delay;
573   if (timestamp != 0.0) {
574     base::TimeTicks time_to_send =
575         base::TimeTicks() + base::TimeDelta::FromMicroseconds(
576             timestamp * base::Time::kMicrosecondsPerSecond);
577     delay = std::max(time_to_send - base::TimeTicks::Now(), base::TimeDelta());
578   }
579
580   if (!send_thread_.IsRunning())
581     send_thread_.Start();
582
583   OutDeviceInfo* out_port = out_devices_[port_index].get();
584   send_thread_.message_loop()->PostDelayedTask(
585       FROM_HERE,
586       base::Bind(&OutDeviceInfo::Send, base::Unretained(out_port), data),
587       delay);
588
589   // Call back AccumulateMidiBytesSent() on |send_thread_| to emulate the
590   // behavior of MidiManagerMac::SendMidiData.
591   // TODO(yukawa): Do this task in a platform-independent way if possible.
592   // See crbug.com/325810.
593   send_thread_.message_loop()->PostTask(
594       FROM_HERE,
595       base::Bind(&MidiManagerClient::AccumulateMidiBytesSent,
596                  base::Unretained(client), data.size()));
597 }
598
599 MidiManager* MidiManager::Create() {
600   return new MidiManagerWin();
601 }
602
603 }  // namespace media