|
|
|
package tsio
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"gitlab.com/ics_cinnamon/joy4/utils/bits/pio"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
StreamIdH264 = 0xe0
|
|
|
|
StreamIdAAC = 0xc0
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
PAT_PID = 0
|
|
|
|
PMT_PID = 0x1000
|
|
|
|
)
|
|
|
|
|
|
|
|
const TableIdPMT = 2
|
|
|
|
const TableExtPMT = 1
|
|
|
|
|
|
|
|
const TableIdPAT = 0
|
|
|
|
const TableExtPAT = 1
|
|
|
|
|
|
|
|
const MaxPESHeaderLength = 19
|
|
|
|
const MaxTSHeaderLength = 12
|
|
|
|
|
|
|
|
var ErrPESHeader = fmt.Errorf("invalid PES header")
|
|
|
|
var ErrPSIHeader = fmt.Errorf("invalid PSI header")
|
|
|
|
var ErrParsePMT = fmt.Errorf("invalid PMT")
|
|
|
|
var ErrParsePAT = fmt.Errorf("invalid PAT")
|
|
|
|
|
|
|
|
const (
|
|
|
|
ElementaryStreamTypeH264 = 0x1B
|
|
|
|
ElementaryStreamTypeAdtsAAC = 0x0F
|
|
|
|
)
|
|
|
|
|
|
|
|
type PATEntry struct {
|
|
|
|
ProgramNumber uint16
|
|
|
|
NetworkPID uint16
|
|
|
|
ProgramMapPID uint16
|
|
|
|
}
|
|
|
|
|
|
|
|
type PAT struct {
|
|
|
|
Entries []PATEntry
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self PAT) Len() (n int) {
|
|
|
|
return len(self.Entries) * 4
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self PAT) Marshal(b []byte) (n int) {
|
|
|
|
for _, entry := range self.Entries {
|
|
|
|
pio.PutU16BE(b[n:], entry.ProgramNumber)
|
|
|
|
n += 2
|
|
|
|
if entry.ProgramNumber == 0 {
|
|
|
|
pio.PutU16BE(b[n:], entry.NetworkPID&0x1fff|7<<13)
|
|
|
|
n += 2
|
|
|
|
} else {
|
|
|
|
pio.PutU16BE(b[n:], entry.ProgramMapPID&0x1fff|7<<13)
|
|
|
|
n += 2
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *PAT) Unmarshal(b []byte) (n int, err error) {
|
|
|
|
for n < len(b) {
|
|
|
|
if n+4 <= len(b) {
|
|
|
|
var entry PATEntry
|
|
|
|
entry.ProgramNumber = pio.U16BE(b[n:])
|
|
|
|
n += 2
|
|
|
|
if entry.ProgramNumber == 0 {
|
|
|
|
entry.NetworkPID = pio.U16BE(b[n:]) & 0x1fff
|
|
|
|
n += 2
|
|
|
|
} else {
|
|
|
|
entry.ProgramMapPID = pio.U16BE(b[n:]) & 0x1fff
|
|
|
|
n += 2
|
|
|
|
}
|
|
|
|
self.Entries = append(self.Entries, entry)
|
|
|
|
} else {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if n < len(b) {
|
|
|
|
err = ErrParsePAT
|
|
|
|
return
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
type Descriptor struct {
|
|
|
|
Tag uint8
|
|
|
|
Data []byte
|
|
|
|
}
|
|
|
|
|
|
|
|
type ElementaryStreamInfo struct {
|
|
|
|
StreamType uint8
|
|
|
|
ElementaryPID uint16
|
|
|
|
Descriptors []Descriptor
|
|
|
|
}
|
|
|
|
|
|
|
|
type PMT struct {
|
|
|
|
PCRPID uint16
|
|
|
|
ProgramDescriptors []Descriptor
|
|
|
|
ElementaryStreamInfos []ElementaryStreamInfo
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self PMT) Len() (n int) {
|
|
|
|
// 111(3)
|
|
|
|
// PCRPID(13)
|
|
|
|
n += 2
|
|
|
|
|
|
|
|
// desclen(16)
|
|
|
|
n += 2
|
|
|
|
|
|
|
|
for _, desc := range self.ProgramDescriptors {
|
|
|
|
n += 2 + len(desc.Data)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, info := range self.ElementaryStreamInfos {
|
|
|
|
// streamType
|
|
|
|
n += 1
|
|
|
|
|
|
|
|
// Reserved(3)
|
|
|
|
// Elementary PID(13)
|
|
|
|
n += 2
|
|
|
|
|
|
|
|
// Reserved(6)
|
|
|
|
// ES Info length length(10)
|
|
|
|
n += 2
|
|
|
|
|
|
|
|
for _, desc := range info.Descriptors {
|
|
|
|
n += 2 + len(desc.Data)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self PMT) fillDescs(b []byte, descs []Descriptor) (n int) {
|
|
|
|
for _, desc := range descs {
|
|
|
|
b[n] = desc.Tag
|
|
|
|
n++
|
|
|
|
b[n] = uint8(len(desc.Data))
|
|
|
|
n++
|
|
|
|
copy(b[n:], desc.Data)
|
|
|
|
n += len(desc.Data)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self PMT) Marshal(b []byte) (n int) {
|
|
|
|
// 111(3)
|
|
|
|
// PCRPID(13)
|
|
|
|
pio.PutU16BE(b[n:], self.PCRPID|7<<13)
|
|
|
|
n += 2
|
|
|
|
|
|
|
|
hold := n
|
|
|
|
n += 2
|
|
|
|
pos := n
|
|
|
|
n += self.fillDescs(b[n:], self.ProgramDescriptors)
|
|
|
|
desclen := n - pos
|
|
|
|
pio.PutU16BE(b[hold:], uint16(desclen)|0xf<<12)
|
|
|
|
|
|
|
|
for _, info := range self.ElementaryStreamInfos {
|
|
|
|
b[n] = info.StreamType
|
|
|
|
n++
|
|
|
|
|
|
|
|
// Reserved(3)
|
|
|
|
// Elementary PID(13)
|
|
|
|
pio.PutU16BE(b[n:], info.ElementaryPID|7<<13)
|
|
|
|
n += 2
|
|
|
|
|
|
|
|
hold := n
|
|
|
|
n += 2
|
|
|
|
pos := n
|
|
|
|
n += self.fillDescs(b[n:], info.Descriptors)
|
|
|
|
desclen := n - pos
|
|
|
|
pio.PutU16BE(b[hold:], uint16(desclen)|0x3c<<10)
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self PMT) parseDescs(b []byte) (descs []Descriptor, err error) {
|
|
|
|
n := 0
|
|
|
|
for n < len(b) {
|
|
|
|
if n+2 <= len(b) {
|
|
|
|
desc := Descriptor{}
|
|
|
|
desc.Tag = b[n]
|
|
|
|
desc.Data = make([]byte, b[n+1])
|
|
|
|
n += 2
|
|
|
|
if n+len(desc.Data) < len(b) {
|
|
|
|
copy(desc.Data, b[n:])
|
|
|
|
descs = append(descs, desc)
|
|
|
|
n += len(desc.Data)
|
|
|
|
} else {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if n < len(b) {
|
|
|
|
err = ErrParsePMT
|
|
|
|
return
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *PMT) Unmarshal(b []byte) (n int, err error) {
|
|
|
|
if len(b) < n+4 {
|
|
|
|
err = ErrParsePMT
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// 111(3)
|
|
|
|
// PCRPID(13)
|
|
|
|
self.PCRPID = pio.U16BE(b[0:2]) & 0x1fff
|
|
|
|
n += 2
|
|
|
|
|
|
|
|
// Reserved(4)=0xf
|
|
|
|
// Reserved(2)=0x0
|
|
|
|
// Program info length(10)
|
|
|
|
desclen := int(pio.U16BE(b[2:4]) & 0x3ff)
|
|
|
|
n += 2
|
|
|
|
|
|
|
|
if desclen > 0 {
|
|
|
|
if len(b) < n+desclen {
|
|
|
|
err = ErrParsePMT
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if self.ProgramDescriptors, err = self.parseDescs(b[n : n+desclen]); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
n += desclen
|
|
|
|
}
|
|
|
|
|
|
|
|
for n < len(b) {
|
|
|
|
if len(b) < n+5 {
|
|
|
|
err = ErrParsePMT
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var info ElementaryStreamInfo
|
|
|
|
info.StreamType = b[n]
|
|
|
|
n++
|
|
|
|
|
|
|
|
// Reserved(3)
|
|
|
|
// Elementary PID(13)
|
|
|
|
info.ElementaryPID = pio.U16BE(b[n:]) & 0x1fff
|
|
|
|
n += 2
|
|
|
|
|
|
|
|
// Reserved(6)
|
|
|
|
// ES Info length(10)
|
|
|
|
desclen := int(pio.U16BE(b[n:]) & 0x3ff)
|
|
|
|
n += 2
|
|
|
|
|
|
|
|
if desclen > 0 {
|
|
|
|
if len(b) < n+desclen {
|
|
|
|
err = ErrParsePMT
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if info.Descriptors, err = self.parseDescs(b[n : n+desclen]); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
n += desclen
|
|
|
|
}
|
|
|
|
|
|
|
|
self.ElementaryStreamInfos = append(self.ElementaryStreamInfos, info)
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func ParsePSI(h []byte) (tableid uint8, tableext uint16, hdrlen int, datalen int, err error) {
|
|
|
|
if len(h) < 8 {
|
|
|
|
err = ErrPSIHeader
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// pointer(8)
|
|
|
|
pointer := h[0]
|
|
|
|
hdrlen++
|
|
|
|
if pointer > 0 {
|
|
|
|
hdrlen += int(pointer)
|
|
|
|
if len(h) < hdrlen {
|
|
|
|
err = ErrPSIHeader
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(h) < hdrlen+12 {
|
|
|
|
err = ErrPSIHeader
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// table_id(8)
|
|
|
|
tableid = h[hdrlen]
|
|
|
|
hdrlen++
|
|
|
|
|
|
|
|
// section_syntax_indicator(1)=1,private_bit(1)=0,reserved(2)=3,unused(2)=0,section_length(10)
|
|
|
|
datalen = int(pio.U16BE(h[hdrlen:]))&0x3ff - 9
|
|
|
|
hdrlen += 2
|
|
|
|
|
|
|
|
if datalen < 0 {
|
|
|
|
err = ErrPSIHeader
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Table ID extension(16)
|
|
|
|
tableext = pio.U16BE(h[hdrlen:])
|
|
|
|
hdrlen += 2
|
|
|
|
|
|
|
|
// resverd(2)=3
|
|
|
|
// version(5)
|
|
|
|
// Current_next_indicator(1)
|
|
|
|
hdrlen++
|
|
|
|
|
|
|
|
// section_number(8)
|
|
|
|
hdrlen++
|
|
|
|
|
|
|
|
// last_section_number(8)
|
|
|
|
hdrlen++
|
|
|
|
|
|
|
|
// data
|
|
|
|
|
|
|
|
// crc(32)
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
const PSIHeaderLength = 9
|
|
|
|
|
|
|
|
func FillPSI(h []byte, tableid uint8, tableext uint16, datalen int) (n int) {
|
|
|
|
// pointer(8)
|
|
|
|
h[n] = 0
|
|
|
|
n++
|
|
|
|
|
|
|
|
// table_id(8)
|
|
|
|
h[n] = tableid
|
|
|
|
n++
|
|
|
|
|
|
|
|
// section_syntax_indicator(1)=1,private_bit(1)=0,reserved(2)=3,unused(2)=0,section_length(10)
|
|
|
|
pio.PutU16BE(h[n:], uint16(0xa<<12|2+3+4+datalen))
|
|
|
|
n += 2
|
|
|
|
|
|
|
|
// Table ID extension(16)
|
|
|
|
pio.PutU16BE(h[n:], tableext)
|
|
|
|
n += 2
|
|
|
|
|
|
|
|
// resverd(2)=3,version(5)=0,Current_next_indicator(1)=1
|
|
|
|
h[n] = 0x3<<6 | 1
|
|
|
|
n++
|
|
|
|
|
|
|
|
// section_number(8)
|
|
|
|
h[n] = 0
|
|
|
|
n++
|
|
|
|
|
|
|
|
// last_section_number(8)
|
|
|
|
h[n] = 0
|
|
|
|
n++
|
|
|
|
|
|
|
|
n += datalen
|
|
|
|
|
|
|
|
crc := calcCRC32(0xffffffff, h[1:n])
|
|
|
|
pio.PutU32LE(h[n:], crc)
|
|
|
|
n += 4
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func TimeToPCR(tm time.Duration) (pcr uint64) {
|
|
|
|
// base(33)+resverd(6)+ext(9)
|
|
|
|
ts := uint64(tm * PCR_HZ / time.Second)
|
|
|
|
base := ts / 300
|
|
|
|
ext := ts % 300
|
|
|
|
pcr = base<<15 | 0x3f<<9 | ext
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func PCRToTime(pcr uint64) (tm time.Duration) {
|
|
|
|
base := pcr >> 15
|
|
|
|
ext := pcr & 0x1ff
|
|
|
|
ts := base*300 + ext
|
|
|
|
tm = time.Duration(ts) * time.Second / time.Duration(PCR_HZ)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func TimeToTs(tm time.Duration) (v uint64) {
|
|
|
|
ts := uint64(tm * PTS_HZ / time.Second)
|
|
|
|
// 0010 PTS 32..30 1 PTS 29..15 1 PTS 14..00 1
|
|
|
|
v = ((ts>>30)&0x7)<<33 | ((ts>>15)&0x7fff)<<17 | (ts&0x7fff)<<1 | 0x100010001
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func TsToTime(v uint64) (tm time.Duration) {
|
|
|
|
// 0010 PTS 32..30 1 PTS 29..15 1 PTS 14..00 1
|
|
|
|
ts := (((v >> 33) & 0x7) << 30) | (((v >> 17) & 0x7fff) << 15) | ((v >> 1) & 0x7fff)
|
|
|
|
tm = time.Duration(ts) * time.Second / time.Duration(PTS_HZ)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
|
|
|
PTS_HZ = 90000
|
|
|
|
PCR_HZ = 27000000
|
|
|
|
)
|
|
|
|
|
|
|
|
func ParsePESHeader(h []byte) (hdrlen int, streamid uint8, datalen int, pts, dts time.Duration, err error) {
|
|
|
|
if h[0] != 0 || h[1] != 0 || h[2] != 1 {
|
|
|
|
err = ErrPESHeader
|
|
|
|
return
|
|
|
|
}
|
|
|
|
streamid = h[3]
|
|
|
|
|
|
|
|
flags := h[7]
|
|
|
|
hdrlen = int(h[8]) + 9
|
|
|
|
|
|
|
|
datalen = int(pio.U16BE(h[4:6]))
|
|
|
|
if datalen > 0 {
|
|
|
|
datalen -= int(h[8]) + 3
|
|
|
|
}
|
|
|
|
|
|
|
|
const PTS = 1 << 7
|
|
|
|
const DTS = 1 << 6
|
|
|
|
|
|
|
|
if flags&PTS != 0 {
|
|
|
|
if len(h) < 14 {
|
|
|
|
err = ErrPESHeader
|
|
|
|
return
|
|
|
|
}
|
|
|
|
pts = TsToTime(pio.U40BE(h[9:14]))
|
|
|
|
if flags&DTS != 0 {
|
|
|
|
if len(h) < 19 {
|
|
|
|
err = ErrPESHeader
|
|
|
|
return
|
|
|
|
}
|
|
|
|
dts = TsToTime(pio.U40BE(h[14:19]))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func FillPESHeader(h []byte, streamid uint8, datalen int, pts, dts time.Duration) (n int) {
|
|
|
|
h[0] = 0
|
|
|
|
h[1] = 0
|
|
|
|
h[2] = 1
|
|
|
|
h[3] = streamid
|
|
|
|
|
|
|
|
const PTS = 1 << 7
|
|
|
|
const DTS = 1 << 6
|
|
|
|
|
|
|
|
var flags uint8
|
|
|
|
if pts != 0 {
|
|
|
|
flags |= PTS
|
|
|
|
if dts != 0 {
|
|
|
|
flags |= DTS
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if flags&PTS != 0 {
|
|
|
|
n += 5
|
|
|
|
}
|
|
|
|
if flags&DTS != 0 {
|
|
|
|
n += 5
|
|
|
|
}
|
|
|
|
|
|
|
|
// packet_length(16) if zero then variable length
|
|
|
|
// Specifies the number of bytes remaining in the packet after this field. Can be zero.
|
|
|
|
// If the PES packet length is set to zero, the PES packet can be of any length.
|
|
|
|
// A value of zero for the PES packet length can be used only when the PES packet payload is a **video** elementary stream.
|
|
|
|
var pktlen uint16
|
|
|
|
if datalen >= 0 {
|
|
|
|
pktlen = uint16(datalen + n + 3)
|
|
|
|
}
|
|
|
|
pio.PutU16BE(h[4:6], pktlen)
|
|
|
|
|
|
|
|
h[6] = 2<<6 | 1 // resverd(6,2)=2,original_or_copy(0,1)=1
|
|
|
|
h[7] = flags
|
|
|
|
h[8] = uint8(n)
|
|
|
|
|
|
|
|
// pts(40)?
|
|
|
|
// dts(40)?
|
|
|
|
if flags&PTS != 0 {
|
|
|
|
if flags&DTS != 0 {
|
|
|
|
pio.PutU40BE(h[9:14], TimeToTs(pts)|3<<36)
|
|
|
|
pio.PutU40BE(h[14:19], TimeToTs(dts)|1<<36)
|
|
|
|
} else {
|
|
|
|
pio.PutU40BE(h[9:14], TimeToTs(pts)|2<<36)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
n += 9
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
type TSWriter struct {
|
|
|
|
w io.Writer
|
|
|
|
ContinuityCounter uint
|
|
|
|
tshdr []byte
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewTSWriter(pid uint16) *TSWriter {
|
|
|
|
w := &TSWriter{}
|
|
|
|
w.tshdr = make([]byte, 188)
|
|
|
|
w.tshdr[0] = 0x47
|
|
|
|
pio.PutU16BE(w.tshdr[1:3], pid&0x1fff)
|
|
|
|
for i := 6; i < 188; i++ {
|
|
|
|
w.tshdr[i] = 0xff
|
|
|
|
}
|
|
|
|
return w
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *TSWriter) WritePackets(w io.Writer, datav [][]byte, pcr time.Duration, sync bool, paddata bool) (err error) {
|
|
|
|
datavlen := pio.VecLen(datav)
|
|
|
|
writev := make([][]byte, len(datav))
|
|
|
|
writepos := 0
|
|
|
|
|
|
|
|
for writepos < datavlen {
|
|
|
|
self.tshdr[1] = self.tshdr[1] & 0x1f
|
|
|
|
self.tshdr[3] = byte(self.ContinuityCounter)&0xf | 0x30
|
|
|
|
self.tshdr[5] = 0 // flags
|
|
|
|
hdrlen := 6
|
|
|
|
self.ContinuityCounter++
|
|
|
|
|
|
|
|
if writepos == 0 {
|
|
|
|
self.tshdr[1] = 0x40 | self.tshdr[1] // Payload Unit Start Indicator
|
|
|
|
if pcr != 0 {
|
|
|
|
hdrlen += 6
|
|
|
|
self.tshdr[5] = 0x10 | self.tshdr[5] // PCR flag (Discontinuity indicator 0x80)
|
|
|
|
pio.PutU48BE(self.tshdr[6:12], TimeToPCR(pcr))
|
|
|
|
}
|
|
|
|
if sync {
|
|
|
|
self.tshdr[5] = 0x40 | self.tshdr[5] // Random Access indicator
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
padtail := 0
|
|
|
|
end := writepos + 188 - hdrlen
|
|
|
|
if end > datavlen {
|
|
|
|
if paddata {
|
|
|
|
padtail = end - datavlen
|
|
|
|
} else {
|
|
|
|
hdrlen += end - datavlen
|
|
|
|
}
|
|
|
|
end = datavlen
|
|
|
|
}
|
|
|
|
n := pio.VecSliceTo(datav, writev, writepos, end)
|
|
|
|
|
|
|
|
self.tshdr[4] = byte(hdrlen) - 5 // length
|
|
|
|
if _, err = w.Write(self.tshdr[:hdrlen]); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
for i := 0; i < n; i++ {
|
|
|
|
if _, err = w.Write(writev[i]); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if padtail > 0 {
|
|
|
|
if _, err = w.Write(self.tshdr[188-padtail : 188]); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
writepos = end
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func ParseTSHeader(tshdr []byte) (pid uint16, start bool, iskeyframe bool, hdrlen int, err error) {
|
|
|
|
// https://en.wikipedia.org/wiki/MPEG_transport_stream
|
|
|
|
if tshdr[0] != 0x47 {
|
|
|
|
err = fmt.Errorf("tshdr sync invalid")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
pid = uint16((tshdr[1]&0x1f))<<8 | uint16(tshdr[2])
|
|
|
|
start = tshdr[1]&0x40 != 0
|
|
|
|
hdrlen += 4
|
|
|
|
if tshdr[3]&0x20 != 0 {
|
|
|
|
hdrlen += int(tshdr[4]) + 1
|
|
|
|
iskeyframe = tshdr[5]&0x40 != 0
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|