summaryrefslogtreecommitdiffstats
path: root/media/midi
diff options
context:
space:
mode:
authoryukawa@chromium.org <yukawa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-11-27 12:09:35 +0000
committeryukawa@chromium.org <yukawa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-11-27 12:09:35 +0000
commitb2f8b5916131272bb121f1e224a401eaa7edc2ad (patch)
tree6fcd62dcbc2ad4f91c49117455b42a2bc52f9b3e /media/midi
parent0e2e57b6a0ac69b7b3f4fcde50c28b939bb843ca (diff)
downloadchromium_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/midi')
-rw-r--r--media/midi/midi_message_queue.cc119
-rw-r--r--media/midi/midi_message_queue.h72
-rw-r--r--media/midi/midi_message_queue_unittest.cc173
-rw-r--r--media/midi/midi_message_util.cc34
-rw-r--r--media/midi/midi_message_util.h25
-rw-r--r--media/midi/midi_message_util_unittest.cc34
6 files changed, 457 insertions, 0 deletions
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