Commit Diff


commit - 62d865db8655e6d23b4fccc3280d73c3cdda8ce9
commit + 8a3d06f2a309055b4e11c8a0796c93e473cf314f
blob - 6a360bdd85a88ac4a54558990481a227ade66c3c
blob + d422847b6bd1d0a1739e00a5b1c0dce02e7a1283
--- scte35/splice_descriptor.go
+++ scte35/splice_descriptor.go
@@ -314,16 +314,53 @@ func (d AudioDescriptor) Data() []byte {
 		b = append(b, ch.ComponentTag)
 		b = append(b, ch.Language[:]...)
 		var c byte
-		c |= (ch.BitstreamMode << 5) // set left 3 bits
-		c |= (ch.Count & 0x0f)       // only want 4 bits
+		c |= (ch.BitstreamMode << 5)  // set left 3 bits (7-5)
+		c |= ((ch.Count & 0x0f) << 1) // 4 bits shifted left for bits 4-1
 		if ch.FullService {
-			c |= 0x01 // set last remaining bit
+			c |= 0x01 // set last remaining bit (0)
 		}
 		b = append(b, c)
 	}
 	return b
 }
 
+func unmarshalAudioDescriptor(buf []byte) AudioDescriptor {
+	if len(buf) == 0 {
+		return AudioDescriptor{}
+	}
+
+	// Extract channel count from upper 4 bits of first byte
+	count := int(buf[0] >> 4)
+	if count == 0 {
+		return AudioDescriptor{}
+	}
+
+	// Each channel requires 5 bytes (1 + 3 + 1)
+	expectedLen := 1 + (count * 5)
+	if len(buf) < expectedLen {
+		return AudioDescriptor{}
+	}
+
+	channels := make([]AudioChannel, count)
+	buf = buf[1:] // skip first byte with count
+
+	for i := 0; i < count; i++ {
+		ch := &channels[i]
+		ch.ComponentTag = buf[0]
+		copy(ch.Language[:], buf[1:4])
+
+		// Decode the packed byte: BitstreamMode (3 bits) + Count (4 bits) + FullService (1 bit)
+		packed := buf[4]
+		ch.BitstreamMode = (packed >> 5) & 0x07 // upper 3 bits
+		ch.Count = (packed >> 1) & 0x0F         // middle 4 bits
+		ch.FullService = (packed & 0x01) != 0   // lowest bit
+
+		buf = buf[5:] // move to next channel
+	}
+
+	return AudioDescriptor(channels)
+}
+
 func decodeAllDescriptors(buf []byte) ([]SpliceDescriptor, error) {
 	var sds []SpliceDescriptor
 	for len(buf) >= 6 {
@@ -365,6 +402,8 @@ func unmarshalSpliceDescriptor(buf []byte) (SpliceDesc
 		return unmarshalSegDescriptor(buf), nil
 	case TagDTMF:
 		return unmarshalDTMF(buf), nil
+	case TagAudio:
+		return unmarshalAudioDescriptor(buf), nil
 	}
 	return nil, fmt.Errorf("unmarshal %d unsupported", tag)
 }
blob - /dev/null
blob + d3d08df7bb6e5f0efb4e7cb077430d111c069ff2 (mode 644)
--- /dev/null
+++ scte35/splice_descriptor_test.go
@@ -0,0 +1,139 @@
+package scte35
+
+import (
+	"reflect"
+	"testing"
+)
+
+func TestAudioDescriptorRoundTrip(t *testing.T) {
+	tests := []struct {
+		name string
+		desc AudioDescriptor
+	}{
+		{
+			name: "empty",
+			desc: AudioDescriptor{},
+		},
+		{
+			name: "single channel",
+			desc: AudioDescriptor{
+				{
+					ComponentTag:  0x12,
+					Language:      [3]byte{'e', 'n', 'g'},
+					BitstreamMode: 0x05,
+					Count:         6,
+					FullService:   true,
+				},
+			},
+		},
+		{
+			name: "multiple channels",
+			desc: AudioDescriptor{
+				{
+					ComponentTag:  0x12,
+					Language:      [3]byte{'e', 'n', 'g'},
+					BitstreamMode: 0x05,
+					Count:         6,
+					FullService:   true,
+				},
+				{
+					ComponentTag:  0x34,
+					Language:      [3]byte{'s', 'p', 'a'},
+					BitstreamMode: 0x03,
+					Count:         2,
+					FullService:   false,
+				},
+				{
+					ComponentTag:  0x56,
+					Language:      [3]byte{'f', 'r', 'a'},
+					BitstreamMode: 0x07,
+					Count:         8,
+					FullService:   true,
+				},
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			// Test basic properties
+			if got := tt.desc.Tag(); got != TagAudio {
+				t.Errorf("Tag() = %v, want %v", got, TagAudio)
+			}
+			if got := tt.desc.ID(); got != descriptorIDCUEI {
+				t.Errorf("ID() = %v, want %v", got, descriptorIDCUEI)
+			}
+
+			// Test round-trip marshalling/unmarshalling
+			data := tt.desc.Data()
+			unmarshaled := unmarshalAudioDescriptor(data)
+
+			if !reflect.DeepEqual(tt.desc, unmarshaled) {
+				t.Errorf("Round-trip failed:\noriginal:    %+v\nunmarshaled: %+v", tt.desc, unmarshaled)
+			}
+		})
+	}
+}
+
+func TestAudioDescriptorUnmarshalSpliceDescriptor(t *testing.T) {
+	// Test that AudioDescriptor can be unmarshalled via unmarshalSpliceDescriptor
+	desc := AudioDescriptor{
+		{
+			ComponentTag:  0x12,
+			Language:      [3]byte{'e', 'n', 'g'},
+			BitstreamMode: 0x05,
+			Count:         6,
+			FullService:   true,
+		},
+	}
+
+	// Encode as a full splice descriptor
+	encoded := encodeSpliceDescriptor(desc)
+
+	// Unmarshal via the main function
+	unmarshaled, err := unmarshalSpliceDescriptor(encoded)
+	if err != nil {
+		t.Fatalf("unmarshalSpliceDescriptor failed: %v", err)
+	}
+
+	// Check that we got back an AudioDescriptor
+	audioDesc, ok := unmarshaled.(AudioDescriptor)
+	if !ok {
+		t.Fatalf("Expected AudioDescriptor, got %T", unmarshaled)
+	}
+
+	if !reflect.DeepEqual(desc, audioDesc) {
+		t.Errorf("Round-trip via unmarshalSpliceDescriptor failed:\noriginal:    %+v\nunmarshaled: %+v", desc, audioDesc)
+	}
+}
+
+func TestAudioDescriptorDataFormat(t *testing.T) {
+	// Test specific encoding format
+	desc := AudioDescriptor{
+		{
+			ComponentTag:  0x12,
+			Language:      [3]byte{'e', 'n', 'g'},
+			BitstreamMode: 0x05, // 3 bits: 101
+			Count:         6,    // 4 bits: 0110
+			FullService:   true, // 1 bit: 1
+		},
+	}
+
+	data := desc.Data()
+	expected := []byte{
+		0x10,          // count=1 in upper 4 bits: 00010000
+		0x12,          // ComponentTag
+		'e', 'n', 'g', // Language
+		0xAD, // BitstreamMode(101) + Count(0110) + FullService(1) = 10101101 = 0xAD
+	}
+
+	if !reflect.DeepEqual(data, expected) {
+		t.Errorf("Data() encoding mismatch:\ngot:      %x\nexpected: %x", data, expected)
+	}
+
+	// Test unmarshalling the expected format
+	unmarshaled := unmarshalAudioDescriptor(expected)
+	if !reflect.DeepEqual(desc, unmarshaled) {
+		t.Errorf("Unmarshalling expected format failed:\noriginal:    %+v\nunmarshaled: %+v", desc, unmarshaled)
+	}
+}