streaming

Media streaming and broadcast systems in Go
Log | Files | Refs | README | LICENSE

commit a5231de6e2b1728f3869fbae7ba221a868e09b61
parent 30cd5b9b300855303d26c8b63ff5c38c291bfce1
Author: Oliver Lowe <o@olowe.co>
Date:   Fri, 10 May 2024 12:21:55 +1000

internal/scte35: use an interface to represent SpliceDescriptor

This is a clearer representation of how each of TimeDescriptor,
SegmentationDescriptor are related: they are all different
implementations of SpliceDescriptor. This is also the wording used in
the SCTE 35 spec anyway!

Diffstat:
Minternal/scte35/splice_descriptor.go | 270+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Minternal/scte35/splice_info.go | 2+-
2 files changed, 204 insertions(+), 68 deletions(-)

diff --git a/internal/scte35/splice_descriptor.go b/internal/scte35/splice_descriptor.go @@ -7,6 +7,9 @@ import ( const DescriptorIDCUEI = "CUEI" +// DescriptorIDCUEI in ASCII +const descriptorIDCUEI uint32 = 0x43554549 + const ( TagAvail uint8 = iota TagDTMF @@ -15,33 +18,42 @@ const ( TagAudio ) -type SpliceDescriptor struct { +type SpliceDescriptor interface { // Tag identifies the type of descriptor. If ID is // DescriptorIDCUEI, then the the values [TagAvail] et al. may be used. - Tag uint8 + Tag() uint8 // For private descriptors, this value must not be DescriptorIDCUEI. - ID uint32 - // Data holds an encoded splice descriptor implementation. - // If the tag is one of [TagAvail] et al., then the corresponding + ID() uint32 + // Data is the encoded splice descriptor implementation. + // If Tag is one of [TagAvail] et al., then the corresponding // types (AvailDescriptor, DTMFDescriptor...) may be used to // decode/encode this field. - Data []byte + Data() []byte } -func encodeSpliceDescriptor(sd *SpliceDescriptor) []byte { +func encodeSpliceDescriptor(sd SpliceDescriptor) []byte { var buf []byte - buf = append(buf, byte(sd.Tag)) - buf = append(buf, byte(len(sd.Data))) + buf = append(buf, byte(sd.Tag())) + buf = append(buf, byte(len(sd.Data()))) ibuf := make([]byte, 4) // uint32 length - binary.LittleEndian.PutUint32(ibuf, sd.ID) + binary.LittleEndian.PutUint32(ibuf, sd.ID()) buf = append(buf, ibuf...) - return append(buf, sd.Data...) + return append(buf, sd.Data()...) } // AvailDescriptor is a type of splice descriptor described in SCTE 35 section 10.3.1. // Its only value is a so-called "provider avail ID". type AvailDescriptor uint32 +func (d AvailDescriptor) Tag() uint8 { return TagAvail } +func (d AvailDescriptor) ID() uint32 { return descriptorIDCUEI } + +func (d AvailDescriptor) Data() []byte { + buf := make([]byte, 4) + binary.BigEndian.PutUint32(buf, uint32(d)) + return buf +} + // DTMFDescriptor is a type of a splice descriptor as described in SCTE 35 10.3.2. // DTMF stands for [Dual-tone multi-frequency signaling]. // @@ -53,6 +65,23 @@ type DTMFDescriptor struct { Chars []byte } +func (d DTMFDescriptor) Tag() uint8 { return TagDTMF } +func (d DTMFDescriptor) ID() uint32 { return descriptorIDCUEI } + +func (d DTMFDescriptor) Data() []byte { + // preroll + char count + chars + b := make([]byte, 1+1+len(d.Chars)) + b[0] = byte(d.Preroll) + // set 3 bits, right-most 5 are reserved. + b[1] = byte(len(d.Chars)) << 5 + i := 2 + for j := range d.Chars { + b[i] = d.Chars[j] + i++ + } + return b +} + type DeliveryRestrictions uint8 const ( @@ -87,6 +116,102 @@ type SegmentationDescriptor struct { SubExpected uint8 } +func (d SegmentationDescriptor) Tag() uint8 { return TagSegmentation } +func (d SegmentationDescriptor) ID() uint32 { return descriptorIDCUEI } + +func (d SegmentationDescriptor) Data() []byte { + buf := make([]byte, 5) + binary.BigEndian.PutUint32(buf[:4], d.EventID) + if d.Cancel { + buf[4] |= (1 << 7) + } + if d.EventIDCompliance { + buf[4] |= (1 << 6) + } + // next 6 bits are reserved. + + if !d.Cancel { + buf = append(buf, 0x00) + // assume program_segmentation is always set; we do not support the deprecated component mode. + buf[5] |= (1 << 7) + if d.Duration != nil { + buf[5] |= (1 << 6) + } + if d.Restrictions != 0 { + buf[5] |= (1 << 5) + buf[5] |= byte(d.Restrictions) + } + + if d.Duration != nil { + b := make([]byte, 8) // uint64 needs 8 + binary.BigEndian.PutUint64(b, *d.Duration) + // append 40 bits (5 bytes) + buf = append(buf, b[:4]...) + } + + buf = append(buf, byte(d.UPID.Type)) + buf = append(buf, uint8(len(d.UPID.Value))) + buf = append(buf, d.UPID.Value...) + + buf = append(buf, byte(d.Type), byte(d.Number), byte(d.Expected)) + switch d.Type { + // TODO(otl): use named constants from section 10.3.3.1 Table 23 - segmentation_type_id + case 0x34, 0x30, 0x32, 0x36, 0x38, 0x3a, 0x44, 0x46: + buf = append(buf, d.SubNumber, d.SubExpected) + } + } + return buf +} + +func unmarshalSegDescriptor(buf []byte) SegmentationDescriptor { + var desc SegmentationDescriptor + desc.EventID = binary.BigEndian.Uint32(buf[:5]) + if buf[1]&0b10000000 > 0 { + desc.Cancel = true + } + if buf[1]&0b01000000 > 0 { + desc.EventIDCompliance = true + } + // next 6 bits are reserved + + // always assume program_segmentation_flag is set at 0b10000000 + // we don't support the deprecated component mode. + + if !desc.Cancel { + desc.Restrictions = DeliveryRestrictions(buf[5] & 0b00111111) // remaining 6 bits + // is segmentation duration flag set? + if buf[5]&0b01000000 > 0 { + b := buf[5:10] // get 40-bit integer + b = append(b, 0x00, 0x00, 0x00) + dur := binary.BigEndian.Uint64(b) + desc.Duration = &dur + buf = buf[10:] + } else { + buf = buf[6:] + } + + uplen := int(buf[1]) + desc.UPID = UPID{ + Type: UPIDType(buf[0]), + Value: buf[2 : 2+uplen], + } + buf = buf[2+uplen:] + + // TODO(otl): use named constants from section 10.3.3.1 Table 23 - segmentation_type_id + desc.Type = uint8(buf[0]) + desc.Number = uint8(buf[1]) + desc.Expected = uint8(buf[2]) + switch desc.Type { + // TODO(otl): use named constants from section 10.3.3.1 Table 23 - segmentation_type_id + case 0x34, 0x30, 0x32, 0x36, 0x38, 0x3a, 0x44, 0x46: + desc.SubNumber = uint8(buf[3]) + desc.SubExpected = uint8(buf[4]) + } + } + + return desc +} + // UPID represents a segmentation_upid structure as specified in SCTE 35 section 10.3.3.1. type UPID struct { Type UPIDType @@ -135,27 +260,62 @@ type TimeDescriptor struct { UTCOffset uint16 } +func (d TimeDescriptor) Tag() uint8 { return TagTime } +func (d TimeDescriptor) ID() uint32 { return descriptorIDCUEI } + +func (d TimeDescriptor) Data() []byte { + // 48 bits + 32 bits + 16 bits + b := make([]byte, 0, 6+4+2) + b = binary.BigEndian.AppendUint64(b, d.Seconds) + b = b[:6] // only want 48-bits + b = binary.BigEndian.AppendUint32(b, d.Nanoseconds) + return binary.BigEndian.AppendUint16(b, d.UTCOffset) +} + type AudioChannel struct { ComponentTag uint8 - // A 3-byte language code from ISO 639-2. - Language string - // A 3-bit field from ATSC A/52 Table 5.7. + // A language code from ISO 639-2. + Language [3]byte + // A 3-bit integer from ATSC A/52 Table 5.7. BitstreamMode uint8 // Number of channels as a 4-bit integer, from ATSC A/52 Table A4.5. Count uint8 FullService bool } +type AudioDescriptor []AudioChannel + +func (d AudioDescriptor) Tag() uint8 { return TagAudio } +func (d AudioDescriptor) ID() uint32 { return descriptorIDCUEI } + +func (d AudioDescriptor) Data() []byte { + var b []byte + count := len(d) + b = append(b, byte(count<<4)) // right-most 4 bits are reserved + for _, ch := range d { + 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 + if ch.FullService { + c |= 0x01 // set last remaining bit + } + b = append(b, c) + } + return b +} + func DecodeAllDescriptors(buf []byte) ([]SpliceDescriptor, error) { var sds []SpliceDescriptor - for len(buf) > 0 { + for len(buf) >= 6 { desc, err := UnmarshalSpliceDescriptor(buf) if err != nil { return sds, err } - sds = append(sds, *desc) + sds = append(sds, desc) // desc.Tag + desclength + desc.ID + data - dlen := 1 + 1 + 4 + len(desc.Data) + dlen := 1 + 1 + 4 + len(desc.Data()) if dlen >= len(buf) { break } @@ -165,61 +325,37 @@ func DecodeAllDescriptors(buf []byte) ([]SpliceDescriptor, error) { return sds, nil } -func UnmarshalSpliceDescriptor(buf []byte) (*SpliceDescriptor, error) { +// UnmarshalSpliceDescriptor reads exactly one descriptor from buf. +func UnmarshalSpliceDescriptor(buf []byte) (SpliceDescriptor, error) { if len(buf) < 6 { - return nil, fmt.Errorf("need at least 5 bytes") - } - d := &SpliceDescriptor{ - Tag: uint8(buf[0]), - ID: binary.LittleEndian.Uint32(buf[2:6]), + return nil, fmt.Errorf("short slice: need at least 5 bytes") } + tag := uint8(buf[0]) length := uint8(buf[1]) - if uint8(buf[1]) > 0 { - d.Data = buf[6 : 6+length] + if len(buf[2:]) < int(length) { + return nil, fmt.Errorf("short slice: want %d bytes, have %d", length, len(buf)) } - return d, nil -} - -func encodeSegDescriptor(sd *SegmentationDescriptor) []byte { - buf := make([]byte, 5) - binary.BigEndian.PutUint32(buf[:4], sd.EventID) - return buf - if sd.Cancel { - buf[4] |= (1 << 7) + buf = buf[2 : 2+length] + id := binary.BigEndian.Uint32(buf[:4]) + buf = buf[4:] + if id != descriptorIDCUEI { + return PrivateDescriptor{tag, id, buf}, nil } - if sd.EventIDCompliance { - buf[4] |= (1 << 6) + switch tag { + case TagAvail: + return AvailDescriptor(binary.BigEndian.Uint32(buf)), nil + case TagSegmentation: + return unmarshalSegDescriptor(buf), nil } - // next 6 bits are reserved. - - if sd.Cancel { - buf = append(buf, 0x00) - // assume program_segmentation is always set; we do not support the deprecated component mode. - buf[5] |= (1 << 7) - if sd.Duration != nil { - buf[5] |= (1 << 6) - } - if sd.Restrictions != 0 { - buf[5] |= (1 << 5) - buf[5] |= byte(sd.Restrictions) - } - - if sd.Duration != nil { - b := make([]byte, 8) // uint64 needs 8 - binary.BigEndian.PutUint64(b, *sd.Duration) - // append 40 bits (5 bytes) - buf = append(buf, b[:4]...) - } - - buf = append(buf, byte(sd.UPID.Type)) - buf = append(buf, uint8(len(sd.UPID.Value))) + return nil, fmt.Errorf("unmarshal %d unsupported", tag) +} - buf = append(buf, byte(sd.Type), byte(sd.Number), byte(sd.Expected)) - switch sd.Type { - // TODO(otl): use named constants. - case 0x34, 0x30, 0x32, 0x36, 0x38, 0x3a, 0x44, 0x46: - buf = append(buf, sd.SubNumber, sd.SubExpected) - } - } - return buf +type PrivateDescriptor struct { + PTag uint8 + PID uint32 + PData []byte } + +func (d PrivateDescriptor) Tag() uint8 { return d.PTag } +func (d PrivateDescriptor) ID() uint32 { return d.PID } +func (d PrivateDescriptor) Data() []byte { return d.PData } diff --git a/internal/scte35/splice_info.go b/internal/scte35/splice_info.go @@ -114,7 +114,7 @@ func encodeSpliceInfo(sis *SpliceInfo) ([]byte, error) { var buf1 []byte for _, desc := range sis.Descriptors { - buf1 = append(buf1, encodeSpliceDescriptor(&desc)...) + buf1 = append(buf1, encodeSpliceDescriptor(desc)...) } b := make([]byte, 2) binary.LittleEndian.PutUint16(b, uint16(len(buf1)))