streaming

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

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:
Minternal/scte35/splice_info.go | 72+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Minternal/scte35/splice_info_test.go | 45++++++++++++++++++++++++++++++++++++++++++---
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) + } +}