diff options
author | yukawa@chromium.org <yukawa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-11-27 12:09:35 +0000 |
---|---|---|
committer | yukawa@chromium.org <yukawa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-11-27 12:09:35 +0000 |
commit | b2f8b5916131272bb121f1e224a401eaa7edc2ad (patch) | |
tree | 6fcd62dcbc2ad4f91c49117455b42a2bc52f9b3e /media | |
parent | 0e2e57b6a0ac69b7b3f4fcde50c28b939bb843ca (diff) | |
download | chromium_src-b2f8b5916131272bb121f1e224a401eaa7edc2ad.zip chromium_src-b2f8b5916131272bb121f1e224a401eaa7edc2ad.tar.gz chromium_src-b2f8b5916131272bb121f1e224a401eaa7edc2ad.tar.bz2 |
Use MIDIMessageQueue/IsValidWebMIDIData for MIDI byte stream validation
WebMIDI spec draft: http://www.w3.org/TR/webmidi/
WebMIDI API guarantees that MIDIInput::onmessage is called back with a single MIDI message. To guarantee this, this CL introduces MIDIMessageQueue class, which 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, can be placed at the boundary of complete MIDI messages.
- (Optional) Reconstruct complete MIDI messages from data stream that is compressed with "running status".
This CL also replaces existing System Exclusive message validation logic in MIDIHost::OnSendData with MIDIHost::IsValidWebMIDIData, which can detect SysEx message even when it is concatenated with non-SysEx messages.
With this change, renderer/blink can be much simpler and free from this kind of data validation.
BUG=303599, 317355
TEST=media_unittests --gtest_filter=MIDI*, content_unittests --gtest_filter=MIDI*
Review URL: https://codereview.chromium.org/68353002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@237558 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r-- | media/media.gyp | 6 | ||||
-rw-r--r-- | media/midi/midi_message_queue.cc | 119 | ||||
-rw-r--r-- | media/midi/midi_message_queue.h | 72 | ||||
-rw-r--r-- | media/midi/midi_message_queue_unittest.cc | 173 | ||||
-rw-r--r-- | media/midi/midi_message_util.cc | 34 | ||||
-rw-r--r-- | media/midi/midi_message_util.h | 25 | ||||
-rw-r--r-- | media/midi/midi_message_util_unittest.cc | 34 |
7 files changed, 463 insertions, 0 deletions
diff --git a/media/media.gyp b/media/media.gyp index e2223c1..daff23d 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -400,6 +400,10 @@ 'midi/midi_manager.h', 'midi/midi_manager_mac.cc', 'midi/midi_manager_mac.h', + 'midi/midi_message_queue.cc', + 'midi/midi_message_queue.h', + 'midi/midi_message_util.cc', + 'midi/midi_message_util.h', 'midi/midi_port_info.cc', 'midi/midi_port_info.h', 'video/capture/android/video_capture_device_android.cc', @@ -978,6 +982,8 @@ 'filters/video_decoder_selector_unittest.cc', 'filters/video_frame_stream_unittest.cc', 'filters/video_renderer_impl_unittest.cc', + 'midi/midi_message_queue_unittest.cc', + 'midi/midi_message_util_unittest.cc', 'video/capture/video_capture_device_unittest.cc', 'webm/cluster_builder.cc', 'webm/cluster_builder.h', diff --git a/media/midi/midi_message_queue.cc b/media/midi/midi_message_queue.cc new file mode 100644 index 0000000..3452e80 --- /dev/null +++ b/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/media/midi/midi_message_queue.h b/media/midi/midi_message_queue.h new file mode 100644 index 0000000..06f0f47 --- /dev/null +++ b/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/media/midi/midi_message_queue_unittest.cc b/media/midi/midi_message_queue_unittest.cc new file mode 100644 index 0000000..a00eea6 --- /dev/null +++ b/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/media/midi/midi_message_util.cc b/media/midi/midi_message_util.cc new file mode 100644 index 0000000..83d3cc0 --- /dev/null +++ b/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/media/midi/midi_message_util.h b/media/midi/midi_message_util.h new file mode 100644 index 0000000..1dc6d3c --- /dev/null +++ b/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/media/midi/midi_message_util_unittest.cc b/media/midi/midi_message_util_unittest.cc new file mode 100644 index 0000000..af3679c --- /dev/null +++ b/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 |