summaryrefslogtreecommitdiff
path: root/chromium/media/midi
diff options
context:
space:
mode:
authorAndras Becsi <andras.becsi@digia.com>2014-03-18 13:16:26 +0100
committerFrederik Gladhorn <frederik.gladhorn@digia.com>2014-03-20 15:55:39 +0100
commit3f0f86b0caed75241fa71c95a5d73bc0164348c5 (patch)
tree92b9fb00f2e9e90b0be2262093876d4f43b6cd13 /chromium/media/midi
parente90d7c4b152c56919d963987e2503f9909a666d2 (diff)
downloadqtwebengine-chromium-3f0f86b0caed75241fa71c95a5d73bc0164348c5.tar.gz
Update to new stable branch 1750
This also includes an updated ninja and chromium dependencies needed on Windows. Change-Id: Icd597d80ed3fa4425933c9f1334c3c2e31291c42 Reviewed-by: Zoltan Arvai <zarvai@inf.u-szeged.hu> Reviewed-by: Zeno Albisser <zeno.albisser@digia.com>
Diffstat (limited to 'chromium/media/midi')
-rw-r--r--chromium/media/midi/midi_manager.cc28
-rw-r--r--chromium/media/midi/midi_manager.h34
-rw-r--r--chromium/media/midi/midi_manager_mac.cc63
-rw-r--r--chromium/media/midi/midi_manager_mac.h18
-rw-r--r--chromium/media/midi/midi_manager_win.cc597
-rw-r--r--chromium/media/midi/midi_manager_win.h40
-rw-r--r--chromium/media/midi/midi_message_queue.cc119
-rw-r--r--chromium/media/midi/midi_message_queue.h72
-rw-r--r--chromium/media/midi/midi_message_queue_unittest.cc173
-rw-r--r--chromium/media/midi/midi_message_util.cc34
-rw-r--r--chromium/media/midi/midi_message_util.h25
-rw-r--r--chromium/media/midi/midi_message_util_unittest.cc34
12 files changed, 1171 insertions, 66 deletions
diff --git a/chromium/media/midi/midi_manager.cc b/chromium/media/midi/midi_manager.cc
index b3262e4a034..6d3f1d30b95 100644
--- a/chromium/media/midi/midi_manager.cc
+++ b/chromium/media/midi/midi_manager.cc
@@ -6,12 +6,10 @@
#include "base/bind.h"
#include "base/bind_helpers.h"
-#include "base/message_loop/message_loop.h"
-#include "base/threading/thread.h"
namespace media {
-#if !defined(OS_MACOSX)
+#if !defined(OS_MACOSX) && !defined(OS_WIN)
// TODO(crogers): implement MIDIManager for other platforms.
MIDIManager* MIDIManager::Create() {
return NULL;
@@ -22,7 +20,8 @@ MIDIManager::MIDIManager()
: initialized_(false) {
}
-MIDIManager::~MIDIManager() {}
+MIDIManager::~MIDIManager() {
+}
bool MIDIManager::StartSession(MIDIManagerClient* client) {
// Lazily initialize the MIDI back-end.
@@ -63,25 +62,4 @@ void MIDIManager::ReceiveMIDIData(
(*i)->ReceiveMIDIData(port_index, data, length, timestamp);
}
-bool MIDIManager::CurrentlyOnMIDISendThread() {
- return send_thread_->message_loop() == base::MessageLoop::current();
-}
-
-void MIDIManager::DispatchSendMIDIData(MIDIManagerClient* client,
- uint32 port_index,
- const std::vector<uint8>& data,
- double timestamp) {
- // Lazily create the thread when first needed.
- if (!send_thread_) {
- send_thread_.reset(new base::Thread("MIDISendThread"));
- send_thread_->Start();
- send_message_loop_ = send_thread_->message_loop_proxy();
- }
-
- send_message_loop_->PostTask(
- FROM_HERE,
- base::Bind(&MIDIManager::SendMIDIData, base::Unretained(this),
- client, port_index, data, timestamp));
-}
-
} // namespace media
diff --git a/chromium/media/midi/midi_manager.h b/chromium/media/midi/midi_manager.h
index 6a301a942d9..f42a40de769 100644
--- a/chromium/media/midi/midi_manager.h
+++ b/chromium/media/midi/midi_manager.h
@@ -9,16 +9,10 @@
#include <vector>
#include "base/basictypes.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/message_loop/message_loop_proxy.h"
#include "base/synchronization/lock.h"
#include "media/base/media_export.h"
#include "media/midi/midi_port_info.h"
-namespace base {
-class Thread;
-}
-
namespace media {
// A MIDIManagerClient registers with the MIDIManager to receive MIDI data.
@@ -63,17 +57,18 @@ class MEDIA_EXPORT MIDIManager {
// A client calls ReleaseSession() to stop receiving MIDI data.
void EndSession(MIDIManagerClient* client);
- // DispatchSendMIDIData() schedules one or more messages to be sent
- // at the given time on a dedicated thread.
+ // DispatchSendMIDIData() is called when MIDI data should be sent to the MIDI
+ // system.
+ // This method is supposed to return immediately and should not block.
// |port_index| represents the specific output port from output_ports().
// |data| represents a series of bytes encoding one or more MIDI messages.
// |length| is the number of bytes in |data|.
// |timestamp| is the time to send the data, in seconds. A value of 0
// means send "now" or as soon as possible.
- void DispatchSendMIDIData(MIDIManagerClient* client,
- uint32 port_index,
- const std::vector<uint8>& data,
- double timestamp);
+ virtual void DispatchSendMIDIData(MIDIManagerClient* client,
+ uint32 port_index,
+ const std::vector<uint8>& data,
+ double timestamp) = 0;
// input_ports() is a list of MIDI ports for receiving MIDI data.
// Each individual port in this list can be identified by its
@@ -89,13 +84,6 @@ class MEDIA_EXPORT MIDIManager {
// Initializes the MIDI system, returning |true| on success.
virtual bool Initialize() = 0;
- // Implements the platform-specific details of sending MIDI data.
- // This function runs on MIDISendThread.
- virtual void SendMIDIData(MIDIManagerClient* client,
- uint32 port_index,
- const std::vector<uint8>& data,
- double timestamp) = 0;
-
void AddInputPort(const MIDIPortInfo& info);
void AddOutputPort(const MIDIPortInfo& info);
@@ -105,9 +93,6 @@ class MEDIA_EXPORT MIDIManager {
size_t length,
double timestamp);
- // Checks if current thread is MIDISendThread.
- bool CurrentlyOnMIDISendThread();
-
bool initialized_;
// Keeps track of all clients who wish to receive MIDI data.
@@ -120,11 +105,6 @@ class MEDIA_EXPORT MIDIManager {
MIDIPortInfoList input_ports_;
MIDIPortInfoList output_ports_;
- // |send_thread_| is used to send MIDI data by calling the platform-specific
- // API.
- scoped_ptr<base::Thread> send_thread_;
- scoped_refptr<base::MessageLoopProxy> send_message_loop_;
-
DISALLOW_COPY_AND_ASSIGN(MIDIManager);
};
diff --git a/chromium/media/midi/midi_manager_mac.cc b/chromium/media/midi/midi_manager_mac.cc
index 4477944e773..a36d1debe13 100644
--- a/chromium/media/midi/midi_manager_mac.cc
+++ b/chromium/media/midi/midi_manager_mac.cc
@@ -4,12 +4,13 @@
#include "media/midi/midi_manager_mac.h"
-#include <iostream>
#include <string>
#include "base/debug/trace_event.h"
+#include "base/message_loop/message_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/sys_string_conversions.h"
+
#include <CoreAudio/HostTime.h>
using base::IntToString;
@@ -31,7 +32,8 @@ MIDIManagerMac::MIDIManagerMac()
coremidi_input_(0),
coremidi_output_(0),
packet_list_(NULL),
- midi_packet_(NULL) {
+ midi_packet_(NULL),
+ send_thread_("MIDISendThread") {
}
bool MIDIManagerMac::Initialize() {
@@ -103,13 +105,31 @@ bool MIDIManagerMac::Initialize() {
return true;
}
+void MIDIManagerMac::DispatchSendMIDIData(MIDIManagerClient* client,
+ uint32 port_index,
+ const std::vector<uint8>& data,
+ double timestamp) {
+ if (!send_thread_.IsRunning())
+ send_thread_.Start();
+
+ // OK to use base::Unretained(this) since we join to thread in dtor().
+ send_thread_.message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&MIDIManagerMac::SendMIDIData, base::Unretained(this),
+ client, port_index, data, timestamp));
+}
+
MIDIManagerMac::~MIDIManagerMac() {
+ // Wait for the termination of |send_thread_| before disposing MIDI ports.
+ send_thread_.Stop();
+
if (coremidi_input_)
MIDIPortDispose(coremidi_input_);
if (coremidi_output_)
MIDIPortDispose(coremidi_output_);
}
+// static
void MIDIManagerMac::ReadMIDIDispatch(const MIDIPacketList* packet_list,
void* read_proc_refcon,
void* src_conn_refcon) {
@@ -133,7 +153,7 @@ void MIDIManagerMac::ReadMIDI(MIDIEndpointRef source,
uint32 port_index = source_map_[source];
// Go through each packet and process separately.
- for(size_t i = 0; i < packet_list->numPackets; i++) {
+ for (size_t i = 0; i < packet_list->numPackets; i++) {
// Each packet contains MIDI data for one or more messages (like note-on).
const MIDIPacket &packet = packet_list->packet[i];
double timestamp_seconds = MIDITimeStampToSeconds(packet.timeStamp);
@@ -150,7 +170,7 @@ void MIDIManagerMac::SendMIDIData(MIDIManagerClient* client,
uint32 port_index,
const std::vector<uint8>& data,
double timestamp) {
- DCHECK(CurrentlyOnMIDISendThread());
+ DCHECK(send_thread_.message_loop_proxy()->BelongsToCurrentThread());
// System Exclusive has already been filtered.
MIDITimeStamp coremidi_timestamp = SecondsToMIDITimeStamp(timestamp);
@@ -177,34 +197,57 @@ void MIDIManagerMac::SendMIDIData(MIDIManagerClient* client,
client->AccumulateMIDIBytesSent(data.size());
}
+// static
MIDIPortInfo MIDIManagerMac::GetPortInfoFromEndpoint(
MIDIEndpointRef endpoint) {
SInt32 id_number = 0;
MIDIObjectGetIntegerProperty(endpoint, kMIDIPropertyUniqueID, &id_number);
string id = IntToString(id_number);
+ string manufacturer;
CFStringRef manufacturer_ref = NULL;
- MIDIObjectGetStringProperty(
+ OSStatus result = MIDIObjectGetStringProperty(
endpoint, kMIDIPropertyManufacturer, &manufacturer_ref);
- string manufacturer = SysCFStringRefToUTF8(manufacturer_ref);
+ if (result == noErr) {
+ manufacturer = SysCFStringRefToUTF8(manufacturer_ref);
+ } else {
+ // kMIDIPropertyManufacturer is not supported in IAC driver providing
+ // endpoints, and the result will be kMIDIUnknownProperty (-10835).
+ DLOG(WARNING) << "Failed to get kMIDIPropertyManufacturer with status "
+ << result;
+ }
+ string name;
CFStringRef name_ref = NULL;
- MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &name_ref);
- string name = SysCFStringRefToUTF8(name_ref);
+ result = MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &name_ref);
+ if (result == noErr)
+ name = SysCFStringRefToUTF8(name_ref);
+ else
+ DLOG(WARNING) << "Failed to get kMIDIPropertyName with status " << result;
+ string version;
SInt32 version_number = 0;
- MIDIObjectGetIntegerProperty(
+ result = MIDIObjectGetIntegerProperty(
endpoint, kMIDIPropertyDriverVersion, &version_number);
- string version = IntToString(version_number);
+ if (result == noErr) {
+ version = IntToString(version_number);
+ } else {
+ // kMIDIPropertyDriverVersion is not supported in IAC driver providing
+ // endpoints, and the result will be kMIDIUnknownProperty (-10835).
+ DLOG(WARNING) << "Failed to get kMIDIPropertyDriverVersion with status "
+ << result;
+ }
return MIDIPortInfo(id, manufacturer, name, version);
}
+// static
double MIDIManagerMac::MIDITimeStampToSeconds(MIDITimeStamp timestamp) {
UInt64 nanoseconds = AudioConvertHostTimeToNanos(timestamp);
return static_cast<double>(nanoseconds) / 1.0e9;
}
+// static
MIDITimeStamp MIDIManagerMac::SecondsToMIDITimeStamp(double seconds) {
UInt64 nanos = UInt64(seconds * 1.0e9);
return AudioConvertNanosToHostTime(nanos);
diff --git a/chromium/media/midi/midi_manager_mac.h b/chromium/media/midi/midi_manager_mac.h
index 2397b8034f7..cc8bf74a3c5 100644
--- a/chromium/media/midi/midi_manager_mac.h
+++ b/chromium/media/midi/midi_manager_mac.h
@@ -12,6 +12,7 @@
#include "base/basictypes.h"
#include "base/compiler_specific.h"
+#include "base/threading/thread.h"
#include "media/midi/midi_manager.h"
#include "media/midi/midi_port_info.h"
@@ -24,10 +25,10 @@ class MEDIA_EXPORT MIDIManagerMac : public MIDIManager {
// MIDIManager implementation.
virtual bool Initialize() OVERRIDE;
- virtual void SendMIDIData(MIDIManagerClient* client,
- uint32 port_index,
- const std::vector<uint8>& data,
- double timestamp) OVERRIDE;
+ virtual void DispatchSendMIDIData(MIDIManagerClient* client,
+ uint32 port_index,
+ const std::vector<uint8>& data,
+ double timestamp) OVERRIDE;
private:
// CoreMIDI callback for MIDI data.
@@ -39,6 +40,12 @@ class MEDIA_EXPORT MIDIManagerMac : public MIDIManager {
void *src_conn_refcon);
virtual void ReadMIDI(MIDIEndpointRef source, const MIDIPacketList *pktlist);
+ // An internal callback that runs on MIDISendThread.
+ void SendMIDIData(MIDIManagerClient* client,
+ uint32 port_index,
+ const std::vector<uint8>& data,
+ double timestamp);
+
// Helper
static media::MIDIPortInfo GetPortInfoFromEndpoint(MIDIEndpointRef endpoint);
static double MIDITimeStampToSeconds(MIDITimeStamp timestamp);
@@ -62,6 +69,9 @@ class MEDIA_EXPORT MIDIManagerMac : public MIDIManager {
// Keeps track of all destinations.
std::vector<MIDIEndpointRef> destinations_;
+ // |send_thread_| is used to send MIDI data.
+ base::Thread send_thread_;
+
DISALLOW_COPY_AND_ASSIGN(MIDIManagerMac);
};
diff --git a/chromium/media/midi/midi_manager_win.cc b/chromium/media/midi/midi_manager_win.cc
new file mode 100644
index 00000000000..d250e6aefff
--- /dev/null
+++ b/chromium/media/midi/midi_manager_win.cc
@@ -0,0 +1,597 @@
+// Copyright 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
diff --git a/chromium/media/midi/midi_manager_win.h b/chromium/media/midi/midi_manager_win.h
new file mode 100644
index 00000000000..eef8b4e5680
--- /dev/null
+++ b/chromium/media/midi/midi_manager_win.h
@@ -0,0 +1,40 @@
+// Copyright 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.
+
+#ifndef MEDIA_MIDI_MIDI_MANAGER_WIN_H_
+#define MEDIA_MIDI_MIDI_MANAGER_WIN_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/thread.h"
+#include "media/midi/midi_manager.h"
+
+namespace media {
+
+class MIDIManagerWin : public MIDIManager {
+ public:
+ MIDIManagerWin();
+ virtual ~MIDIManagerWin();
+
+ // MIDIManager implementation.
+ virtual bool Initialize() OVERRIDE;
+ virtual void DispatchSendMIDIData(MIDIManagerClient* client,
+ uint32 port_index,
+ const std::vector<uint8>& data,
+ double timestamp) OVERRIDE;
+
+ private:
+ class InDeviceInfo;
+ class OutDeviceInfo;
+ std::vector<scoped_ptr<InDeviceInfo> > in_devices_;
+ std::vector<scoped_ptr<OutDeviceInfo> > out_devices_;
+ base::Thread send_thread_;
+ DISALLOW_COPY_AND_ASSIGN(MIDIManagerWin);
+};
+
+} // namespace media
+
+#endif // MEDIA_MIDI_MIDI_MANAGER_WIN_H_
diff --git a/chromium/media/midi/midi_message_queue.cc b/chromium/media/midi/midi_message_queue.cc
new file mode 100644
index 00000000000..3452e80be9d
--- /dev/null
+++ b/chromium/media/midi/midi_message_queue.cc
@@ -0,0 +1,119 @@
+// Copyright 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_message_queue.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "media/midi/midi_message_util.h"
+
+namespace media {
+namespace {
+
+const uint8 kSysEx = 0xf0;
+const uint8 kEndOfSysEx = 0xf7;
+
+bool IsDataByte(uint8 data) {
+ return (data & 0x80) == 0;
+}
+
+bool IsFirstStatusByte(uint8 data) {
+ return !IsDataByte(data) && data != kEndOfSysEx;
+}
+
+bool IsSystemRealTimeMessage(uint8 data) {
+ return 0xf8 <= data && data <= 0xff;
+}
+
+} // namespace
+
+MIDIMessageQueue::MIDIMessageQueue(bool allow_running_status)
+ : allow_running_status_(allow_running_status) {}
+
+MIDIMessageQueue::~MIDIMessageQueue() {}
+
+void MIDIMessageQueue::Add(const std::vector<uint8>& data) {
+ queue_.insert(queue_.end(), data.begin(), data.end());
+}
+
+void MIDIMessageQueue::Add(const uint8* data, size_t length) {
+ queue_.insert(queue_.end(), data, data + length);
+}
+
+void MIDIMessageQueue::Get(std::vector<uint8>* message) {
+ message->clear();
+
+ while (true) {
+ if (queue_.empty())
+ return;
+
+ const uint8 next = queue_.front();
+ queue_.pop_front();
+
+ // "System Real Time Messages" is a special kind of MIDI messages, which can
+ // appear at arbitrary byte position of MIDI stream. Here we reorder
+ // "System Real Time Messages" prior to |next_message_| so that each message
+ // can be clearly separated as a complete MIDI message.
+ if (IsSystemRealTimeMessage(next)) {
+ message->push_back(next);
+ return;
+ }
+
+ // Here |next_message_[0]| may contain the previous status byte when
+ // |allow_running_status_| is true. Following condition fixes up
+ // |next_message_| if running status condition is not fulfilled.
+ if (!next_message_.empty() &&
+ ((next_message_[0] == kSysEx && IsFirstStatusByte(next)) ||
+ (next_message_[0] != kSysEx && !IsDataByte(next)))) {
+ // An invalid data sequence is found or running status condition is not
+ // fulfilled.
+ next_message_.clear();
+ }
+
+ if (next_message_.empty()) {
+ if (IsFirstStatusByte(next)) {
+ next_message_.push_back(next);
+ } else {
+ // MIDI protocol doesn't provide any error correction mechanism in
+ // physical layers, and incoming messages can be corrupted, and should
+ // be corrected here.
+ }
+ continue;
+ }
+
+ // Here we can assume |next_message_| starts with a valid status byte.
+ const uint8 status_byte = next_message_[0];
+ next_message_.push_back(next);
+
+ if (status_byte == kSysEx) {
+ if (next == kEndOfSysEx) {
+ std::swap(*message, next_message_);
+ next_message_.clear();
+ return;
+ }
+ continue;
+ }
+
+ DCHECK(IsDataByte(next));
+ DCHECK_NE(kSysEx, status_byte);
+ const size_t target_len = GetMIDIMessageLength(status_byte);
+ if (next_message_.size() < target_len)
+ continue;
+ if (next_message_.size() == target_len) {
+ std::swap(*message, next_message_);
+ next_message_.clear();
+ if (allow_running_status_) {
+ // Speculatively keep the status byte in case of running status. If this
+ // assumption is not true, |next_message_| will be cleared anyway.
+ next_message_.push_back(status_byte);
+ }
+ return;
+ }
+
+ NOTREACHED();
+ }
+}
+
+} // namespace media
diff --git a/chromium/media/midi/midi_message_queue.h b/chromium/media/midi/midi_message_queue.h
new file mode 100644
index 00000000000..06f0f4787fb
--- /dev/null
+++ b/chromium/media/midi/midi_message_queue.h
@@ -0,0 +1,72 @@
+// Copyright 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.
+
+#ifndef MEDIA_MIDI_MIDI_MESSAGE_QUEUE_H_
+#define MEDIA_MIDI_MIDI_MESSAGE_QUEUE_H_
+
+#include <deque>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "media/base/media_export.h"
+
+namespace media {
+
+// A simple message splitter for possibly unsafe/corrupted MIDI data stream.
+// This class allows you to:
+// - maintain fragmented MIDI message.
+// - skip any invalid data sequence.
+// - reorder MIDI messages so that "System Real Time Message", which can be
+// inserted at any point of the byte stream, is placed at the boundary of
+// complete MIDI messages.
+// - (Optional) reconstruct complete MIDI messages from data stream where
+// MIDI status byte is abbreviated (a.k.a. "running status").
+//
+// Example (pseudo message loop):
+// MIDIMessageQueue queue(true); // true to support "running status"
+// while (true) {
+// if (is_incoming_midi_data_available()) {
+// std::vector<uint8> incoming_data;
+// read_incoming_midi_data(&incoming_data)
+// queue.Add(incoming_data);
+// }
+// while (true) {
+// std::vector<uint8> next_message;
+// queue.Get(&next_message);
+// if (!next_message.empty())
+// dispatch(next_message);
+// }
+// }
+class MEDIA_EXPORT MIDIMessageQueue {
+ public:
+ // Initializes the queue. Set true to |allow_running_status| to enable
+ // "MIDI running status" reconstruction.
+ explicit MIDIMessageQueue(bool allow_running_status);
+ ~MIDIMessageQueue();
+
+ // Enqueues |data| to the internal buffer.
+ void Add(const std::vector<uint8>& data);
+ void Add(const uint8* data, size_t length);
+
+ // Fills the next complete MIDI message into |message|. If |message| is
+ // not empty, the data sequence falls into one of the following types of
+ // MIDI message.
+ // - Single "Channel Voice Message" (w/o "System Real Time Messages")
+ // - Single "Channel Mode Message" (w/o "System Real Time Messages")
+ // - Single "System Exclusive Message" (w/o "System Real Time Messages")
+ // - Single "System Common Message" (w/o "System Real Time Messages")
+ // - Single "System Real Time message"
+ // |message| is empty if there is no complete MIDI message any more.
+ void Get(std::vector<uint8>* message);
+
+ private:
+ std::deque<uint8> queue_;
+ std::vector<uint8> next_message_;
+ const bool allow_running_status_;
+ DISALLOW_COPY_AND_ASSIGN(MIDIMessageQueue);
+};
+
+} // namespace media
+
+#endif // MEDIA_MIDI_MIDI_MESSAGE_QUEUE_H_
diff --git a/chromium/media/midi/midi_message_queue_unittest.cc b/chromium/media/midi/midi_message_queue_unittest.cc
new file mode 100644
index 00000000000..a00eea6b9e4
--- /dev/null
+++ b/chromium/media/midi/midi_message_queue_unittest.cc
@@ -0,0 +1,173 @@
+// Copyright 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_message_queue.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+namespace {
+
+const uint8 kGMOn[] = { 0xf0, 0x7e, 0x7f, 0x09, 0x01, 0xf7 };
+const uint8 kGSOn[] = {
+ 0xf0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7f, 0x00, 0x41, 0xf7,
+};
+const uint8 kNoteOn[] = { 0x90, 0x3c, 0x7f };
+const uint8 kNoteOnWithRunningStatus[] = {
+ 0x90, 0x3c, 0x7f, 0x3c, 0x7f, 0x3c, 0x7f,
+};
+const uint8 kChannelPressure[] = { 0xd0, 0x01 };
+const uint8 kChannelPressureWithRunningStatus[] = {
+ 0xd0, 0x01, 0x01, 0x01,
+};
+const uint8 kTimingClock[] = { 0xf8 };
+const uint8 kBrokenData1[] = { 0x90 };
+const uint8 kBrokenData2[] = { 0xf7 };
+const uint8 kBrokenData3[] = { 0xf2, 0x00 };
+const uint8 kDataByte0[] = { 0x00 };
+
+template <typename T, size_t N>
+void Add(MIDIMessageQueue* queue, const T(&array)[N]) {
+ queue->Add(array, N);
+}
+
+template <typename T, size_t N>
+::testing::AssertionResult ExpectEqualSequence(
+ const char* expr1, const char* expr2,
+ const T(&expected)[N], const std::vector<T>& actual) {
+ if (actual.size() != N) {
+ return ::testing::AssertionFailure()
+ << "expected: " << ::testing::PrintToString(expected)
+ << ", actual: " << ::testing::PrintToString(actual);
+ }
+ for (size_t i = 0; i < N; ++i) {
+ if (expected[i] != actual[i]) {
+ return ::testing::AssertionFailure()
+ << "expected: " << ::testing::PrintToString(expected)
+ << ", actual: " << ::testing::PrintToString(actual);
+ }
+ }
+ return ::testing::AssertionSuccess();
+}
+
+#define EXPECT_MESSAGE(expected, actual) \
+ EXPECT_PRED_FORMAT2(ExpectEqualSequence, expected, actual)
+
+TEST(MIDIMessageQueueTest, EmptyData) {
+ MIDIMessageQueue queue(false);
+ std::vector<uint8> message;
+ queue.Get(&message);
+ EXPECT_TRUE(message.empty());
+}
+
+TEST(MIDIMessageQueueTest, RunningStatusDisabled) {
+ MIDIMessageQueue queue(false);
+ Add(&queue, kGMOn);
+ Add(&queue, kBrokenData1);
+ Add(&queue, kNoteOnWithRunningStatus);
+ Add(&queue, kBrokenData2);
+ Add(&queue, kChannelPressureWithRunningStatus);
+ Add(&queue, kBrokenData3);
+ Add(&queue, kNoteOn);
+ Add(&queue, kBrokenData1);
+ Add(&queue, kGSOn);
+ Add(&queue, kBrokenData2);
+ Add(&queue, kTimingClock);
+ Add(&queue, kBrokenData3);
+
+ std::vector<uint8> message;
+ queue.Get(&message);
+ EXPECT_MESSAGE(kGMOn, message);
+ queue.Get(&message);
+ EXPECT_MESSAGE(kNoteOn, message) << "Running status should be ignored";
+ queue.Get(&message);
+ EXPECT_MESSAGE(kChannelPressure, message)
+ << "Running status should be ignored";
+ queue.Get(&message);
+ EXPECT_MESSAGE(kNoteOn, message);
+ queue.Get(&message);
+ EXPECT_MESSAGE(kGSOn, message);
+ queue.Get(&message);
+ EXPECT_MESSAGE(kTimingClock, message);
+ queue.Get(&message);
+ EXPECT_TRUE(message.empty());
+}
+
+TEST(MIDIMessageQueueTest, RunningStatusEnabled) {
+ MIDIMessageQueue queue(true);
+ Add(&queue, kGMOn);
+ Add(&queue, kBrokenData1);
+ Add(&queue, kNoteOnWithRunningStatus);
+ Add(&queue, kBrokenData2);
+ Add(&queue, kChannelPressureWithRunningStatus);
+ Add(&queue, kBrokenData3);
+ Add(&queue, kNoteOn);
+ Add(&queue, kBrokenData1);
+ Add(&queue, kGSOn);
+ Add(&queue, kBrokenData2);
+ Add(&queue, kTimingClock);
+ Add(&queue, kDataByte0);
+
+ std::vector<uint8> message;
+ queue.Get(&message);
+ EXPECT_MESSAGE(kGMOn, message);
+ queue.Get(&message);
+ EXPECT_MESSAGE(kNoteOn, message);
+ queue.Get(&message);
+ EXPECT_MESSAGE(kNoteOn, message)
+ << "Running status should be converted into a canonical MIDI message";
+ queue.Get(&message);
+ EXPECT_MESSAGE(kNoteOn, message)
+ << "Running status should be converted into a canonical MIDI message";
+ queue.Get(&message);
+ EXPECT_MESSAGE(kChannelPressure, message);
+ queue.Get(&message);
+ EXPECT_MESSAGE(kChannelPressure, message)
+ << "Running status should be converted into a canonical MIDI message";
+ queue.Get(&message);
+ EXPECT_MESSAGE(kChannelPressure, message)
+ << "Running status should be converted into a canonical MIDI message";
+ queue.Get(&message);
+ EXPECT_MESSAGE(kNoteOn, message);
+ queue.Get(&message);
+ EXPECT_MESSAGE(kGSOn, message);
+ queue.Get(&message);
+ EXPECT_MESSAGE(kTimingClock, message);
+ queue.Get(&message);
+ EXPECT_TRUE(message.empty())
+ << "Running status must not be applied to real time messages";
+}
+
+TEST(MIDIMessageQueueTest, RunningStatusEnabledWithRealTimeEvent) {
+ MIDIMessageQueue queue(true);
+ const uint8 kNoteOnWithRunningStatusWithkTimingClock[] = {
+ 0x90, 0xf8, 0x3c, 0xf8, 0x7f, 0xf8, 0x3c, 0xf8, 0x7f, 0xf8, 0x3c, 0xf8,
+ 0x7f,
+ };
+ Add(&queue, kNoteOnWithRunningStatusWithkTimingClock);
+ std::vector<uint8> message;
+ queue.Get(&message);
+ EXPECT_MESSAGE(kTimingClock, message);
+ queue.Get(&message);
+ EXPECT_MESSAGE(kTimingClock, message);
+ queue.Get(&message);
+ EXPECT_MESSAGE(kNoteOn, message);
+ queue.Get(&message);
+ EXPECT_MESSAGE(kTimingClock, message);
+ queue.Get(&message);
+ EXPECT_MESSAGE(kTimingClock, message);
+ queue.Get(&message);
+ EXPECT_MESSAGE(kNoteOn, message);
+ queue.Get(&message);
+ EXPECT_MESSAGE(kTimingClock, message);
+ queue.Get(&message);
+ EXPECT_MESSAGE(kTimingClock, message);
+ queue.Get(&message);
+ EXPECT_MESSAGE(kNoteOn, message);
+ queue.Get(&message);
+ EXPECT_TRUE(message.empty());
+}
+
+} // namespace
+} // namespace media
diff --git a/chromium/media/midi/midi_message_util.cc b/chromium/media/midi/midi_message_util.cc
new file mode 100644
index 00000000000..83d3cc071d9
--- /dev/null
+++ b/chromium/media/midi/midi_message_util.cc
@@ -0,0 +1,34 @@
+// Copyright 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_message_util.h"
+
+namespace media {
+
+size_t GetMIDIMessageLength(uint8 status_byte) {
+ if (status_byte < 0x80)
+ return 0;
+ if (0x80 <= status_byte && status_byte <= 0xbf)
+ return 3;
+ if (0xc0 <= status_byte && status_byte <= 0xdf)
+ return 2;
+ if (0xe0 <= status_byte && status_byte <= 0xef)
+ return 3;
+ if (status_byte == 0xf0)
+ return 0;
+ if (status_byte == 0xf1)
+ return 2;
+ if (status_byte == 0xf2)
+ return 3;
+ if (status_byte == 0xf3)
+ return 2;
+ if (0xf4 <= status_byte && status_byte <= 0xf6)
+ return 1;
+ if (status_byte == 0xf7)
+ return 0;
+ // 0xf8 <= status_byte && status_byte <= 0xff
+ return 1;
+}
+
+} // namespace media
diff --git a/chromium/media/midi/midi_message_util.h b/chromium/media/midi/midi_message_util.h
new file mode 100644
index 00000000000..1dc6d3cba78
--- /dev/null
+++ b/chromium/media/midi/midi_message_util.h
@@ -0,0 +1,25 @@
+// Copyright 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.
+
+#ifndef MEDIA_MIDI_MIDI_MESSAGE_UTIL_H_
+#define MEDIA_MIDI_MIDI_MESSAGE_UTIL_H_
+
+#include <deque>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "media/base/media_export.h"
+
+namespace media {
+
+// Returns the length of a MIDI message in bytes. Never returns 4 or greater.
+// Returns 0 if |status_byte| is:
+// - not a valid status byte, namely data byte.
+// - the MIDI System Exclusive message.
+// - the End of System Exclusive message.
+MEDIA_EXPORT size_t GetMIDIMessageLength(uint8 status_byte);
+
+} // namespace media
+
+#endif // MEDIA_MIDI_MIDI_MESSAGE_UTIL_H_
diff --git a/chromium/media/midi/midi_message_util_unittest.cc b/chromium/media/midi/midi_message_util_unittest.cc
new file mode 100644
index 00000000000..af3679c2987
--- /dev/null
+++ b/chromium/media/midi/midi_message_util_unittest.cc
@@ -0,0 +1,34 @@
+// Copyright 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_message_util.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+namespace {
+
+const uint8 kGMOn[] = { 0xf0, 0x7e, 0x7f, 0x09, 0x01, 0xf7 };
+const uint8 kNoteOn[] = { 0x90, 0x3c, 0x7f };
+const uint8 kChannelPressure[] = { 0xd0, 0x01 };
+const uint8 kTimingClock[] = { 0xf8 };
+
+TEST(GetMIDIMessageLengthTest, BasicTest) {
+ // Check basic functionarity
+ EXPECT_EQ(arraysize(kNoteOn), GetMIDIMessageLength(kNoteOn[0]));
+ EXPECT_EQ(arraysize(kChannelPressure),
+ GetMIDIMessageLength(kChannelPressure[0]));
+ EXPECT_EQ(arraysize(kTimingClock), GetMIDIMessageLength(kTimingClock[0]));
+
+ // SysEx message should be mapped to 0-length
+ EXPECT_EQ(0u, GetMIDIMessageLength(kGMOn[0]));
+
+ // Any data byte should be mapped to 0-length
+ EXPECT_EQ(0u, GetMIDIMessageLength(kGMOn[1]));
+ EXPECT_EQ(0u, GetMIDIMessageLength(kNoteOn[1]));
+ EXPECT_EQ(0u, GetMIDIMessageLength(kChannelPressure[1]));
+}
+
+} // namespace
+} // namespace media