commit 47d1cec1ab17a35dffdab92234fa96b52131e105
parent ee99ace2ce39638619b8f0a4328edd2b8a6c574f
Author: Oliver Lowe <o@olowe.co>
Date: Wed, 8 May 2024 15:59:21 +1000
internal/scte35: implement more decoding of SpliceInfo
Up to and including Command field.
Diffstat:
2 files changed, 113 insertions(+), 4 deletions(-)
diff --git a/internal/scte35/splice_info.go b/internal/scte35/splice_info.go
@@ -25,8 +25,10 @@ func (t SAPType) String() string {
return "SAP Type 2 (closed GOP with leading pictures)"
case SAPOpenGOP:
return "SAP Type 3 (open GOP)"
+ case SAPNone:
+ return "none"
}
- return "none"
+ return "invalid"
}
type SpliceInfo struct {
@@ -131,3 +133,71 @@ func packTier(tier uint16) [2]byte {
a[1] = byte(tier)
return a
}
+
+func decodeSpliceInfo(buf []byte) (*SpliceInfo, error) {
+ if len(buf) < 3 {
+ return nil, fmt.Errorf("need at least 2 bytes")
+ }
+ // skip buf[0], we don't store table_id.
+
+ var info SpliceInfo
+ // skip 2 bits, straight to sap_type.
+ st := buf[1] & 0b00110000
+ info.SAPType = SAPType(st >> 4)
+
+ length := binary.BigEndian.Uint16([]byte{buf[1], buf[2]})
+ length &= 0x0fff // 12-bit field
+ buf = buf[3:]
+ if len(buf) != int(length) {
+ return nil, fmt.Errorf("message declares %d bytes but have %d", length, len(buf))
+ }
+
+ // skip version byte, we don't store version as it's constant.
+ if buf[1]&0b10000000 == 1 {
+ info.Encrypted = true
+ // right-most bit is used by PTSAdjustment.
+ info.EncryptionAlgorithm = Cipher(buf[1] & 0b01111110)
+ }
+
+ pts := make([]byte, 8)
+ pts[0] = buf[1] & (1 << 1)
+ pts[1] = buf[2]
+ pts[2] = buf[3]
+ pts[3] = buf[4]
+ pts[4] = buf[5]
+ info.PTSAdjustment = binary.BigEndian.Uint64(pts)
+
+ info.CWIndex = uint8(buf[6])
+
+ // want left-most 12 bits, remaining is used by command length.
+ // TODO(otl): still not getting expected values here;
+ // check TestDecodeSpliceInfo
+ tier := binary.BigEndian.Uint16([]byte{buf[7], buf[8] & 0xf0})
+ info.Tier = tier
+
+ // 4-bits out of buf[8], then all of buf[9] for a 12-bit integer.
+ cmdlength := binary.BigEndian.Uint16([]byte{buf[8] & 0x0f, buf[9]})
+ var cmd Command
+ cmd.Type = CommandType(buf[10])
+ cmdbuf := buf[11 : 11+cmdlength]
+ switch cmd.Type {
+ case SpliceNull, BandwidthReservation:
+ //
+ case TimeSignal:
+ // check if time specified flag is set.
+ if cmdbuf[0]&0x80 == 1<<7 {
+ b := make([]byte, 8)
+ b[3] = cmdbuf[0] & 0x01 // ignoring flag and reserved bits
+ b[4] = cmdbuf[1]
+ b[5] = cmdbuf[2]
+ b[6] = cmdbuf[3]
+ b[7] = cmdbuf[4]
+ t := binary.BigEndian.Uint64(b)
+ cmd.TimeSignal = &t
+ }
+ default:
+ return nil, fmt.Errorf("cannot decode command %s", cmd.Type)
+ }
+ info.Command = &cmd
+ return &info, nil
+}
diff --git a/internal/scte35/splice_info_test.go b/internal/scte35/splice_info_test.go
@@ -3,14 +3,12 @@ package scte35
import (
"encoding/base64"
"encoding/binary"
- "fmt"
+ "reflect"
"testing"
)
func TestPackTier(t *testing.T) {
want := maxTier - 1
- fmt.Printf("%012b\n", want)
-
packed := packTier(want)
got := uint16(packed[1]) | uint16(packed[0])<<8
if got != want {
@@ -59,3 +57,44 @@ func TestReadSpliceInfo(t *testing.T) {
t.Fatalf("want %s, got %s", want, got)
}
}
+
+func TestDecodeSpliceInfo(t *testing.T) {
+ var tsig uint64 = 0x072bd0050
+ want := &SpliceInfo{
+ SAPType: SAPNone,
+ Tier: 0x0fff,
+ Command: &Command{
+ Type: TimeSignal,
+ TimeSignal: &tsig,
+ },
+ Descriptors: []SpliceDescriptor{
+ {
+ Tag: TagSegmentation,
+ ID: binary.BigEndian.Uint32([]byte(DescriptorIDCUEI)),
+ Data: []byte("TODO"),
+ },
+ },
+ CRC32: 0x9ac9d17e,
+ }
+ b, err := base64.StdEncoding.DecodeString("/DA0AAAAAAAA///wBQb+cr0AUAAeAhxDVUVJSAAAjn/PAAGlmbAICAAAAAAsoKGKNAIAmsnRfg==") // sample 14.1
+ if err != nil {
+ t.Fatal("decode example splice info:", err)
+ }
+
+ info, err := decodeSpliceInfo(b)
+ if err != nil {
+ t.Fatalf("decode splice info: %v", err)
+ }
+ if want.SAPType != info.SAPType {
+ t.Errorf("want SAPType %s, got %s", want.SAPType, info.SAPType)
+ }
+ if want.Tier != info.Tier {
+ t.Errorf("want tier %#x, got %#x", want.Tier, info.Tier)
+ }
+ if *want.Command.TimeSignal != *info.Command.TimeSignal {
+ t.Errorf("want timesig %x, got %x", *want.Command.TimeSignal, *info.Command.TimeSignal)
+ }
+ if !reflect.DeepEqual(want, info) {
+ t.Errorf("decode splice info: want %+v, got %+v", want, info)
+ }
+}