Home | History | Annotate | Download | only in midi
      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 WideToUTF8(text);
     43 }
     44 
     45 std::string GetOutErrorMessage(MMRESULT result) {
     46   wchar_t text[MAXERRORLENGTH];
     47   MMRESULT get_result = midiOutGetErrorText(result, text, arraysize(text));
     48   if (get_result != MMSYSERR_NOERROR) {
     49     DLOG(ERROR) << "Failed to get error message."
     50                 << " original error: " << result
     51                 << " midiOutGetErrorText error: " << get_result;
     52     return std::string();
     53   }
     54   return WideToUTF8(text);
     55 }
     56 
     57 class MIDIHDRDeleter {
     58  public:
     59   void operator()(MIDIHDR* header) {
     60     if (!header)
     61       return;
     62     delete[] static_cast<char*>(header->lpData);
     63     header->lpData = NULL;
     64     header->dwBufferLength = 0;
     65     delete header;
     66   }
     67 };
     68 
     69 typedef scoped_ptr<MIDIHDR, MIDIHDRDeleter> ScopedMIDIHDR;
     70 
     71 ScopedMIDIHDR CreateMIDIHDR(size_t size) {
     72   ScopedMIDIHDR header(new MIDIHDR);
     73   ZeroMemory(header.get(), sizeof(*header));
     74   header->lpData = new char[size];
     75   header->dwBufferLength = size;
     76   return header.Pass();
     77 }
     78 
     79 void SendShortMIDIMessageInternal(HMIDIOUT midi_out_handle,
     80                                   const std::vector<uint8>& message) {
     81   if (message.size() >= 4)
     82     return;
     83 
     84   DWORD packed_message = 0;
     85   for (size_t i = 0; i < message.size(); ++i)
     86     packed_message |= (static_cast<uint32>(message[i]) << (i * 8));
     87   MMRESULT result = midiOutShortMsg(midi_out_handle, packed_message);
     88   DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
     89       << "Failed to output short message: " << GetOutErrorMessage(result);
     90 }
     91 
     92 void SendLongMIDIMessageInternal(HMIDIOUT midi_out_handle,
     93                                  const std::vector<uint8>& message) {
     94   // Implementation note:
     95   // Sending long MIDI message can be performed synchronously or asynchronously
     96   // depending on the driver. There are 2 options to support both cases:
     97   // 1) Call midiOutLongMsg() API and wait for its completion within this
     98   //   function. In this approach, we can avoid memory copy by directly pointing
     99   //   |message| as the data buffer to be sent.
    100   // 2) Allocate a buffer and copy |message| to it, then call midiOutLongMsg()
    101   //   API. The buffer will be freed in the MOM_DONE event hander, which tells
    102   //   us that the task of midiOutLongMsg() API is completed.
    103   // Here we 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   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 
    353 class 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 
    493 MIDIManagerWin::MIDIManagerWin()
    494     : send_thread_("MIDISendThread") {
    495 }
    496 
    497 bool 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 
    546 MIDIManagerWin::~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 
    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
    598