streaming

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

commit da1a099e334bded321dbb6e85d7c9248c026ffdd
parent b0b2e0a93b500d0c8699eded2f8cea1035a83a46
Author: Oliver Lowe <o@olowe.co>
Date:   Tue,  7 May 2024 12:38:05 +1000

internal/scte35: don't refactor, reimplement scte35 package

This is a huge WIP patch which should never have come out like this,
but we made it all while live streaming so it will have to do for now.
All external dependencies are removed, and there are far fewer types
to juggle. Progress so far is we're almost able to encode SpliceInfo.

Diffstat:
Dinternal/scte35/audio_descriptor.go | 114-------------------------------------------------------------------------------
Dinternal/scte35/avail_descriptor.go | 76----------------------------------------------------------------------------
Dinternal/scte35/bandwidth_reservation.go | 52----------------------------------------------------
Ainternal/scte35/break_duration.go | 24++++++++++++++++++++++++
Ainternal/scte35/break_duration_test.go | 21+++++++++++++++++++++
Ainternal/scte35/command.go | 249+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Minternal/scte35/crc_32.go | 1-
Dinternal/scte35/dtmf_descriptor.go | 85-------------------------------------------------------------------------------
Minternal/scte35/encrypted_packet.go | 28++++++++++++++++------------
Ainternal/scte35/encrypted_packet_test.go | 30++++++++++++++++++++++++++++++
Dinternal/scte35/private_command.go | 82-------------------------------------------------------------------------------
Ainternal/scte35/pts.go | 21+++++++++++++++++++++
Ainternal/scte35/pts_test.go | 17+++++++++++++++++
Minternal/scte35/scte35.go | 14++------------
Minternal/scte35/scte35_test.go | 37++++++++++++++++++++++---------------
Dinternal/scte35/segmentation_descriptor.go | 542-------------------------------------------------------------------------------
Dinternal/scte35/segmentation_upid.go | 302------------------------------------------------------------------------------
Dinternal/scte35/segmentation_upid_test.go | 11-----------
Dinternal/scte35/splice_command.go | 53-----------------------------------------------------
Minternal/scte35/splice_descriptor.go | 5++++-
Ainternal/scte35/splice_info.go | 149+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Minternal/scte35/splice_info_section.go | 109++++++++++++++++++-------------------------------------------------------------
Minternal/scte35/splice_info_section_test.go | 29++++++++++-------------------
Ainternal/scte35/splice_info_test.go | 17+++++++++++++++++
Dinternal/scte35/splice_insert.go | 280-------------------------------------------------------------------------------
Dinternal/scte35/splice_null.go | 45---------------------------------------------
Dinternal/scte35/splice_schedule.go | 200-------------------------------------------------------------------------------
Ainternal/scte35/splice_schedule_test.go | 17+++++++++++++++++
Dinternal/scte35/time_descriptor.go | 77-----------------------------------------------------------------------------
Dinternal/scte35/time_signal.go | 102-------------------------------------------------------------------------------
30 files changed, 624 insertions(+), 2165 deletions(-)

diff --git a/internal/scte35/audio_descriptor.go b/internal/scte35/audio_descriptor.go @@ -1,114 +0,0 @@ -// Copyright 2021 Comcast Cable Communications Management, LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// SPDX-License-Identifier: Apache-2.0 - -package scte35 - -import ( - "fmt" - - "github.com/bamiaux/iobit" -) - -// AudioDescriptorTag is the splice_descriptor_tag for an audio descriptor. -const AudioDescriptorTag = 0x04 - -// AudioDescriptor is an implementation of a audio_descriptor. The -// audio_descriptor() should be used when programmers and/or MVPDs do not -// support dynamic signaling (e.g., signaling of audio language changes) and -// with legacy audio formats that do not support dynamic signaling. -type AudioDescriptor struct { - AudioChannels []AudioChannel -} - -// Tag returns the splice_descriptor_tag. -func (sd *AudioDescriptor) Tag() uint32 { return AudioDescriptorTag } - -// decode updates this SpliceDescriptor from binary. -func (sd *AudioDescriptor) decode(b []byte) error { - r := iobit.NewReader(b) - r.Skip(8) // splice_descriptor_tag - r.Skip(8) // descriptor_length - r.Skip(32) // identifier - audioCount := int(r.Uint32(4)) - r.Skip(4) // reserved - sd.AudioChannels = make([]AudioChannel, audioCount) - for i := 0; i < audioCount; i++ { - ac := AudioChannel{} - ac.ComponentTag = r.Uint32(8) - ac.ISOCode = r.String(3) - ac.BitStreamMode = r.Uint32(3) - ac.NumChannels = r.Uint32(4) - ac.FullSrvcAudio = r.Bit() - sd.AudioChannels[i] = ac - } - - if err := readerError(r); err != nil { - return fmt.Errorf("audio_descriptor: %w", err) - } - return nil -} - -// encode this SpliceDescriptor to binary. -func (sd *AudioDescriptor) encode() ([]byte, error) { - length := sd.length() - - // add 2 bytes to contain splice_descriptor_tag & descriptor_length - buf := make([]byte, length+2) - iow := iobit.NewWriter(buf) - iow.PutUint32(8, AudioDescriptorTag) - iow.PutUint32(8, uint32(length)) - iow.PutUint32(32, CUEIdentifier) - iow.PutUint32(8, uint32(len(sd.AudioChannels))) - iow.PutUint32(4, Reserved) - for _, ad := range sd.AudioChannels { - iow.PutUint32(8, ad.ComponentTag) - _, _ = iow.Write([]byte(ad.ISOCode)) - iow.PutUint32(3, ad.BitStreamMode) - iow.PutUint32(4, ad.NumChannels) - iow.PutBit(ad.FullSrvcAudio) - } - return buf, nil -} - -// descriptorLength returns the descriptor_length -func (sd *AudioDescriptor) length() int { - length := 32 // identifier - length += 4 // audio_count - length += 4 // reserved - for i := range sd.AudioChannels { - length += sd.AudioChannels[i].length() * 8 - } - return length / 8 -} - -// AudioChannel collects the audio PID details. -type AudioChannel struct { - ComponentTag uint32 - ISOCode string - BitStreamMode uint32 - NumChannels uint32 - FullSrvcAudio bool -} - -// length returns audio_channel length. -func (ac *AudioChannel) length() int { - length := 8 // component_tag - length += 24 // iso_code - length += 3 // bit_stream_mode - length += 4 // num_channels - length++ // full_srvc_audio - return length / 8 -} diff --git a/internal/scte35/avail_descriptor.go b/internal/scte35/avail_descriptor.go @@ -1,76 +0,0 @@ -// Copyright 2021 Comcast Cable Communications Management, LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// SPDX-License-Identifier: Apache-2.0 - -package scte35 - -import ( - "fmt" - - "github.com/bamiaux/iobit" -) - -// AvailDescriptorTag is the splice_descriptor_tag for an avail_descriptor -const AvailDescriptorTag = 0x00 - -// AvailDescriptor is an implementation of a splice_descriptor. It provides an -// optional extension to the splice_insert() command that allows an -// authorization identifier to be sent for an avail. Multiple copies of this -// descriptor may be included by using the loop mechanism provided. This -// identifier is intended to replicate the functionality of the cue tone system -// used in analog systems for ad insertion. This descriptor is intended only -// for use with a splice_insert() command, within a splice_info_section. -type AvailDescriptor struct { - ProviderAvailID uint32 -} - -// Tag returns the splice_descriptor_tag. -func (sd *AvailDescriptor) Tag() uint32 { return AvailDescriptorTag } - -// decode updates this splice_descriptor from binary. -func (sd *AvailDescriptor) decode(b []byte) error { - r := iobit.NewReader(b) - r.Skip(8) // splice_descriptor_tag - r.Skip(8) // descriptor_length - r.Skip(32) // identifier - sd.ProviderAvailID = r.Uint32(32) - - if err := readerError(r); err != nil { - return fmt.Errorf("avail_descriptor: %w", err) - } - return nil -} - -// encode this splice_descriptor to binary. -func (sd *AvailDescriptor) encode() ([]byte, error) { - length := sd.length() - // add 2 bytes to contain splice_descriptor_tag & descriptor_length - buf := make([]byte, length+2) - iow := iobit.NewWriter(buf) - - iow.PutUint32(8, AvailDescriptorTag) // splice_descriptor_tag - iow.PutUint32(8, uint32(length)) // descriptor_length - iow.PutUint32(32, CUEIdentifier) // identifier - iow.PutUint32(32, sd.ProviderAvailID) // provider_avail_id - - return buf, iow.Flush() -} - -// descriptorLength returns the descriptor_length -func (sd *AvailDescriptor) length() int { - length := 32 // identifier - length += 32 // provider_avail_id - return length / 8 -} diff --git a/internal/scte35/bandwidth_reservation.go b/internal/scte35/bandwidth_reservation.go @@ -1,52 +0,0 @@ -// Copyright 2021 Comcast Cable Communications Management, LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// SPDX-License-Identifier: Apache-2.0 - -package scte35 - -import "fmt" - -// BandwidthReservationType is the splice_command_type for -// bandwidth_reservation() -const BandwidthReservationType = 0x07 - -// BandwidthReservation command is provided for reserving bandwidth in a -// multiplex. A typical usage would be in a satellite delivery system that -// requires packets of a certain PID to always be present at the intended -// repetition rate to guarantee a certain bandwidth for that PID. This message -// differs from a splice_null() command so that it can easily be handled in a -// unique way by receiving equipment (i.e. removed from the multiplex by a -// satellite receiver). If a descriptor is sent with this command, it can not be -// expected that it will be carried through the entire transmission chain and it -// should be a private descriptor that is utilized only by the bandwidth -// reservation process. -type BandwidthReservation struct{} - -// Type returns the splice_command_type. -func (cmd *BandwidthReservation) Type() uint32 { return BandwidthReservationType } - -// decode a binary bandwidth_reservation. -func (cmd *BandwidthReservation) decode(b []byte) error { - if len(b) > 0 { - return fmt.Errorf("bandwidth_reservation: %w", ErrBufferOverflow) - } - return nil -} - -// encode this bandwidth_reservation to binary. -func (cmd *BandwidthReservation) encode() ([]byte, error) { return nil, nil } - -// commandLength returns the splice_command_length -func (cmd *BandwidthReservation) length() int { return 0 } diff --git a/internal/scte35/break_duration.go b/internal/scte35/break_duration.go @@ -0,0 +1,24 @@ +package scte35 + +type BreakDuration struct { + AutoReturn bool + // Holds a number of ticks of a 90KHz clock. + Duration uint64 +} + +func packBreakDuration(b *BreakDuration) [5]byte { + var p [5]byte + if b.AutoReturn { + p[0] |= (1 << 7) + } + // next 6 bits are reserved. + pts := toPTS(b.Duration) + // 1 bit remaining in the first byte, so pack 1 bit from the timestamp + p[0] |= pts[0] + + p[1] = pts[1] + p[2] = pts[2] + p[3] = pts[3] + p[4] = pts[4] + return p +} diff --git a/internal/scte35/break_duration_test.go b/internal/scte35/break_duration_test.go @@ -0,0 +1,21 @@ +package scte35 + +import ( + "testing" +) + +func TestPackBreakDuration(t *testing.T) { + dur := uint64(8589934492) // 2^33 - 100 + bd := BreakDuration{true, dur} + want := [5]byte{ + 0b10000001, + 0b11111111, + 0b11111111, + 0b11111111, + 0b10011100, + } + got := packBreakDuration(&bd) + if want != got { + t.Errorf("packBreakDuration(%v) = %08b, want %08b", bd, got, want) + } +} diff --git a/internal/scte35/command.go b/internal/scte35/command.go @@ -0,0 +1,249 @@ +package scte35 + +import ( + "fmt" + "time" +) + +// GPS epoch is 1980-01-06T00:00:00Z +var gpsEpoch time.Time = time.Date(1980, 1, 6, 0, 0, 0, 0, time.UTC) + +// Command represents a splice command described in +// SCTE 35 section 9.7. +type Command struct { + Type CommandType + Schedule []Event // SpliceSchedule + Insert *Insert + // Number of ticks of a 90KHz clock. + TimeSignal *uint64 + Private *PrivateCommand +} + +type CommandType uint8 + +const ( + SpliceNull CommandType = 0 + SpliceSchedule = 0x04 + iota + SpliceInsert + TimeSignal + BandwidthReservation + Private = 0xff +) + +func (t CommandType) String() string { + switch t { + case SpliceNull: + return "splice_null" + case SpliceSchedule: + return "splice_schedule" + case SpliceInsert: + return "splice_insert" + case TimeSignal: + return "time_signal" + case BandwidthReservation: + return "bandwidth_reservation" + case Private: + return "private_command" + } + return "reserved" +} + +func encodeCommand(c *Command) ([]byte, error) { + switch c.Type { + case SpliceNull, BandwidthReservation: + return nil, nil + case SpliceSchedule: + b, err := packEvents(c.Schedule) + if err != nil { + return b, fmt.Errorf("pack events: %w", err) + } + return b, nil + case SpliceInsert: + b := encodeInsert(c.Insert) + return b, nil + case TimeSignal: + if c.TimeSignal == nil { + return nil, fmt.Errorf("command type is %s, but nil TimeSignal value set", c.Type) + } + b := encodeSpliceTime(*c.TimeSignal) + return b[:], nil + case Private: + return encodePrivateCommand(c.Private), nil + default: + return nil, fmt.Errorf("encoding command %s unsupported", c.Type) + } +} + +// Event is a single event within a splice_schedule. +type Event struct { + ID uint32 + // Indicates a previously sent event identified by ID should + // be cancelled. + Cancel bool + // Indicates the event's ID is prepared in the method + // described in SCTE 35 section 9.3.3. + IDCompliance bool + + OutOfNetwork bool + // TODO(otl): should always be true? should we support + // deprecated Component Splice Mode? + // see section 9.7.2.1. + // ProgramSplice bool + SpliceTime time.Time + BreakDuration *BreakDuration + + ProgramID uint16 + AvailNum uint8 + AvailExpected uint8 +} + +func packEvents(events []Event) ([]byte, error) { + if len(events) > 255 { + return nil, fmt.Errorf("too many events (%d), need 255 or less", len(events)) + } + var packed []byte + packed[0] = uint8(len(events)) + for i := range events { + b := packEvent(&events[i]) + packed = append(packed, b...) + } + return packed, nil +} + +func packEvent(e *Event) []byte { + // length is e.ID + flags + p := make([]byte, 4+1) + + p[0] = byte(e.ID >> 24) + p[1] = byte(e.ID >> 16) + p[2] = byte(e.ID >> 8) + p[3] = byte(e.ID) + + if e.Cancel { + p[4] |= 1 << 7 + } + if e.IDCompliance { + p[4] |= 1 << 6 + } + // 6 remaining bits are reserved. + + if !e.Cancel { + p = append(p, 0x00) + if e.OutOfNetwork { + p[5] |= 1 << 7 + } + // assume program_splice is always set; + // we don't support component splice mode. + p[5] |= 1 << 6 + if e.BreakDuration != nil { + p[5] |= 1 << 5 + } + // 5 remaining bits are reserved + + seconds := e.SpliceTime.Sub(gpsEpoch) / time.Second + p = append(p, byte(seconds>>24)) + p = append(p, byte(seconds>>16)) + p = append(p, byte(seconds>>8)) + p = append(p, byte(seconds)) + + if e.BreakDuration != nil { + bd := packBreakDuration(e.BreakDuration) + p = append(p, bd[:]...) + } + } + + p = append(p, byte(e.ProgramID>>8)) + p = append(p, byte(e.ProgramID)) + p = append(p, byte(e.AvailNum)) + p = append(p, byte(e.AvailExpected)) + return p +} + +type PrivateCommand struct { + ID uint32 + Data []byte +} + +func encodePrivateCommand(c *PrivateCommand) []byte { + buf := make([]byte, 4+len(c.Data)) + buf[0] = byte(c.ID >> 24) + buf[1] = byte(c.ID >> 16) + buf[2] = byte(c.ID >> 8) + buf[3] = byte(c.ID) + i := 4 + for j := range c.Data { + buf[i] = c.Data[j] + i++ + } + return buf +} + +// Insert represents the splice_insert command +// as specified in SCTE 35 section 9.7.3. +type Insert struct { + ID uint32 + Cancel bool + OutOfNetwork bool + Immediate bool + EventIDCompliance bool + // Number of ticks of a 90KHz clock. + SpliceTime *uint64 + Duration *BreakDuration + ProgramID uint16 + AvailNum uint8 + AvailExpected uint8 +} + +func encodeInsert(ins *Insert) []byte { + buf := make([]byte, 4+1) // uint32 + 1 byte + buf[0] = byte(ins.ID >> 24) + buf[1] = byte(ins.ID >> 16) + buf[2] = byte(ins.ID >> 8) + buf[3] = byte(ins.ID) + if ins.Cancel { + buf[4] |= (1 << 7) + } + // next 7 bits are reserved. + + if !ins.Cancel { + buf = append(buf, 0x00) + if ins.OutOfNetwork { + buf[5] |= (1 << 7) + } + if ins.SpliceTime != nil { + buf[5] |= (1 << 6) + } + if ins.Duration != nil { + buf[5] |= (1 << 5) + } + if ins.Immediate { + buf[5] |= (1 << 4) + } + if ins.EventIDCompliance { + buf[5] |= (1 << 3) + } + // next 3 bits are reserved. + + if ins.SpliceTime != nil && !ins.Immediate { + b := encodeSpliceTime(*ins.SpliceTime) + buf = append(buf, b[:]...) + } + + if ins.Duration != nil { + b := packBreakDuration(ins.Duration) + buf = append(buf, b[:]...) + } + buf = append(buf, byte(ins.ProgramID>>8)) + buf = append(buf, byte(ins.ProgramID)) + buf = append(buf, byte(ins.AvailNum)) + buf = append(buf, byte(ins.AvailExpected)) + } + return buf +} + +func encodeSpliceTime(ticks uint64) [5]byte { + pts := toPTS(ticks) + // set time_specified_flag + pts[0] |= (1 << 7) + return pts +} diff --git a/internal/scte35/crc_32.go b/internal/scte35/crc_32.go @@ -311,7 +311,6 @@ func verifyCRC32(b []byte) error { calculated := calculateCRC32(payload) if calculated != crc { - Logger.Printf("CRC_32 calculated (%d) != reported (%d)\n", calculated, crc) return ErrCRC32Invalid } diff --git a/internal/scte35/dtmf_descriptor.go b/internal/scte35/dtmf_descriptor.go @@ -1,85 +0,0 @@ -// Copyright 2021 Comcast Cable Communications Management, LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// SPDX-License-Identifier: Apache-2.0 - -package scte35 - -import ( - "fmt" - - "github.com/bamiaux/iobit" -) - -// DTMFDescriptorTag is the splice_descriptor_tag for a dtmf_descriptor -const DTMFDescriptorTag = 0x01 - -// DTMFDescriptor is an implementation of a splice_descriptor. It provides an -// optional extension to the splice_insert() command that allows a receiver -// device to generate a legacy analog DTMF sequence based on a -// splice_info_section being received. -type DTMFDescriptor struct { - Preroll uint32 - DTMFChars string -} - -// Tag returns the splice_descriptor_tag. -func (sd *DTMFDescriptor) Tag() uint32 { return DTMFDescriptorTag } - -// decode updates this splice_descriptor from binary. -func (sd *DTMFDescriptor) decode(b []byte) error { - r := iobit.NewReader(b) - r.Skip(8) // splice_descriptor_tag - r.Skip(8) // descriptor_length - r.Skip(32) // identifier - sd.Preroll = r.Uint32(8) - dtmfCount := int(r.Uint32(3)) - r.Skip(5) // reserved - sd.DTMFChars = r.String(dtmfCount) - - if err := readerError(r); err != nil { - return fmt.Errorf("dtmf_descriptor: %w", err) - } - return readerError(r) -} - -// encode this splice_descriptor to binary. -func (sd *DTMFDescriptor) encode() ([]byte, error) { - length := sd.length() - - // add 2 bytes to contain splice_descriptor_tag & descriptor_length - buf := make([]byte, length+2) - iow := iobit.NewWriter(buf) - iow.PutUint32(8, DTMFDescriptorTag) // splice_descriptor_tag - iow.PutUint32(8, uint32(length)) // descriptor_length - iow.PutUint32(32, CUEIdentifier) // identifier - iow.PutUint32(8, sd.Preroll) // preroll - iow.PutUint32(3, uint32(len(sd.DTMFChars))) // dtmf_count - iow.PutUint32(5, Reserved) // reserved - _, err := iow.Write([]byte(sd.DTMFChars)) // dtmf_chars - if err != nil { - return buf, err - } - return buf, iow.Flush() -} - -// descriptorLength returns the descriptor_length. -func (sd *DTMFDescriptor) length() int { - length := 32 // identifier - length += 8 // preroll - length += 3 // dtmf_count - length += 5 // reserved - length += len(sd.DTMFChars) * 8 // dtmf_char - return length / 8 -} diff --git a/internal/scte35/encrypted_packet.go b/internal/scte35/encrypted_packet.go @@ -20,31 +20,35 @@ type EncryptedPacket struct { // cipher is a 6-bit field specifying the algorithm used to encrypt // payloads as defined in SCTE 35 section 11.3. -type cipher uint8 +type Cipher uint8 const ( - cipherNone cipher = iota - des_ECB // SCTE 35 section 11.3.1 - des_CBC // SCTE 35 section 11.3.2 - tripleDES // SCTE 35 section 11.3.3 + CipherNone Cipher = iota + DES_ECB // SCTE 35 section 11.3.1 + DES_CBC // SCTE 35 section 11.3.2 + TripleDES // SCTE 35 section 11.3.3 reserved // Values 32 through 63 are available for "User private" // algorithms. See SCTE 35 section 11.3.4. ) -func (c cipher) String() string { +const maxCipher = 63 + +func (c Cipher) String() string { switch c { - case cipherNone: + case CipherNone: return "none" - case des_ECB: + case DES_ECB: return "DES – ECB mode" - case des_CBC: + case DES_CBC: return "DES – CBC mode" - case tripleDES: + case TripleDES: return "Triple DES EDE3 – ECB mode" } - if c >= reserved && c < 32 { + if c >= reserved && c <= 31 { return "reserved" + } else if c <= maxCipher { + return "user private" } - return "user private" + return "invalid" } diff --git a/internal/scte35/encrypted_packet_test.go b/internal/scte35/encrypted_packet_test.go @@ -0,0 +1,30 @@ +package scte35 + +import "testing" + +func TestPackEncryption(t *testing.T) { + type ptest struct { + sis SpliceInfo + want uint8 + } + var tests = []ptest{ + { + sis: SpliceInfo{Encrypted: true, EncryptionAlgorithm: DES_CBC}, + want: 0b10000100, + }, + { + sis: SpliceInfo{Encrypted: true, EncryptionAlgorithm: TripleDES}, + want: 0b10000110, + }, + } + for _, tt := range tests { + var b byte + if tt.sis.Encrypted { + b |= (1 << 7) + } + b |= byte(tt.sis.EncryptionAlgorithm) << 1 + if b != tt.want { + t.Errorf("pack encryption info %v: got %08b, want %08b", tt.sis, b, tt.want) + } + } +} diff --git a/internal/scte35/private_command.go b/internal/scte35/private_command.go @@ -1,82 +0,0 @@ -// Copyright 2021 Comcast Cable Communications Management, LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// SPDX-License-Identifier: Apache-2.0 - -package scte35 - -import ( - "encoding/binary" - "fmt" - - "github.com/bamiaux/iobit" -) - -// PrivateCommandType is the splice_command_type for private_command() -const PrivateCommandType = 0xFF - -// PrivateCommand provides a means to distribute user-defined commands using the -// SCTE 35 protocol. The first bit field in each user-defined command is a -// 32-bit identifier, unique for each participating vendor. Receiving equipment -// should skip any splice_info_section() messages containing private_command() -// structures with unknown identifiers. -type PrivateCommand struct { - Identifier uint32 - PrivateBytes Bytes -} - -// IdentifierString returns the identifier as a string. -func (cmd *PrivateCommand) IdentifierString() string { - b := make([]byte, 4) - binary.BigEndian.PutUint32(b, cmd.Identifier) - return string(b) -} - -// Type returns the splice_command_type. -func (cmd *PrivateCommand) Type() uint32 { return PrivateCommandType } - -// decode a binary private_command. -func (cmd *PrivateCommand) decode(b []byte) error { - r := iobit.NewReader(b) - - cmd.Identifier = r.Uint32(32) - cmd.PrivateBytes = r.LeftBytes() - // LeftBytes doesnt advance position - r.Skip(uint(len(cmd.PrivateBytes) * 8)) - - if err := readerError(r); err != nil { - return fmt.Errorf("private_command: %w", err) - } - return readerError(r) -} - -// encode this private_command to binary. -func (cmd *PrivateCommand) encode() ([]byte, error) { - buf := make([]byte, cmd.length()) - - iow := iobit.NewWriter(buf) - iow.PutUint32(32, cmd.Identifier) - _, err := iow.Write(cmd.PrivateBytes) - if err != nil { - return buf, err - } - return buf, iow.Flush() -} - -// commandLength returns the splice_command_length. -func (cmd *PrivateCommand) length() int { - length := 32 // identifier - length += len(cmd.PrivateBytes) * 8 // private_bytes - return length / 8 -} diff --git a/internal/scte35/pts.go b/internal/scte35/pts.go @@ -0,0 +1,21 @@ +package scte35 + +// PTS represents a presentation timestamp - the number of ticks of a +// 90KHz clock - as a 33-bit field. +type PTS [5]byte + +func toPTS(ticks uint64) PTS { + var p PTS + p[0] = byte(ticks >> 32) + // mask off 7 bits; we only want 33 total, not 40. + p[0] &= 0b00000001 + p[1] = byte(ticks >> 24) + p[2] = byte(ticks >> 16) + p[3] = byte(ticks >> 8) + p[4] = byte(ticks) + 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 +} diff --git a/internal/scte35/pts_test.go b/internal/scte35/pts_test.go @@ -0,0 +1,17 @@ +package scte35 + +import ( + "testing" +) + +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) + } + } +} diff --git a/internal/scte35/scte35.go b/internal/scte35/scte35.go @@ -19,13 +19,9 @@ package scte35 import ( - "encoding/base64" "encoding/hex" "errors" - "io" - "log" "math" - "strings" "time" "github.com/bamiaux/iobit" @@ -53,6 +49,7 @@ var ( ErrUnsupportedEncoding = errors.New("invalid or unsupported encoding") ) +/* // Logger for emitting debug messages. var Logger = log.New(io.Discard, "SCTE35 ", log.Ldate|log.Ltime|log.Llongfile) @@ -83,6 +80,7 @@ func DecodeHex(s string) (*SpliceInfoSection, error) { err = sis.Decode(b) return sis, err } +*/ // DurationToTicks converts a duration to 90MhZ ticks. func DurationToTicks(d time.Duration) uint64 { @@ -95,14 +93,6 @@ func TicksToDuration(ticks uint64) time.Duration { return time.Duration(int64(s * float64(time.Second))) } -// BreakDuration specifies the duration of the commercial break(s). It may be -// used to give the splicer an indication of when the break will be over and -// when the network In Point will occur. -type BreakDuration struct { - AutoReturn bool - Duration uint64 -} - // Bytes is a byte array. type Bytes []byte diff --git a/internal/scte35/scte35_test.go b/internal/scte35/scte35_test.go @@ -18,10 +18,10 @@ package scte35 import ( "encoding/binary" - "errors" "testing" ) +/* func TestDecodeBase64(t *testing.T) { // when adding tests that contain multiple splice descriptors, care must be // taken to ensure they are in the order specified in the custom UnmarshalXML @@ -35,9 +35,12 @@ func TestDecodeBase64(t *testing.T) { binary: "/DA0AAAAAAAA///wBQb+cr0AUAAeAhxDVUVJSAAAjn/PAAGlmbAICAAAAAAsoKGKNAIAmsnRfg==", expected: SpliceInfoSection{ EncryptedPacket: EncryptedPacket{EncryptionAlgorithm: EncryptionAlgorithmNone, CWIndex: 255}, - SpliceCommand: &TimeSignal{ - SpliceTime: SpliceTime{ - PTSTime: uint64ptr(0x072bd0050), + SpliceCommand: &Command{ + Type: CommandTimeSignal, + TimeSignal: &TimeSignal{ + SpliceTime: SpliceTime{ + PTSTime: uint64ptr(0x072bd0050), + }, }, }, SpliceDescriptors: []SpliceDescriptor{ @@ -64,16 +67,19 @@ func TestDecodeBase64(t *testing.T) { binary: "/DAvAAAAAAAA///wFAVIAACPf+/+c2nALv4AUsz1AAAAAAAKAAhDVUVJAAABNWLbowo=", expected: SpliceInfoSection{ EncryptedPacket: EncryptedPacket{EncryptionAlgorithm: EncryptionAlgorithmNone, CWIndex: 255}, - SpliceCommand: &SpliceInsert{ - BreakDuration: &BreakDuration{ - AutoReturn: true, - Duration: uint64(0x00052ccf5), - }, - SpliceEventID: uint32(0x4800008f), - OutOfNetworkIndicator: true, - Program: &SpliceInsertProgram{ - SpliceTime: SpliceTime{ - PTSTime: uint64ptr(0x07369c02e), + SpliceCommand: &Command{ + Type: CommandSpliceInsert, + Insert: &SpliceInsert{ + BreakDuration: &BreakDuration{ + AutoReturn: true, + Duration: uint64(0x00052ccf5), + }, + SpliceEventID: uint32(0x4800008f), + OutOfNetworkIndicator: true, + Program: &SpliceInsertProgram{ + SpliceTime: SpliceTime{ + PTSTime: uint64ptr(0x07369c02e), + }, }, }, }, @@ -476,7 +482,7 @@ func TestDecodeBase64(t *testing.T) { "Splice Null - Heartbeat": { binary: "/DARAAAAAAAAAP/wAAAAAHpPv/8=", expected: SpliceInfoSection{ - SpliceCommand: &SpliceNull{}, + SpliceCommand: nil, Tier: 4095, SAPType: 3, }, @@ -729,6 +735,7 @@ func TestEncodeWithAlignmentStuffing(t *testing.T) { }) } } +*/ func TestTicksToDuration(t *testing.T) { // test a wide range of tick values diff --git a/internal/scte35/segmentation_descriptor.go b/internal/scte35/segmentation_descriptor.go @@ -1,542 +0,0 @@ -// Copyright 2021 Comcast Cable Communications Management, LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// SPDX-License-Identifier: Apache-2.0 - -package scte35 - -import ( - "fmt" - - "github.com/bamiaux/iobit" -) - -const ( - // SegmentationDescriptorTag is the splice_descriptor_tag for - // segmentation_descriptor - SegmentationDescriptorTag = 0x02 - - // SegmentationTypeNotIndicated is the segmentation_type_id for Not Indicated. - SegmentationTypeNotIndicated = 0x00 - // SegmentationTypeContentIdentification is the segmentation_type_id for - // Content Identification. - SegmentationTypeContentIdentification = 0x01 - // SegmentationTypeProgramStart is the segmentation_type_id for Program Start. - SegmentationTypeProgramStart = 0x10 - // SegmentationTypeProgramEnd is the segmentation_type_id for Program End. - SegmentationTypeProgramEnd = 0x11 - // SegmentationTypeProgramEarlyTermination is the segmentation_type_id for - // Program Early Termination. - SegmentationTypeProgramEarlyTermination = 0x12 - // SegmentationTypeProgramBreakaway is the segmentation_type_id for - // Program Breakaway. - SegmentationTypeProgramBreakaway = 0x13 - // SegmentationTypeProgramResumption is the segmentation_type_id for Program - // Resumption. - SegmentationTypeProgramResumption = 0x14 - // SegmentationTypeProgramRunoverPlanned is the segmentation_type_id for - // Program Runover Planned. - SegmentationTypeProgramRunoverPlanned = 0x15 - // SegmentationTypeProgramRunoverUnplanned is the segmentation_type_id for - // Program Runover Unplanned. - SegmentationTypeProgramRunoverUnplanned = 0x16 - // SegmentationTypeProgramOverlapStart is the segmentation_type_id for Program - // Overlap Start. - SegmentationTypeProgramOverlapStart = 0x17 - // SegmentationTypeProgramBlackoutOverride is the segmentation_type_id for - // Program Blackout Override. - SegmentationTypeProgramBlackoutOverride = 0x18 - // SegmentationTypeProgramStartInProgress is the segmentation_type_id for - // Program Start - In Progress. - SegmentationTypeProgramStartInProgress = 0x19 - // SegmentationTypeChapterStart is the segmentation_type_id for Chapter Start. - SegmentationTypeChapterStart = 0x20 - // SegmentationTypeChapterEnd is the segmentation_type_id for Chapter End. - SegmentationTypeChapterEnd = 0x21 - // SegmentationTypeBreakStart is the segmentation_type_id for Break Start. - // Added in ANSI/SCTE 2017. - SegmentationTypeBreakStart = 0x22 - // SegmentationTypeBreakEnd is the segmentation_type_id for Break End. - // Added in ANSI/SCTE 2017. - SegmentationTypeBreakEnd = 0x23 - // SegmentationTypeOpeningCreditStart is the segmentation_type_id for - // Opening Credit Start. Added in ANSI/SCTE 2020. - SegmentationTypeOpeningCreditStart = 0x24 - // SegmentationTypeOpeningCreditEnd is the segmentation_type_id for - // Opening Credit End. Added in ANSI/SCTE 2020. - SegmentationTypeOpeningCreditEnd = 0x25 - // SegmentationTypeClosingCreditStart is the segmentation_type_id for - // Closing Credit Start. Added in ANSI/SCTE 2020. - SegmentationTypeClosingCreditStart = 0x26 - // SegmentationTypeClosingCreditEnd is the segmentation_type_id for - // Closing Credit End. Added in ANSI/SCTE 2020. - SegmentationTypeClosingCreditEnd = 0x27 - // SegmentationTypeProviderAdStart is the segmentation_type_id for Provider - // Ad Start. - SegmentationTypeProviderAdStart = 0x30 - // SegmentationTypeProviderAdEnd is the segmentation_type_id for Provider Ad - // End. - SegmentationTypeProviderAdEnd = 0x31 - // SegmentationTypeDistributorAdStart is the segmentation_type_id for - // Distributor Ad Start. - SegmentationTypeDistributorAdStart = 0x32 - // SegmentationTypeDistributorAdEnd is the segmentation_type_id for - // Distributor Ad End. - SegmentationTypeDistributorAdEnd = 0x33 - // SegmentationTypeProviderPOStart is the segmentation_type_id for Provider - // PO Start. - SegmentationTypeProviderPOStart = 0x34 - // SegmentationTypeProviderPOEnd is the segmentation_type_id for Provider PO - // End. - SegmentationTypeProviderPOEnd = 0x35 - // SegmentationTypeDistributorPOStart is the segmentation_type_id for - // Distributor PO Start. - SegmentationTypeDistributorPOStart = 0x36 - // SegmentationTypeDistributorPOEnd is the segmentation_type_id for - // Distributor PO End. - SegmentationTypeDistributorPOEnd = 0x37 - // SegmentationTypeProviderOverlayPOStart is the segmentation_type_id for - // Provider Overlay Placement Opportunity Start. - SegmentationTypeProviderOverlayPOStart = 0x38 - // SegmentationTypeProviderOverlayPOEnd is the segmentation_type_id for - // Provider Overlay Placement Opportunity End. - SegmentationTypeProviderOverlayPOEnd = 0x39 - // SegmentationTypeDistributorOverlayPOStart is the segmentation_type_id for - // Distributor Overlay Placement Opportunity Start. - SegmentationTypeDistributorOverlayPOStart = 0x3a - // SegmentationTypeDistributorOverlayPOEnd is the segmentation_type_id for - // Distributor Overlay Placement Opportunity End. - SegmentationTypeDistributorOverlayPOEnd = 0x3b - // SegmentationTypeProviderPromoStart is the segmentation_type_id for - // Provider Promo Start. Added in ANSI/SCTE 2020. - SegmentationTypeProviderPromoStart = 0x3c - // SegmentationTypeProviderPromoEnd is the segmentation_type_id for - // Provider Promo End. Added in ANSI/SCTE 2020. - SegmentationTypeProviderPromoEnd = 0x3d - // SegmentationTypeDistributorPromoStart is the segmentation_type_id for - // Distributor Promo Start. Added in ANSI/SCTE 2020. - SegmentationTypeDistributorPromoStart = 0x3e - // SegmentationTypeDistributorPromoEnd is the segmentation_type_id for - // Distributor Promo End. Added in ANSI/SCTE 2020. - SegmentationTypeDistributorPromoEnd = 0x3f - // SegmentationTypeUnscheduledEventStart is the segmentation_type_id for - // Unscheduled Event Start. - SegmentationTypeUnscheduledEventStart = 0x40 - // SegmentationTypeUnscheduledEventEnd is the segmentation_type_id for - // Unscheduled Event End. - SegmentationTypeUnscheduledEventEnd = 0x41 - // SegmentationTypeAltConOppStart is the segmentation_type_id for - // Alternate Content Opportunity Start. Added in ANSI/SCTE 2020. - SegmentationTypeAltConOppStart = 0x42 - // SegmentationTypeAltConOppEnd is the segmentation_type_id for - // Alternate Content Opportunity End. Added in ANSI/SCTE 2020. - SegmentationTypeAltConOppEnd = 0x43 - // SegmentationTypeProviderAdBlockStart is the segmentation_type_id for - // Provider Ad Block Start. Added in ANSI/SCTE 2020. - SegmentationTypeProviderAdBlockStart = 0x44 - // SegmentationTypeProviderAdBlockEnd is the segmentation_type_id for - // Provider Ad Block End. Added in ANSI/SCTE 2020. - SegmentationTypeProviderAdBlockEnd = 0x45 - // SegmentationTypeDistributorAdBlockStart is the segmentation_type_id for - // Distributor Ad Block Start. Added in ANSI/SCTE 2020. - SegmentationTypeDistributorAdBlockStart = 0x46 - // SegmentationTypeDistributorAdBlockEnd is the segmentation_type_id for - // Distributor Ad Block End. Added in ANSI/SCTE 2020. - SegmentationTypeDistributorAdBlockEnd = 0x47 - // SegmentationTypeNetworkStart is the segmentation_type_id for Network Start. - SegmentationTypeNetworkStart = 0x50 - // SegmentationTypeNetworkEnd is the segmentation_type_id for Network End. - SegmentationTypeNetworkEnd = 0x51 -) - -// SegmentationDescriptor is an implementation of a splice_descriptor(). It -// provides an optional extension to the time_signal() and splice_insert() -// commands that allows for segmentation messages to be sent in a time/video -// accurate method. This descriptor shall only be used with the time_signal(), -// splice_insert() and the splice_null() commands. The time_signal() or -// splice_insert() message should be sent at least once a minimum of 4 seconds -// in advance of the signaled splice_time() to permit the insertion device to -// place the splice_info_section( ) accurately. -type SegmentationDescriptor struct { - DeliveryRestrictions *DeliveryRestrictions - SegmentationUPIDs []SegmentationUPID - Components []SegmentationDescriptorComponent - SegmentationEventID uint32 - SegmentationEventCancelIndicator bool - SegmentationDuration *uint64 - SegmentationTypeID uint32 - SegmentNum uint32 - SegmentsExpected uint32 - SubSegmentNum *uint32 - SubSegmentsExpected *uint32 -} - -// Name returns the human-readable string for the segmentation_type_id. -func (sd *SegmentationDescriptor) Name() string { - switch sd.SegmentationTypeID { - case SegmentationTypeNotIndicated: - return "Not Indicated" - case SegmentationTypeContentIdentification: - return "Content Identification" - case SegmentationTypeProgramStart: - return "Program Start" - case SegmentationTypeProgramEnd: - return "Program End" - case SegmentationTypeProgramEarlyTermination: - return "Program Early Termination" - case SegmentationTypeProgramBreakaway: - return "Program Breakaway" - case SegmentationTypeProgramResumption: - return "Program Resumption" - case SegmentationTypeProgramRunoverPlanned: - return "Program Runover Planned" - case SegmentationTypeProgramRunoverUnplanned: - return "Program Runover Unplanned" - case SegmentationTypeProgramOverlapStart: - return "Program Overlap Start" - case SegmentationTypeProgramBlackoutOverride: - return "Program Blackout Override" - case SegmentationTypeProgramStartInProgress: - return "Program Start - In Progress" - case SegmentationTypeChapterStart: - return "Chapter Start" - case SegmentationTypeChapterEnd: - return "Chapter End" - case SegmentationTypeBreakStart: - return "Break Start" - case SegmentationTypeBreakEnd: - return "Break End" - case SegmentationTypeOpeningCreditStart: - return "Opening Credit Start" - case SegmentationTypeOpeningCreditEnd: - return "Opening Credit End" - case SegmentationTypeClosingCreditStart: - return "Closing Credit Start" - case SegmentationTypeClosingCreditEnd: - return "Closing Credit End" - case SegmentationTypeProviderAdStart: - return "Provider Advertisement Start" - case SegmentationTypeProviderAdEnd: - return "Provider Advertisement End" - case SegmentationTypeDistributorAdStart: - return "Distributor Advertisement Start" - case SegmentationTypeDistributorAdEnd: - return "Distributor Advertisement End" - case SegmentationTypeProviderPOStart: - return "Provider Placement Opportunity Start" - case SegmentationTypeProviderPOEnd: - return "Provider Placement Opportunity End" - case SegmentationTypeDistributorPOStart: - return "Distributor Placement Opportunity Start" - case SegmentationTypeDistributorPOEnd: - return "Distributor Placement Opportunity End" - case SegmentationTypeProviderOverlayPOStart: - return "Provider Overlay Placement Opportunity Start" - case SegmentationTypeProviderOverlayPOEnd: - return "Provider Overlay Placement Opportunity End" - case SegmentationTypeDistributorOverlayPOStart: - return "Distributor Overlay Placement Opportunity Start" - case SegmentationTypeDistributorOverlayPOEnd: - return "Distributor Overlay Placement Opportunity End" - case SegmentationTypeProviderPromoStart: - return "Provider Promo Start" - case SegmentationTypeProviderPromoEnd: - return "Provider Promo End" - case SegmentationTypeDistributorPromoStart: - return "Distributor Promo Start" - case SegmentationTypeDistributorPromoEnd: - return "Distributor Promo End" - case SegmentationTypeUnscheduledEventStart: - return "Unscheduled Event Start" - case SegmentationTypeUnscheduledEventEnd: - return "Unscheduled Event End" - case SegmentationTypeAltConOppStart: - return "Alternate Content Opportunity Start" - case SegmentationTypeAltConOppEnd: - return "Alternate Content Opportunity End" - case SegmentationTypeProviderAdBlockStart: - return "Provider Ad Block Start" - case SegmentationTypeProviderAdBlockEnd: - return "Provider Ad Block End" - case SegmentationTypeDistributorAdBlockStart: - return "Distributor Ad Block Start" - case SegmentationTypeDistributorAdBlockEnd: - return "Distributor Ad Block End" - case SegmentationTypeNetworkStart: - return "Network Start" - case SegmentationTypeNetworkEnd: - return "Network End" - default: - return "Unknown" - } -} - -// Tag returns the splice_descriptor_tag. -func (sd *SegmentationDescriptor) Tag() uint32 { return SegmentationDescriptorTag } - -// DeliveryNotRestrictedFlag returns the delivery_not_restricted_flag. -func (sd *SegmentationDescriptor) DeliveryNotRestrictedFlag() bool { - return sd.DeliveryRestrictions == nil -} - -// ProgramSegmentationFlag returns the program_segmentation_flag. -func (sd *SegmentationDescriptor) ProgramSegmentationFlag() bool { - return len(sd.Components) == 0 -} - -// SegmentationDurationFlag returns the segmentation_duration_flag. -func (sd *SegmentationDescriptor) SegmentationDurationFlag() bool { - return sd.SegmentationDuration != nil -} - -// SegmentationUpidLength return the segmentation_upid_length -func (sd *SegmentationDescriptor) SegmentationUpidLength() int { - length := 0 - if len(sd.SegmentationUPIDs) == 1 { - length += len(sd.SegmentationUPIDs[0].valueBytes()) * 8 // segmentation_upid() (bytes -> bits) - } else if len(sd.SegmentationUPIDs) > 1 { - // for MID, include type & length with each contained upid - for _, upid := range sd.SegmentationUPIDs { - length += 8 // segmentation_upid_type - length += 8 // segmentation_upid_length - length += len(upid.valueBytes()) * 8 // segmentation_upid (bytes -> bits) - } - } - return length / 8 -} - -// decode updates this splice_descriptor from binary. -func (sd *SegmentationDescriptor) decode(b []byte) error { - var err error - - r := iobit.NewReader(b) - r.Skip(8) // splice_descriptor_tag - r.Skip(8) // descriptor_length - r.Skip(32) // identifier - sd.SegmentationEventID = r.Uint32(32) - sd.SegmentationEventCancelIndicator = r.Bit() - r.Skip(7) // reserved - - if !sd.SegmentationEventCancelIndicator { - programSegmentationFlag := r.Bit() - segmentationDurationFlag := r.Bit() - deliveryNotRestrictedFlag := r.Bit() - - if !deliveryNotRestrictedFlag { - sd.DeliveryRestrictions = &DeliveryRestrictions{} - sd.DeliveryRestrictions.WebDeliveryAllowedFlag = r.Bit() - sd.DeliveryRestrictions.NoRegionalBlackoutFlag = r.Bit() - sd.DeliveryRestrictions.ArchiveAllowedFlag = r.Bit() - sd.DeliveryRestrictions.DeviceRestrictions = r.Uint32(2) - } else { - r.Skip(5) // reserved - } - - if !programSegmentationFlag { - componentCount := int(r.Uint32(8)) - sd.Components = make([]SegmentationDescriptorComponent, componentCount) - for i := 0; i < componentCount; i++ { - c := SegmentationDescriptorComponent{} - c.Tag = r.Uint32(8) - r.Skip(7) // reserved - c.PTSOffset = r.Uint64(33) - sd.Components[i] = c - } - } - - if segmentationDurationFlag { - dur := r.Uint64(40) - sd.SegmentationDuration = &dur - } - - segmentationUpidType := r.Uint32(8) - segmentationUpidLength := int(r.Uint32(8)) - if segmentationUpidLength > 0 { - segmentationUpidValue := r.Bytes(segmentationUpidLength) - - if segmentationUpidType == SegmentationUPIDTypeMID { - upidr := iobit.NewReader(segmentationUpidValue) - sd.SegmentationUPIDs = []SegmentationUPID{} - for upidr.LeftBits() > 0 { - upidType := upidr.Uint32(8) - upidLength := int(upidr.Uint32(8)) - upidValue := upidr.Bytes(upidLength) - if len(upidValue) < upidLength { - Logger.Printf("Cannot read value for segmentation_upid_type %d; %d of %d bytes remaining.", upidType, len(upidValue), upidLength) - } - sd.SegmentationUPIDs = append( - sd.SegmentationUPIDs, - NewSegmentationUPID(upidType, upidValue), - ) - } - } else { - sd.SegmentationUPIDs = []SegmentationUPID{ - NewSegmentationUPID(segmentationUpidType, segmentationUpidValue), - } - } - } - - sd.SegmentationTypeID = r.Uint32(8) - sd.SegmentNum = r.Uint32(8) - sd.SegmentsExpected = r.Uint32(8) - - // these fields are new in 2016 so we need a secondary check whether - // they were actually included in the binary payload - if sd.SegmentationTypeID == SegmentationTypeProviderPOStart || sd.SegmentationTypeID == SegmentationTypeDistributorPOStart { - if r.LeftBits() == 16 { - n := r.Uint32(8) - e := r.Uint32(8) - sd.SubSegmentNum = &n - sd.SubSegmentsExpected = &e - } - } - } - - if err != nil { - return err - } - if err := readerError(r); err != nil { - return fmt.Errorf("segmentation_descriptor: %w", err) - } - return nil -} - -// encode this splice_descriptor to binary. -func (sd *SegmentationDescriptor) encode() ([]byte, error) { - length := sd.length() - - // add 2 bytes to contain splice_descriptor_tag & descriptor_length - buf := make([]byte, length+2) - iow := iobit.NewWriter(buf) - iow.PutUint32(8, SegmentationDescriptorTag) - iow.PutUint32(8, uint32(length)) - iow.PutUint32(32, CUEIdentifier) - iow.PutUint32(32, sd.SegmentationEventID) - iow.PutBit(sd.SegmentationEventCancelIndicator) - iow.PutUint32(7, Reserved) - - if !sd.SegmentationEventCancelIndicator { - iow.PutBit(sd.ProgramSegmentationFlag()) - iow.PutBit(sd.SegmentationDurationFlag()) - - iow.PutBit(sd.DeliveryNotRestrictedFlag()) - if sd.DeliveryRestrictions != nil { - iow.PutBit(sd.DeliveryRestrictions.WebDeliveryAllowedFlag) - iow.PutBit(sd.DeliveryRestrictions.NoRegionalBlackoutFlag) - iow.PutBit(sd.DeliveryRestrictions.ArchiveAllowedFlag) - iow.PutUint32(2, sd.DeliveryRestrictions.DeviceRestrictions) - } else { - iow.PutUint32(5, Reserved) - } - - if !sd.ProgramSegmentationFlag() { - iow.PutUint32(8, uint32(len(sd.Components))) - for _, c := range sd.Components { - iow.PutUint32(8, c.Tag) - iow.PutUint32(7, Reserved) - iow.PutUint64(33, c.PTSOffset) - } - } - - if sd.SegmentationDurationFlag() { - iow.PutUint64(40, *sd.SegmentationDuration) - } - - switch len(sd.SegmentationUPIDs) { - case 0: - iow.PutUint32(8, 0x00) // segmentation_upid_type - iow.PutUint32(8, 0x00) // segmentation_upid_length - case 1: - vb := sd.SegmentationUPIDs[0].valueBytes() - iow.PutUint32(8, sd.SegmentationUPIDs[0].Type) - iow.PutUint32(8, uint32(len(vb))) - _, _ = iow.Write(vb) - default: - iow.PutUint32(8, SegmentationUPIDTypeMID) - iow.PutUint32(8, uint32(sd.SegmentationUpidLength())) - for _, upid := range sd.SegmentationUPIDs { - vb := upid.valueBytes() - iow.PutUint32(8, upid.Type) - iow.PutUint32(8, uint32(len(vb))) - _, _ = iow.Write(vb) - } - } - - iow.PutUint32(8, sd.SegmentationTypeID) - iow.PutUint32(8, sd.SegmentNum) - iow.PutUint32(8, sd.SegmentsExpected) - - if sd.SubSegmentNum != nil { - iow.PutUint32(8, *sd.SubSegmentNum) - } - if sd.SubSegmentsExpected != nil { - iow.PutUint32(8, *sd.SubSegmentsExpected) - } - } - - return buf, iow.Flush() -} - -// descriptorLength returns the descriptor_length -func (sd *SegmentationDescriptor) length() int { - length := 32 // identifier - length += 32 // segmentation_event_id - length++ // segmentation_event_cancel_indicator - length += 7 // reserved - - // if segmentation_event_cancel_indicator == 0 - if !sd.SegmentationEventCancelIndicator { - length++ // program_segmentation_flag - length++ // segmentation_duration_flag - length++ // delivery_not_restricted_flag - length += 5 // delivery restriction flags or reserved - - // if program_segmentation_flag == 0 - if !sd.ProgramSegmentationFlag() { - length += 8 // component_count - - // for i=0 to component_count - for range sd.Components { - length += 8 // component_tag - length += 7 // reserved - length += 33 // pts_offset - } - } - if sd.SegmentationDurationFlag() { - length += 40 // segmentation_duration - } - length += 8 // segmentation_upid_type - length += 8 // segmentation_upid_length - length += sd.SegmentationUpidLength() * 8 // segmentation_upid() (bytes -> bits) - length += 8 // segmentation_type_id - length += 8 // segment_num - length += 8 // segments_expected - - if sd.SubSegmentNum != nil { - length += 8 // sub_segment_num - } - if sd.SubSegmentsExpected != nil { - length += 8 // sub_segments_expected - } - } - - return length / 8 -} - -// SegmentationDescriptorComponent describes the Component element contained -// within the SegmentationDescriptorType XML schema definition. -type SegmentationDescriptorComponent struct { - Tag uint32 - PTSOffset uint64 -} diff --git a/internal/scte35/segmentation_upid.go b/internal/scte35/segmentation_upid.go @@ -1,302 +0,0 @@ -// Copyright 2021 Comcast Cable Communications Management, LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// SPDX-License-Identifier: Apache-2.0 - -package scte35 - -import ( - "bytes" - "encoding/base64" - "encoding/binary" - "encoding/hex" - "fmt" - "strconv" - "strings" - - "github.com/bamiaux/iobit" - "golang.org/x/text/encoding/charmap" -) - -const ( - // SegmentationUPIDTypeNotUsed is the segmentation_upid_type for Not Used. - SegmentationUPIDTypeNotUsed = 0x00 - // SegmentationUPIDTypeUserDefined is the segmentation_upid_type for User - // Defined. - SegmentationUPIDTypeUserDefined = 0x01 - // SegmentationUPIDTypeISCI is the segmentation_upid_type for ISCI - SegmentationUPIDTypeISCI = 0x02 - // SegmentationUPIDTypeAdID is the segmentation_upid_type for Ad-ID - SegmentationUPIDTypeAdID = 0x03 - // SegmentationUPIDTypeUMID is the segmentation_upid_type for UMID - SegmentationUPIDTypeUMID = 0x04 - // SegmentationUPIDTypeISANDeprecated is the segmentation_upid_type for - // ISAN Deprecated. - SegmentationUPIDTypeISANDeprecated = 0x05 - // SegmentationUPIDTypeISAN is the segmentation_upid_type for ISAN. - SegmentationUPIDTypeISAN = 0x06 - // SegmentationUPIDTypeTID is the segmentation_upid_type for TID. - SegmentationUPIDTypeTID = 0x07 - // SegmentationUPIDTypeTI is the segmentation_upid_type for TI. - SegmentationUPIDTypeTI = 0x08 - // SegmentationUPIDTypeADI is the segmentation_upid_type for ADI. - SegmentationUPIDTypeADI = 0x09 - // SegmentationUPIDTypeEIDR is the segmentation_upid_type for EIDR. - SegmentationUPIDTypeEIDR = 0x0a - // SegmentationUPIDTypeATSC is the segmentation_upid_type for ATSC Content - // Identifier. - SegmentationUPIDTypeATSC = 0x0b - // SegmentationUPIDTypeMPU is the segmentation_upid_type for MPU(). - SegmentationUPIDTypeMPU = 0x0c - // SegmentationUPIDTypeMID is the segmentation_upid_type for MID(). - SegmentationUPIDTypeMID = 0x0d - // SegmentationUPIDTypeADS is the segmentation_upid_type for ADS Information. - SegmentationUPIDTypeADS = 0x0e - // SegmentationUPIDTypeURI is the segmentation_upid_type for URI. - SegmentationUPIDTypeURI = 0x0f - // SegmentationUPIDTypeUUID is the segmentation_upid_type for UUID. - SegmentationUPIDTypeUUID = 0x10 - // SegmentationUPIDTypeSCR is the segmentation_upid_type for SCR. - SegmentationUPIDTypeSCR = 0x11 -) - -// NewSegmentationUPID construct a new SegmentationUPID -func NewSegmentationUPID(upidType uint32, buf []byte) SegmentationUPID { - r := iobit.NewReader(buf) - - switch upidType { - // EIDR - custom - case SegmentationUPIDTypeEIDR: - return SegmentationUPID{ - Type: upidType, - Value: canonicalEIDR(r.LeftBytes()), - } - // ISAN - base64 - case SegmentationUPIDTypeISAN, SegmentationUPIDTypeISANDeprecated: - return SegmentationUPID{ - Type: upidType, - Value: base64.StdEncoding.EncodeToString(r.LeftBytes()), - } - // MPU - custom - case SegmentationUPIDTypeMPU: - fi := r.Uint32(32) - return SegmentationUPID{ - Type: upidType, - FormatIdentifier: &fi, - Value: base64.StdEncoding.EncodeToString(r.LeftBytes()), - } - // TI - unsigned int - case SegmentationUPIDTypeTI: - return SegmentationUPID{ - Type: upidType, - Value: strconv.FormatUint(r.Uint64(r.LeftBits()), 10), - } - // everything else - plain text - default: - // decode troublesome Latin1 characters to their UTF8 equivalents - b, _ := charmap.ISO8859_1.NewDecoder().Bytes(r.LeftBytes()) - return SegmentationUPID{ - Type: upidType, - Value: string(b), - } - } -} - -// SegmentationUPID is used to express a UPID in an XML document. -type SegmentationUPID struct { - Type uint32 - FormatIdentifier *uint32 - Value string - // Deprecated: no longer used and will be removed in a future release - Format string -} - -// Name returns the name for the segmentation_upid_type. -func (upid *SegmentationUPID) Name() string { - switch upid.Type { - case SegmentationUPIDTypeNotUsed: - return "Not Used" - case SegmentationUPIDTypeUserDefined: - return "User Defined" - case SegmentationUPIDTypeISCI: - return "ISCI" - case SegmentationUPIDTypeAdID: - return "Ad-ID" - case SegmentationUPIDTypeUMID: - return "UMID" - case SegmentationUPIDTypeISANDeprecated: - return "ISAN (Deprecated)" - case SegmentationUPIDTypeISAN: - return "ISAN" - case SegmentationUPIDTypeTID: - return "TID" - case SegmentationUPIDTypeTI: - return "TI" - case SegmentationUPIDTypeADI: - return "ADI" - case SegmentationUPIDTypeEIDR: - return "EIDR: " + upid.eidrTypeName() - case SegmentationUPIDTypeATSC: - return "ATSC Content Identifier" - case SegmentationUPIDTypeMPU: - return "MPU()" - case SegmentationUPIDTypeMID: - return "MID()" - case SegmentationUPIDTypeADS: - return "ADS Information" - case SegmentationUPIDTypeURI: - return "URI" - case SegmentationUPIDTypeUUID: - return "UUID" - case SegmentationUPIDTypeSCR: - return "SCR" - default: - return "Unknown" - } -} - -// ASCIIValue returns Value as an ASCII string. Characters outside the printable -// range are represented by a dot ("."). -func (upid *SegmentationUPID) ASCIIValue() string { - b := upid.valueBytes() - rs := make([]byte, len(b)) - for i := range b { - if b[i] > 31 && b[i] < 127 { - rs[i] = b[i] - continue - } - rs[i] = '.' - } - return string(rs) -} - -// compressEIRD returns a compressed EIDR. -func (upid *SegmentationUPID) compressEIDR(s string) []byte { - parts := strings.FieldsFunc(s, func(r rune) bool { - return r == '.' || r == '/' - }) - - if len(parts) != 3 { - Logger.Printf("EIDR string contains too many parts: %s", s) - return []byte(s) - } - - i, err := strconv.Atoi(parts[1]) - if err != nil { - Logger.Printf("Non-canonical EIDR prefix: '%s'", s) - return []byte(s) - } - - b := make([]byte, 12) - iow := iobit.NewWriter(b) - iow.PutUint32(16, uint32(i)) - - h, err := hex.DecodeString(strings.ReplaceAll(parts[2], "-", "")) - if err != nil { - Logger.Printf("Non-canonical EIDR suffix: '%s'", s) - return []byte(s) - } - - _, _ = iow.Write(h) - _ = iow.Flush() - - return b -} - -// eidrTypeName returns the EIDR type name. -func (upid *SegmentationUPID) eidrTypeName() string { - if strings.HasPrefix(upid.Value, "10.5237") { - return "Party ID" - } - if strings.HasPrefix(upid.Value, "10.5238") { - return "User ID" - } - if strings.HasPrefix(upid.Value, "10.5239") { - return "Service ID" - } - if strings.HasPrefix(upid.Value, "10.5240") { - return "Content ID" - } - return "" -} - -// formatIdentifierString returns the format identifier as a string -func (upid *SegmentationUPID) formatIdentifierString() string { - b := make([]byte, 4) - binary.BigEndian.PutUint32(b, *upid.FormatIdentifier) - return string(b) -} - -// valueBytes returns the value as a byte array. -func (upid *SegmentationUPID) valueBytes() []byte { - upid.Value = strings.TrimSpace(upid.Value) - - // this switch should align with the constructor above - switch upid.Type { - // EIDR - custom - case SegmentationUPIDTypeEIDR: - return upid.compressEIDR(upid.Value) - // ISAN - base64 - case SegmentationUPIDTypeISAN, SegmentationUPIDTypeISANDeprecated: - b, err := base64.StdEncoding.DecodeString(upid.Value) - if err != nil { - Logger.Printf("Error parsing UPID value: %s", err) - return b - } - return b - // MPU - custom - case SegmentationUPIDTypeMPU: - b := make([]byte, 4) - binary.BigEndian.PutUint32(b, *upid.FormatIdentifier) - v, err := base64.StdEncoding.DecodeString(upid.Value) - if err != nil { - Logger.Printf("Error parsing UPID value: %s", err) - return b - } - b = append(b, v...) - return b - // TI - unsigned int - case SegmentationUPIDTypeTI: - b := make([]byte, 8) - i, err := strconv.ParseUint(strings.TrimSpace(upid.Value), 10, 64) - if err != nil { - Logger.Printf("Error parsing UPID value: %s", err) - return b - } - binary.BigEndian.PutUint64(b, i) - return b - // everything else - plain text - default: - // encode UTF8 values as Latin1 (reversing the Decode above) - b, _ := charmap.ISO8859_1.NewEncoder().Bytes([]byte(upid.Value)) - return b - } -} - -// canonicalEIDR returns a canonical EIDR. -func canonicalEIDR(b []byte) string { - // already canonical - if bytes.Contains(b, []byte("/")) { - return string(b) - } - - // dunno what this is - if len(b) != 12 { - Logger.Printf("Unexpected eidr value received: %s", b) - return "" - } - - i := int(binary.BigEndian.Uint16(b[:2])) - return fmt.Sprintf("10.%d/%X-%X-%X-%X-%X", i, b[2:4], b[4:6], b[6:8], b[8:10], b[10:12]) -} diff --git a/internal/scte35/segmentation_upid_test.go b/internal/scte35/segmentation_upid_test.go @@ -1,11 +0,0 @@ -package scte35 - -import "testing" - -func TestSegmentationUPID_ASCIIValue(t *testing.T) { - upid := SegmentationUPID{Type: 0x09, Value: "SIGNAL:z1sFOMjCnV4AAAAAAAABAQ=="} - want := "SIGNAL:z1sFOMjCnV4AAAAAAAABAQ==" - if want != upid.ASCIIValue() { - t.Errorf("want %s, got %s", want, upid.ASCIIValue()) - } -} diff --git a/internal/scte35/splice_command.go b/internal/scte35/splice_command.go @@ -1,53 +0,0 @@ -// Copyright 2021 Comcast Cable Communications Management, LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// SPDX-License-Identifier: Apache-2.0 - -package scte35 - -// NewSpliceCommand returns the splice command appropriate for the given type. -func NewSpliceCommand(spliceCommandType uint32) SpliceCommand { - switch spliceCommandType { - case SpliceNullType: - return &SpliceNull{} - case SpliceScheduleType: - return &SpliceSchedule{} - case SpliceInsertType: - return &SpliceInsert{} - case TimeSignalType: - return &TimeSignal{} - case BandwidthReservationType: - return &BandwidthReservation{} - default: - return &PrivateCommand{} - } -} - -// SpliceCommand is an interface for splice_command. -type SpliceCommand interface { - Type() uint32 - decode(b []byte) error - encode() ([]byte, error) - length() int -} - -// decodeSpliceCommand decodes the supplied byte array into the desired -// splice_command_type. -func decodeSpliceCommand(spliceCommandType uint32, b []byte) (SpliceCommand, error) { - cmd := NewSpliceCommand(spliceCommandType) - if err := cmd.decode(b); err != nil { - return cmd, err - } - return cmd, nil -} diff --git a/internal/scte35/splice_descriptor.go b/internal/scte35/splice_descriptor.go @@ -16,7 +16,7 @@ package scte35 -import "github.com/bamiaux/iobit" +/* // CUEIdentifier is 32-bit number used to identify the owner of the // descriptor. The identifier shall have a value of 0x43554549 (ASCII “CUEI”). @@ -91,3 +91,5 @@ func decodeSpliceDescriptors(b []byte) ([]SpliceDescriptor, error) { return sds, nil } + +*/ +\ No newline at end of file diff --git a/internal/scte35/splice_info.go b/internal/scte35/splice_info.go @@ -0,0 +1,149 @@ +package scte35 + +import ( + "encoding/binary" + "fmt" +) + +// SAPType represents the two-bit field used to indicate that a Stream +// Access Point (SAP) in the stream +// as specified in SCTE 35 section 9.6.1. +type SAPType uint8 + +const ( + SAPClosedGOP SAPType = iota + SAPClosedGOPLeading + SAPOpenGOP + SAPNone +) + +func (t SAPType) String() string { + switch t { + case SAPClosedGOP: + return "SAP Type 1 (closed GOP)" + case SAPClosedGOPLeading: + return "SAP Type 2 (closed GOP with leading pictures)" + case SAPOpenGOP: + return "SAP Type 3 (open GOP)" + } + return "none" +} + +type SpliceInfo struct { + SAPType SAPType + Encrypted bool + EncryptionAlgorithm Cipher + // Holds a 33-bit unsigned integer representing the number of ticks of a 90KHz clock. + PTSAdjustment uint64 + CWIndex uint8 + // Holds a 12-bit field representing an authorization tier. + Tier uint16 + Command *Command + Descriptors []SpliceDescriptor + CRC32 uint32 +} + +// fields of Splice Info Section which MUST have their values set... +// as specified in SCTE 35 section 9.6.1. +const ( + tableID uint8 = 0xfc + protocolVersion = 0x0 + sectionSyntax = false + privateIndicator = false +) + +// maximum 12-bit uint (2^12 - 1) +const maxTier uint16 = 0xfff + +func encodeSpliceInfo(sis *SpliceInfo) ([]byte, error) { + buf := make([]byte, 3) + buf[0] = byte(tableID) + // next 2 bits (section_syntax_indicator, private_indicator) must be 0. + // 0b00000000 + switch sis.SAPType { + case SAPClosedGOP: + // nothing to do + case SAPClosedGOPLeading: + buf[0] |= (1 << 2) + case SAPOpenGOP: + buf[0] |= (1 << 3) + case SAPNone: + buf[0] |= 0b00001100 + default: + return nil, fmt.Errorf("invalid SAP type %x", sis.SAPType) + } + // length, buf[1,2] set at the end + buf[3] = protocolVersion + + buf = append(buf, 0x00) + if sis.Encrypted { + buf[4] |= (1 << 7) + } + if sis.EncryptionAlgorithm > maxCipher { + return nil, fmt.Errorf("encryption algorithm %d larger than max value %d", sis.EncryptionAlgorithm, maxCipher) + } + // pack 6-bit cipher into next 6. Keep 1 bit for PTSAdjustment. + buf[4] |= byte(sis.EncryptionAlgorithm) << 1 + pts := toPTS(sis.PTSAdjustment) + buf[4] |= pts[0] + buf = append(buf, pts[1:]...) + + buf = append(buf, byte(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 + if sis.Command == nil { + return nil, fmt.Errorf("nil command") + } + cmd, err := encodeCommand(sis.Command) + if err != nil { + return nil, fmt.Errorf("encode splice command: %w", err) + } + length := uint16(len(cmd)) + // stuff remaining 4 bits into the last byte. + buf[len(buf)-1] |= byte(length >> 8) + buf = append(buf, byte(length)) + buf = append(buf, cmd...) + + var buf1 []byte + for _, desc := range sis.Descriptors { + buf1 = append(buf1, encodeSpliceDescriptor(&desc)...) + } + b := make([]byte, 2) + binary.LittleEndian.PutUint16(b, uint16(len(buf1))) + buf = append(buf, b...) + + return buf, nil +} + +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 +} + +const DescriptorIDCUEI = 0x43554549 // "CUEI" in ASCII + +type SpliceDescriptor struct { + Tag uint8 + // For private descriptors, this value must not be DescriptorIDCUEI. + ID uint32 + Data []byte +} + +func encodeSpliceDescriptor(sd *SpliceDescriptor) []byte { + var buf []byte + 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) + buf = append(buf, ibuf...) + return append(buf, sd.Data...) +} diff --git a/internal/scte35/splice_info_section.go b/internal/scte35/splice_info_section.go @@ -16,11 +16,10 @@ package scte35 +/* import ( "encoding/base64" "encoding/hex" - "encoding/json" - "errors" "fmt" "time" @@ -56,7 +55,7 @@ const ( // one (per the requirements of section syntax usage per [MPEG Systems]). type SpliceInfoSection struct { EncryptedPacket EncryptedPacket - SpliceCommand SpliceCommand + SpliceCommand *Command SpliceDescriptors SpliceDescriptors SAPType uint32 PreRollMilliSeconds uint32 // no corresponding binary field @@ -95,16 +94,16 @@ func (sis *SpliceInfoSection) Decode(b []byte) (err error) { sis.Tier = r.Uint32(12) spliceCommandLength := int(r.Uint32(12)) // in bytes - spliceCommandType := r.Uint32(8) + //spliceCommandType := r.Uint32(8) switch spliceCommandLength { case 0xFFF: // legacy signal, decode and skip (buffer underflow expected here) - r2 := r.Peek() - sis.SpliceCommand, err = decodeSpliceCommand(spliceCommandType, r2.LeftBytes()) - if err != nil && !errors.Is(err, ErrBufferUnderflow) { - return err - } - r.Skip(uint(sis.SpliceCommand.length() * 8)) + //r2 := r.Peek() + sis.SpliceCommand, err = decodeSpliceCommand(spliceCommandType, r2.LeftBytes()) + if err != nil && !errors.Is(err, ErrBufferUnderflow) { + return err + } + //r.Skip(uint(sis.SpliceCommand.length() * 8)) default: // standard signal, decode as usual sis.SpliceCommand, err = decodeSpliceCommand(spliceCommandType, r.Bytes(spliceCommandLength)) @@ -147,11 +146,16 @@ func (sis *SpliceInfoSection) Decode(b []byte) (err error) { // Duration attempts to return the duration of the signal. func (sis *SpliceInfoSection) Duration() time.Duration { // if this is a splice insert with a duration, use it - if sc, ok := sis.SpliceCommand.(*SpliceInsert); ok { - if sc.BreakDuration != nil { - return TicksToDuration(sc.BreakDuration.Duration) - } + if sis.SpliceCommand == nil { + return 0 } + if sis.SpliceCommand == CommandSpliceInsert { + + if sc, ok := sis.SpliceCommand.(*SpliceInsert); ok { + if sc.BreakDuration != nil { + return TicksToDuration(sc.BreakDuration.Duration) + } + } ticks := uint64(0) for _, sd := range sis.SpliceDescriptors { @@ -184,17 +188,14 @@ func (sis *SpliceInfoSection) Encode() ([]byte, error) { iow.PutUint32(12, sis.Tier) if sis.SpliceCommand != nil { - iow.PutUint32(12, uint32(sis.SpliceCommand.length())) - iow.PutUint32(8, sis.SpliceCommand.Type()) - sc, err := sis.SpliceCommand.encode() + b, err := encodeCommand(sis.SpliceCommand) if err != nil { - return buf, err + return buf, fmt.Errorf("encode command: %w", err) } - if _, err = iow.Write(sc); err != nil { - return buf, err + for _, bb := range b { + iow.PutByte(bb) } } - iow.PutUint32(16, uint32(sis.descriptorLoopLength())) for _, sd := range sis.SpliceDescriptors { sde, err := sd.encode() @@ -258,7 +259,7 @@ func (sis *SpliceInfoSection) sectionLength() int { length += 12 // splice_command_length length += 8 // splice_command_type if sis.SpliceCommand != nil { - length += sis.SpliceCommand.length() * 8 // bytes -> bits + // length += sis.SpliceCommand.length() * 8 // bytes -> bits } length += 16 // descriptor_loop_length (bytes remaining value) length += sis.descriptorLoopLength() * 8 // bytes -> bits @@ -285,65 +286,4 @@ func (sis *SpliceInfoSection) descriptorLoopLength() int { return length / 8 } -// iSIS is an internal SpliceInfoSection used to support (un)marshalling -// polymorphic fields. -type iSIS struct { - EncryptedPacket EncryptedPacket - SpliceCommandRaw json.RawMessage - SpliceNull *SpliceNull - SpliceSchedule *SpliceSchedule - SpliceInsert *SpliceInsert - TimeSignal *TimeSignal - BandwidthReservation *BandwidthReservation - PrivateCommand *PrivateCommand - SpliceDescriptors SpliceDescriptors - SAPType *uint32 - PTSAdjustment uint64 - ProtocolVersion uint32 - Tier uint32 -} - -// SpliceCommand returns the polymorphic splice_command. -func (i *iSIS) SpliceCommand() SpliceCommand { - if i.SpliceNull != nil { - return i.SpliceNull - } - if i.SpliceSchedule != nil { - return i.SpliceSchedule - } - if i.SpliceInsert != nil { - return i.SpliceInsert - } - if i.TimeSignal != nil { - return i.TimeSignal - } - if i.BandwidthReservation != nil { - return i.BandwidthReservation - } - - // no valid splice_command? - if i.SpliceCommandRaw == nil { - return nil - } - - // struct to determine the splice command's type - type sctype struct { - Type uint32 `json:"type"` - } - - // get the type - var st sctype - if err := json.Unmarshal(i.SpliceCommandRaw, &st); err != nil { - Logger.Printf("error unmarshalling splice command type: %s", err) - return nil - } - - // and decode it - sc := NewSpliceCommand(st.Type) - if err := json.Unmarshal(i.SpliceCommandRaw, sc); err != nil { - Logger.Printf("error unmarshalling splice command: %s", err) - return nil - } - - return sc -} +*/ +\ No newline at end of file diff --git a/internal/scte35/splice_info_section_test.go b/internal/scte35/splice_info_section_test.go @@ -1,5 +1,6 @@ package scte35 +/* type sistest struct { name string sis SpliceInfoSection @@ -9,26 +10,16 @@ var tsis SpliceInfoSection = SpliceInfoSection{ Tier: uint32(4095), SAPType: SAPTypeNotSpecified, EncryptedPacket: EncryptedPacket{CWIndex: 255}, - SpliceCommand: &TimeSignal{ - SpliceTime: SpliceTime{PTSTime: uint64ptr(1924989008)}, - }, - SpliceDescriptors: SpliceDescriptors{ - &SegmentationDescriptor{ - DeliveryRestrictions: &DeliveryRestrictions{ - ArchiveAllowedFlag: true, - NoRegionalBlackoutFlag: true, - DeviceRestrictions: 3, - }, - SegmentationUPIDs: []SegmentationUPID{ - { - Type: SegmentationUPIDTypeTI, - Value: "748724618", - }, + SpliceCommand: &Command{ + Type: SpliceSchedule, + Schedule: []Event{ + Event{ + ID: 12345, + Cancel: true, + OutOfNetwork: true, + BreakDuration: &BreakDuration{true, 9876543}, }, - SegmentationEventID: uint32(1207959694), - SegmentationDuration: uint64ptr(27630000), - SegmentationTypeID: SegmentationTypeProviderPOStart, - SegmentNum: 2, }, }, } +*/ diff --git a/internal/scte35/splice_info_test.go b/internal/scte35/splice_info_test.go @@ -0,0 +1,17 @@ +package scte35 + +import ( + "fmt" + "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 { + t.Errorf("want packed tier %d, got %d", want, got) + } +} diff --git a/internal/scte35/splice_insert.go b/internal/scte35/splice_insert.go @@ -1,280 +0,0 @@ -// Copyright 2021 Comcast Cable Communications Management, LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// SPDX-License-Identifier: Apache-2.0 - -package scte35 - -import ( - "fmt" - - "github.com/bamiaux/iobit" -) - -// SpliceInsertType is the splice_command_type for splice_insert() -const SpliceInsertType = 0x05 - -// SpliceInsert is a command shall be sent at least once for every splice -// event. -type SpliceInsert struct { - Program *SpliceInsertProgram - Components []SpliceInsertComponent - BreakDuration *BreakDuration - SpliceEventID uint32 - SpliceEventCancelIndicator bool - SpliceImmediateFlag bool - OutOfNetworkIndicator bool - UniqueProgramID uint32 - AvailNum uint32 - AvailsExpected uint32 -} - -// DurationFlag returns the duration_flag. -func (cmd *SpliceInsert) DurationFlag() bool { - return cmd.BreakDuration != nil -} - -// ProgramSpliceFlag returns the program_splice_flag. -func (cmd *SpliceInsert) ProgramSpliceFlag() bool { - return cmd.Program != nil -} - -// TimeSpecifiedFlag returns the time_specified_flag -func (cmd *SpliceInsert) TimeSpecifiedFlag() bool { - return cmd != nil && cmd.Program != nil && cmd.Program.SpliceTime.TimeSpecifiedFlag() -} - -// Type returns the splice_command_type. -func (cmd *SpliceInsert) Type() uint32 { return SpliceInsertType } - -// decode a binary splice_insert. -func (cmd *SpliceInsert) decode(b []byte) error { - r := iobit.NewReader(b) - - cmd.SpliceEventID = r.Uint32(32) - cmd.SpliceEventCancelIndicator = r.Bit() - r.Skip(7) // reserved - if !cmd.SpliceEventCancelIndicator { - cmd.OutOfNetworkIndicator = r.Bit() - programSpliceFlag := r.Bit() - durationFlag := r.Bit() - cmd.SpliceImmediateFlag = r.Bit() - r.Skip(4) // reserved - if programSpliceFlag { - cmd.Program = &SpliceInsertProgram{} - if !cmd.SpliceImmediateFlag { - timeSpecifiedFlag := r.Bit() - if timeSpecifiedFlag { - r.Skip(6) // reserved - ptsTime := r.Uint64(33) - cmd.Program.SpliceTime.PTSTime = &ptsTime - } else { - r.Skip(7) // reserved - } - } - } else { - componentCount := int(r.Uint32(8)) - cmd.Components = make([]SpliceInsertComponent, componentCount) - for i := 0; i < componentCount; i++ { - c := SpliceInsertComponent{} - c.Tag = r.Uint32(8) - if !cmd.SpliceImmediateFlag { - timeSpecifiedFlag := r.Bit() - if timeSpecifiedFlag { - r.Skip(6) // reserved - ptsTime := r.Uint64(33) - c.SpliceTime = &SpliceTime{ - PTSTime: &ptsTime, - } - } else { - r.Skip(7) // reserved - } - } - cmd.Components[i] = c - } - } - if durationFlag { - cmd.BreakDuration = &BreakDuration{} - cmd.BreakDuration.AutoReturn = r.Bit() - r.Skip(6) // reserved - cmd.BreakDuration.Duration = r.Uint64(33) - } - } - cmd.UniqueProgramID = r.Uint32(16) - cmd.AvailNum = r.Uint32(8) - cmd.AvailsExpected = r.Uint32(8) - - if err := readerError(r); err != nil { - return fmt.Errorf("splice_insert: %w", err) - } - return nil -} - -// encode this splice_insert to binary. -func (cmd *SpliceInsert) encode() ([]byte, error) { - buf := make([]byte, cmd.length()) - - iow := iobit.NewWriter(buf) - iow.PutUint32(32, cmd.SpliceEventID) - iow.PutBit(cmd.SpliceEventCancelIndicator) - iow.PutUint32(7, Reserved) - if !cmd.SpliceEventCancelIndicator { - iow.PutBit(cmd.OutOfNetworkIndicator) - iow.PutBit(cmd.ProgramSpliceFlag()) - iow.PutBit(cmd.DurationFlag()) - iow.PutBit(cmd.SpliceImmediateFlag) - iow.PutUint32(4, Reserved) - if cmd.ProgramSpliceFlag() && !cmd.SpliceImmediateFlag { - if cmd.Program.TimeSpecifiedFlag() { - iow.PutBit(true) - iow.PutUint32(6, Reserved) - iow.PutUint64(33, *cmd.Program.SpliceTime.PTSTime) - } else { - iow.PutBit(false) - iow.PutUint32(7, Reserved) - } - } - if !cmd.ProgramSpliceFlag() { - iow.PutUint32(8, uint32(len(cmd.Components))) - for _, c := range cmd.Components { - iow.PutUint32(8, c.Tag) - if !cmd.SpliceImmediateFlag { - if c.TimeSpecifiedFlag() { - iow.PutBit(true) - iow.PutUint32(6, Reserved) - iow.PutUint64(33, *c.SpliceTime.PTSTime) - } else { - iow.PutBit(false) - iow.PutUint32(7, Reserved) - } - } - } - } - if cmd.DurationFlag() { - iow.PutBit(cmd.BreakDuration.AutoReturn) - iow.PutUint32(6, Reserved) - iow.PutUint64(33, cmd.BreakDuration.Duration) - } - iow.PutUint32(16, cmd.UniqueProgramID) - iow.PutUint32(8, cmd.AvailNum) - iow.PutUint32(8, cmd.AvailsExpected) - } - - return buf, iow.Flush() -} - -// length returns the splice_command_length. -func (cmd SpliceInsert) length() int { - length := 32 // splice_event_id - length++ // splice_event_cancel_indicator - length += 7 // reserved - - // if splice_event_cancel_indicator == 0 - if !cmd.SpliceEventCancelIndicator { - length++ // out_of_network_indicator - length++ // program_splice_flag - length++ // duration_flag - length++ // splice_immediate_flag - length += 4 // reserved - - // if program_splice_flag == 1 && splice_immediate_flag == 0 - if cmd.ProgramSpliceFlag() && !cmd.SpliceImmediateFlag { - length++ // time_specified_flag - - // if time_specified_flag == 1 - if cmd.Program.TimeSpecifiedFlag() { - length += 6 // reserved - length += 33 // pts_time - } else { - length += 7 // reserved - } - } - - // if program_splice_flag == 0 - if !cmd.ProgramSpliceFlag() { - length += 8 // component_count - - // for i = 0 to component_count - for _, c := range cmd.Components { - length += 8 // component_tag - - // if splice_immediate_flag == 0 - if !cmd.SpliceImmediateFlag { - length++ // time_specified_flag - - // if time_specified_flag == 1 - if c.TimeSpecifiedFlag() { - length += 6 // reserved - length += 33 // pts_time - } else { - length += 7 // reserved - } - } - } - } - - // if duration_flag == 1 - if cmd.DurationFlag() { - length++ // auto_return - length += 6 // reserved - length += 33 // duration - } - - length += 16 // unique_program_id - length += 8 // avail_num - length += 8 // avails_expected - } - - return length / 8 -} - -// SpliceInsertComponent contains the Splice Point in Component Splice Mode. -type SpliceInsertComponent struct { - Tag uint32 - SpliceTime *SpliceTime -} - -// TimeSpecifiedFlag returns the time_specified_flag. -func (c *SpliceInsertComponent) TimeSpecifiedFlag() bool { - return c != nil && c.SpliceTime != nil && c.SpliceTime.PTSTime != nil -} - -// NewSpliceInsertProgram returns a SpliceInsertProgram with the given ptsTime. -func NewSpliceInsertProgram(ptsTime uint64) *SpliceInsertProgram { - return &SpliceInsertProgram{ - SpliceTime: SpliceTime{ - PTSTime: &ptsTime, - }, - } -} - -// SpliceInsertProgram contains the Splice Point in Program Splice Mode. -type SpliceInsertProgram struct { - SpliceTime SpliceTime -} - -// TimeSpecifiedFlag returns the time_specified_flag. -func (p *SpliceInsertProgram) TimeSpecifiedFlag() bool { - return p != nil && p.SpliceTime.PTSTime != nil -} - -// SpliceTime specifies the time of the splice event. -type SpliceTime struct { - PTSTime *uint64 -} - -// TimeSpecifiedFlag returns true if PTSTime is not nil. -func (t *SpliceTime) TimeSpecifiedFlag() bool { - return t != nil && t.PTSTime != nil -} diff --git a/internal/scte35/splice_null.go b/internal/scte35/splice_null.go @@ -1,45 +0,0 @@ -// Copyright 2021 Comcast Cable Communications Management, LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// SPDX-License-Identifier: Apache-2.0 - -package scte35 - -import "fmt" - -// SpliceNullType is the splice_command_type for splice_null() -const SpliceNullType uint32 = 0 - -// SpliceNull is the command is provided for extensibility of the standard. The -// splice_null() command allows a splice_info_table to be sent that can carry -// descriptors without having to send one of the other defined commands. This -// command may also be used as a “heartbeat message” for monitoring cue -// injection equipment integrity and link integrity. -type SpliceNull struct{} - -// Type returns the splice_command_type. -func (cmd *SpliceNull) Type() uint32 { return SpliceNullType } - -// decode a binary splice_null. -func (cmd *SpliceNull) decode(b []byte) error { - if len(b) > 0 { - return fmt.Errorf("splice_null: %w", ErrBufferOverflow) - } - return nil -} - -func (cmd *SpliceNull) encode() ([]byte, error) { return []byte{}, nil } - -// commandLength returns the splice_command_length. -func (cmd *SpliceNull) length() int { return 0 } diff --git a/internal/scte35/splice_schedule.go b/internal/scte35/splice_schedule.go @@ -1,200 +0,0 @@ -// Copyright 2021 Comcast Cable Communications Management, LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// SPDX-License-Identifier: Apache-2.0 - -package scte35 - -import ( - "fmt" - - "github.com/bamiaux/iobit" -) - -// SpliceScheduleType is the splice_command_type for the splice_schedule() -// command. -const SpliceScheduleType = 0x04 - -// SpliceSchedule is provided to allow a schedule of splice events to be -// conveyed in advance. -type SpliceSchedule struct { - Events []Event -} - -// Type returns the splice_command_type -func (cmd *SpliceSchedule) Type() uint32 { return SpliceScheduleType } - -// decode a binary splice_schedule. -func (cmd *SpliceSchedule) decode(b []byte) error { - r := iobit.NewReader(b) - - spliceCount := int(r.Uint32(8)) - cmd.Events = make([]Event, spliceCount) - for i := 0; i < spliceCount; i++ { - e := Event{} - e.SpliceEventID = r.Uint32(32) - e.SpliceEventCancelIndicator = r.Bit() - if !e.SpliceEventCancelIndicator { - e.OutOfNetworkIndicator = r.Bit() - programSpliceFlag := r.Bit() - durationFlag := r.Bit() - r.Skip(5) // reserved - if programSpliceFlag { - e.Program = &EventProgram{} - e.Program.UTCSpliceTime = NewUTCSpliceTime(r.Uint32(32)) - } else { - componentCount := int(r.Uint32(8)) - e.Components = make([]EventComponent, componentCount) - for j := 0; j < componentCount; j++ { - c := EventComponent{} - c.Tag = r.Uint32(8) - c.UTCSpliceTime = NewUTCSpliceTime(r.Uint32(32)) - e.Components[j] = c - } - } - if durationFlag { - e.BreakDuration = &BreakDuration{} - e.BreakDuration.AutoReturn = r.Bit() - r.Skip(6) // reserved - e.BreakDuration.Duration = r.Uint64(33) - } - } - e.UniqueProgramID = r.Uint32(16) - e.AvailNum = r.Uint32(8) - e.AvailsExpected = r.Uint32(8) - cmd.Events[i] = e - } - - if err := readerError(r); err != nil { - return fmt.Errorf("splice_schedule: %w", err) - } - return nil -} - -// encode this splice_schedule to binary. -func (cmd *SpliceSchedule) encode() ([]byte, error) { - buf := make([]byte, cmd.length()) - iow := iobit.NewWriter(buf) - - iow.PutUint32(8, uint32(len(cmd.Events))) - for _, e := range cmd.Events { - iow.PutUint32(32, e.SpliceEventID) - iow.PutBit(e.SpliceEventCancelIndicator) - iow.PutUint32(7, Reserved) // reserved - if !e.SpliceEventCancelIndicator { - iow.PutBit(e.OutOfNetworkIndicator) - iow.PutBit(e.ProgramSpliceFlag()) - iow.PutBit(e.DurationFlag()) - iow.PutUint32(5, Reserved) // reserved - if e.ProgramSpliceFlag() { - iow.PutUint32(32, e.Program.UTCSpliceTime.GPSSeconds()) - } else { - iow.PutUint32(8, uint32(len(e.Components))) - for _, c := range e.Components { - iow.PutUint32(8, c.Tag) - iow.PutUint32(32, c.UTCSpliceTime.GPSSeconds()) - } - } - if e.DurationFlag() { - iow.PutBit(e.BreakDuration.AutoReturn) - iow.PutUint32(6, Reserved) - iow.PutUint64(33, e.BreakDuration.Duration) - } - } - iow.PutUint32(16, e.UniqueProgramID) - iow.PutUint32(8, e.AvailNum) - iow.PutUint32(8, e.AvailsExpected) - } - - return buf, iow.Flush() -} - -// commandLength returns the splice_command_length -func (cmd SpliceSchedule) length() int { - length := 8 // splice_count - - // for i = 0 to splice_count - for _, e := range cmd.Events { - length += 32 // splice_event_id - length++ // splice_event_cancel_indicator - length += 7 // reserved - - // if splice_event_cancel_indicator == 0 - if !e.SpliceEventCancelIndicator { - length++ // out_of_network_indicator - length++ // program_splice_flag - length++ // duration_flag - length += 5 // reserved - - if e.ProgramSpliceFlag() { - // program_splice_flag == 1 - length += 32 // utc_splice_time - } else { - // program_splice_flag == 0 - length += 8 // component_count - for range e.Components { - length += 8 // component_tag - length += 32 // utc_splice_time - } - } - - // if duration_flag == 1 - if e.DurationFlag() { - length++ // auto_return - length += 6 // reserved - length += 33 // duration - } - - length += 16 // unique_program_id - length += 8 // avail_num - length += 8 // avails_expected - } - } - - return length / 8 -} - -// Event is a single event within a splice_schedule. -type Event struct { - Program *EventProgram - Components []EventComponent - BreakDuration *BreakDuration - SpliceEventID uint32 - SpliceEventCancelIndicator bool - OutOfNetworkIndicator bool - UniqueProgramID uint32 - AvailNum uint32 - AvailsExpected uint32 -} - -// DurationFlag returns the duration_flag. -func (e *Event) DurationFlag() bool { - return e != nil && e.BreakDuration != nil -} - -// ProgramSpliceFlag returns the program_splice_flag. -func (e *Event) ProgramSpliceFlag() bool { - return e != nil && e.Program != nil -} - -// EventComponent contains the Splice Points in Component Splice Mode. -type EventComponent struct { - Tag uint32 - UTCSpliceTime UTCSpliceTime -} - -// EventProgram contains the Splice Point in Program Splice Mode -type EventProgram struct { - UTCSpliceTime UTCSpliceTime -} diff --git a/internal/scte35/splice_schedule_test.go b/internal/scte35/splice_schedule_test.go @@ -0,0 +1,17 @@ +package scte35 + +import ( + "testing" + "time" +) + +func TestPackEvent(t *testing.T) { + ev := Event{ + ID: 6969, + SpliceTime: time.Now().Add(5 * time.Second), + ProgramID: uint16(500), + AvailNum: 4, + AvailExpected: 2, + } + packEvent(&ev) +} diff --git a/internal/scte35/time_descriptor.go b/internal/scte35/time_descriptor.go @@ -1,77 +0,0 @@ -// Copyright 2021 Comcast Cable Communications Management, LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// SPDX-License-Identifier: Apache-2.0 - -package scte35 - -import ( - "github.com/bamiaux/iobit" -) - -// TimeDescriptorTag is the splice_descriptor_tag for a time_descriptor. -const TimeDescriptorTag = 0x03 - -// TimeDescriptor is an implementation of a splice_descriptor. It provides an -// optional extension to the splice_insert(), splice_null() and time_signal() -// commands that allows a programmer’s wall clock time to be sent to a client. -// For the highest accuracy, this descriptor should be used with the -// time_signal() or splice_insert( ) command. This command may be inserted using -// SCTE 104 or by out of band provisioning on the device inserting this message. -type TimeDescriptor struct { - TAISeconds uint64 - TAINS uint32 - UTCOffset uint32 -} - -// Tag returns the splice_descriptor_tag. -func (sd *TimeDescriptor) Tag() uint32 { return TimeDescriptorTag } - -// decode updates this splice_descriptor from binary. -func (sd *TimeDescriptor) decode(b []byte) error { - r := iobit.NewReader(b) - r.Skip(8) // splice_descriptor_tag - r.Skip(8) // descriptor_length - r.Skip(32) // identifier - sd.TAISeconds = r.Uint64(48) - sd.TAINS = r.Uint32(32) - sd.UTCOffset = r.Uint32(16) - - return readerError(r) -} - -// encode this splice_descriptor to binary. -func (sd *TimeDescriptor) encode() ([]byte, error) { - length := sd.length() - - // add 2 bytes to contain splice_descriptor_tag & descriptor_length - buf := make([]byte, length+2) - iow := iobit.NewWriter(buf) - iow.PutUint32(8, TimeDescriptorTag) - iow.PutUint32(8, uint32(length)) - iow.PutUint32(32, CUEIdentifier) - iow.PutUint64(48, sd.TAISeconds) - iow.PutUint32(32, sd.TAINS) - iow.PutUint32(16, sd.UTCOffset) - return buf, iow.Flush() -} - -// descriptorLength returns descriptor_length. -func (sd *TimeDescriptor) length() int { - length := 32 // identifier - length += 48 // TAI_seconds - length += 32 // TAI_ns - length += 16 // UTC_offset - return length / 8 -} diff --git a/internal/scte35/time_signal.go b/internal/scte35/time_signal.go @@ -1,102 +0,0 @@ -// Copyright 2021 Comcast Cable Communications Management, LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// SPDX-License-Identifier: Apache-2.0 - -package scte35 - -import ( - "fmt" - - "github.com/bamiaux/iobit" -) - -// TimeSignalType is the splice_command_type for a time_signal SpliceCommand. -const TimeSignalType = 0x06 - -// NewTimeSignal constructs a new time_signal command with the -// given pts_time value -func NewTimeSignal(ptsTime uint64) *TimeSignal { - return &TimeSignal{ - SpliceTime: SpliceTime{ - PTSTime: &ptsTime, - }, - } -} - -// TimeSignal provides a time synchronized data delivery mechanism. The syntax -// of the time_signal() allows for the synchronization of the information -// carried in this message with the System Time Clock (STC). The unique payload -// of the message is carried in the descriptor, however the syntax and transport -// capabilities afforded to splice_insert() messages are also afforded to the -// time_signal(). The carriage however can be in a different PID than that -// carrying the other cue messages used for signaling splice points. -type TimeSignal struct { - SpliceTime SpliceTime -} - -// Type returns the splice_command_type. -func (cmd *TimeSignal) Type() uint32 { return TimeSignalType } - -// decode a binary time_signal -func (cmd *TimeSignal) decode(b []byte) error { - r := iobit.NewReader(b) - timeSpecifiedFlag := r.Bit() - if timeSpecifiedFlag { - r.Skip(6) // reserved - ptsTime := r.Uint64(33) - cmd.SpliceTime.PTSTime = &ptsTime - } else { - r.Skip(7) // reserved - } - - if err := readerError(r); err != nil { - return fmt.Errorf("%v: %w", cmd, err) - } - return nil -} - -// encode this time_signal as binary. -func (cmd *TimeSignal) encode() ([]byte, error) { - buf := make([]byte, cmd.length()) - - iow := iobit.NewWriter(buf) - if cmd.timeSpecifiedFlag() { - iow.PutBit(true) - iow.PutUint32(6, Reserved) // reserved - iow.PutUint64(33, *cmd.SpliceTime.PTSTime) - } else { - iow.PutBit(false) - iow.PutUint32(7, Reserved) // reserved - } - - return buf, iow.Flush() -} - -// commandLength returns the splice_command_length. -func (cmd *TimeSignal) length() int { - length := 1 // time_specified_flag - if cmd.timeSpecifiedFlag() { - length += 6 // reserved - length += 33 // pts_time - } else { - length += 7 // reserved - } - return length / 8 -} - -// timeSpecifiedFlag return the time_specified_flag. -func (cmd *TimeSignal) timeSpecifiedFlag() bool { - return cmd != nil && cmd.SpliceTime.PTSTime != nil -}