diff options
author | yhirano@chromium.org <yhirano@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-01-22 10:39:41 +0000 |
---|---|---|
committer | yhirano@chromium.org <yhirano@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-01-22 10:39:41 +0000 |
commit | 1e0f832b9b057a284de798e6c6acd1846821418a (patch) | |
tree | f7e231993175bfc7839060c44786df3ef8b5fb2d | |
parent | e5f77dce153f6515f2a91ec5554869432a675539 (diff) | |
download | chromium_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.cc | 14 | ||||
-rw-r--r-- | media/media.gyp | 7 | ||||
-rw-r--r-- | media/midi/midi_message_util.h | 8 | ||||
-rw-r--r-- | media/midi/usb_midi_device.h | 31 | ||||
-rw-r--r-- | media/midi/usb_midi_input_stream.cc | 92 | ||||
-rw-r--r-- | media/midi/usb_midi_input_stream.h | 80 | ||||
-rw-r--r-- | media/midi/usb_midi_input_stream_unittest.cc | 175 | ||||
-rw-r--r-- | media/midi/usb_midi_output_stream.cc | 187 | ||||
-rw-r--r-- | media/midi/usb_midi_output_stream.h | 55 | ||||
-rw-r--r-- | media/midi/usb_midi_output_stream_unittest.cc | 276 |
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, ¤t, &data_to_send)) + break; + } else if ((first_byte & kSysMessageBitMask) == kSysMessageBitPattern) { + if (first_byte & 0x08) { + // System Real-Time messages + PushSysRTMessage(data, ¤t, &data_to_send); + } else { + // System Common messages + if (!PushSysCommonMessage(data, ¤t, &data_to_send)) + break; + } + } else if (first_byte & 0x80) { + if (!PushChannelMessage(data, ¤t, &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 |