streaming

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

command.go (6433B)


      1 package scte35
      2 
      3 import (
      4 	"encoding/binary"
      5 	"fmt"
      6 	"time"
      7 )
      8 
      9 // GPS epoch is 1980-01-06T00:00:00Z
     10 var gpsEpoch time.Time = time.Date(1980, 1, 6, 0, 0, 0, 0, time.UTC)
     11 
     12 // Command represents a splice command described in
     13 // SCTE 35 section 9.7.
     14 type Command struct {
     15 	Type     CommandType
     16 	Schedule []Event // SpliceSchedule
     17 	Insert   *Insert
     18 	// Number of ticks of a 90KHz clock since midnight UTC.
     19 	// TODO(otl): use time.Time here instead,
     20 	// then calculate ticks when converting to wire format?
     21 	TimeSignal *uint64
     22 	Private    *PrivateCommand
     23 }
     24 
     25 type CommandType uint8
     26 
     27 const (
     28 	SpliceNull           CommandType = 0
     29 	SpliceSchedule                   = 0x04
     30 	SpliceInsert                     = 0x05
     31 	TimeSignal                       = 0x06
     32 	BandwidthReservation             = 0x07
     33 	Private                          = 0xff
     34 )
     35 
     36 func (t CommandType) String() string {
     37 	switch t {
     38 	case SpliceNull:
     39 		return "splice_null"
     40 	case SpliceSchedule:
     41 		return "splice_schedule"
     42 	case SpliceInsert:
     43 		return "splice_insert"
     44 	case TimeSignal:
     45 		return "time_signal"
     46 	case BandwidthReservation:
     47 		return "bandwidth_reservation"
     48 	case Private:
     49 		return "private_command"
     50 	}
     51 	return "reserved"
     52 }
     53 
     54 func encodeCommand(c *Command) ([]byte, error) {
     55 	switch c.Type {
     56 	case SpliceNull, BandwidthReservation:
     57 		// Since SpliceNull == 0 (default) check if we've
     58 		// accidentally set another field.
     59 		if c.Schedule != nil {
     60 			return nil, fmt.Errorf("command %s has non-nil schedule", c.Type)
     61 		} else if c.Insert != nil {
     62 			return nil, fmt.Errorf("command %s has non-nil Insert", c.Type)
     63 		} else if c.TimeSignal != nil {
     64 			return nil, fmt.Errorf("command %s has non-nil TimeSignal", c.Type)
     65 		} else if c.Private != nil {
     66 			return nil, fmt.Errorf("command %s has non-nil Private", c.Type)
     67 		}
     68 		return nil, nil
     69 	case SpliceSchedule:
     70 		b, err := packEvents(c.Schedule)
     71 		if err != nil {
     72 			return b, fmt.Errorf("pack events: %w", err)
     73 		}
     74 		return b, nil
     75 	case SpliceInsert:
     76 		return encodeInsert(c.Insert), nil
     77 	case TimeSignal:
     78 		if c.TimeSignal == nil {
     79 			return nil, fmt.Errorf("cannot encode nil TimeSignal")
     80 		}
     81 		b := encodeSpliceTime(*c.TimeSignal)
     82 		return b[:], nil
     83 	case Private:
     84 		return encodePrivateCommand(c.Private), nil
     85 	default:
     86 		return nil, fmt.Errorf("encoding command %s unsupported", c.Type)
     87 	}
     88 }
     89 
     90 // Event is a single event within a splice_schedule.
     91 type Event struct {
     92 	ID uint32
     93 	// Indicates a previously sent event identified by ID should
     94 	// be cancelled.
     95 	Cancel bool
     96 
     97 	OutOfNetwork bool
     98 	// TODO(otl): should always be true? should we support
     99 	// deprecated Component Splice Mode?
    100 	// see section 9.7.2.1.
    101 	// ProgramSplice bool
    102 	SpliceTime    time.Time
    103 	BreakDuration *BreakDuration
    104 
    105 	ProgramID     uint16
    106 	AvailNum      uint8
    107 	AvailExpected uint8
    108 	// Indicates the event's ID is prepared in the method
    109 	// described in SCTE 35 section 9.3.3.
    110 	// TODO(otl): can we calculate this at runtime?
    111 	// See https://github.com/untangledco/streaming/issues/2
    112 	idCompliance bool
    113 }
    114 
    115 func packEvents(events []Event) ([]byte, error) {
    116 	if len(events) > 255 {
    117 		return nil, fmt.Errorf("too many events (%d), need 255 or less", len(events))
    118 	}
    119 	var packed []byte
    120 	packed[0] = uint8(len(events))
    121 	for i := range events {
    122 		b := packEvent(&events[i])
    123 		packed = append(packed, b...)
    124 	}
    125 	return packed, nil
    126 }
    127 
    128 func packEvent(e *Event) []byte {
    129 	// length is e.ID + flags
    130 	p := make([]byte, 4+1)
    131 	binary.BigEndian.PutUint32(p[:4], e.ID)
    132 	if e.Cancel {
    133 		p[4] |= 1 << 7
    134 	}
    135 	if e.idCompliance {
    136 		p[4] |= 1 << 6
    137 	}
    138 	// 6 remaining bits are reserved.
    139 
    140 	if !e.Cancel {
    141 		p = append(p, 0x00)
    142 		if e.OutOfNetwork {
    143 			p[5] |= 1 << 7
    144 		}
    145 		// assume program_splice is always set;
    146 		// we don't support component splice mode.
    147 		p[5] |= 1 << 6
    148 		if e.BreakDuration != nil {
    149 			p[5] |= 1 << 5
    150 		}
    151 		// 5 remaining bits are reserved
    152 
    153 		seconds := e.SpliceTime.Sub(gpsEpoch) / time.Second
    154 		p = binary.BigEndian.AppendUint32(p, uint32(seconds))
    155 
    156 		if e.BreakDuration != nil {
    157 			bd := packBreakDuration(e.BreakDuration)
    158 			p = append(p, bd[:]...)
    159 		}
    160 	}
    161 
    162 	p = binary.BigEndian.AppendUint16(p, e.ProgramID)
    163 	p = append(p, e.AvailNum)
    164 	p = append(p, e.AvailExpected)
    165 	return p
    166 }
    167 
    168 type PrivateCommand struct {
    169 	ID   uint32
    170 	Data []byte
    171 }
    172 
    173 func encodePrivateCommand(c *PrivateCommand) []byte {
    174 	buf := make([]byte, 4+len(c.Data))
    175 	binary.BigEndian.PutUint32(buf[:4], c.ID)
    176 	copy(buf[4:], c.Data)
    177 	return buf
    178 }
    179 
    180 func decodePrivateCommand(b []byte) (PrivateCommand, error) {
    181 	if len(b) < 4 {
    182 		return PrivateCommand{}, fmt.Errorf("need at least 4 bytes, have %d", len(b))
    183 	}
    184 	return PrivateCommand{
    185 		ID:   binary.BigEndian.Uint32(b[:4]),
    186 		Data: b[4:],
    187 	}, nil
    188 }
    189 
    190 // Insert represents the splice_insert command
    191 // as specified in SCTE 35 section 9.7.3.
    192 type Insert struct {
    193 	ID           uint32
    194 	Cancel       bool
    195 	OutOfNetwork bool
    196 	Immediate    bool
    197 	// Number of ticks of a 90KHz clock.
    198 	SpliceTime    *uint64
    199 	Duration      *BreakDuration
    200 	ProgramID     uint16
    201 	AvailNum      uint8
    202 	AvailExpected uint8
    203 	// Indicates the event's ID is prepared in the method
    204 	// described in SCTE 35 section 9.3.3.
    205 	// TODO(otl): can we calculate this at runtime?
    206 	// See https://github.com/untangledco/streaming/issues/2
    207 	idCompliance bool
    208 }
    209 
    210 func encodeInsert(ins *Insert) []byte {
    211 	buf := make([]byte, 4+1) // uint32 + 1 byte
    212 	binary.BigEndian.PutUint32(buf[:4], ins.ID)
    213 	buf[4] |= 0x7f // toggle reserved bits
    214 	if ins.Cancel {
    215 		buf[4] |= (1 << 7) // toggle unreserved bit
    216 		return buf
    217 	}
    218 
    219 	var flags byte
    220 	if ins.OutOfNetwork {
    221 		flags |= (1 << 7)
    222 	}
    223 	// assume program_splice is set;
    224 	// we do not support the deprecated component_count mode.
    225 	flags |= (1 << 6)
    226 	if ins.Duration != nil {
    227 		flags |= (1 << 5)
    228 	}
    229 	if ins.Immediate {
    230 		flags |= (1 << 4)
    231 	}
    232 	if ins.idCompliance {
    233 		flags |= (1 << 3)
    234 	}
    235 	// toggle remaining 3 reserved bits.
    236 	flags |= 0x07
    237 	buf = append(buf, flags)
    238 
    239 	if ins.SpliceTime != nil && !ins.Immediate {
    240 		b := encodeSpliceTime(*ins.SpliceTime)
    241 		buf = append(buf, b[:]...)
    242 	}
    243 
    244 	if ins.Duration != nil {
    245 		b := packBreakDuration(ins.Duration)
    246 		buf = append(buf, b[:]...)
    247 	}
    248 	buf = append(buf, byte(ins.ProgramID>>8))
    249 	buf = append(buf, byte(ins.ProgramID))
    250 	buf = append(buf, byte(ins.AvailNum), byte(ins.AvailExpected))
    251 	return buf
    252 }
    253 
    254 func encodeSpliceTime(ticks uint64) [5]byte {
    255 	pts := toPTS(ticks)
    256 	// set time_specified_flag
    257 	pts[0] |= (1 << 7)
    258 	// toggle 6 reserved bits, so that we match the spec.
    259 	pts[0] |= 0x7e
    260 	return pts
    261 }