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
30namespace media {
31namespace {
32
33std::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
45std::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
57class 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
69typedef scoped_ptr<MIDIHDR, MIDIHDRDeleter> ScopedMIDIHDR;
70
71ScopedMIDIHDR 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
79void 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
92void 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
150class 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
353class 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
493MIDIManagerWin::MIDIManagerWin()
494    : send_thread_("MIDISendThread") {
495}
496
497bool 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
546MIDIManagerWin::~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
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