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:
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)))