package psi import ( "errors" "hash/crc32" "math/bits" ) // TODO: Finish off mts/psi so that we can create pat and pmt tables instead // of hardcoding. var ( StdPat = []byte{ 0x00, // pointer // ---- section included in data sent to CRC32 during check // table header 0x00, // table id 0xb0, // section syntax indicator:1|private bit:1|reserved:2|section length:2|more bytes...:2 0x0d, // more bytes... // syntax section 0x00, 0x01, // table id extension 0xc1, // reserved bits:3|version:5|use now:1 1100 0001 0x00, // section number 0x00, // last section number // table data 0x00, 0x01, // Program number 0xf0, 0x00, // reserved:3|program map PID:13 // 0x2a, 0xb1, 0x04, 0xb2, // CRC // ---- } StdPmt = []byte{ 0x00, // pointer // ---- section included in data sent to CRC32 during check // table header 0x02, // table id 0xb0, // section syntax indicator:1|private bit:1|reserved:2|section length:2|more bytes...:2 0x12, // more bytes... // syntax section 0x00, 0x01, // table id extension 0xc1, // reserved bits:3|version:5|use now:1 0x00, // section number 0x00, // last section number // table data 0xe1, 0x00, // reserved:3|PCR PID:13 0xf0, 0x00, // reserved:4|unused:2|program info length:10 // No program descriptors since program info length is 0. // elementary stream info data 0x1b, // stream type 0xe1, 0x00, // reserved:3|elementary PID:13 0xf0, 0x00, // reserved:4|unused:2|ES info length:10 // No elementary stream descriptors since ES info length is 0. // 0x15, 0xbd, 0x4d, 0x56, // CRC // ---- } ) // Some common lengths const ( ESSDDefLen = 5 DescDefLen = 2 PMTDefLen = 4 PATLen = 4 TSSDefLen = 5 PSIDefLen = 3 ) // Table Type IDs const ( PATTableID = 0x00 PMTTableID = 0x02 ) const ( timestampDescTag = 234 ) // Program specific information type PSI struct { Pf byte // Point field Pfb []byte // Pointer filler bytes Tid byte // Table ID Ssi bool // Section syntax indicator (1 for PAT, PMT, CAT) Pb bool // Private bit (0 for PAT, PMT, CAT) Sl uint16 // Section length Tss *TSS // Table syntax section (length defined by SL) if length 0 then nil Crc uint32 // crc32 of entire table excluding pointer field, pointer filler bytes and the trailing CRC32 } // Table syntax section type TSS struct { Tide uint16 // Table ID extension V byte // Version number Cni bool // Current/next indicator Sn byte // Section number Lsn byte // Last section number Sd SD // Specific data PAT/PMT } // Specific Data, (could be PAT or PMT) type SD interface { Bytes() []byte } // Program association table, implements SD type PAT struct { Pn uint16 // Program Number Pmpid uint16 // Program map PID } // Program mapping table, implements SD type PMT struct { Pcrpid uint16 // Program clock reference pid Pil uint16 // Program info length Pd []Desc // Program descriptors Essd *ESSD // Elementary stream specific data } // Elementary stream specific data type ESSD struct { St byte // Stream type Epid uint16 // Elementary pid Esil uint16 // Elementary stream Esd []Desc // Elementary stream desriptors } // Descriptor type Desc struct { Dt byte // Descriptor tag Dl byte // Descriptor length Dd []byte // Descriptor data } // ReadPSI creates a PSI data structure from a given byte slice that represents a PSI func ReadPSI(data []byte) *PSI { psi := PSI{} pos := 0 psi.Pf = data[pos] if psi.Pf != 0 { psi.Pfb = make([]byte, 0, psi.Pf) pos++ for i := 0; i < int(psi.Pf); i++ { psi.Pfb = append(psi.Pfb, data[pos]) pos++ } } psi.Tid = data[pos] pos++ psi.Ssi = byteToBool(data[pos] & 0x80) psi.Pb = byteToBool(data[pos] & 0x40) psi.Sl = uint16(data[pos]&0x03)<<8 | uint16(data[pos+1]) pos += 2 psi.Tss = readTSS(data[pos:], &psi) return &psi } // ReadTSS creates a TSS data structure from a given byte slice that represents a TSS func readTSS(data []byte, p *PSI) *TSS { tss := TSS{} pos := 0 tss.Tide = uint16(data[pos])<<8 | uint16(data[pos+1]) pos += 2 tss.V = (data[pos] & 0x3e) >> 1 tss.Cni = byteToBool(data[pos] & 0x01) pos++ tss.Sn = data[pos] pos++ tss.Lsn = data[pos] pos++ switch p.Tid { case PATTableID: tss.Sd = readPAT(data[pos:], &tss) case PMTTableID: tss.Sd = readPMT(data[pos:], &tss) default: panic("Can't yet deal with tables that are not PAT or PMT") } return &tss } func readPAT(data []byte, p *TSS) *PAT { pat := PAT{} pos := 0 pat.Pn = uint16(data[pos])<<8 | uint16(data[pos+1]) pos += 2 pat.Pmpid = uint16(data[pos]&0x1f)<<8 | uint16(data[pos+1]) return &pat } func readPMT(data []byte, p *TSS) *PAT { pmt := PMT{} pos := 0 pmt.Pcrpid = uint16(data[pos]&0x1f)<<8 | uint16(data[pos+1]) pos += 2 pmt.Pil = uint16(data[pos]&0x03)<<8 | uint16(data[pos+1]) pos += 2 if pmt.Pil != 0 { pmt.Pd = readDescs(data[pos:], int(pmt.Pil)) } pos += int(pmt.Pil) // TODO Read ES stuff pmt.Essd = readEssd(data[pos:]) return nil } // readDescs reads provides a slice of Descs given a byte slice that represents Descs // and the no of bytes that the descs accumilate func readDescs(data []byte, descLen int) (o []Desc) { pos := 0 o = make([]Desc, 1) o[0].Dt = data[pos] pos++ o[0].Dl = data[pos] pos++ o[0].Dd = make([]byte, o[0].Dl) for i := 0; i < int(o[0].Dl); i++ { o[0].Dd[i] = data[pos] pos++ } if 2+len(o[0].Dd) != descLen { panic("No support for reading more than one descriptor") } return } func readEssd(data []byte) *ESSD { essd := ESSD{} pos := 0 essd.St = data[pos] pos++ essd.Epid = uint16(data[pos]&0x1f)<<8 | uint16(data[pos+1]) pos += 2 essd.Esil = uint16(data[pos]&0x03)<<8 | uint16(data[pos+1]) pos += 2 essd.Esd = readDescs(data[pos:], int(essd.Esil)) return &essd } // Bytes outputs a byte slice representation of the PSI func (p *PSI) Bytes() []byte { l := 1 + len(p.Pfb) out := make([]byte, l+PSIDefLen) out[0] = p.Pf for i, b := range p.Pfb { out[1+i] = b } out[l] = p.Tid out[l+1] = 0x80 | 0x40 | 0x30 | (0x03 & byte(p.Sl>>8)) out[l+2] = byte(p.Sl) out = append(out, p.Tss.Bytes()...) crc32 := crc32_Update(0xffffffff, crc32_MakeTable(bits.Reverse32(crc32.IEEE)), out[l:]) out = append(out, make([]byte, 0, 4)...) out = append(out, byte(crc32>>24)) out = append(out, byte(crc32>>16)) out = append(out, byte(crc32>>8)) out = append(out, byte(crc32)) return out } // Bytes outputs a byte slice representation of the TSS func (t *TSS) Bytes() []byte { out := make([]byte, TSSDefLen) out[0] = byte(t.Tide >> 8) out[1] = byte(t.Tide) out[2] = 0xc0 | (0x3e & (t.V << 1)) | (0x01 & boolToByte(t.Cni)) out[3] = t.Sn out[4] = t.Lsn out = append(out, t.Sd.Bytes()...) return out } // Bytes outputs a byte slice representation of the PAT func (p *PAT) Bytes() []byte { out := make([]byte, PATLen) out[0] = byte(p.Pn >> 8) out[1] = byte(p.Pn) out[2] = 0xe0 | (0x1f & byte(p.Pmpid>>8)) out[3] = byte(p.Pmpid) return out } // Bytes outputs a byte slice representation of the PMT func (p *PMT) Bytes() []byte { out := make([]byte, PMTDefLen) out[0] = 0xe0 | (0x1f & byte(p.Pcrpid>>8)) out[1] = byte(p.Pcrpid) out[2] = 0xf0 | (0x03 & byte(p.Pil>>8)) out[3] = byte(p.Pil) for _, d := range p.Pd { out = append(out, d.Bytes()...) } out = append(out, p.Essd.Bytes()...) return out } // Bytes outputs a byte slice representation of the Desc func (d *Desc) Bytes() []byte { out := make([]byte, DescDefLen) out[0] = d.Dt out[1] = d.Dl out = append(out, d.Dd...) return out } // Bytes outputs a byte slice representation of the ESSD func (e *ESSD) Bytes() []byte { out := make([]byte, ESSDDefLen) out[0] = e.St out[1] = 0xe0 | (0x1f & byte(e.Epid>>8)) out[2] = byte(e.Epid) out[3] = 0xf0 | (0x03 & byte(e.Esil>>8)) out[4] = byte(e.Esil) for _, d := range e.Esd { out = append(out, d.Bytes()...) } return out } func boolToByte(b bool) (o byte) { if b { o = 0x01 } return } func byteToBool(b byte) (o bool) { if b != 0 { o = true } return } func crc32_MakeTable(poly uint32) *crc32.Table { var t crc32.Table for i := range t { crc := uint32(i) << 24 for j := 0; j < 8; j++ { if crc&0x80000000 != 0 { crc = (crc << 1) ^ poly } else { crc <<= 1 } } t[i] = crc } return &t } func crc32_Update(crc uint32, tab *crc32.Table, p []byte) uint32 { for _, v := range p { crc = tab[byte(crc>>24)^v] ^ (crc << 8) } return crc } // UpdateTimestamp func UpdateTimestamp(data []byte, t int) error { psi := ReadPSI(data) if psi.Tid != PATTableID { return errors.New("Timestamp update failed because psi is not a PMT") } return nil }