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