commit f8b93b8eb11ea50ba94dee757fd4ae066ded24d7
parent 00a48be87c264d3c0182efd16ee0ec04195b260e
Author: Oliver Lowe <o@olowe.co>
Date: Mon, 13 May 2024 13:57:50 +1000
internal/scte35: implement time_signal command encoding
Diffstat:
7 files changed, 102 insertions(+), 68 deletions(-)
diff --git a/internal/scte35/command.go b/internal/scte35/command.go
@@ -189,16 +189,17 @@ func encodeInsert(ins *Insert) []byte {
if ins.Cancel {
buf[4] |= (1 << 7)
}
- // next 7 bits are reserved.
+ // toggle remaining reserved 7 bits
+ buf[4] |= 0x7f
if !ins.Cancel {
buf = append(buf, 0x00)
if ins.OutOfNetwork {
buf[5] |= (1 << 7)
}
- if ins.SpliceTime != nil {
- buf[5] |= (1 << 6)
- }
+ // assume program_splice is set;
+ // we do not support the deprecated component_count mode.
+ buf[5] |= (1 << 6)
if ins.Duration != nil {
buf[5] |= (1 << 5)
}
@@ -231,5 +232,7 @@ func encodeSpliceTime(ticks uint64) [5]byte {
pts := toPTS(ticks)
// set time_specified_flag
pts[0] |= (1 << 7)
+ // toggle 6 reserved bits, so that we match the spec.
+ pts[0] |= 0x7e
return pts
}
diff --git a/internal/scte35/pts.go b/internal/scte35/pts.go
@@ -4,8 +4,8 @@ package scte35
// 90KHz clock - as a 33-bit field.
type PTS [5]byte
-func toPTS(ticks uint64) PTS {
- var p PTS
+func toPTS(ticks uint64) [5]byte {
+ var p [5]byte
p[0] = byte(ticks >> 32)
// mask off 7 bits; we only want 33 total, not 40.
p[0] &= 0b00000001
@@ -16,6 +16,8 @@ func toPTS(ticks uint64) PTS {
return p
}
-func ticks(pts PTS) uint64 {
- return uint64(pts[4]) | uint64(pts[3])<<8 | uint64(pts[2])<<16 | uint64(pts[1])<<24 | uint64(pts[0])<<32
+func putPTS(buf []byte, ticks uint64) {
+ pts := toPTS(ticks)
+ buf[0] |= pts[0]
+ copy(buf[1:5], pts[1:5])
}
diff --git a/internal/scte35/pts_test.go b/internal/scte35/pts_test.go
@@ -5,13 +5,19 @@ import (
)
func TestPTS(t *testing.T) {
- cases := []uint64{1, 128, 8589934492}
-
- for _, tt := range cases {
- pts := toPTS(tt)
- count := ticks(pts)
- if count != tt {
- t.Errorf("ticks(%b) = %d, want %d", pts, count, tt)
- }
+ ticks := uint64(8589934591) // max 33-bit uint
+ buf := make([]byte, 5)
+ putPTS(buf, ticks)
+ want := [5]byte{
+ 0x01,
+ 0xff,
+ 0xff,
+ 0xff,
+ 0xff,
+ }
+ var got [5]byte
+ copy(got[:], buf)
+ if want != got {
+ t.Errorf("putPTS(buf, %d); want %#x, got %#x", ticks, want, got)
}
}
diff --git a/internal/scte35/scte35_test.go b/internal/scte35/scte35_test.go
@@ -13,6 +13,7 @@ var samples = []sample{
want: SpliceInfo{
SAPType: SAPNone,
Tier: 0x0fff,
+ CWIndex: 0xff,
Command: &Command{
Type: TimeSignal,
TimeSignal: newuint64(0x072bd0050),
@@ -40,6 +41,7 @@ var samples = []sample{
encoded: "/DAvAAAAAAAA///wFAVIAACPf+/+c2nALv4AUsz1AAAAAAAKAAhDVUVJAAABNWLbowo=",
want: SpliceInfo{
SAPType: SAPNone,
+ CWIndex: 0xff,
Tier: 0x0fff,
Command: &Command{
Type: SpliceInsert,
diff --git a/internal/scte35/splice_descriptor.go b/internal/scte35/splice_descriptor.go
@@ -34,8 +34,8 @@ type SpliceDescriptor interface {
func encodeSpliceDescriptor(sd SpliceDescriptor) []byte {
var buf []byte
buf = append(buf, byte(sd.Tag()))
- buf = append(buf, byte(len(sd.Data())))
- buf = binary.LittleEndian.AppendUint32(buf, sd.ID())
+ buf = append(buf, byte(len(sd.Data())+4)) // len(sd.ID()) == 4
+ buf = binary.BigEndian.AppendUint32(buf, sd.ID())
return append(buf, sd.Data()...)
}
@@ -126,25 +126,16 @@ func (d SegmentationDescriptor) Data() []byte {
if d.EventIDCompliance {
buf[4] |= (1 << 6)
}
- // next 6 bits are reserved.
+ // toggle next remaining 6 reserved bits.
+ buf[4] |= 0b00111111
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)
+ buf = append(buf, segDescFlags(&d))
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)
+ b := make([]byte, 8) // uint64 needs 8
+ binary.BigEndian.PutUint64(b, *d.Duration<<24) // 40 bits
// append 40 bits (5 bytes)
- buf = append(buf, b[:4]...)
+ buf = append(buf, b[:5]...)
}
buf = append(buf, byte(d.UPID.Type))
@@ -155,7 +146,9 @@ func (d SegmentationDescriptor) Data() []byte {
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)
+ if d.Expected > 0 {
+ buf = append(buf, d.SubNumber, d.SubExpected)
+ }
}
}
return buf
@@ -360,3 +353,18 @@ type PrivateDescriptor struct {
func (d PrivateDescriptor) Tag() uint8 { return d.PTag }
func (d PrivateDescriptor) ID() uint32 { return d.PID }
func (d PrivateDescriptor) Data() []byte { return d.PData }
+
+func segDescFlags(seg *SegmentationDescriptor) uint8 {
+ var b uint8
+ // assume program_segmentation is always set; we do not support the deprecated component mode.
+ b |= (1 << 7)
+ if seg.Duration != nil {
+ b |= (1 << 6)
+ }
+ if seg.Restrictions != 0 {
+ b |= byte(seg.Restrictions)
+ } else {
+ b |= (1 << 5)
+ }
+ return b
+}
diff --git a/internal/scte35/splice_info.go b/internal/scte35/splice_info.go
@@ -11,10 +11,10 @@ import (
type SAPType uint8
const (
- SAPClosedGOP SAPType = 0
- SAPClosedGOPLeading = 0x10
- SAPOpenGOP = 0x20
- SAPNone = 0x30
+ SAPClosedGOP SAPType = 0
+ SAPClosedGOPLeading = 0x10
+ SAPOpenGOP = 0x20
+ SAPNone = 0x30
)
func (t SAPType) String() string {
@@ -58,7 +58,7 @@ const (
const maxTier uint16 = 0xfff
func encodeSpliceInfo(sis *SpliceInfo) ([]byte, error) {
- buf := make([]byte, 5)
+ buf := make([]byte, 4)
buf[0] = byte(tableID)
// next 2 bits (section_syntax_indicator, private_indicator) must be 0.
// 0b00000000
@@ -76,20 +76,17 @@ func encodeSpliceInfo(sis *SpliceInfo) ([]byte, error) {
// pack cipher, keeping 1 bit for PTSAdjustment.
b |= byte(sis.Cipher) << 1
}
- pts := toPTS(sis.PTSAdjustment)
- // set the remaining 1 bit in b from the 33-bit timestamp. append the rest.
- b |= pts[0]
buf = append(buf, b)
- buf = append(buf, pts[1:]...)
- buf = append(buf, byte(sis.CWIndex))
+ buf = append(buf, 0, 0, 0, 0)
+ putPTS(buf[4:], sis.PTSAdjustment)
+ buf = append(buf, sis.CWIndex)
if sis.Tier > maxTier {
return nil, fmt.Errorf("tier %d greater than max %d", sis.Tier, maxTier)
}
- tier := packTier(sis.Tier)
- buf = append(buf, tier[0])
- buf = append(buf, tier[1]<<4)
- // next 4 bits will be from the command length
+ tier := sis.Tier & 0x0fff // just 12 bits
+ // right 4 bits are for command length
+ buf = binary.BigEndian.AppendUint16(buf, tier<<4)
if sis.Command == nil {
return nil, fmt.Errorf("nil command")
}
@@ -97,10 +94,11 @@ func encodeSpliceInfo(sis *SpliceInfo) ([]byte, error) {
if err != nil {
return nil, fmt.Errorf("encode splice command: %w", err)
}
- length := uint16(len(cmd))
+ cmdlen := uint16(len(cmd)) & 0x0fff
// stuff remaining 4 bits into the last byte.
- buf[len(buf)-1] |= byte(length >> 8)
- buf = append(buf, byte(length))
+ buf[len(buf)-1] |= byte(cmdlen >> 8)
+ buf = append(buf, byte(cmdlen))
+ buf = append(buf, byte(sis.Command.Type))
buf = append(buf, cmd...)
var buf1 []byte
@@ -108,16 +106,16 @@ func encodeSpliceInfo(sis *SpliceInfo) ([]byte, error) {
buf1 = append(buf1, encodeSpliceDescriptor(desc)...)
}
buf = binary.BigEndian.AppendUint16(buf, uint16(len(buf1)))
+ buf = append(buf, buf1...)
- return binary.BigEndian.AppendUint32(buf, calculateCRC32(buf)), nil
-}
+ // want only 12 bits, left 4 bits are used by flags, saptype.
+ buflen := uint16(len(buf)) & 0x0fff
+ buflen++ // TODO(otl): is this required because of alignment stuffing?
+ buf[1] |= byte(buflen >> 8)
+ buf[2] = byte(buflen)
-func packTier(tier uint16) [2]byte {
- var a [2]byte
- // mask off last 4 bits; we want a 12-bit integer.
- a[0] = byte(tier>>8) & 0b00001111
- a[1] = byte(tier)
- return a
+ crc := calculateCRC32(buf)
+ return binary.BigEndian.AppendUint32(buf, crc), nil
}
func decodeSpliceInfo(buf []byte) (*SpliceInfo, error) {
@@ -153,6 +151,8 @@ func decodeSpliceInfo(buf []byte) (*SpliceInfo, error) {
if info.Encrypted {
info.CWIndex = uint8(buf[6])
+ } else {
+ info.CWIndex = 0xff
}
// want left-most 12 bits, remaining is used by command length.
diff --git a/internal/scte35/splice_info_test.go b/internal/scte35/splice_info_test.go
@@ -8,15 +8,6 @@ import (
"testing"
)
-func TestPackTier(t *testing.T) {
- want := maxTier - 1
- packed := packTier(want)
- got := uint16(packed[1]) | uint16(packed[0])<<8
- if got != want {
- t.Errorf("want packed tier %d, got %d", want, got)
- }
-}
-
func diffInfo(a, b SpliceInfo) string {
buf := &strings.Builder{}
if a.SAPType != b.SAPType {
@@ -137,3 +128,25 @@ func TestEncodeSpliceInfo(t *testing.T) {
})
}
}
+
+func TestEncodeInsert(t *testing.T) {
+ // want := 0x4800008f7feffe7369c02efe
+ ins := samples[1].want.Command.Insert
+ b := encodeInsert(ins)
+ want := []byte{
+ 0x48,
+ 0x00,
+ 0x00,
+ 0x8f,
+ 0x7f,
+ 0xef,
+ 0xfe,
+ 0x73,
+ 0x69,
+ 0xc0,
+ 0x2e,
+ 0xfe,
+ }
+ fmt.Printf("%#x\n", want)
+ fmt.Printf("%#x\n", b)
+}