summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoryhirano@chromium.org <yhirano@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-01-22 10:39:41 +0000
committeryhirano@chromium.org <yhirano@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-01-22 10:39:41 +0000
commit1e0f832b9b057a284de798e6c6acd1846821418a (patch)
treef7e231993175bfc7839060c44786df3ef8b5fb2d
parente5f77dce153f6515f2a91ec5554869432a675539 (diff)
downloadchromium_src-1e0f832b9b057a284de798e6c6acd1846821418a.zip
chromium_src-1e0f832b9b057a284de798e6c6acd1846821418a.tar.gz
chromium_src-1e0f832b9b057a284de798e6c6acd1846821418a.tar.bz2
[WebMIDI] Introduce UsbMidi{Input, Output}Stream.
UsbMidiInputStream converts USB-MIDI messages to MIDI messages. UsbMidiOutputStream converts MIDI MIDI messages to USB-MIDI messages. BUG=303596 R=toyoshim@chromium.org Review URL: https://codereview.chromium.org/107513012 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@246277 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--content/browser/renderer_host/media/midi_host.cc14
-rw-r--r--media/media.gyp7
-rw-r--r--media/midi/midi_message_util.h8
-rw-r--r--media/midi/usb_midi_device.h31
-rw-r--r--media/midi/usb_midi_input_stream.cc92
-rw-r--r--media/midi/usb_midi_input_stream.h80
-rw-r--r--media/midi/usb_midi_input_stream_unittest.cc175
-rw-r--r--media/midi/usb_midi_output_stream.cc187
-rw-r--r--media/midi/usb_midi_output_stream.h55
-rw-r--r--media/midi/usb_midi_output_stream_unittest.cc276
10 files changed, 918 insertions, 7 deletions
diff --git a/content/browser/renderer_host/media/midi_host.cc b/content/browser/renderer_host/media/midi_host.cc
index 2eb5ec6..765ebc1 100644
--- a/content/browser/renderer_host/media/midi_host.cc
+++ b/content/browser/renderer_host/media/midi_host.cc
@@ -35,9 +35,6 @@ const size_t kMaxInFlightBytes = 10 * 1024 * 1024; // 10 MB.
// how many bytes will be sent before reporting back to the renderer.
const size_t kAcknowledgementThresholdBytes = 1024 * 1024; // 1 MB.
-const uint8 kSysExMessage = 0xf0;
-const uint8 kEndOfSysExMessage = 0xf7;
-
bool IsDataByte(uint8 data) {
return (data & 0x80) == 0;
}
@@ -48,6 +45,9 @@ bool IsSystemRealTimeMessage(uint8 data) {
} // namespace
+using media::kSysExByte;
+using media::kEndOfSysExByte;
+
MIDIHost::MIDIHost(int renderer_process_id, media::MIDIManager* midi_manager)
: renderer_process_id_(renderer_process_id),
has_sys_ex_permission_(false),
@@ -119,7 +119,7 @@ void MIDIHost::OnSendData(uint32 port,
// in JavaScript. The actual permission check for security purposes
// happens here in the browser process.
if (!has_sys_ex_permission_ &&
- (std::find(data.begin(), data.end(), kSysExMessage) != data.end())) {
+ std::find(data.begin(), data.end(), kSysExByte) != data.end()) {
RecordAction(base::UserMetricsAction("BadMessageTerminate_MIDI"));
BadMessageReceived();
return;
@@ -162,7 +162,7 @@ void MIDIHost::ReceiveMIDIData(
// MIDI devices may send a system exclusive messages even if the renderer
// doesn't have a permission to receive it. Don't kill the renderer as
// OnSendData() does.
- if (message[0] == kSysExMessage && !has_sys_ex_permission_)
+ if (message[0] == kSysExByte && !has_sys_ex_permission_)
continue;
// Send to the renderer.
@@ -204,13 +204,13 @@ bool MIDIHost::IsValidWebMIDIData(const std::vector<uint8>& data) {
continue; // Found data byte as expected.
}
if (in_sysex) {
- if (data[i] == kEndOfSysExMessage)
+ if (data[i] == kEndOfSysExByte)
in_sysex = false;
else if (!IsDataByte(current))
return false; // Error: |current| should have been data byte.
continue; // Found data byte as expected.
}
- if (current == kSysExMessage) {
+ if (current == kSysExByte) {
in_sysex = true;
continue; // Found SysEX
}
diff --git a/media/media.gyp b/media/media.gyp
index a983499..6e5d89b 100644
--- a/media/media.gyp
+++ b/media/media.gyp
@@ -427,7 +427,12 @@
'midi/midi_port_info.h',
'midi/usb_midi_descriptor_parser.cc',
'midi/usb_midi_descriptor_parser.h',
+ 'midi/usb_midi_device.h',
+ 'midi/usb_midi_input_stream.cc',
+ 'midi/usb_midi_input_stream.h',
'midi/usb_midi_jack.h',
+ 'midi/usb_midi_output_stream.cc',
+ 'midi/usb_midi_output_stream.h',
'video/capture/android/video_capture_device_android.cc',
'video/capture/android/video_capture_device_android.h',
'video/capture/fake_video_capture_device.cc',
@@ -1019,6 +1024,8 @@
'midi/midi_message_queue_unittest.cc',
'midi/midi_message_util_unittest.cc',
'midi/usb_midi_descriptor_parser_unittest.cc',
+ 'midi/usb_midi_input_stream_unittest.cc',
+ 'midi/usb_midi_output_stream_unittest.cc',
'video/capture/video_capture_device_unittest.cc',
'webm/cluster_builder.cc',
'webm/cluster_builder.h',
diff --git a/media/midi/midi_message_util.h b/media/midi/midi_message_util.h
index 1dc6d3c..b40d814 100644
--- a/media/midi/midi_message_util.h
+++ b/media/midi/midi_message_util.h
@@ -20,6 +20,14 @@ namespace media {
// - the End of System Exclusive message.
MEDIA_EXPORT size_t GetMIDIMessageLength(uint8 status_byte);
+const uint8 kSysExByte = 0xf0;
+const uint8 kEndOfSysExByte = 0xf7;
+
+const uint8 kSysMessageBitMask = 0xf0;
+const uint8 kSysMessageBitPattern = 0xf0;
+const uint8 kSysRTMessageBitMask = 0xf8;
+const uint8 kSysRTMessageBitPattern = 0xf8;
+
} // namespace media
#endif // MEDIA_MIDI_MIDI_MESSAGE_UTIL_H_
diff --git a/media/midi/usb_midi_device.h b/media/midi/usb_midi_device.h
new file mode 100644
index 0000000..3f29d64
--- /dev/null
+++ b/media/midi/usb_midi_device.h
@@ -0,0 +1,31 @@
+// Copyright 2014 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_USB_MIDI_DEVICE_H_
+#define MEDIA_MIDI_USB_MIDI_DEVICE_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "media/base/media_export.h"
+
+namespace media {
+
+// UsbMidiDevice represents a USB-MIDI device.
+// This is an interface class and each platform-dependent implementation class
+// will be a derived class.
+class MEDIA_EXPORT UsbMidiDevice {
+ public:
+ virtual ~UsbMidiDevice() {}
+
+ // Returns the descriptor of this device.
+ virtual std::vector<uint8> GetDescriptor() = 0;
+
+ // Sends |data| to the given USB endpoint of this device.
+ virtual void Send(int endpoint_number, const std::vector<uint8>& data) = 0;
+};
+
+} // namespace media
+
+#endif // MEDIA_MIDI_USB_MIDI_DEVICE_H_
diff --git a/media/midi/usb_midi_input_stream.cc b/media/midi/usb_midi_input_stream.cc
new file mode 100644
index 0000000..f68436c
--- /dev/null
+++ b/media/midi/usb_midi_input_stream.cc
@@ -0,0 +1,92 @@
+// Copyright 2014 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/usb_midi_input_stream.h"
+
+#include <string.h>
+#include <map>
+#include <vector>
+
+#include "base/logging.h"
+#include "media/midi/usb_midi_device.h"
+#include "media/midi/usb_midi_jack.h"
+
+namespace media {
+
+UsbMidiInputStream::JackUniqueKey::JackUniqueKey(UsbMidiDevice* device,
+ int endpoint_number,
+ int cable_number)
+ : device(device),
+ endpoint_number(endpoint_number),
+ cable_number(cable_number) {}
+
+bool UsbMidiInputStream::JackUniqueKey::operator==(
+ const JackUniqueKey& that) const {
+ return device == that.device &&
+ endpoint_number == that.endpoint_number &&
+ cable_number == that.cable_number;
+}
+
+bool UsbMidiInputStream::JackUniqueKey::operator<(
+ const JackUniqueKey& that) const {
+ if (device != that.device)
+ return device < that.device;
+ if (endpoint_number != that.endpoint_number)
+ return endpoint_number < that.endpoint_number;
+ return cable_number < that.cable_number;
+}
+
+UsbMidiInputStream::UsbMidiInputStream(const std::vector<UsbMidiJack>& jacks,
+ Delegate* delegate)
+ : delegate_(delegate) {
+ for (size_t i = 0; i < jacks.size(); ++i) {
+ jack_dictionary_.insert(
+ std::make_pair(JackUniqueKey(jacks[i].device,
+ jacks[i].endpoint_number(),
+ jacks[i].cable_number),
+ i));
+ }
+}
+
+UsbMidiInputStream::~UsbMidiInputStream() {}
+
+void UsbMidiInputStream::OnReceivedData(UsbMidiDevice* device,
+ int endpoint_number,
+ const uint8* data,
+ size_t size,
+ double timestamp) {
+ DCHECK_EQ(0u, size % kPacketSize);
+ size_t current = 0;
+ while (current + kPacketSize <= size) {
+ ProcessOnePacket(device, endpoint_number, &data[current], timestamp);
+ current += kPacketSize;
+ }
+}
+
+void UsbMidiInputStream::ProcessOnePacket(UsbMidiDevice* device,
+ int endpoint_number,
+ const uint8* packet,
+ double timestamp) {
+ // The first 4 bytes of the packet is accessible here.
+ uint8 code_index = packet[0] & 0x0f;
+ uint8 cable_number = packet[0] >> 4;
+ const size_t packet_size_table[16] = {
+ 0, 0, 2, 3, 3, 1, 2, 3, 3, 3, 3, 3, 2, 2, 3, 1,
+ };
+ size_t packet_size = packet_size_table[code_index];
+ if (packet_size == 0) {
+ // These CINs are reserved. Ignore them.
+ DVLOG(1) << "code index number (" << code_index << ") arrives "
+ << "but it is reserved.";
+ return;
+ }
+ std::map<JackUniqueKey, size_t>::const_iterator it =
+ jack_dictionary_.find(JackUniqueKey(device,
+ endpoint_number,
+ cable_number));
+ if (it != jack_dictionary_.end())
+ delegate_->OnReceivedData(it->second, &packet[1], packet_size, timestamp);
+}
+
+} // namespace media
diff --git a/media/midi/usb_midi_input_stream.h b/media/midi/usb_midi_input_stream.h
new file mode 100644
index 0000000..70ed306
--- /dev/null
+++ b/media/midi/usb_midi_input_stream.h
@@ -0,0 +1,80 @@
+// Copyright 2014 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_USB_MIDI_INPUT_STREAM_H_
+#define MEDIA_MIDI_USB_MIDI_INPUT_STREAM_H_
+
+#include <map>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/containers/hash_tables.h"
+#include "media/base/media_export.h"
+#include "media/midi/usb_midi_jack.h"
+
+namespace media {
+
+class UsbMidiDevice;
+
+// UsbMidiInputStream converts USB-MIDI data to MIDI data.
+// See "USB Device Class Definition for MIDI Devices" Release 1.0,
+// Section 4 "USB-MIDI Event Packets" for details.
+class MEDIA_EXPORT UsbMidiInputStream {
+ public:
+ class Delegate {
+ public:
+ virtual ~Delegate() {}
+ // This function is called when some data arrives to a USB-MIDI jack.
+ // An input USB-MIDI jack corresponds to an input MIDIPortInfo.
+ virtual void OnReceivedData(size_t jack_index,
+ const uint8* data,
+ size_t size,
+ double timestamp) = 0;
+ };
+
+ UsbMidiInputStream(const std::vector<UsbMidiJack>& jacks,
+ Delegate* delegate);
+ ~UsbMidiInputStream();
+
+ // This function should be called when some data arrives to a USB-MIDI
+ // endpoint. This function converts the data to MIDI data and call
+ // |delegate->OnReceivedData| with it.
+ // |size| must be a multiple of |kPacketSize|.
+ void OnReceivedData(UsbMidiDevice* device,
+ int endpoint_number,
+ const uint8* data,
+ size_t size,
+ double timestamp);
+
+ private:
+ static const size_t kPacketSize = 4;
+ struct JackUniqueKey {
+ JackUniqueKey(UsbMidiDevice* device, int endpoint_number, int cable_number);
+ bool operator==(const JackUniqueKey& that) const;
+ bool operator<(const JackUniqueKey& that) const;
+
+ UsbMidiDevice* device;
+ int endpoint_number;
+ int cable_number;
+ };
+
+ // Processes a USB-MIDI Event Packet.
+ // The first |kPacketSize| bytes of |packet| must be accessible.
+ void ProcessOnePacket(UsbMidiDevice* device,
+ int endpoint_number,
+ const uint8* packet,
+ double timestamp);
+
+ // A map from UsbMidiJack to its index in |jacks_|.
+ std::map<JackUniqueKey, size_t> jack_dictionary_;
+
+ // Not owned
+ Delegate* delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(UsbMidiInputStream);
+};
+
+} // namespace media
+
+#endif // MEDIA_MIDI_USB_MIDI_INPUT_STREAM_H_
diff --git a/media/midi/usb_midi_input_stream_unittest.cc b/media/midi/usb_midi_input_stream_unittest.cc
new file mode 100644
index 0000000..589f490
--- /dev/null
+++ b/media/midi/usb_midi_input_stream_unittest.cc
@@ -0,0 +1,175 @@
+// Copyright 2014 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/usb_midi_input_stream.h"
+
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/stringprintf.h"
+#include "media/midi/usb_midi_device.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+
+namespace {
+
+class TestUsbMidiDevice : public UsbMidiDevice {
+ public:
+ TestUsbMidiDevice() {}
+ virtual ~TestUsbMidiDevice() {}
+ virtual std::vector<uint8> GetDescriptor() OVERRIDE {
+ return std::vector<uint8>();
+ }
+ virtual void Send(int endpoint_number,
+ const std::vector<uint8>& data) OVERRIDE {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestUsbMidiDevice);
+};
+
+class MockDelegate : public UsbMidiInputStream::Delegate {
+ public:
+ MockDelegate() {}
+ virtual ~MockDelegate() {}
+ virtual void OnReceivedData(size_t jack_index,
+ const uint8* data,
+ size_t size,
+ double timestamp) OVERRIDE {
+ for (size_t i = 0; i < size; ++i)
+ received_data_ += base::StringPrintf("0x%02x ", data[i]);
+ received_data_ += "\n";
+ }
+
+ const std::string& received_data() const { return received_data_; }
+
+ private:
+ std::string received_data_;
+ DISALLOW_COPY_AND_ASSIGN(MockDelegate);
+};
+
+class UsbMidiInputStreamTest : public ::testing::Test {
+ protected:
+ UsbMidiInputStreamTest() {
+ std::vector<UsbMidiJack> jacks;
+
+ jacks.push_back(UsbMidiJack(&device1_,
+ 84, // jack_id
+ 4, // cable_number
+ 135)); // endpoint_address
+ jacks.push_back(UsbMidiJack(&device2_,
+ 85,
+ 5,
+ 137));
+ jacks.push_back(UsbMidiJack(&device2_,
+ 84,
+ 4,
+ 135));
+ jacks.push_back(UsbMidiJack(&device1_,
+ 85,
+ 5,
+ 135));
+
+ stream_.reset(new UsbMidiInputStream(jacks, &delegate_));
+ }
+
+ TestUsbMidiDevice device1_;
+ TestUsbMidiDevice device2_;
+ MockDelegate delegate_;
+ scoped_ptr<UsbMidiInputStream> stream_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(UsbMidiInputStreamTest);
+};
+
+TEST_F(UsbMidiInputStreamTest, UnknownMessage) {
+ uint8 data[] = {
+ 0x40, 0xff, 0xff, 0xff,
+ 0x41, 0xff, 0xff, 0xff,
+ };
+
+ stream_->OnReceivedData(&device1_, 7, data, arraysize(data), 0);
+ EXPECT_EQ("", delegate_.received_data());
+}
+
+TEST_F(UsbMidiInputStreamTest, SystemCommonMessage) {
+ uint8 data[] = {
+ 0x45, 0xf8, 0x00, 0x00,
+ 0x42, 0xf3, 0x22, 0x00,
+ 0x43, 0xf2, 0x33, 0x44,
+ };
+
+ stream_->OnReceivedData(&device1_, 7, data, arraysize(data), 0);
+ EXPECT_EQ("0xf8 \n"
+ "0xf3 0x22 \n"
+ "0xf2 0x33 0x44 \n", delegate_.received_data());
+}
+
+TEST_F(UsbMidiInputStreamTest, SystemExclusiveMessage) {
+ uint8 data[] = {
+ 0x44, 0xf0, 0x11, 0x22,
+ 0x45, 0xf7, 0x00, 0x00,
+ 0x46, 0xf0, 0xf7, 0x00,
+ 0x47, 0xf0, 0x33, 0xf7,
+ };
+
+ stream_->OnReceivedData(&device1_, 7, data, arraysize(data), 0);
+ EXPECT_EQ("0xf0 0x11 0x22 \n"
+ "0xf7 \n"
+ "0xf0 0xf7 \n"
+ "0xf0 0x33 0xf7 \n", delegate_.received_data());
+}
+
+TEST_F(UsbMidiInputStreamTest, ChannelMessage) {
+ uint8 data[] = {
+ 0x48, 0x80, 0x11, 0x22,
+ 0x49, 0x90, 0x33, 0x44,
+ 0x4a, 0xa0, 0x55, 0x66,
+ 0x4b, 0xb0, 0x77, 0x88,
+ 0x4c, 0xc0, 0x99, 0x00,
+ 0x4d, 0xd0, 0xaa, 0x00,
+ 0x4e, 0xe0, 0xbb, 0xcc,
+ };
+
+ stream_->OnReceivedData(&device1_, 7, data, arraysize(data), 0);
+ EXPECT_EQ("0x80 0x11 0x22 \n"
+ "0x90 0x33 0x44 \n"
+ "0xa0 0x55 0x66 \n"
+ "0xb0 0x77 0x88 \n"
+ "0xc0 0x99 \n"
+ "0xd0 0xaa \n"
+ "0xe0 0xbb 0xcc \n", delegate_.received_data());
+}
+
+TEST_F(UsbMidiInputStreamTest, SingleByteMessage) {
+ uint8 data[] = {
+ 0x4f, 0xf8, 0x00, 0x00,
+ };
+
+ stream_->OnReceivedData(&device1_, 7, data, arraysize(data), 0);
+ EXPECT_EQ("0xf8 \n", delegate_.received_data());
+}
+
+TEST_F(UsbMidiInputStreamTest, DispatchForMultipleCables) {
+ uint8 data[] = {
+ 0x4f, 0xf8, 0x00, 0x00,
+ 0x5f, 0xfa, 0x00, 0x00,
+ 0x6f, 0xfb, 0x00, 0x00,
+ };
+
+ stream_->OnReceivedData(&device1_, 7, data, arraysize(data), 99);
+ EXPECT_EQ("0xf8 \n0xfa \n", delegate_.received_data());
+}
+
+TEST_F(UsbMidiInputStreamTest, DispatchForDevice2) {
+ uint8 data[] = { 0x4f, 0xf8, 0x00, 0x00 };
+
+ stream_->OnReceivedData(&device2_, 7, data, arraysize(data), 99);
+ EXPECT_EQ("0xf8 \n", delegate_.received_data());
+}
+
+} // namespace
+
+} // namespace media
diff --git a/media/midi/usb_midi_output_stream.cc b/media/midi/usb_midi_output_stream.cc
new file mode 100644
index 0000000..1aef282
--- /dev/null
+++ b/media/midi/usb_midi_output_stream.cc
@@ -0,0 +1,187 @@
+// Copyright 2014 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/usb_midi_output_stream.h"
+
+#include "base/logging.h"
+#include "media/midi/midi_message_util.h"
+#include "media/midi/usb_midi_device.h"
+
+namespace media {
+
+UsbMidiOutputStream::UsbMidiOutputStream(const UsbMidiJack& jack)
+ : jack_(jack), pending_size_(0), is_sending_sysex_(false) {}
+
+void UsbMidiOutputStream::Send(const std::vector<uint8>& data) {
+ // To prevent link errors caused by DCHECK_*.
+ const size_t kPacketContentSize = UsbMidiOutputStream::kPacketContentSize;
+ DCHECK_LT(jack_.cable_number, 16u);
+
+ std::vector<uint8> data_to_send;
+ size_t current = 0;
+ size_t size = GetSize(data);
+ while (current < size) {
+ uint8 first_byte = Get(data, current);
+ if (first_byte == kSysExByte || is_sending_sysex_) {
+ // System Exclusive messages
+ if (!PushSysExMessage(data, &current, &data_to_send))
+ break;
+ } else if ((first_byte & kSysMessageBitMask) == kSysMessageBitPattern) {
+ if (first_byte & 0x08) {
+ // System Real-Time messages
+ PushSysRTMessage(data, &current, &data_to_send);
+ } else {
+ // System Common messages
+ if (!PushSysCommonMessage(data, &current, &data_to_send))
+ break;
+ }
+ } else if (first_byte & 0x80) {
+ if (!PushChannelMessage(data, &current, &data_to_send))
+ break;
+ } else {
+ // Unknown messages
+ DVLOG(1) << "Unknown byte: " << static_cast<unsigned int>(first_byte);
+ ++current;
+ }
+ }
+
+ if (data_to_send.size() > 0)
+ jack_.device->Send(jack_.endpoint_number(), data_to_send);
+
+ DCHECK_LE(current, size);
+ DCHECK_LE(size - current, kPacketContentSize);
+ // Note that this can be a self-copying and the iteration order is important.
+ for (size_t i = current; i < size; ++i)
+ pending_data_[i - current] = Get(data, i);
+ pending_size_ = size - current;
+}
+
+size_t UsbMidiOutputStream::GetSize(const std::vector<uint8>& data) const {
+ return data.size() + pending_size_;
+}
+
+uint8_t UsbMidiOutputStream::Get(const std::vector<uint8>& data,
+ size_t index) const {
+ DCHECK_LT(index, GetSize(data));
+ if (index < pending_size_)
+ return pending_data_[index];
+ return data[index - pending_size_];
+}
+
+bool UsbMidiOutputStream::PushSysExMessage(const std::vector<uint8>& data,
+ size_t* current,
+ std::vector<uint8>* data_to_send) {
+ size_t index = *current;
+ size_t message_size = 0;
+ const size_t kMessageSizeMax = 3;
+ uint8 message[kMessageSizeMax] = {};
+
+ while (index < GetSize(data)) {
+ if (message_size == kMessageSizeMax) {
+ // We can't find the end-of-message mark in the three bytes.
+ *current = index;
+ data_to_send->push_back((jack_.cable_number << 4) | 0x4);
+ data_to_send->insert(data_to_send->end(),
+ message,
+ message + arraysize(message));
+ is_sending_sysex_ = true;
+ return true;
+ }
+ uint8 byte = Get(data, index);
+ if ((byte & kSysRTMessageBitMask) == kSysRTMessageBitPattern) {
+ // System Real-Time messages interleaved in a SysEx message
+ PushSysRTMessage(data, &index, data_to_send);
+ continue;
+ }
+
+ message[message_size] = byte;
+ ++message_size;
+ if (byte == kEndOfSysExByte) {
+ uint8 code_index = message_size + 0x4;
+ DCHECK(code_index == 0x5 || code_index == 0x6 || code_index == 0x7);
+ data_to_send->push_back((jack_.cable_number << 4) | code_index);
+ data_to_send->insert(data_to_send->end(),
+ message,
+ message + arraysize(message));
+ *current = index + 1;
+ is_sending_sysex_ = false;
+ return true;
+ }
+ ++index;
+ }
+ return false;
+}
+
+bool UsbMidiOutputStream::PushSysCommonMessage(
+ const std::vector<uint8>& data,
+ size_t* current,
+ std::vector<uint8>* data_to_send) {
+ size_t index = *current;
+ uint8 first_byte = Get(data, index);
+ DCHECK_LE(0xf1, first_byte);
+ DCHECK_LE(first_byte, 0xf7);
+ const size_t message_size_table[8] = {
+ 0, 2, 3, 2, 1, 1, 1, 0,
+ };
+ size_t message_size = message_size_table[first_byte & 0x0f];
+ DCHECK_NE(0u, message_size);
+ DCHECK_LE(message_size, 3u);
+
+ if (GetSize(data) < index + message_size) {
+ // The message is incomplete.
+ return false;
+ }
+
+ uint8 code_index = message_size == 1 ? 0x5 : static_cast<uint8>(message_size);
+ data_to_send->push_back((jack_.cable_number << 4) | code_index);
+ for (size_t i = index; i < index + 3; ++i)
+ data_to_send->push_back(i < index + message_size ? Get(data, i) : 0);
+ *current += message_size;
+ return true;
+}
+
+void UsbMidiOutputStream::PushSysRTMessage(const std::vector<uint8>& data,
+ size_t* current,
+ std::vector<uint8>* data_to_send) {
+ size_t index = *current;
+ uint8 first_byte = Get(data, index);
+ DCHECK_LE(0xf8, first_byte);
+ DCHECK_LE(first_byte, 0xff);
+
+ data_to_send->push_back((jack_.cable_number << 4) | 0x5);
+ data_to_send->push_back(first_byte);
+ data_to_send->push_back(0);
+ data_to_send->push_back(0);
+ *current += 1;
+}
+
+bool UsbMidiOutputStream::PushChannelMessage(const std::vector<uint8>& data,
+ size_t* current,
+ std::vector<uint8>* data_to_send) {
+ size_t index = *current;
+ uint8 first_byte = Get(data, index);
+ DCHECK_LE(0x80, (first_byte & 0xf0));
+ DCHECK_LE((first_byte & 0xf0), 0xe0);
+
+ const size_t message_size_table[8] = {
+ 3, 3, 3, 3, 2, 3, 3, 0,
+ };
+ uint8 code_index = first_byte >> 4;
+ size_t message_size = message_size_table[code_index & 0x7];
+ DCHECK_NE(0u, message_size);
+ DCHECK_LE(message_size, 3u);
+
+ if (GetSize(data) < index + message_size) {
+ // The message is incomplete.
+ return false;
+ }
+
+ data_to_send->push_back((jack_.cable_number << 4) | code_index);
+ for (size_t i = index; i < index + 3; ++i)
+ data_to_send->push_back(i < index + message_size ? Get(data, i) : 0);
+ *current += message_size;
+ return true;
+}
+
+} // namespace media
diff --git a/media/midi/usb_midi_output_stream.h b/media/midi/usb_midi_output_stream.h
new file mode 100644
index 0000000..008da4c
--- /dev/null
+++ b/media/midi/usb_midi_output_stream.h
@@ -0,0 +1,55 @@
+// Copyright 2014 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_USB_MIDI_OUTPUT_STREAM_H_
+#define MEDIA_MIDI_USB_MIDI_OUTPUT_STREAM_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "media/base/media_export.h"
+#include "media/midi/usb_midi_jack.h"
+
+namespace media {
+
+// UsbMidiOutputStream converts MIDI data to USB-MIDI data.
+// See "USB Device Class Definition for MIDI Devices" Release 1.0,
+// Section 4 "USB-MIDI Event Packets" for details.
+class MEDIA_EXPORT UsbMidiOutputStream {
+ public:
+ explicit UsbMidiOutputStream(const UsbMidiJack& jack);
+
+ // Converts |data| to USB-MIDI data and send it to the jack.
+ void Send(const std::vector<uint8>& data);
+
+ private:
+ size_t GetSize(const std::vector<uint8>& data) const;
+ uint8_t Get(const std::vector<uint8>& data, size_t index) const;
+
+ bool PushSysExMessage(const std::vector<uint8>& data,
+ size_t* current,
+ std::vector<uint8>* data_to_send);
+ bool PushSysCommonMessage(const std::vector<uint8>& data,
+ size_t* current,
+ std::vector<uint8>* data_to_send);
+ void PushSysRTMessage(const std::vector<uint8>& data,
+ size_t* current,
+ std::vector<uint8>* data_to_send);
+ bool PushChannelMessage(const std::vector<uint8>& data,
+ size_t* current,
+ std::vector<uint8>* data_to_send);
+
+ static const size_t kPacketContentSize = 3;
+
+ UsbMidiJack jack_;
+ size_t pending_size_;
+ uint8 pending_data_[kPacketContentSize];
+ bool is_sending_sysex_;
+
+ DISALLOW_COPY_AND_ASSIGN(UsbMidiOutputStream);
+};
+
+} // namespace media
+
+#endif // MEDIA_MIDI_USB_MIDI_OUTPUT_STREAM_H_
diff --git a/media/midi/usb_midi_output_stream_unittest.cc b/media/midi/usb_midi_output_stream_unittest.cc
new file mode 100644
index 0000000..661d611
--- /dev/null
+++ b/media/midi/usb_midi_output_stream_unittest.cc
@@ -0,0 +1,276 @@
+// Copyright 2014 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/usb_midi_output_stream.h"
+
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/stringprintf.h"
+#include "media/midi/usb_midi_device.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+
+namespace {
+
+template<typename T, size_t N>
+std::vector<T> ToVector(const T((&array)[N])) {
+ return std::vector<T>(array, array + N);
+}
+
+class MockUsbMidiDevice : public UsbMidiDevice {
+ public:
+ MockUsbMidiDevice() {}
+ virtual ~MockUsbMidiDevice() {}
+
+ virtual std::vector<uint8> GetDescriptor() OVERRIDE {
+ return std::vector<uint8>();
+ }
+
+ virtual void Send(int endpoint_number, const std::vector<uint8>& data)
+ OVERRIDE {
+ for (size_t i = 0; i < data.size(); ++i) {
+ log_ += base::StringPrintf("0x%02x ", data[i]);
+ }
+ log_ += base::StringPrintf("(endpoint = %d)\n", endpoint_number);
+ }
+
+ const std::string& log() const { return log_; }
+
+ void ClearLog() { log_ = ""; }
+
+ private:
+ std::string log_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockUsbMidiDevice);
+};
+
+class UsbMidiOutputStreamTest : public ::testing::Test {
+ protected:
+ UsbMidiOutputStreamTest() {
+ UsbMidiJack jack(&device_, 1, 2, 4);
+ stream_.reset(new UsbMidiOutputStream(jack));
+ }
+
+ MockUsbMidiDevice device_;
+ scoped_ptr<UsbMidiOutputStream> stream_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(UsbMidiOutputStreamTest);
+};
+
+TEST_F(UsbMidiOutputStreamTest, SendEmpty) {
+ stream_->Send(std::vector<uint8>());
+
+ EXPECT_EQ("", device_.log());
+}
+
+TEST_F(UsbMidiOutputStreamTest, SendNoteOn) {
+ uint8 data[] = { 0x90, 0x45, 0x7f};
+
+ stream_->Send(ToVector(data));
+ EXPECT_EQ("0x29 0x90 0x45 0x7f (endpoint = 4)\n", device_.log());
+}
+
+TEST_F(UsbMidiOutputStreamTest, SendNoteOnPending) {
+ stream_->Send(std::vector<uint8>(1, 0x90));
+ stream_->Send(std::vector<uint8>(1, 0x45));
+ EXPECT_EQ("", device_.log());
+
+ stream_->Send(std::vector<uint8>(1, 0x7f));
+ EXPECT_EQ("0x29 0x90 0x45 0x7f (endpoint = 4)\n", device_.log());
+ device_.ClearLog();
+
+ stream_->Send(std::vector<uint8>(1, 0x90));
+ stream_->Send(std::vector<uint8>(1, 0x45));
+ EXPECT_EQ("", device_.log());
+}
+
+TEST_F(UsbMidiOutputStreamTest, SendNoteOnBurst) {
+ uint8 data1[] = { 0x90, };
+ uint8 data2[] = { 0x45, 0x7f, 0x90, 0x45, 0x71, 0x90, 0x45, 0x72, 0x90, };
+
+ stream_->Send(ToVector(data1));
+ stream_->Send(ToVector(data2));
+ EXPECT_EQ("0x29 0x90 0x45 0x7f "
+ "0x29 0x90 0x45 0x71 "
+ "0x29 0x90 0x45 0x72 (endpoint = 4)\n", device_.log());
+}
+
+TEST_F(UsbMidiOutputStreamTest, SendNoteOff) {
+ uint8 data[] = { 0x80, 0x33, 0x44, };
+
+ stream_->Send(ToVector(data));
+ EXPECT_EQ("0x28 0x80 0x33 0x44 (endpoint = 4)\n", device_.log());
+}
+
+TEST_F(UsbMidiOutputStreamTest, SendPolyphonicKeyPress) {
+ uint8 data[] = { 0xa0, 0x33, 0x44, };
+
+ stream_->Send(ToVector(data));
+ EXPECT_EQ("0x2a 0xa0 0x33 0x44 (endpoint = 4)\n", device_.log());
+}
+
+TEST_F(UsbMidiOutputStreamTest, SendControlChange) {
+ uint8 data[] = { 0xb7, 0x33, 0x44, };
+
+ stream_->Send(ToVector(data));
+ EXPECT_EQ("0x2b 0xb7 0x33 0x44 (endpoint = 4)\n", device_.log());
+}
+
+TEST_F(UsbMidiOutputStreamTest, SendProgramChange) {
+ uint8 data[] = { 0xc2, 0x33, };
+
+ stream_->Send(ToVector(data));
+ EXPECT_EQ("0x2c 0xc2 0x33 0x00 (endpoint = 4)\n", device_.log());
+}
+
+TEST_F(UsbMidiOutputStreamTest, SendChannelPressure) {
+ uint8 data[] = { 0xd1, 0x33, 0x44, };
+
+ stream_->Send(ToVector(data));
+ EXPECT_EQ("0x2d 0xd1 0x33 0x44 (endpoint = 4)\n", device_.log());
+}
+
+TEST_F(UsbMidiOutputStreamTest, SendPitchWheelChange) {
+ uint8 data[] = { 0xe4, 0x33, 0x44, };
+
+ stream_->Send(ToVector(data));
+ EXPECT_EQ("0x2e 0xe4 0x33 0x44 (endpoint = 4)\n", device_.log());
+}
+
+TEST_F(UsbMidiOutputStreamTest, SendTwoByteSysEx) {
+ uint8 data[] = { 0xf0, 0xf7, };
+
+ stream_->Send(ToVector(data));
+ EXPECT_EQ("0x26 0xf0 0xf7 0x00 (endpoint = 4)\n", device_.log());
+}
+
+TEST_F(UsbMidiOutputStreamTest, SendThreeByteSysEx) {
+ uint8 data[] = { 0xf0, 0x4f, 0xf7, };
+
+ stream_->Send(ToVector(data));
+ EXPECT_EQ("0x27 0xf0 0x4f 0xf7 (endpoint = 4)\n", device_.log());
+}
+
+TEST_F(UsbMidiOutputStreamTest, SendFourByteSysEx) {
+ uint8 data[] = { 0xf0, 0x00, 0x01, 0xf7, };
+
+ stream_->Send(ToVector(data));
+ EXPECT_EQ("0x24 0xf0 0x00 0x01 "
+ "0x25 0xf7 0x00 0x00 (endpoint = 4)\n", device_.log());
+}
+
+TEST_F(UsbMidiOutputStreamTest, SendFiveByteSysEx) {
+ uint8 data[] = { 0xf0, 0x00, 0x01, 0x02, 0xf7, };
+
+ stream_->Send(ToVector(data));
+ EXPECT_EQ("0x24 0xf0 0x00 0x01 "
+ "0x26 0x02 0xf7 0x00 (endpoint = 4)\n", device_.log());
+}
+
+TEST_F(UsbMidiOutputStreamTest, SendSixByteSysEx) {
+ uint8 data[] = { 0xf0, 0x00, 0x01, 0x02, 0x03, 0xf7, };
+
+ stream_->Send(ToVector(data));
+ EXPECT_EQ("0x24 0xf0 0x00 0x01 "
+ "0x27 0x02 0x03 0xf7 (endpoint = 4)\n", device_.log());
+}
+
+TEST_F(UsbMidiOutputStreamTest, SendPendingSysEx) {
+ uint8 data1[] = { 0xf0, 0x33, };
+ uint8 data2[] = { 0x44, 0x55, 0x66, };
+ uint8 data3[] = { 0x77, 0x88, 0x99, 0xf7, };
+
+ stream_->Send(ToVector(data1));
+ EXPECT_EQ("", device_.log());
+
+ stream_->Send(ToVector(data2));
+ EXPECT_EQ("0x24 0xf0 0x33 0x44 (endpoint = 4)\n", device_.log());
+ device_.ClearLog();
+
+ stream_->Send(ToVector(data3));
+ EXPECT_EQ("0x24 0x55 0x66 0x77 0x27 0x88 0x99 0xf7 (endpoint = 4)\n",
+ device_.log());
+}
+
+TEST_F(UsbMidiOutputStreamTest, SendNoteOnAfterSysEx) {
+ uint8 data[] = { 0xf0, 0x00, 0x01, 0x02, 0x03, 0xf7, 0x90, 0x44, 0x33, };
+
+ stream_->Send(ToVector(data));
+ EXPECT_EQ("0x24 0xf0 0x00 0x01 "
+ "0x27 0x02 0x03 0xf7 "
+ "0x29 0x90 0x44 0x33 (endpoint = 4)\n", device_.log());
+}
+
+TEST_F(UsbMidiOutputStreamTest, SendTimeCodeQuarterFrame) {
+ uint8 data[] = { 0xf1, 0x22, };
+
+ stream_->Send(ToVector(data));
+ EXPECT_EQ("0x22 0xf1 0x22 0x00 (endpoint = 4)\n", device_.log());
+}
+
+TEST_F(UsbMidiOutputStreamTest, SendSongPositionPointer) {
+ uint8 data[] = { 0xf2, 0x22, 0x33, };
+
+ stream_->Send(ToVector(data));
+ EXPECT_EQ("0x23 0xf2 0x22 0x33 (endpoint = 4)\n", device_.log());
+}
+
+TEST_F(UsbMidiOutputStreamTest, SendSongSelect) {
+ uint8 data[] = { 0xf3, 0x22, };
+
+ stream_->Send(ToVector(data));
+ EXPECT_EQ("0x22 0xf3 0x22 0x00 (endpoint = 4)\n", device_.log());
+}
+
+TEST_F(UsbMidiOutputStreamTest, TuneRequest) {
+ uint8 data[] = { 0xf6, };
+
+ stream_->Send(ToVector(data));
+ EXPECT_EQ("0x25 0xf6 0x00 0x00 (endpoint = 4)\n", device_.log());
+}
+
+TEST_F(UsbMidiOutputStreamTest, SendSongPositionPointerPending) {
+ uint8 data1[] = { 0xf2, 0x22, };
+ uint8 data2[] = { 0x33, };
+
+ stream_->Send(ToVector(data1));
+ EXPECT_EQ("", device_.log());
+
+ stream_->Send(ToVector(data2));
+ EXPECT_EQ("0x23 0xf2 0x22 0x33 (endpoint = 4)\n", device_.log());
+}
+
+TEST_F(UsbMidiOutputStreamTest, SendRealTimeMessages) {
+ uint8 data[] = { 0xf8, 0xfa, 0xfb, 0xfc, 0xfe, 0xff, };
+
+ stream_->Send(ToVector(data));
+ EXPECT_EQ("0x25 0xf8 0x00 0x00 "
+ "0x25 0xfa 0x00 0x00 "
+ "0x25 0xfb 0x00 0x00 "
+ "0x25 0xfc 0x00 0x00 "
+ "0x25 0xfe 0x00 0x00 "
+ "0x25 0xff 0x00 0x00 (endpoint = 4)\n", device_.log());
+}
+
+TEST_F(UsbMidiOutputStreamTest, SendRealTimeInSysExMessage) {
+ uint8 data[] = {
+ 0xf0, 0x00, 0x01, 0x02,
+ 0xf8, 0xfa,
+ 0x03, 0xf7,
+ };
+
+ stream_->Send(ToVector(data));
+ EXPECT_EQ("0x24 0xf0 0x00 0x01 "
+ "0x25 0xf8 0x00 0x00 "
+ "0x25 0xfa 0x00 0x00 "
+ "0x27 0x02 0x03 0xf7 (endpoint = 4)\n", device_.log());
+}
+
+} // namespace
+
+} // namespace media