clone

a desktop tv and program guide for over-the-air broadcasts: what's on air, what your tuner gets, live tv, recordings

main
airwaves / internal / ts / filter.go
filter.go 3.2K
1package ts23import "io"45// flushAt is how much output a Filter gathers before writing it: seven6// packets, as an HDHomeRun sends them over UDP.7const flushAt = 7 * PacketSize89// Filter writes one program of a multiplex as a transport stream of its10// own, as players and recordings expect: a PAT that lists only that11// program, then the program's PMT, its elementary streams and its PCR.12// Other programs, PSIP and null packets are left out. Nothing goes out13// until the program's PMT has been read, and then the output starts with14// the PAT and a PMT from its first packet, so it starts clean. A new PAT15// or PMT is followed.16type Filter struct {17 w io.Writer18 program int19 d *Demux20 gen int // d.gen when pass was set21 pass [8192]bool22 passed []int // the PIDs set in pass23 pmtPID int // -1 until the program's PMT is known24 started bool // a PMT has gone out25 patCC byte26 pat [PacketSize]byte27 buf []byte28 err error29}3031// NewFilter returns a Filter writing program to w.32func NewFilter(w io.Writer, program int) *Filter {33 return &Filter{w: w, program: program, d: NewDemux(), gen: -1, pmtPID: -1}34}3536// Packet takes the multiplex's next packet. Output is written in batches37// of a few packets; Flush writes what is held. A write error is returned38// from then on.39func (f *Filter) Packet(pkt []byte) error {40 if f.err != nil {41 return f.err42 }43 f.d.Packet(pkt)44 if f.d.gen != f.gen {45 f.update()46 }47 if f.pmtPID < 0 {48 return nil49 }50 pid := PID(pkt)51 switch {52 case pid == 0:53 if pkt[1]&0x40 != 0 {54 f.writePAT() // ours, each time the multiplex sends its own55 }56 case pid == f.pmtPID:57 if !f.started && pkt[1]&0x40 == 0 {58 return nil59 }60 f.started = true61 f.write(pkt)62 case f.pass[pid] && f.started:63 f.write(pkt)64 }65 return f.err66}6768// Flush writes the output held back.69func (f *Filter) Flush() error {70 if f.err != nil || len(f.buf) == 0 {71 return f.err72 }73 _, f.err = f.w.Write(f.buf)74 f.buf = f.buf[:0]75 return f.err76}7778// update follows a new PAT or PMT.79func (f *Filter) update() {80 f.gen = f.d.gen81 for _, pid := range f.passed {82 f.pass[pid] = false83 }84 f.passed = f.passed[:0]85 p, ok := f.d.Program(f.program)86 if !ok {87 f.pmtPID, f.started = -1, false88 return89 }90 for _, s := range p.Streams {91 f.allow(s.PID)92 }93 f.allow(p.PCRPID)94 if p.PMTPID != f.pmtPID {95 // A PMT that moved, or the first: start on its next section.96 f.pmtPID, f.started = p.PMTPID, false97 f.writePAT()98 }99}100101func (f *Filter) allow(pid int) {102 if pid > 0 && pid < nullPID && !f.pass[pid] {103 f.pass[pid] = true104 f.passed = append(f.passed, pid)105 }106}107108// writePAT writes a PAT listing only the program.109func (f *Filter) writePAT() {110 tsid, _ := f.d.TSID()111 s := []byte{112 0x00, 0xb0, 13, byte(tsid >> 8), byte(tsid), 0xc1 | byte(f.d.patVer&0x1f)<<1, 0, 0,113 byte(f.program >> 8), byte(f.program), 0xe0 | byte(f.pmtPID>>8), byte(f.pmtPID),114 }115 crc := CRC32(s)116 s = append(s, byte(crc>>24), byte(crc>>16), byte(crc>>8), byte(crc))117 p := f.pat[:]118 p[0], p[1], p[2], p[3], p[4] = syncByte, 0x40, 0x00, 0x10|f.patCC, 0119 f.patCC = (f.patCC + 1) & 0x0f120 n := 5 + copy(p[5:], s)121 for i := n; i < PacketSize; i++ {122 p[i] = 0xff123 }124 f.write(p)125}126127func (f *Filter) write(pkt []byte) {128 f.buf = append(f.buf, pkt...)129 if len(f.buf) >= flushAt {130 f.Flush()131 }132}