streaming

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

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:
Minternal/scte35/command.go | 11+++++++----
Minternal/scte35/pts.go | 10++++++----
Minternal/scte35/pts_test.go | 22++++++++++++++--------
Minternal/scte35/scte35_test.go | 2++
Minternal/scte35/splice_descriptor.go | 44++++++++++++++++++++++++++------------------
Minternal/scte35/splice_info.go | 50+++++++++++++++++++++++++-------------------------
Minternal/scte35/splice_info_test.go | 31++++++++++++++++++++++---------
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) +}