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 }