github.com/puellanivis/breton@v0.2.16/lib/mpeg/ts/ts.go (about) 1 // Package ts implements a Mux/Demux for the MPEG Transport Stream protocol. 2 package ts 3 4 import ( 5 "io" 6 "sync" 7 "time" 8 9 "github.com/pkg/errors" 10 "github.com/puellanivis/breton/lib/mpeg/ts/dvb" 11 "github.com/puellanivis/breton/lib/mpeg/ts/packet" 12 "github.com/puellanivis/breton/lib/mpeg/ts/psi" 13 ) 14 15 // PacketSize is the length in bytes of an MPEG-TS packet. 16 const PacketSize = packet.Length 17 18 // Option defines a function that will apply some value or behavior to a TransportStream. 19 type Option func(*TransportStream) Option 20 21 // WithDebug sets a function that will be called for each MPEG-TS Packet processed by the TransportStream. 22 func WithDebug(fn func(*packet.Packet)) Option { 23 return func(ts *TransportStream) Option { 24 ts.once.Do(ts.init) 25 ts.mu.Lock() 26 defer ts.mu.Unlock() 27 28 save := ts.debug 29 ts.debug = fn 30 31 return WithDebug(save) 32 } 33 } 34 35 func (ts *TransportStream) getDebug() func(*packet.Packet) { 36 ts.once.Do(ts.init) 37 ts.mu.Lock() 38 defer ts.mu.Unlock() 39 40 return ts.debug 41 } 42 43 // WithUpdateRate sets the rate at which the TransportStream will update TODO. 44 func WithUpdateRate(d time.Duration) Option { 45 return func(ts *TransportStream) Option { 46 ts.once.Do(ts.init) 47 ts.mu.Lock() 48 defer ts.mu.Unlock() 49 50 save := ts.updateRate 51 ts.updateRate = d 52 53 return WithUpdateRate(save) 54 } 55 } 56 57 func (ts *TransportStream) getUpdateRate() time.Duration { 58 ts.once.Do(ts.init) 59 ts.mu.Lock() 60 defer ts.mu.Unlock() 61 62 return ts.updateRate 63 } 64 65 // TransportStream defines an MPEG Transport Stream. 66 type TransportStream struct { 67 sink io.Writer 68 69 ticker chan struct{} 70 counter int 71 72 once sync.Once 73 mu sync.Mutex 74 75 debug func(*packet.Packet) 76 updateRate time.Duration 77 78 patReady chan struct{} 79 pat map[uint16]uint16 80 pmts map[uint16]*Program 81 dvbSDT *dvb.ServiceDescriptorTable 82 83 nextStreamPID uint16 84 nextProgramPID uint16 85 lastStreamID uint16 86 87 m *Mux 88 } 89 90 func (ts *TransportStream) init() { 91 ts.patReady = make(chan struct{}) 92 ts.nextStreamPID = 0x100 93 ts.nextProgramPID = 0x1000 94 ts.updateRate = 1 * time.Second / 25 // 25 Hz 95 } 96 97 func (ts *TransportStream) writePackets(pkts ...*packet.Packet) (n int, err error) { 98 debug := ts.getDebug() 99 100 var q [][]byte 101 102 for _, pkt := range pkts { 103 if debug != nil { 104 debug(pkt) 105 } 106 107 b, err2 := pkt.Marshal() 108 if err2 != nil { 109 if err == nil { 110 err = err2 111 } 112 113 continue 114 } 115 116 q = append(q, b) 117 } 118 119 if len(q) < 1 { 120 return 0, err 121 } 122 123 ts.mu.Lock() 124 defer ts.mu.Unlock() 125 126 for _, b := range q { 127 n2, err2 := ts.sink.Write(b) 128 129 n += n2 130 if err == nil { 131 err = err2 132 } 133 } 134 135 if len(q) > 1 { 136 ts.counter += 15 137 return n, err 138 } 139 140 ts.counter-- 141 if ts.counter <= 0 { 142 select { 143 case ts.ticker <- struct{}{}: 144 ts.counter += 15 // TODO: make configurable 145 default: 146 } 147 } 148 149 return n, err 150 } 151 152 func (ts *TransportStream) newStreamPID() uint16 { 153 ts.mu.Lock() 154 defer ts.mu.Unlock() 155 156 pid := ts.nextStreamPID 157 ts.nextStreamPID++ 158 return pid 159 } 160 161 // NewProgram returns a new Program assigned to the given Stream ID. 162 func (ts *TransportStream) NewProgram(streamID uint16) (*Program, error) { 163 ts.once.Do(ts.init) 164 ts.mu.Lock() 165 defer ts.mu.Unlock() 166 167 if ts.m == nil { 168 return nil, errors.New("cannot make a new program on a non-outbound transport stream") 169 } 170 171 if streamID == 0 { 172 streamID = ts.lastStreamID + 1 173 174 for ts.pat[streamID] != 0 { 175 streamID++ 176 } 177 } 178 ts.lastStreamID = streamID 179 180 if ts.pat[streamID] != 0 { 181 return nil, errors.Errorf("stream_id 0x%04X is already assigned", streamID) 182 } 183 184 pid := ts.nextProgramPID 185 ts.nextProgramPID++ 186 187 p := &Program{ 188 pid: pid, 189 ts: ts, 190 pmt: &psi.PMT{ 191 Syntax: &psi.SectionSyntax{ 192 TableIDExtension: streamID, 193 Current: true, 194 }, 195 PCRPID: 0x1FFF, 196 }, 197 } 198 199 if ts.pat == nil { 200 ts.pat = make(map[uint16]uint16) 201 202 select { 203 case <-ts.patReady: 204 default: 205 close(ts.patReady) 206 } 207 } 208 209 ts.pat[streamID] = pid 210 211 if ts.pmts == nil { 212 ts.pmts = make(map[uint16]*Program) 213 } 214 215 ts.pmts[pid] = p 216 217 return p, nil 218 } 219 220 func cloneSDT(src *dvb.ServiceDescriptorTable) *dvb.ServiceDescriptorTable { 221 if src == nil { 222 return nil 223 } 224 225 sdt := *src 226 227 if src.Syntax != nil { 228 s := *src.Syntax 229 sdt.Syntax = &s 230 } 231 232 sdt.Services = nil 233 for _, s := range src.Services { 234 n := *s 235 n.Descriptors = nil 236 237 for _, d := range s.Descriptors { 238 if d, ok := d.(*dvb.ServiceDescriptor); ok { 239 nd := *d 240 n.Descriptors = append(n.Descriptors, &nd) 241 } 242 } 243 244 sdt.Services = append(sdt.Services, &n) 245 } 246 247 return &sdt 248 } 249 250 // SetDVBSDT sets the DVB Service Description Table for the TransportStream. 251 func (ts *TransportStream) SetDVBSDT(sdt *dvb.ServiceDescriptorTable) { 252 clone := cloneSDT(sdt) 253 254 ts.mu.Lock() 255 defer ts.mu.Unlock() 256 257 ts.dvbSDT = clone 258 } 259 260 func (ts *TransportStream) getDVBSDT() *dvb.ServiceDescriptorTable { 261 ts.mu.Lock() 262 defer ts.mu.Unlock() 263 264 return ts.dvbSDT 265 } 266 267 // GetDVBSDT returns a deep copy of the DVB Service Description Table being used by the TransportStream. 268 func (ts *TransportStream) GetDVBSDT() *dvb.ServiceDescriptorTable { 269 ts.mu.Lock() 270 defer ts.mu.Unlock() 271 272 return cloneSDT(ts.dvbSDT) 273 } 274 275 // GetPAT returns a map defining the Program Allocation Table of the TransportStream. 276 func (ts *TransportStream) GetPAT() map[uint16]uint16 { 277 ts.once.Do(ts.init) 278 279 <-ts.patReady 280 281 ts.mu.Lock() 282 defer ts.mu.Unlock() 283 284 return ts.pat 285 } 286 287 func (ts *TransportStream) setPAT(pat map[uint16]uint16) { 288 ts.once.Do(ts.init) 289 ts.mu.Lock() 290 defer ts.mu.Unlock() 291 292 if pat != nil { 293 ts.pat = pat 294 } 295 296 select { 297 case <-ts.patReady: 298 default: 299 close(ts.patReady) 300 } 301 } 302 303 // GetPMTs returns a map defining the Program Map Tables indexed by their pid. 304 // 305 // TODO: confirm behavior. 306 func (ts *TransportStream) GetPMTs() map[uint16]*Program { 307 ts.once.Do(ts.init) 308 ts.mu.Lock() 309 defer ts.mu.Unlock() 310 311 return ts.pmts 312 }