commit - 62d865db8655e6d23b4fccc3280d73c3cdda8ce9
commit + 8a3d06f2a309055b4e11c8a0796c93e473cf314f
blob - 6a360bdd85a88ac4a54558990481a227ade66c3c
blob + d422847b6bd1d0a1739e00a5b1c0dce02e7a1283
--- scte35/splice_descriptor.go
+++ scte35/splice_descriptor.go
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 {
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
+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)
+ }
+}