Chromium Code Reviews| Index: media/midi/midi_manager_win.cc |
| diff --git a/media/midi/midi_manager_win.cc b/media/midi/midi_manager_win.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..ccc345f613568bcbf5cf37c4ff170f95b574da96 |
| --- /dev/null |
| +++ b/media/midi/midi_manager_win.cc |
| @@ -0,0 +1,570 @@ |
| +// Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "media/midi/midi_manager_win.h" |
| + |
| +#include <Windows.h> |
|
scherkus (not reviewing)
2013/11/27 22:07:30
Windows -> windows
yukawa
2013/12/04 17:51:22
Done.
|
| + |
| +// Prevent unnecessary functions from being included from <mmsystem.h> |
| +#define MMNODRV |
| +#define MMNOSOUND |
| +#define MMNOWAVE |
| +#define MMNOAUX |
| +#define MMNOMIXER |
| +#define MMNOTIMER |
| +#define MMNOJOY |
| +#define MMNOMCI |
| +#define MMNOMMIO |
| +#include <mmsystem.h> |
| + |
| +#include "base/bind.h" |
| +#include "base/message_loop/message_loop.h" |
| +#include "base/strings/string_number_conversions.h" |
| +#include "base/strings/utf_string_conversions.h" |
| +#include "base/threading/thread.h" |
| +#include "media/midi/midi_message_queue.h" |
| +#include "media/midi/midi_message_util.h" |
| +#include "media/midi/midi_port_info.h" |
| + |
| +namespace media { |
| +namespace { |
| + |
| +std::string GetInErrorMessage(MMRESULT result) { |
| + wchar_t text[MAXERRORLENGTH]; |
| + MMRESULT get_result = midiInGetErrorText(result, text, arraysize(text)); |
| + if (get_result != MMSYSERR_NOERROR) { |
| + DLOG(ERROR) << "Failed to get error message." |
| + << " original error: " << result |
| + << " midiInGetErrorText error: " << get_result; |
| + return std::string(); |
| + } |
| + return WideToUTF8(text); |
| +} |
| + |
| +std::string GetOutErrorMessage(MMRESULT result) { |
| + wchar_t text[MAXERRORLENGTH]; |
| + MMRESULT get_result = midiOutGetErrorText(result, text, arraysize(text)); |
| + if (get_result != MMSYSERR_NOERROR) { |
| + DLOG(ERROR) << "Failed to get error message." |
| + << " original error: " << result |
| + << " midiOutGetErrorText error: " << get_result; |
| + return std::string(); |
| + } |
| + return WideToUTF8(text); |
| +} |
| + |
| +class MIDIHDRDeleter { |
| + public: |
| + void operator()(MIDIHDR* header) { |
| + if (!header) |
| + return; |
| + delete[] static_cast<char*>(header->lpData); |
| + header->lpData = NULL; |
| + header->dwBufferLength = 0; |
| + delete header; |
| + } |
| +}; |
| + |
| +typedef scoped_ptr<MIDIHDR, MIDIHDRDeleter> ScopedMIDIHDR; |
| + |
| +ScopedMIDIHDR CreateMIDIHDR(size_t size) { |
| + ScopedMIDIHDR header(new MIDIHDR); |
| + ZeroMemory(header.get(), sizeof(*header)); |
| + header->lpData = new char[size]; |
| + header->dwBufferLength = size; |
| + return header.Pass(); |
| +} |
| + |
| +void SendShortMIDIMessageInternal(HMIDIOUT midi_out_handle, |
| + const std::vector<uint8>& message) { |
| + if (message.size() >= 4) |
| + return; |
| + |
| + DWORD packed_message = 0; |
| + for (size_t i = 0; i < message.size(); ++i) |
| + packed_message |= (static_cast<uint32>(message[i]) << (i * 8)); |
| + MMRESULT result = midiOutShortMsg(midi_out_handle, packed_message); |
| + DLOG_IF(ERROR, result != MMSYSERR_NOERROR) |
| + << "Failed to output short message: " << GetOutErrorMessage(result); |
| +} |
| + |
| +void SendLongMIDIMessageInternal(HMIDIOUT midi_out_handle, |
| + const std::vector<uint8>& message) { |
| + // Implementation note: |
| + // Sending long MIDI message can be performed synchronously or asynchronously |
| + // depending on the driver. There are 2 options to support both cases: |
| + // 1) Call midiOutLongMsg API and wait for its completion within this |
|
scherkus (not reviewing)
2013/11/27 22:07:30
nit: can you annotate function calls in comments w
yukawa
2013/12/04 17:51:22
Done.
|
| + // function. In this approach, we can avoid memory copy by directly pointing |
| + // |message| as the data buffer to be sent. |
| + // 2) Allocate a buffer and copy |message| to it, then call midiOutLongMsg |
|
scherkus (not reviewing)
2013/11/27 22:07:30
ditto here + on line 102
yukawa
2013/12/04 17:51:22
Done.
|
| + // API. The buffer will be freed in the MOM_DONE event hander, which tells |
| + // us that the task of midiOutLongMsg API is completed. |
| + // Here we use option 2) in favor of asynchronous design. |
| + // |
| + // Open question: |
| + // In the era of USB-MIDI, OS built-in driver seems to always perform |
| + // synchronously. Thus option 1) still might be a good option. |
|
scherkus (not reviewing)
2013/11/27 22:07:30
how long are these synchronous operations?
as you
yukawa
2013/12/04 17:51:22
On my Windows environment with a certain USB-MIDI
|
| + |
| + ScopedMIDIHDR midi_header(CreateMIDIHDR(message.size())); |
| + for (size_t i = 0; i < message.size(); ++i) |
| + midi_header->lpData[i] = static_cast<char>(message[i]); |
| + |
| + MMRESULT result = midiOutPrepareHeader( |
| + midi_out_handle, midi_header.get(), sizeof(*midi_header)); |
| + if (result != MMSYSERR_NOERROR) { |
| + DLOG(ERROR) << "Failed to prepare output buffer: " |
| + << GetOutErrorMessage(result); |
| + return; |
| + } |
| + |
| + result = midiOutLongMsg( |
| + midi_out_handle, midi_header.get(), sizeof(*midi_header)); |
| + if (result != MMSYSERR_NOERROR) { |
| + DLOG(ERROR) << "Failed to output long message: " |
| + << GetOutErrorMessage(result); |
| + result = midiOutUnprepareHeader( |
| + midi_out_handle, midi_header.get(), sizeof(*midi_header)); |
| + DLOG_IF(ERROR, result != MMSYSERR_NOERROR) |
| + << "Failed to uninitialize output buffer: " |
| + << GetOutErrorMessage(result); |
| + return; |
| + } |
| + |
| + // The ownership of |midi_header| is moved to MOM_DONE event handler. |
| + midi_header.release(); |
| +} |
| + |
| +void SendMIDIDataInternal(HMIDIOUT midi_out_handle, |
| + const std::vector<uint8>& data) { |
| + // MIDI Running status must not be enabled for |data|. |
| + MIDIMessageQueue message_queue(false); |
| + message_queue.Add(data); |
| + std::vector<uint8> message; |
| + while (true) { |
| + message_queue.Get(&message); |
| + if (message.empty()) |
| + break; |
| + // SendShortMIDIMessageInternal can send a MIDI message up to 3 bytes. |
| + if (message.size() <= 3) |
| + SendShortMIDIMessageInternal(midi_out_handle, message); |
| + else |
| + SendLongMIDIMessageInternal(midi_out_handle, message); |
| + } |
| +} |
| + |
| +} // namespace |
| + |
| +class MIDIManagerWin::InPortInfo { |
| + public: |
| + ~InPortInfo() { |
| + Uninitialize(); |
| + } |
| + void set_port_index(int index) { |
| + port_index_ = index; |
| + } |
| + int port_index() const { |
| + return port_index_; |
| + } |
| + bool device_to_be_closed() const { |
| + return device_to_be_closed_; |
| + } |
| + HMIDIIN midi_handle() const { |
| + return midi_handle_; |
| + } |
| + const base::TimeDelta& start_time_offset() const { |
| + return start_time_offset_; |
| + } |
| + |
| + static scoped_ptr<InPortInfo> Create(MIDIManagerWin* manager, |
| + UINT device_id) { |
| + scoped_ptr<InPortInfo> obj(new InPortInfo(manager)); |
| + if (!obj->Initialize(device_id)) |
| + obj.reset(NULL); |
|
scherkus (not reviewing)
2013/11/27 22:07:30
nit: no need for null
yukawa
2013/12/04 17:51:22
Done.
|
| + return obj.Pass(); |
| + } |
| + |
| + private: |
| + static const int kInvalidPortIndex = -1; |
| + static const size_t kBufferLength = 32 * 1024; |
| + |
| + explicit InPortInfo(MIDIManagerWin* manager) |
| + : manager_(manager), |
| + port_index_(kInvalidPortIndex), |
| + midi_handle_(NULL), |
| + started_(false), |
| + device_to_be_closed_(false) { |
| + } |
| + |
| + bool Initialize(DWORD device_id) { |
| + Uninitialize(); |
| + midi_header_ = CreateMIDIHDR(kBufferLength); |
| + |
| + // Here we use |CALLBACK_FUNCTION| to subscribe MIM_DATA, MIM_LONGDATA, and |
| + // MIM_CLOSE events. |
| + // - MIM_DATA: This is the only way to get a short MIDI message with |
| + // timestamp information. |
| + // - MIM_LONGDATA: This is the only way to get a long MIDI message with |
| + // timestamp information. |
| + // - MIN_CLOSE: This event is notified when the device is removed logically |
| + // or physically. |
| + MMRESULT result = midiInOpen(&midi_handle_, |
| + device_id, |
| + reinterpret_cast<DWORD_PTR>(&HandleMessage), |
| + reinterpret_cast<DWORD_PTR>(this), |
| + CALLBACK_FUNCTION); |
| + if (result != MMSYSERR_NOERROR) { |
| + DLOG(ERROR) << "Failed to open output device. " |
| + << " id: " << device_id |
| + << " message: " << GetInErrorMessage(result); |
| + return false; |
| + } |
| + result = midiInPrepareHeader( |
| + midi_handle_, midi_header_.get(), sizeof(*midi_header_)); |
| + if (result != MMSYSERR_NOERROR) { |
| + DLOG(ERROR) << "Failed to initialize input buffer: " |
| + << GetInErrorMessage(result); |
| + return false; |
| + } |
| + result = midiInAddBuffer( |
| + midi_handle_, midi_header_.get(), sizeof(*midi_header_)); |
| + if (result != MMSYSERR_NOERROR) { |
| + DLOG(ERROR) << "Failed to attach input buffer: " |
| + << GetInErrorMessage(result); |
| + return false; |
| + } |
| + result = midiInStart(midi_handle_); |
| + if (result != MMSYSERR_NOERROR) { |
| + DLOG(ERROR) << "Failed to start input port: " |
| + << GetInErrorMessage(result); |
| + return false; |
| + } |
| + started_ = true; |
| + start_time_offset_ = base::TimeTicks::Now() - base::TimeTicks(); |
| + return true; |
| + } |
| + |
| + void Uninitialize() { |
| + MMRESULT result = MMSYSERR_NOERROR; |
| + if (midi_handle_ && started_) { |
| + result = midiInStop(midi_handle_); |
| + DLOG_IF(ERROR, result != MMSYSERR_NOERROR) |
| + << "Failed to stop input port: " << GetInErrorMessage(result); |
| + started_ = false; |
| + start_time_offset_ = base::TimeDelta(); |
| + } |
| + if (midi_handle_) { |
| + // midiInReset flushes pending messages. We ignore these messages. |
| + device_to_be_closed_ = true; |
| + result = midiInReset(midi_handle_); |
| + DLOG_IF(ERROR, result != MMSYSERR_NOERROR) |
| + << "Failed to reset input port: " << GetInErrorMessage(result); |
| + result = midiInClose(midi_handle_); |
| + DLOG_IF(ERROR, result != MMSYSERR_NOERROR) |
| + << "Failed to close input port: " << GetInErrorMessage(result); |
| + } |
| + device_to_be_closed_ = false; |
| + } |
| + |
| + static void CALLBACK HandleMessage(HMIDIIN midi_in_handle, |
| + UINT message, |
| + DWORD_PTR instance, |
| + DWORD_PTR param1, |
| + DWORD_PTR param2) { |
| + // This method is called back by Windows multimedia subsystem and not always |
| + // in the UI thread. |
| + InPortInfo* self = reinterpret_cast<InPortInfo*>(instance); |
| + if (!self) |
| + return; |
| + if (self->midi_handle() != midi_in_handle) |
| + return; |
| + self->HandleMessageInternal(message, param1, param2); |
| + } |
| + |
| + void HandleMessageInternal(UINT message, DWORD_PTR param1, DWORD_PTR param2) { |
| + // This method is called from HandleMessage method and not always in the UI |
| + // thread. |
| + switch (message) { |
| + case MIM_DATA: |
| + HandleShortMessageInternal(static_cast<uint8>(param1 & 0xff), |
| + static_cast<uint8>((param1 >> 8) & 0xff), |
| + static_cast<uint8>((param1 >> 16) & 0xff), |
| + TickToTimeDelta(param2)); |
| + return; |
| + case MIM_LONGDATA: |
| + HandleLongMessageInternal(reinterpret_cast<MIDIHDR*>(param1), |
| + TickToTimeDelta(param2)); |
| + return; |
| + case MIM_CLOSE: |
| + HandleCloseMessageInternal(); |
| + } |
| + } |
| + |
| + void HandleShortMessageInternal(uint8 status_byte, |
| + uint8 first_data_byte, |
| + uint8 second_data_byte, |
| + base::TimeDelta timestamp) { |
| + if (device_to_be_closed()) |
| + return; |
| + const size_t len = GetMIDIMessageLength(status_byte); |
| + if (len == 0 || port_index() == kInvalidPortIndex) |
| + return; |
| + const uint8 kData[] = { status_byte, first_data_byte, second_data_byte }; |
| + DCHECK_LE(len, arraysize(kData)); |
| + manager_->ReceiveMIDIData(port_index(), kData, len, timestamp.InSecondsF()); |
| + } |
| + |
| + void HandleLongMessageInternal(MIDIHDR* header, base::TimeDelta timestamp) { |
| + if (header != midi_header_.get()) |
| + return; |
| + MMRESULT result = MMSYSERR_NOERROR; |
| + if (device_to_be_closed()) { |
| + if (midi_header_ && |
| + (midi_header_->dwFlags & MHDR_PREPARED) == MHDR_PREPARED) { |
| + result = midiInUnprepareHeader( |
| + midi_handle_, midi_header_.get(), sizeof(*midi_header_)); |
| + DLOG_IF(ERROR, result != MMSYSERR_NOERROR) |
| + << "Failed to uninitialize input buffer: " |
| + << GetInErrorMessage(result); |
| + } |
| + return; |
| + } |
| + if (header->dwBytesRecorded > 0 && port_index() != kInvalidPortIndex) { |
| + manager_->ReceiveMIDIData(port_index_, |
| + reinterpret_cast<const uint8*>(header->lpData), |
| + header->dwBytesRecorded, |
| + timestamp.InSecondsF()); |
| + } |
| + result = midiInAddBuffer(midi_handle(), header, sizeof(*header)); |
| + DLOG_IF(ERROR, result != MMSYSERR_NOERROR) |
| + << "Failed to attach input port: " << GetInErrorMessage(result); |
| + } |
| + |
| + void HandleCloseMessageInternal() { |
| + if (midi_handle_ && midi_header_ && |
| + (midi_header_->dwFlags & MHDR_PREPARED) == MHDR_PREPARED) { |
| + MMRESULT result = midiInUnprepareHeader( |
| + midi_handle_, midi_header_.get(), sizeof(*midi_header_)); |
| + DLOG_IF(ERROR, result != MMSYSERR_NOERROR) |
| + << "Failed to uninitialize input buffer: " |
| + << GetInErrorMessage(result); |
| + } |
| + midi_header_.reset(); |
| + midi_handle_ = NULL; |
| + port_index_ = kInvalidPortIndex; |
| + return; |
| + } |
| + |
| + base::TimeDelta TickToTimeDelta(DWORD tick) const { |
| + const base::TimeDelta delta = |
| + base::TimeDelta::FromMicroseconds(static_cast<uint32>(tick)); |
| + return start_time_offset_ + delta; |
| + } |
| + |
| + MIDIManagerWin* manager_; |
| + int port_index_; |
| + HMIDIIN midi_handle_; |
| + ScopedMIDIHDR midi_header_; |
| + base::TimeDelta start_time_offset_; |
| + bool started_; |
| + bool device_to_be_closed_; |
| + DISALLOW_COPY_AND_ASSIGN(MIDIManagerWin::InPortInfo); |
| +}; |
| + |
| +class MIDIManagerWin::OutPortInfo { |
| + public: |
| + ~OutPortInfo() { |
| + Uninitialize(); |
| + } |
| + |
| + HMIDIOUT midi_handle() const { |
| + return midi_handle_; |
| + } |
| + |
| + static scoped_ptr<OutPortInfo> Create(UINT device_id) { |
| + scoped_ptr<OutPortInfo> obj(new OutPortInfo); |
| + if (!obj->Initialize(device_id)) |
| + obj.reset(NULL); |
|
scherkus (not reviewing)
2013/11/27 22:07:30
nit: no need for NULL
yukawa
2013/12/04 17:51:22
Done.
|
| + return obj.Pass(); |
| + } |
| + |
| + private: |
| + OutPortInfo() |
| + : midi_handle_(NULL) {} |
| + |
| + bool Initialize(DWORD device_id) { |
| + Uninitialize(); |
| + // Here we use |CALLBACK_FUNCTION| to subscribe MOM_DONE and MOM_CLOSE |
| + // events. |
| + // - MOM_DONE: SendLongMIDIMessageInternal relies on this event to clean up |
|
scherkus (not reviewing)
2013/11/27 22:07:30
ditto for function names in comments
yukawa
2013/12/04 17:51:22
Done.
|
| + // the backing store where a long MIDI message is stored. |
| + // - MOM_CLOSE: This event is notified when the device is removed logically |
| + // or physically. |
| + MMRESULT result = midiOutOpen(&midi_handle_, |
| + device_id, |
| + reinterpret_cast<DWORD_PTR>(&HandleMessage), |
| + reinterpret_cast<DWORD_PTR>(this), |
| + CALLBACK_FUNCTION); |
| + if (result != MMSYSERR_NOERROR) { |
| + DLOG(ERROR) << "Failed to open output device. " |
| + << " id: " << device_id |
| + << " message: "<< GetOutErrorMessage(result); |
| + midi_handle_ = NULL; |
| + return false; |
| + } |
| + return true; |
| + } |
| + |
| + void Uninitialize() { |
| + if (midi_handle_ == NULL) |
|
scherkus (not reviewing)
2013/11/27 22:07:30
nit: use !midi_handle_
yukawa
2013/12/04 17:51:22
Done.
|
| + return; |
| + MMRESULT result = midiOutReset(midi_handle_); |
| + DLOG_IF(ERROR, result != MMSYSERR_NOERROR) |
| + << "Failed to reset output port: " << GetOutErrorMessage(result); |
| + result = midiOutClose(midi_handle_); |
| + DLOG_IF(ERROR, result != MMSYSERR_NOERROR) |
| + << "Failed to close output port: " << GetOutErrorMessage(result); |
| + midi_handle_ = NULL; |
| + } |
| + |
| + static void CALLBACK HandleMessage(HMIDIIN midi_in_handle, |
| + UINT message, |
| + DWORD_PTR instance, |
| + DWORD_PTR param1, |
| + DWORD_PTR param2) { |
| + // This method is called back by Windows multimedia subsystem and not always |
| + // in the UI thread. |
|
scherkus (not reviewing)
2013/11/27 22:07:30
s/in/on/
also what do you mean by "not always"?
yukawa
2013/12/04 17:51:22
Done and added comment.
|
| + |
| + OutPortInfo* self = reinterpret_cast<OutPortInfo*>(instance); |
| + if (!self) |
| + return; |
| + switch (message) { |
| + case MOM_DONE: { |
| + // Take the ownership of the MIDIHDR object. |
| + ScopedMIDIHDR header(reinterpret_cast<MIDIHDR*>(param1)); |
| + if (!header) |
| + return; |
| + MMRESULT result = midiOutUnprepareHeader( |
| + self->midi_handle(), header.get(), sizeof(*header)); |
| + DLOG_IF(ERROR, result != MMSYSERR_NOERROR) |
| + << "Failed to uninitialize output buffer: " |
| + << GetOutErrorMessage(result); |
| + return; |
| + } |
| + case MOM_CLOSE: |
| + self->midi_handle_ = NULL; |
| + return; |
| + } |
| + } |
| + |
| + HMIDIOUT midi_handle_; |
| + DISALLOW_COPY_AND_ASSIGN(MIDIManagerWin::OutPortInfo); |
| +}; |
| + |
| +MIDIManagerWin::MIDIManagerWin() { |
| +} |
| + |
| +bool MIDIManagerWin::Initialize() { |
| + { |
|
scherkus (not reviewing)
2013/11/27 22:07:30
do we need the extra scope blocks?
I'm not seeing
yukawa
2013/12/04 17:51:22
OK. Removed these blocks.
|
| + const UINT num_in_devices = midiInGetNumDevs(); |
| + in_ports_.reserve(num_in_devices); |
| + for (UINT device_id = 0; device_id < num_in_devices; ++device_id) { |
| + MIDIINCAPS caps = {}; |
| + MMRESULT result = midiInGetDevCaps(device_id, &caps, sizeof(caps)); |
| + if (result != MMSYSERR_NOERROR) { |
| + DLOG(ERROR) << "Failed to obtain input device info: " |
| + << GetInErrorMessage(result); |
| + continue; |
| + } |
| + scoped_ptr<InPortInfo> in_port(InPortInfo::Create(this, device_id)); |
| + if (!in_port) |
| + continue; |
| + MIDIPortInfo info( |
| + base::IntToString(static_cast<int>(device_id)), |
| + "", |
| + base::WideToUTF8(caps.szPname), |
| + base::IntToString(static_cast<int>(caps.vDriverVersion))); |
| + AddInputPort(info); |
| + in_port->set_port_index(input_ports_.size() - 1); |
| + in_ports_.push_back(in_port.Pass()); |
| + } |
| + } |
| + { |
| + const UINT num_out_devices = midiOutGetNumDevs(); |
| + out_ports_.reserve(num_out_devices); |
| + for (UINT device_id = 0; device_id < num_out_devices; ++device_id) { |
| + MIDIOUTCAPS caps = {}; |
| + MMRESULT result = midiOutGetDevCaps(device_id, &caps, sizeof(caps)); |
| + if (result != MMSYSERR_NOERROR) { |
| + DLOG(ERROR) << "Failed to obtain output device info: " |
| + << GetOutErrorMessage(result); |
| + continue; |
| + } |
| + scoped_ptr<OutPortInfo> out_port(OutPortInfo::Create(device_id)); |
| + if (!out_port) |
| + continue; |
| + MIDIPortInfo info( |
| + base::IntToString(static_cast<int>(device_id)), |
| + "", |
| + base::WideToUTF8(caps.szPname), |
| + base::IntToString(static_cast<int>(caps.vDriverVersion))); |
| + AddOutputPort(info); |
| + out_ports_.push_back(out_port.Pass()); |
| + } |
| + } |
| + return true; |
| +} |
| + |
| +MIDIManagerWin::~MIDIManagerWin() { |
| + // |send_thread_| must be terminated here to ensure that |
| + // MIDIManagerWin::SendMIDIData will not be called any longer. A drawback |
|
scherkus (not reviewing)
2013/11/27 22:07:30
ditto for function names in comments
yukawa
2013/12/04 17:51:22
Done.
|
| + // of this approach is if |send_thread_| is somehow blocked forever, |
|
scherkus (not reviewing)
2013/11/27 22:07:30
do you know how long does it take to run SendMIDID
yukawa
2013/12/04 17:51:22
It is supposed to be fast operation as long as the
|
| + // the UI thread is also blocked forever. |
| + send_thread_.reset(NULL); |
|
scherkus (not reviewing)
2013/11/27 22:07:30
nit: NULL not needed
yukawa
2013/12/04 17:51:22
Done.
|
| +} |
| + |
| +void MIDIManagerWin::SendMIDIData(MIDIManagerClient* client, |
| + uint32 port_index, |
| + const std::vector<uint8>& data, |
| + double timestamp) { |
| + DCHECK(CurrentlyOnMIDISendThread()); |
| + // Caveat: Whether |this| is still valid or not is non-trivial question |
| + // because this method is called back in a background thread that is owned by |
| + // MIDIManager rather than MIDIManagerWin. Currently |this| is considered to |
| + // be always valid because ~MIDIManagerWin() internally waits for the |
| + // termination of this background thread. |
|
scherkus (not reviewing)
2013/11/27 22:07:30
technically this comment would apply to MIDIManage
yukawa
2013/12/04 17:51:22
Mas issue has been resolved. PostDelayedTask() is
|
| + |
| + if (out_ports_.size() <= port_index) |
| + return; |
| + |
| + // Check if the target device is still available. Note that |handle| can be |
| + // NULL once the device is logically or physically removed. |
| + const HMIDIOUT handle = out_ports_[port_index]->midi_handle(); |
| + if (!handle) |
| + return; |
| + |
| + base::TimeDelta delay; |
| + if (timestamp != 0.0) { |
| + base::TimeTicks time_to_send = |
| + base::TimeTicks() + base::TimeDelta::FromMicroseconds( |
| + timestamp * base::Time::kMicrosecondsPerSecond); |
| + delay = time_to_send - base::TimeTicks::Now(); |
| + } |
| + |
| + if (delay.InMilliseconds() <= 0) { |
| + SendMIDIDataInternal(handle, data); |
| + } else { |
| + base::MessageLoop::current()->PostDelayedTask( |
| + FROM_HERE, |
| + base::Bind(&SendMIDIDataInternal, handle, data), |
|
scherkus (not reviewing)
2013/11/27 22:07:30
since handle and data get copied, is it possible f
yukawa
2013/12/04 17:51:22
Yeah, your concern was real, although Windows stri
|
| + delay); |
| + } |
| + |
| + client->AccumulateMIDIBytesSent(data.size()); |
| +} |
| + |
| +MIDIManager* MIDIManager::Create() { |
| + return new MIDIManagerWin(); |
| +} |
| + |
| +} // namespace media |