| 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..fdba4510bb42bc74753c7d81e67aa88778da76bd
|
| --- /dev/null
|
| +++ b/media/midi/midi_manager_win.cc
|
| @@ -0,0 +1,597 @@
|
| +// 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>
|
| +
|
| +// 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
|
| + // 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()
|
| + // 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 choose option 2) in favor of asynchronous design.
|
| +
|
| + // Note for built-in USB-MIDI driver:
|
| + // From an observation on Windows 7/8.1 with a USB-MIDI keyboard,
|
| + // midiOutLongMsg() will be always blocked. Sending 64 bytes or less data
|
| + // takes roughly 300 usecs. Sending 2048 bytes or more data takes roughly
|
| + // |message.size() / (75 * 1024)| secs in practice. Here we put 60 KB size
|
| + // limit on SysEx message, with hoping that midiOutLongMsg will be blocked at
|
| + // most 1 sec or so with a typical USB-MIDI device.
|
| + const size_t kSysExSizeLimit = 60 * 1024;
|
| + if (message.size() >= kSysExSizeLimit) {
|
| + DVLOG(1) << "Ingnoreing SysEx message due to the size limit"
|
| + << ", size = " << message.size();
|
| + return;
|
| + }
|
| +
|
| + 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();
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +class MIDIManagerWin::InDeviceInfo {
|
| + public:
|
| + ~InDeviceInfo() {
|
| + 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<InDeviceInfo> Create(MIDIManagerWin* manager,
|
| + UINT device_id) {
|
| + scoped_ptr<InDeviceInfo> obj(new InDeviceInfo(manager));
|
| + if (!obj->Initialize(device_id))
|
| + obj.reset();
|
| + return obj.Pass();
|
| + }
|
| +
|
| + private:
|
| + static const int kInvalidPortIndex = -1;
|
| + static const size_t kBufferLength = 32 * 1024;
|
| +
|
| + explicit InDeviceInfo(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.
|
| + // - MIM_CLOSE: This event is sent when 1) midiInClose() is called, or 2)
|
| + // the MIDI device becomes unavailable for some reasons, e.g., the cable
|
| + // is disconnected. As for the former case, HMIDIOUT will be invalidated
|
| + // soon after the callback is finished. As for the later case, however,
|
| + // HMIDIOUT continues to be valid until midiInClose() is called.
|
| + 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_);
|
| + device_to_be_closed_ = false;
|
| + DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
|
| + << "Failed to close input port: " << GetInErrorMessage(result);
|
| + midi_header_.reset();
|
| + midi_handle_ = NULL;
|
| + port_index_ = kInvalidPortIndex;
|
| + }
|
| + }
|
| +
|
| + static void CALLBACK HandleMessage(HMIDIIN midi_in_handle,
|
| + UINT message,
|
| + DWORD_PTR instance,
|
| + DWORD_PTR param1,
|
| + DWORD_PTR param2) {
|
| + // This method can be called back on any thread depending on Windows
|
| + // multimedia subsystem and underlying MIDI drivers.
|
| + InDeviceInfo* self = reinterpret_cast<InDeviceInfo*>(instance);
|
| + if (!self)
|
| + return;
|
| + if (self->midi_handle() != midi_in_handle)
|
| + return;
|
| +
|
| + switch (message) {
|
| + case MIM_DATA:
|
| + self->OnShortMessageReceived(static_cast<uint8>(param1 & 0xff),
|
| + static_cast<uint8>((param1 >> 8) & 0xff),
|
| + static_cast<uint8>((param1 >> 16) & 0xff),
|
| + self->TickToTimeDelta(param2));
|
| + return;
|
| + case MIM_LONGDATA:
|
| + self->OnLongMessageReceived(reinterpret_cast<MIDIHDR*>(param1),
|
| + self->TickToTimeDelta(param2));
|
| + return;
|
| + case MIM_CLOSE:
|
| + // TODO(yukawa): Implement crbug.com/279097.
|
| + return;
|
| + }
|
| + }
|
| +
|
| + void OnShortMessageReceived(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 OnLongMessageReceived(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);
|
| + }
|
| +
|
| + 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::InDeviceInfo);
|
| +};
|
| +
|
| +class MIDIManagerWin::OutDeviceInfo {
|
| + public:
|
| + ~OutDeviceInfo() {
|
| + Uninitialize();
|
| + }
|
| +
|
| + static scoped_ptr<OutDeviceInfo> Create(UINT device_id) {
|
| + scoped_ptr<OutDeviceInfo> obj(new OutDeviceInfo);
|
| + if (!obj->Initialize(device_id))
|
| + obj.reset();
|
| + return obj.Pass();
|
| + }
|
| +
|
| + HMIDIOUT midi_handle() const {
|
| + return midi_handle_;
|
| + }
|
| +
|
| + void Quit() {
|
| + quitting_ = true;
|
| + }
|
| +
|
| + void Send(const std::vector<uint8>& data) {
|
| + // Check if the attached device is still available or not.
|
| + if (!midi_handle_)
|
| + return;
|
| +
|
| + // Give up sending MIDI messages here if the device is already closed.
|
| + // Note that this check is optional. Regardless of that we check |closed_|
|
| + // or not, nothing harmful happens as long as |midi_handle_| is still valid.
|
| + if (closed_)
|
| + return;
|
| +
|
| + // MIDI Running status must be filtered out.
|
| + MIDIMessageQueue message_queue(false);
|
| + message_queue.Add(data);
|
| + std::vector<uint8> message;
|
| + while (!quitting_) {
|
| + message_queue.Get(&message);
|
| + if (message.empty())
|
| + break;
|
| + // SendShortMIDIMessageInternal can send a MIDI message up to 3 bytes.
|
| + if (message.size() <= 3)
|
| + SendShortMIDIMessageInternal(midi_handle_, message);
|
| + else
|
| + SendLongMIDIMessageInternal(midi_handle_, message);
|
| + }
|
| + }
|
| +
|
| + private:
|
| + OutDeviceInfo()
|
| + : midi_handle_(NULL),
|
| + closed_(false),
|
| + quitting_(false) {}
|
| +
|
| + 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 the backing store where a long MIDI message is stored.
|
| + // - MOM_CLOSE: This event is sent when 1) midiOutClose() is called, or 2)
|
| + // the MIDI device becomes unavailable for some reasons, e.g., the cable
|
| + // is disconnected. As for the former case, HMIDIOUT will be invalidated
|
| + // soon after the callback is finished. As for the later case, however,
|
| + // HMIDIOUT continues to be valid until midiOutClose() is called.
|
| + 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_)
|
| + 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;
|
| + closed_ = true;
|
| + }
|
| +
|
| + static void CALLBACK HandleMessage(HMIDIOUT midi_out_handle,
|
| + UINT message,
|
| + DWORD_PTR instance,
|
| + DWORD_PTR param1,
|
| + DWORD_PTR param2) {
|
| + // This method can be called back on any thread depending on Windows
|
| + // multimedia subsystem and underlying MIDI drivers.
|
| +
|
| + OutDeviceInfo* self = reinterpret_cast<OutDeviceInfo*>(instance);
|
| + if (!self)
|
| + return;
|
| + if (self->midi_handle() != midi_out_handle)
|
| + return;
|
| + switch (message) {
|
| + case MOM_DONE: {
|
| + // Take 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:
|
| + // No lock is required since this flag is just a hint to avoid
|
| + // unnecessary API calls that will result in failure anyway.
|
| + self->closed_ = true;
|
| + // TODO(yukawa): Implement crbug.com/279097.
|
| + return;
|
| + }
|
| + }
|
| +
|
| + HMIDIOUT midi_handle_;
|
| +
|
| + // True if the device is already closed.
|
| + volatile bool closed_;
|
| +
|
| + // True if the MIDIManagerWin is trying to stop the sender thread.
|
| + volatile bool quitting_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(MIDIManagerWin::OutDeviceInfo);
|
| +};
|
| +
|
| +MIDIManagerWin::MIDIManagerWin()
|
| + : send_thread_("MIDISendThread") {
|
| +}
|
| +
|
| +bool MIDIManagerWin::Initialize() {
|
| + const UINT num_in_devices = midiInGetNumDevs();
|
| + in_devices_.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<InDeviceInfo> in_device(InDeviceInfo::Create(this, device_id));
|
| + if (!in_device)
|
| + continue;
|
| + MIDIPortInfo info(
|
| + base::IntToString(static_cast<int>(device_id)),
|
| + "",
|
| + base::WideToUTF8(caps.szPname),
|
| + base::IntToString(static_cast<int>(caps.vDriverVersion)));
|
| + AddInputPort(info);
|
| + in_device->set_port_index(input_ports_.size() - 1);
|
| + in_devices_.push_back(in_device.Pass());
|
| + }
|
| +
|
| + const UINT num_out_devices = midiOutGetNumDevs();
|
| + out_devices_.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<OutDeviceInfo> out_port(OutDeviceInfo::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_devices_.push_back(out_port.Pass());
|
| + }
|
| +
|
| + return true;
|
| +}
|
| +
|
| +MIDIManagerWin::~MIDIManagerWin() {
|
| + // Cleanup order is important. |send_thread_| must be stopped before
|
| + // |out_devices_| is cleared.
|
| + for (size_t i = 0; i < output_ports_.size(); ++i)
|
| + out_devices_[i]->Quit();
|
| + send_thread_.Stop();
|
| +
|
| + out_devices_.clear();
|
| + output_ports_.clear();
|
| + in_devices_.clear();
|
| + input_ports_.clear();
|
| +}
|
| +
|
| +void MIDIManagerWin::DispatchSendMIDIData(MIDIManagerClient* client,
|
| + uint32 port_index,
|
| + const std::vector<uint8>& data,
|
| + double timestamp) {
|
| + if (out_devices_.size() <= port_index)
|
| + return;
|
| +
|
| + base::TimeDelta delay;
|
| + if (timestamp != 0.0) {
|
| + base::TimeTicks time_to_send =
|
| + base::TimeTicks() + base::TimeDelta::FromMicroseconds(
|
| + timestamp * base::Time::kMicrosecondsPerSecond);
|
| + delay = std::max(time_to_send - base::TimeTicks::Now(), base::TimeDelta());
|
| + }
|
| +
|
| + if (!send_thread_.IsRunning())
|
| + send_thread_.Start();
|
| +
|
| + OutDeviceInfo* out_port = out_devices_[port_index].get();
|
| + send_thread_.message_loop()->PostDelayedTask(
|
| + FROM_HERE,
|
| + base::Bind(&OutDeviceInfo::Send, base::Unretained(out_port), data),
|
| + delay);
|
| +
|
| + // Call back AccumulateMIDIBytesSent() on |send_thread_| to emulate the
|
| + // behavior of MIDIManagerMac::SendMIDIData.
|
| + // TODO(yukawa): Do this task in a platform-independent way if possible.
|
| + // See crbug.com/325810.
|
| + send_thread_.message_loop()->PostTask(
|
| + FROM_HERE,
|
| + base::Bind(&MIDIManagerClient::AccumulateMIDIBytesSent,
|
| + base::Unretained(client), data.size()));
|
| +}
|
| +
|
| +MIDIManager* MIDIManager::Create() {
|
| + return new MIDIManagerWin();
|
| +}
|
| +
|
| +} // namespace media
|
|
|