From 23701c4f9c8965497769d9616c377010369c6df7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20M=C3=BCller?= Date: Tue, 25 May 2021 21:37:57 +0200 Subject: [PATCH 1/4] Fix unit test for UAC2 ClockSourceDescriptor --- usb_protocol/types/descriptors/uac2.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/usb_protocol/types/descriptors/uac2.py b/usb_protocol/types/descriptors/uac2.py index 5dd7988..007fb13 100644 --- a/usb_protocol/types/descriptors/uac2.py +++ b/usb_protocol/types/descriptors/uac2.py @@ -884,7 +884,7 @@ class UAC2Cases(unittest.TestCase): parsed = ClockSourceDescriptor.parse([ 0x08, # Length 0x24, # Type - 0x0B, # Subtype + 0x0A, # Subtype 0x01, # Clock ID 0x01, # Attributes 0x01, # Controls @@ -916,7 +916,7 @@ class UAC2Cases(unittest.TestCase): self.assertEqual(data, bytes([ 0x08, # Length 0x24, # Type - 0x0B, # Subtype + 0x0A, # Subtype 0x01, # Clock ID 0x01, # Attributes 0x01, # Controls From 70d0a7c5e1a858ff2df38365fe5b6348113841d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20M=C3=BCller?= Date: Tue, 25 May 2021 22:04:41 +0200 Subject: [PATCH 2/4] Add UAC2 FeatureUnitDescriptor --- usb_protocol/emitters/descriptors/uac2.py | 1 + usb_protocol/types/descriptors/uac2.py | 63 +++++++++++++++++++++-- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/usb_protocol/emitters/descriptors/uac2.py b/usb_protocol/emitters/descriptors/uac2.py index e9d4de4..1383186 100644 --- a/usb_protocol/emitters/descriptors/uac2.py +++ b/usb_protocol/emitters/descriptors/uac2.py @@ -24,6 +24,7 @@ class ClassSpecificAudioControlInterfaceDescriptorEmitter(ComplexDescriptorEmitt ClockSourceDescriptorEmitter = emitter_for_format(ClockSourceDescriptor) InputTerminalDescriptorEmitter = emitter_for_format(InputTerminalDescriptor) OutputTerminalDescriptorEmitter = emitter_for_format(OutputTerminalDescriptor) +FeatureUnitDescriptorEmitter = emitter_for_format(FeatureUnitDescriptor) AudioStreamingInterfaceDescriptorEmitter = emitter_for_format(AudioStreamingInterfaceDescriptor) ClassSpecificAudioStreamingInterfaceDescriptorEmitter = emitter_for_format(ClassSpecificAudioStreamingInterfaceDescriptor) TypeIFormatTypeDescriptorEmitter = emitter_for_format(TypeIFormatTypeDescriptor) diff --git a/usb_protocol/types/descriptors/uac2.py b/usb_protocol/types/descriptors/uac2.py index 007fb13..2754bd1 100644 --- a/usb_protocol/types/descriptors/uac2.py +++ b/usb_protocol/types/descriptors/uac2.py @@ -19,7 +19,7 @@ import construct from .standard import StandardDescriptorNumbers from ..descriptor import \ - DescriptorField, DescriptorNumber, DescriptorFormat + DescriptorField, DescriptorNumber, DescriptorFormat, DescriptorLength class AudioInterfaceClassCode(IntEnum): @@ -635,7 +635,7 @@ InputTerminalDescriptor = DescriptorFormat( "bmChannelConfig" / DescriptorField(description="describes the spatial location of the logical channels", default=0, length=4), "bmControls" / DescriptorField(description="OR combination of ClockFrequencyControl, CopyProtectControl, ConnectorControl, ClusterControl, UnderflowControl and OverflowControl", default=0, length=2), "iChannelNames" / DescriptorField(description="string descriptor index of the first logical channel name", default=0), - "iTerminal" / DescriptorField(description="ID of the input terminal string description", default=0) + "iTerminal" / DescriptorField(description="ID of the input terminal string descriptor", default=0) ) OutputTerminalDescriptor = DescriptorFormat( @@ -648,7 +648,19 @@ OutputTerminalDescriptor = DescriptorFormat( "bSourceID" / DescriptorField(description="ID of the unit or terminal which is connected to this terminal"), "bCSourceID" / DescriptorField(description="ID of the clock which is connected to this terminal"), "bmControls" / DescriptorField(description="OR combination of ClockFrequencyControl, CopyProtectControl, ConnectorControl, UnderflowControl>>2 and OverflowControl>>2", default=0, length=2), - "iTerminal" / DescriptorField(description="ID of the input terminal string description", default=0) + "iTerminal" / DescriptorField(description="ID of the input terminal string descriptor", default=0) +) + +FeatureUnitDescriptorLength = construct.Rebuild(construct.Int8ul, construct.len_(construct.this.bmaControls) * 4 + 6) + +FeatureUnitDescriptor = DescriptorFormat( + "bLength" / FeatureUnitDescriptorLength, + "bDescriptorType" / DescriptorNumber(AudioClassSpecificStandardDescriptorNumbers.CS_INTERFACE), + "bDescriptorSubtype" / DescriptorNumber(AudioClassSpecificACInterfaceDescriptorSubtypes.FEATURE_UNIT), + "bUnitID" / DescriptorField(description="unique identifier for the unit within the audio function."), + "bSourceID" / DescriptorField(description="ID of the unit or terminal which is connected to this terminal"), + "bmaControls" / construct.Array((construct.this.bLength - 6)//4, construct.Int32ul) * "The control bitmap for all channels", + "iFeature" / DescriptorField(description="ID of the feature unit string descriptor", default=0) ) AudioStreamingInterfaceDescriptor = DescriptorFormat( @@ -1032,6 +1044,51 @@ class UAC2Cases(unittest.TestCase): 0x42 # Terminal name ])) + def test_parse_feature_unit_descriptor(self): + # Parse the relevant descriptor ... + parsed = FeatureUnitDescriptor.parse([ + 0x12, # Length + 0x24, # Type + 0x06, # Subtype + 0x06, # Unit ID + 0x09, # Source ID + 0x01, 0x00, 0x00, 0x00, # Controls 0 + 0x02, 0x00, 0x00, 0x00, # Controls 1 + 0x03, 0x00, 0x00, 0x00, # Controls 2 + 0x42 # Unit name + ]) + + # ... and check the descriptor's fields. + self.assertEqual(parsed.bLength, 18) + self.assertEqual(parsed.bDescriptorType, AudioClassSpecificStandardDescriptorNumbers.CS_INTERFACE) + self.assertEqual(parsed.bDescriptorSubtype, AudioClassSpecificACInterfaceDescriptorSubtypes.FEATURE_UNIT) + self.assertEqual(parsed.bUnitID, 0x06) + self.assertEqual(parsed.bSourceID, 0x09) + self.assertEqual(parsed.bmaControls, [0x0001, 0x0002, 0x0003]) + self.assertEqual(parsed.iFeature, 0x42) + + def test_build_feature_unit_descriptor(self): + # Build the relevant descriptor + data = FeatureUnitDescriptor.build({ + 'bUnitID': 6, + 'bSourceID': 9, + 'bmaControls': [1, 2, 3], + 'iFeature': 0x42, + }) + + # ... and check the binary output + self.assertEqual(data, bytes([ + 0x12, # Length + 0x24, # Type + 0x06, # Subtype + 0x06, # Unit ID + 0x09, # Source ID + 0x01, 0x00, 0x00, 0x00, # Controls 0 + 0x02, 0x00, 0x00, 0x00, # Controls 1 + 0x03, 0x00, 0x00, 0x00, # Controls 2 + 0x42 # Unit name + ])) + def test_parse_audio_streaming_interface_descriptor(self): # Parse the relevant descriptor ... parsed = AudioStreamingInterfaceDescriptor.parse([ From 0b925fd96414426e3eba2e0b68accfaac0120011 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20M=C3=BCller?= Date: Wed, 26 May 2021 13:50:08 +0200 Subject: [PATCH 3/4] Fix UAC2 Input Terminal Descriptor --- usb_protocol/types/descriptors/uac2.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/usb_protocol/types/descriptors/uac2.py b/usb_protocol/types/descriptors/uac2.py index 2754bd1..97ee4ef 100644 --- a/usb_protocol/types/descriptors/uac2.py +++ b/usb_protocol/types/descriptors/uac2.py @@ -633,8 +633,8 @@ InputTerminalDescriptor = DescriptorFormat( "bCSourceID" / DescriptorField(description="ID of the clock which is connected to this terminal"), "bNrChannels" / DescriptorField(description="number of logical output channels in the terminal’s output channel cluster"), "bmChannelConfig" / DescriptorField(description="describes the spatial location of the logical channels", default=0, length=4), - "bmControls" / DescriptorField(description="OR combination of ClockFrequencyControl, CopyProtectControl, ConnectorControl, ClusterControl, UnderflowControl and OverflowControl", default=0, length=2), "iChannelNames" / DescriptorField(description="string descriptor index of the first logical channel name", default=0), + "bmControls" / DescriptorField(description="OR combination of ClockFrequencyControl, CopyProtectControl, ConnectorControl, ClusterControl, UnderflowControl and OverflowControl", default=0, length=2), "iTerminal" / DescriptorField(description="ID of the input terminal string descriptor", default=0) ) @@ -948,8 +948,8 @@ class UAC2Cases(unittest.TestCase): 0x01, # Clock ID 0x02, # Number of channels 0x03, 0x00, 0x00, 0x00, # Channel configuration - 0x00, # First channel name - 0x00, 0x00, # Controls + 0x23, # First channel name + 0x05, 0x00, # Controls 0x42 # Terminal name ]) @@ -963,7 +963,8 @@ class UAC2Cases(unittest.TestCase): self.assertEqual(parsed.bCSourceID, 0x01) self.assertEqual(parsed.bNrChannels, 0x02) self.assertEqual(parsed.bmChannelConfig, 0x0003) - self.assertEqual(parsed.iChannelNames, 0x00) + self.assertEqual(parsed.iChannelNames, 0x23) + self.assertEqual(parsed.bmControls, 5) self.assertEqual(parsed.iTerminal, 0x42) def test_build_input_terminal_descriptor(self): @@ -974,6 +975,8 @@ class UAC2Cases(unittest.TestCase): 'bCSourceID': 1, 'bNrChannels': 2, 'bmChannelConfig': 3, + 'iChannelNames': 0x23, + 'bmControls': 5, 'iTerminal': 0x42, }) @@ -988,8 +991,8 @@ class UAC2Cases(unittest.TestCase): 0x01, # Clock ID 0x02, # Number of channels 0x03, 0x00, 0x00, 0x00, # Channel configuration - 0x00, # First channel name - 0x00, 0x00, # Controls + 0x23, # First channel name + 0x05, 0x00, # Controls 0x42 # Terminal name ])) From 4da76c4bd03198938dc3a22ec991a2b9203b594b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20M=C3=BCller?= Date: Mon, 17 Jan 2022 19:12:29 +0100 Subject: [PATCH 4/4] Address review comments * DescriptorLength is unused and therefore removed * InputTerminalDescriptor has OverloadControl and no ClockFrequencyControl according to Table 4-9 of [Audio20] --- usb_protocol/types/descriptors/uac2.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/usb_protocol/types/descriptors/uac2.py b/usb_protocol/types/descriptors/uac2.py index 97ee4ef..f51bddb 100644 --- a/usb_protocol/types/descriptors/uac2.py +++ b/usb_protocol/types/descriptors/uac2.py @@ -19,7 +19,7 @@ import construct from .standard import StandardDescriptorNumbers from ..descriptor import \ - DescriptorField, DescriptorNumber, DescriptorFormat, DescriptorLength + DescriptorField, DescriptorNumber, DescriptorFormat class AudioInterfaceClassCode(IntEnum): @@ -634,7 +634,7 @@ InputTerminalDescriptor = DescriptorFormat( "bNrChannels" / DescriptorField(description="number of logical output channels in the terminal’s output channel cluster"), "bmChannelConfig" / DescriptorField(description="describes the spatial location of the logical channels", default=0, length=4), "iChannelNames" / DescriptorField(description="string descriptor index of the first logical channel name", default=0), - "bmControls" / DescriptorField(description="OR combination of ClockFrequencyControl, CopyProtectControl, ConnectorControl, ClusterControl, UnderflowControl and OverflowControl", default=0, length=2), + "bmControls" / DescriptorField(description="OR combination of CopyProtectControl, ConnectorControl, OverloadControl, ClusterControl, UnderflowControl and OverflowControl", default=0, length=2), "iTerminal" / DescriptorField(description="ID of the input terminal string descriptor", default=0) )