summaryrefslogtreecommitdiffstats
path: root/media/midi
diff options
context:
space:
mode:
authoryhirano@chromium.org <yhirano@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-01-10 06:08:31 +0000
committeryhirano@chromium.org <yhirano@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-01-10 06:08:31 +0000
commit656de4e2655dbc7680a3cc934e8502e5218e30e2 (patch)
tree6d3474f0a2a5a38b299cbfc47b00386fc5ffe11e /media/midi
parentc33ce310f198ba564b6edcfc43787792b88c93b4 (diff)
downloadchromium_src-656de4e2655dbc7680a3cc934e8502e5218e30e2.zip
chromium_src-656de4e2655dbc7680a3cc934e8502e5218e30e2.tar.gz
chromium_src-656de4e2655dbc7680a3cc934e8502e5218e30e2.tar.bz2
Introduce USB MIDI descriptor parser
In order to recognize USB MIDI jacks associated with endpoints, this CL introduces a USB descriptor parser. BUG=303596 R=toyoshim@chromium.org Review URL: https://codereview.chromium.org/105043008 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@244101 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media/midi')
-rw-r--r--media/midi/usb_midi_descriptor_parser.cc235
-rw-r--r--media/midi/usb_midi_descriptor_parser.h60
-rw-r--r--media/midi/usb_midi_descriptor_parser_unittest.cc101
-rw-r--r--media/midi/usb_midi_jack.h51
4 files changed, 447 insertions, 0 deletions
diff --git a/media/midi/usb_midi_descriptor_parser.cc b/media/midi/usb_midi_descriptor_parser.cc
new file mode 100644
index 0000000..d454ff9
--- /dev/null
+++ b/media/midi/usb_midi_descriptor_parser.cc
@@ -0,0 +1,235 @@
+// 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_descriptor_parser.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+
+namespace media {
+
+namespace {
+
+// The constants below are specified in USB spec, USB audio spec
+// and USB midi spec.
+
+enum DescriptorType {
+ TYPE_DEVICE = 1,
+ TYPE_CONFIGURATION = 2,
+ TYPE_STRING = 3,
+ TYPE_INTERFACE = 4,
+ TYPE_ENDPOINT = 5,
+ TYPE_DEVICE_QUALIFIER = 6,
+ TYPE_OTHER_SPEED_CONFIGURATION = 7,
+ TYPE_INTERFACE_POWER = 8,
+
+ TYPE_CS_INTERFACE = 36,
+ TYPE_CS_ENDPOINT = 37,
+};
+
+enum DescriptorSubType {
+ SUBTYPE_MS_DESCRIPTOR_UNDEFINED = 0,
+ SUBTYPE_MS_HEADER = 1,
+ SUBTYPE_MIDI_IN_JACK = 2,
+ SUBTYPE_MIDI_OUT_JACK = 3,
+ SUBTYPE_ELEMENT = 4,
+};
+
+enum JackType {
+ JACK_TYPE_UNDEFINED = 0,
+ JACK_TYPE_EMBEDDED = 1,
+ JACK_TYPE_EXTERNAL = 2,
+};
+
+const uint8 kAudioInterfaceClass = 1;
+const uint8 kAudioMidiInterfaceSubclass = 3;
+
+class JackMatcher {
+ public:
+ explicit JackMatcher(uint8 id) : id_(id) {}
+
+ bool operator() (const UsbMidiJack& jack) const {
+ return jack.jack_id == id_;
+ }
+
+ private:
+ uint8 id_;
+};
+
+} // namespace
+
+UsbMidiDescriptorParser::UsbMidiDescriptorParser()
+ : is_parsing_usb_midi_interface_(false),
+ current_endpoint_address_(0),
+ current_cable_number_(0) {}
+
+UsbMidiDescriptorParser::~UsbMidiDescriptorParser() {}
+
+bool UsbMidiDescriptorParser::Parse(UsbMidiDevice* device,
+ const uint8* data,
+ size_t size,
+ std::vector<UsbMidiJack>* jacks) {
+ jacks->clear();
+ bool result = ParseInternal(device, data, size, jacks);
+ if (!result)
+ jacks->clear();
+ Clear();
+ return result;
+}
+
+bool UsbMidiDescriptorParser::ParseInternal(UsbMidiDevice* device,
+ const uint8* data,
+ size_t size,
+ std::vector<UsbMidiJack>* jacks) {
+ for (const uint8* current = data;
+ current < data + size;
+ current += current[0]) {
+ uint8 length = current[0];
+ if (length < 2) {
+ DVLOG(1) << "Descriptor Type is not accessible.";
+ return false;
+ }
+ if (current + length > data + size) {
+ DVLOG(1) << "The header size is incorrect.";
+ return false;
+ }
+ DescriptorType descriptor_type = static_cast<DescriptorType>(current[1]);
+ if (descriptor_type != TYPE_INTERFACE && !is_parsing_usb_midi_interface_)
+ continue;
+
+ switch (descriptor_type) {
+ case TYPE_INTERFACE:
+ if (!ParseInterface(current, length))
+ return false;
+ break;
+ case TYPE_CS_INTERFACE:
+ // We are assuming that the corresponding INTERFACE precedes
+ // the CS_INTERFACE descriptor, as specified.
+ if (!ParseCSInterface(device, current, length))
+ return false;
+ break;
+ case TYPE_ENDPOINT:
+ // We are assuming that endpoints are contained in an interface.
+ if (!ParseEndpoint(current, length))
+ return false;
+ break;
+ case TYPE_CS_ENDPOINT:
+ // We are assuming that the corresponding ENDPOINT precedes
+ // the CS_ENDPOINT descriptor, as specified.
+ if (!ParseCSEndpoint(current, length, jacks))
+ return false;
+ break;
+ default:
+ // Ignore uninteresting types.
+ break;
+ }
+ }
+ return true;
+}
+
+bool UsbMidiDescriptorParser::ParseInterface(const uint8* data, size_t size) {
+ if (size != 9) {
+ DVLOG(1) << "INTERFACE header size is incorrect.";
+ return false;
+ }
+ incomplete_jacks_.clear();
+
+ uint8 interface_class = data[5];
+ uint8 interface_subclass = data[6];
+
+ // All descriptors of endpoints contained in this interface
+ // precede the next INTERFACE descriptor.
+ is_parsing_usb_midi_interface_ =
+ interface_class == kAudioInterfaceClass &&
+ interface_subclass == kAudioMidiInterfaceSubclass;
+ return true;
+}
+
+bool UsbMidiDescriptorParser::ParseCSInterface(UsbMidiDevice* device,
+ const uint8* data,
+ size_t size) {
+ // Descriptor Type and Descriptor Subtype should be accessible.
+ if (size < 3) {
+ DVLOG(1) << "CS_INTERFACE header size is incorrect.";
+ return false;
+ }
+
+ DescriptorSubType subtype = static_cast<DescriptorSubType>(data[2]);
+
+ if (subtype != SUBTYPE_MIDI_OUT_JACK &&
+ subtype != SUBTYPE_MIDI_IN_JACK)
+ return true;
+
+ if (size < 6) {
+ DVLOG(1) << "CS_INTERFACE (MIDI JACK) header size is incorrect.";
+ return false;
+ }
+ uint8 jack_type = data[3];
+ uint8 id = data[4];
+ if (jack_type == JACK_TYPE_EMBEDDED) {
+ // We can't determine the associated endpoint now.
+ incomplete_jacks_.push_back(UsbMidiJack(device, id, 0, 0));
+ }
+ return true;
+}
+
+bool UsbMidiDescriptorParser::ParseEndpoint(const uint8* data, size_t size) {
+ if (size < 4) {
+ DVLOG(1) << "ENDPOINT header size is incorrect.";
+ return false;
+ }
+ current_endpoint_address_ = data[2];
+ current_cable_number_ = 0;
+ return true;
+}
+
+bool UsbMidiDescriptorParser::ParseCSEndpoint(const uint8* data,
+ size_t size,
+ std::vector<UsbMidiJack>* jacks) {
+ const size_t kSizeForEmptyJacks = 4;
+ // CS_ENDPOINT must be of size 4 + n where n is the number of associated
+ // jacks.
+ if (size < kSizeForEmptyJacks) {
+ DVLOG(1) << "CS_ENDPOINT header size is incorrect.";
+ return false;
+ }
+ uint8 num_jacks = data[3];
+ if (size != kSizeForEmptyJacks + num_jacks) {
+ DVLOG(1) << "CS_ENDPOINT header size is incorrect.";
+ return false;
+ }
+
+ for (size_t i = 0; i < num_jacks; ++i) {
+ uint8 jack = data[kSizeForEmptyJacks + i];
+ std::vector<UsbMidiJack>::iterator it =
+ std::find_if(incomplete_jacks_.begin(),
+ incomplete_jacks_.end(),
+ JackMatcher(jack));
+ if (it == incomplete_jacks_.end()) {
+ DVLOG(1) << "A non-existing MIDI jack is associated.";
+ return false;
+ }
+ if (current_cable_number_ > 0xf) {
+ DVLOG(1) << "Cable number should range from 0x0 to 0xf.";
+ return false;
+ }
+ // CS_ENDPOINT follows ENDPOINT and hence we can use the following
+ // member variables.
+ it->cable_number = current_cable_number_++;
+ it->endpoint_address = current_endpoint_address_;
+ jacks->push_back(*it);
+ incomplete_jacks_.erase(it);
+ }
+ return true;
+}
+
+void UsbMidiDescriptorParser::Clear() {
+ is_parsing_usb_midi_interface_ = false;
+ current_endpoint_address_ = 0;
+ current_cable_number_ = 0;
+ incomplete_jacks_.clear();
+}
+
+} // namespace media
diff --git a/media/midi/usb_midi_descriptor_parser.h b/media/midi/usb_midi_descriptor_parser.h
new file mode 100644
index 0000000..826a449
--- /dev/null
+++ b/media/midi/usb_midi_descriptor_parser.h
@@ -0,0 +1,60 @@
+// 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_DESCRIPTOR_PARSER_H_
+#define MEDIA_MIDI_USB_MIDI_DESCRIPTOR_PARSER_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "media/base/media_export.h"
+#include "media/midi/usb_midi_jack.h"
+
+namespace media {
+
+class UsbMidiDevice;
+
+// UsbMidiDescriptorParser parses USB descriptors and
+// generates input / output lists of MIDIPortInfo.
+// This is not a generic USB descriptor parser: this parser is designed
+// for collecting USB-MIDI jacks information from the descriptor.
+class MEDIA_EXPORT UsbMidiDescriptorParser {
+ public:
+ UsbMidiDescriptorParser();
+ ~UsbMidiDescriptorParser();
+
+ // Returns true if the operation succeeds.
+ // When an incorrect input is given, this method may return true but
+ // never crashes.
+ bool Parse(UsbMidiDevice* device,
+ const uint8* data,
+ size_t size,
+ std::vector<UsbMidiJack>* jacks);
+
+ private:
+ bool ParseInternal(UsbMidiDevice* device,
+ const uint8* data,
+ size_t size,
+ std::vector<UsbMidiJack>* jacks);
+ bool ParseInterface(const uint8* data, size_t size);
+ bool ParseCSInterface(UsbMidiDevice* device, const uint8* data, size_t size);
+ bool ParseEndpoint(const uint8* data, size_t size);
+ bool ParseCSEndpoint(const uint8* data,
+ size_t size,
+ std::vector<UsbMidiJack>* jacks);
+ void Clear();
+
+ bool is_parsing_usb_midi_interface_;
+ uint8 current_endpoint_address_;
+ uint8 current_cable_number_;
+
+ std::vector<UsbMidiJack> incomplete_jacks_;
+
+ DISALLOW_COPY_AND_ASSIGN(UsbMidiDescriptorParser);
+};
+
+
+} // namespace media
+
+#endif // MEDIA_MIDI_USB_MIDI_DESCRIPTOR_PARSER_H_
diff --git a/media/midi/usb_midi_descriptor_parser_unittest.cc b/media/midi/usb_midi_descriptor_parser_unittest.cc
new file mode 100644
index 0000000..fe6b3b2
--- /dev/null
+++ b/media/midi/usb_midi_descriptor_parser_unittest.cc
@@ -0,0 +1,101 @@
+// 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_descriptor_parser.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+
+namespace {
+
+TEST(UsbMidiDescriptorParserTest, ParseEmpty) {
+ UsbMidiDescriptorParser parser;
+ std::vector<UsbMidiJack> jacks;
+ EXPECT_TRUE(parser.Parse(NULL, NULL, 0, &jacks));
+ EXPECT_TRUE(jacks.empty());
+}
+
+TEST(UsbMidiDescriptorParserTest, InvalidSize) {
+ UsbMidiDescriptorParser parser;
+ std::vector<UsbMidiJack> jacks;
+ uint8 data[] = {0x04};
+ EXPECT_FALSE(parser.Parse(NULL, data, arraysize(data), &jacks));
+ EXPECT_TRUE(jacks.empty());
+}
+
+TEST(UsbMidiDescriptorParserTest, NonExistingJackIsAssociated) {
+ UsbMidiDescriptorParser parser;
+ std::vector<UsbMidiJack> jacks;
+ // Jack id=1 is found in a CS_ENDPOINT descriptor, but there is no definition
+ // for the jack.
+ uint8 data[] = {
+ 0x09, 0x04, 0x01, 0x00, 0x02, 0x01, 0x03, 0x00, 0x00, 0x07,
+ 0x24, 0x01, 0x00, 0x01, 0x07, 0x00, 0x05, 0x25, 0x01, 0x01,
+ 0x01,
+ };
+ EXPECT_FALSE(parser.Parse(NULL, data, arraysize(data), &jacks));
+ EXPECT_TRUE(jacks.empty());
+}
+
+TEST(UsbMidiDescriptorParserTest,
+ JacksShouldBeIgnoredWhenParserIsNotParsingMIDIInterface) {
+ UsbMidiDescriptorParser parser;
+ std::vector<UsbMidiJack> jacks;
+ // a NON-MIDI INTERFACE descriptor followed by ENDPOINT and CS_ENDPOINT
+ // descriptors (Compare with the previous test case).
+ uint8 data[] = {
+ 0x09, 0x04, 0x01, 0x00, 0x02, 0x01, 0x02, 0x00, 0x00, 0x07,
+ 0x24, 0x01, 0x00, 0x01, 0x07, 0x00, 0x05, 0x25, 0x01, 0x01,
+ 0x01,
+ };
+ EXPECT_TRUE(parser.Parse(NULL, data, arraysize(data), &jacks));
+ EXPECT_TRUE(jacks.empty());
+}
+
+TEST(UsbMidiDescriptorParserTest, Parse) {
+ UsbMidiDescriptorParser parser;
+ std::vector<UsbMidiJack> jacks;
+ // A complete device descriptor.
+ uint8 data[] = {
+ 0x12, 0x01, 0x10, 0x01, 0x00, 0x00, 0x00, 0x08, 0x86, 0x1a,
+ 0x2d, 0x75, 0x54, 0x02, 0x00, 0x02, 0x00, 0x01, 0x09, 0x02,
+ 0x75, 0x00, 0x02, 0x01, 0x00, 0x80, 0x30, 0x09, 0x04, 0x00,
+ 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x09, 0x24, 0x01, 0x00,
+ 0x01, 0x09, 0x00, 0x01, 0x01, 0x09, 0x04, 0x01, 0x00, 0x02,
+ 0x01, 0x03, 0x00, 0x00, 0x07, 0x24, 0x01, 0x00, 0x01, 0x51,
+ 0x00, 0x06, 0x24, 0x02, 0x01, 0x02, 0x00, 0x06, 0x24, 0x02,
+ 0x01, 0x03, 0x00, 0x06, 0x24, 0x02, 0x02, 0x06, 0x00, 0x09,
+ 0x24, 0x03, 0x01, 0x07, 0x01, 0x06, 0x01, 0x00, 0x09, 0x24,
+ 0x03, 0x02, 0x04, 0x01, 0x02, 0x01, 0x00, 0x09, 0x24, 0x03,
+ 0x02, 0x05, 0x01, 0x03, 0x01, 0x00, 0x09, 0x05, 0x02, 0x02,
+ 0x20, 0x00, 0x00, 0x00, 0x00, 0x06, 0x25, 0x01, 0x02, 0x02,
+ 0x03, 0x09, 0x05, 0x82, 0x02, 0x20, 0x00, 0x00, 0x00, 0x00,
+ 0x05, 0x25, 0x01, 0x01, 0x07,
+ };
+ EXPECT_TRUE(parser.Parse(NULL, data, arraysize(data), &jacks));
+ ASSERT_EQ(3u, jacks.size());
+
+ EXPECT_EQ(2u, jacks[0].jack_id);
+ EXPECT_EQ(0u, jacks[0].cable_number);
+ EXPECT_EQ(2u, jacks[0].endpoint_number());
+ EXPECT_EQ(UsbMidiJack::OUT, jacks[0].direction());
+ EXPECT_EQ(NULL, jacks[0].device);
+
+ EXPECT_EQ(3u, jacks[1].jack_id);
+ EXPECT_EQ(1u, jacks[1].cable_number);
+ EXPECT_EQ(2u, jacks[1].endpoint_number());
+ EXPECT_EQ(UsbMidiJack::OUT, jacks[1].direction());
+ EXPECT_EQ(NULL, jacks[1].device);
+
+ EXPECT_EQ(7u, jacks[2].jack_id);
+ EXPECT_EQ(0u, jacks[2].cable_number);
+ EXPECT_EQ(2u, jacks[2].endpoint_number());
+ EXPECT_EQ(UsbMidiJack::IN, jacks[2].direction());
+ EXPECT_EQ(NULL, jacks[2].device);
+}
+
+} // namespace
+
+} // namespace media
diff --git a/media/midi/usb_midi_jack.h b/media/midi/usb_midi_jack.h
new file mode 100644
index 0000000..d3fe9a9
--- /dev/null
+++ b/media/midi/usb_midi_jack.h
@@ -0,0 +1,51 @@
+// 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_JACK_H_
+#define MEDIA_MIDI_USB_MIDI_JACK_H_
+
+#include "base/basictypes.h"
+#include "media/base/media_export.h"
+
+namespace media {
+
+class UsbMidiDevice;
+
+// UsbMidiJack represents an EMBEDDED MIDI jack.
+struct MEDIA_EXPORT UsbMidiJack {
+ // The direction of the endpoint associated with an EMBEDDED MIDI jack.
+ // Note that an IN MIDI jack associated with an OUT endpoint has
+ // ***OUT*** direction.
+ enum Direction {
+ IN,
+ OUT,
+ };
+ UsbMidiJack(UsbMidiDevice* device,
+ uint8 jack_id,
+ uint8 cable_number,
+ uint8 endpoint_address)
+ : device(device),
+ jack_id(jack_id),
+ cable_number(cable_number),
+ endpoint_address(endpoint_address) {}
+ // Not owned
+ UsbMidiDevice* device;
+ // The id of this jack unique in the interface.
+ uint8 jack_id;
+ // The cable number of this jack in the associated endpoint.
+ uint8 cable_number;
+ // The address of the endpoint that this jack is associated with.
+ uint8 endpoint_address;
+
+ Direction direction() const {
+ return (endpoint_address & 0x80) ? IN : OUT;
+ }
+ uint8 endpoint_number() const {
+ return (endpoint_address & 0xf);
+ }
+};
+
+} // namespace media
+
+#endif // MEDIA_MIDI_USB_MIDI_JACK_H_