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
32namespace media {
33namespace {
34
35std::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
47std::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
59class 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
71typedef scoped_ptr<MIDIHDR, MIDIHDRDeleter> ScopedMIDIHDR;
72
73ScopedMIDIHDR 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
81void 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
94void 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
152class 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
355class 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
495MidiManagerWin::MidiManagerWin()
496    : send_thread_("MidiSendThread") {
497}
498
499void 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
548MidiManagerWin::~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
559void 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
593MidiManager* MidiManager::Create() {
594  return new MidiManagerWin();
595}
596
597}  // namespace media
598