streaming

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

splice.go (8257B)


      1 // Package scte35 implements a subset of the
      2 // Digital Program Insertion Cueing Message standard
      3 // as specified in [ANSI/SCTE 35].
      4 //
      5 // [ANSI/SCTE 35]: https://www.scte.org/standards/library/catalog/scte-35-digital-program-insertion-cueing-message/
      6 package scte35
      7 
      8 import (
      9 	"encoding/binary"
     10 	"fmt"
     11 )
     12 
     13 // SAPType represents the two-bit field used to indicate that a Stream
     14 // Access Point (SAP) in the stream
     15 // as specified in SCTE 35 section 9.6.1.
     16 type SAPType uint8
     17 
     18 const (
     19 	SAPClosedGOP        SAPType = 0
     20 	SAPClosedGOPLeading         = 0x10
     21 	SAPOpenGOP                  = 0x20
     22 	SAPNone                     = 0x30
     23 )
     24 
     25 func (t SAPType) String() string {
     26 	switch t {
     27 	case SAPClosedGOP:
     28 		return "SAP Type 1 (closed GOP)"
     29 	case SAPClosedGOPLeading:
     30 		return "SAP Type 2 (closed GOP with leading pictures)"
     31 	case SAPOpenGOP:
     32 		return "SAP Type 3 (open GOP)"
     33 	case SAPNone:
     34 		return "none"
     35 	}
     36 	return "invalid"
     37 }
     38 
     39 type Splice struct {
     40 	SAPType SAPType
     41 
     42 	// If true, indicates that the contents of Command,
     43 	// Descriptors and CRC32 are encrypted with Cipher.
     44 	// TODO(otl): encoding and decoding of encrypted splices is not supported.
     45 	Encrypted bool
     46 	Cipher    Cipher
     47 	// The control word (key) used to decrypt the message.
     48 	CWIndex uint8
     49 
     50 	// Holds a 33-bit unsigned integer representing the number of
     51 	// ticks of a 90KHz clock. The value is an offset added to
     52 	// timestamps in Descriptors by splice devices when executing the
     53 	// provided Command.
     54 	PTSAdjustment uint64
     55 
     56 	// Holds a 12-bit field representing an authorization tier. In
     57 	// most cases, its value should be 0x0fff for backwards
     58 	// compatibility. See 'tier' in SCTE 35 section 9.6.1.
     59 	Tier uint16
     60 
     61 	// Command points to this splice's specific instruction for splice devices.
     62 	Command *Command
     63 	// Descriptors holds zero or more parameters to Command.
     64 	Descriptors []SpliceDescriptor
     65 
     66 	// A checksum of the encoded splice. Splices returned from
     67 	// Decode() will hold a non-zero value. Splices passed to
     68 	// Encode() will have their checksums calculated automatically.
     69 	CRC32 uint32
     70 }
     71 
     72 // fields of Splice Info Section which MUST have their values set...
     73 // as specified in SCTE 35 section 9.6.1.
     74 const (
     75 	tableID          uint8 = 0xfc
     76 	protocolVersion        = 0x0
     77 	sectionSyntax          = false
     78 	privateIndicator       = false
     79 )
     80 
     81 // maximum 12-bit uint (2^12 - 1)
     82 const maxTier uint16 = 0xfff
     83 
     84 func Encode(splice *Splice) ([]byte, error) {
     85 	buf := make([]byte, 4)
     86 	buf[0] = byte(tableID)
     87 	// next 2 bits (section_syntax_indicator, private_indicator) must be 0.
     88 	// 0b00000000
     89 	buf[1] |= byte(splice.SAPType)
     90 
     91 	// length, buf[1,2] set at the end
     92 	buf[3] = protocolVersion
     93 
     94 	var b byte
     95 	if splice.Encrypted {
     96 		b |= (1 << 7)
     97 		if splice.Cipher > maxCipher {
     98 			return nil, fmt.Errorf("cipher %d larger than max %d", splice.Cipher, maxCipher)
     99 		}
    100 		// pack cipher, keeping 1 bit for PTSAdjustment.
    101 		b |= byte(splice.Cipher) << 1
    102 	}
    103 	buf = append(buf, b)
    104 	buf = append(buf, 0, 0, 0, 0)
    105 	putPTS(buf[4:], splice.PTSAdjustment)
    106 	if splice.Encrypted {
    107 		buf = append(buf, splice.CWIndex)
    108 	} else {
    109 		// unused; toggle all bits as in the spec.
    110 		buf = append(buf, 0xff)
    111 	}
    112 
    113 	if splice.Tier > maxTier {
    114 		return nil, fmt.Errorf("tier %d greater than max %d", splice.Tier, maxTier)
    115 	}
    116 	tier := splice.Tier & 0x0fff // just 12 bits
    117 	// right 4 bits are for command length
    118 	buf = binary.BigEndian.AppendUint16(buf, tier<<4)
    119 
    120 	if splice.Command == nil {
    121 		return nil, fmt.Errorf("nil command")
    122 	}
    123 	cmd, err := encodeCommand(splice.Command)
    124 	if err != nil {
    125 		return nil, fmt.Errorf("encode splice command: %w", err)
    126 	}
    127 	cmdlen := uint16(len(cmd)) & 0x0fff
    128 	// stuff remaining 4 bits into the last byte.
    129 	buf[len(buf)-1] |= byte(cmdlen >> 8)
    130 	buf = append(buf, byte(cmdlen))
    131 	buf = append(buf, byte(splice.Command.Type))
    132 	buf = append(buf, cmd...)
    133 
    134 	var buf1 []byte
    135 	for _, desc := range splice.Descriptors {
    136 		buf1 = append(buf1, encodeSpliceDescriptor(desc)...)
    137 	}
    138 	buf = binary.BigEndian.AppendUint16(buf, uint16(len(buf1)))
    139 	buf = append(buf, buf1...)
    140 
    141 	// want only 12 bits, left 4 bits are used by flags, saptype.
    142 	buflen := uint16(len(buf)) & 0x0fff
    143 	buflen++ // TODO(otl): is this required because of alignment stuffing?
    144 	buf[1] |= byte(buflen >> 8)
    145 	buf[2] = byte(buflen)
    146 
    147 	crc := ^updateCRC(0, buf)
    148 	return binary.BigEndian.AppendUint32(buf, crc), nil
    149 }
    150 
    151 func Decode(buf []byte) (*Splice, error) {
    152 	if len(buf) < 3 {
    153 		return nil, fmt.Errorf("need at least 2 bytes")
    154 	}
    155 	// skip buf[0], we don't store table_id.
    156 
    157 	var splice Splice
    158 	// skip 2 bits, straight to sap_type.
    159 	splice.SAPType = SAPType(buf[1] & 0b00110000)
    160 	length := binary.BigEndian.Uint16([]byte{buf[1], buf[2]})
    161 	length &= 0x0fff // 12-bit field
    162 	buf = buf[3:]
    163 	if len(buf) != int(length) {
    164 		return nil, fmt.Errorf("message declares %d bytes but have %d", length, len(buf))
    165 	}
    166 
    167 	// skip version byte at buf[0]. We don't store version as it's constant.
    168 	splice.Encrypted = buf[1]&0b10000000 > 0
    169 	if splice.Encrypted {
    170 		// right-most bit is used by PTSAdjustment.
    171 		splice.Cipher = Cipher(buf[1] & 0b01111110)
    172 	}
    173 
    174 	pts := make([]byte, 8)
    175 	pts[0] = buf[1] & (1 << 1)
    176 	copy(pts[1:], buf[2:6])
    177 	splice.PTSAdjustment = binary.BigEndian.Uint64(pts)
    178 	splice.CWIndex = uint8(buf[6])
    179 
    180 	// want left-most 12 bits, remaining is used by command length.
    181 	tier := binary.BigEndian.Uint16([]byte{buf[7], buf[8] & 0xf0})
    182 	splice.Tier = tier >> 4
    183 
    184 	// 4-bits out of buf[8], then all of buf[9] for a 12-bit integer.
    185 	cmdlen := binary.BigEndian.Uint16([]byte{buf[8] & 0x0f, buf[9]})
    186 	cmd, err := decodeCommand(buf[10 : 10+cmdlen+1])
    187 	if err != nil {
    188 		return nil, fmt.Errorf("decode command: %w", err)
    189 	}
    190 	splice.Command = cmd
    191 	buf = buf[10+cmdlen+1:]
    192 
    193 	desclen := binary.BigEndian.Uint16([]byte{buf[0], buf[1]})
    194 	descriptors, err := decodeAllDescriptors(buf[2 : 2+desclen])
    195 	if err != nil {
    196 		return nil, fmt.Errorf("decode splice descriptors: %w", err)
    197 	}
    198 	splice.Descriptors = descriptors
    199 
    200 	buf = buf[2+desclen:]
    201 	if splice.Encrypted {
    202 		// TODO(otl): handle alignment_stuffing for encrypted packets.
    203 		// skip past E_CRC_32; we don't store it.
    204 		buf = buf[1:]
    205 	}
    206 	splice.CRC32 = binary.BigEndian.Uint32(buf)
    207 	return &splice, nil
    208 }
    209 
    210 func decodeCommand(buf []byte) (*Command, error) {
    211 	var cmd Command
    212 	cmd.Type = CommandType(buf[0])
    213 	switch cmd.Type {
    214 	case SpliceNull, BandwidthReservation:
    215 		// nothing to decode
    216 	case TimeSignal:
    217 		// check if time specified flag is set.
    218 		// If so, extract the 33-bit integer timestamp.
    219 		if buf[1]&0x80 == 1<<7 {
    220 			b := make([]byte, 8)
    221 			b[3] = buf[1] & 0x01 // ignoring flag and reserved bits
    222 			copy(b[4:], buf[2:6])
    223 			t := binary.BigEndian.Uint64(b)
    224 			cmd.TimeSignal = &t
    225 		}
    226 	case SpliceInsert:
    227 		var ins Insert
    228 		ins.ID = binary.BigEndian.Uint32(buf[1:5])
    229 		ins.Cancel = buf[5]&0x80 > 0
    230 		if ins.Cancel {
    231 			cmd.Insert = &ins
    232 			// rebelelder told us to do this.
    233 			return &cmd, nil
    234 		}
    235 		ins.OutOfNetwork = buf[6]&(1<<7) > 0
    236 		// assume program_splice is set at bit 6;
    237 		// we don't support deprecated component mode.
    238 		durflag := buf[6]&(1<<5) > 0
    239 		ins.Immediate = buf[6]&(1<<4) > 0
    240 		ins.idCompliance = buf[6]&(1<<3) > 0
    241 		// next 3 bits are reserved.
    242 
    243 		if !ins.Immediate {
    244 			// is time_specified_flag set? if so, read the 33-bit time.
    245 			if buf[7]&(1<<7) > 0 {
    246 				b := make([]byte, 3)
    247 				b = append(b, buf[7]&0x01)  // skip reserved bits.
    248 				b = append(b, buf[8:12]...) // read remaining 32 bits.
    249 				dur := binary.BigEndian.Uint64(b)
    250 				ins.SpliceTime = newuint64(dur)
    251 				buf = buf[12:]
    252 			} else {
    253 				buf = buf[8:]
    254 			}
    255 		}
    256 
    257 		if durflag {
    258 			a := [5]byte{buf[0], buf[1], buf[2], buf[3], buf[4]}
    259 			ins.Duration = readBreakDuration(a)
    260 			buf = buf[5:]
    261 		}
    262 
    263 		ins.ProgramID = binary.BigEndian.Uint16([]byte{buf[0], buf[1]})
    264 		ins.AvailNum = uint8(buf[2])
    265 		ins.AvailExpected = uint8(buf[3])
    266 		cmd.Insert = &ins
    267 	case Private:
    268 		pcmd, err := decodePrivateCommand(buf[1:])
    269 		if err != nil {
    270 			return nil, fmt.Errorf("decode private command: %w", err)
    271 		}
    272 		cmd.Private = &pcmd
    273 	default:
    274 		// TODO(otl): we could support more commands but we
    275 		// just haven't written the code yet. See issues
    276 		// #28 and #29.
    277 		return nil, fmt.Errorf("cannot decode command type %s", cmd.Type)
    278 	}
    279 	return &cmd, nil
    280 }
    281 
    282 func newuint64(i uint64) *uint64 { p := new(uint64); p = &i; return p }