first commit
This commit is contained in:
		
							
								
								
									
										291
									
								
								format/ts/demuxer.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										291
									
								
								format/ts/demuxer.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,291 @@ | ||||
| package ts | ||||
|  | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/deepch/vdk/av" | ||||
| 	"github.com/deepch/vdk/codec/aacparser" | ||||
| 	"github.com/deepch/vdk/codec/h264parser" | ||||
| 	"github.com/deepch/vdk/format/ts/tsio" | ||||
| 	"github.com/deepch/vdk/utils/bits/pio" | ||||
| ) | ||||
|  | ||||
| type Demuxer struct { | ||||
| 	r *bufio.Reader | ||||
|  | ||||
| 	pkts []av.Packet | ||||
|  | ||||
| 	pat     *tsio.PAT | ||||
| 	pmt     *tsio.PMT | ||||
| 	streams []*Stream | ||||
| 	tshdr   []byte | ||||
|  | ||||
| 	stage int | ||||
| } | ||||
|  | ||||
| func NewDemuxer(r io.Reader) *Demuxer { | ||||
| 	return &Demuxer{ | ||||
| 		tshdr: make([]byte, 188), | ||||
| 		r:     bufio.NewReaderSize(r, pio.RecommendBufioSize), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (self *Demuxer) Streams() (streams []av.CodecData, err error) { | ||||
| 	if err = self.probe(); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	for _, stream := range self.streams { | ||||
| 		streams = append(streams, stream.CodecData) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (self *Demuxer) probe() (err error) { | ||||
| 	if self.stage == 0 { | ||||
| 		for { | ||||
| 			if self.pmt != nil { | ||||
| 				n := 0 | ||||
| 				for _, stream := range self.streams { | ||||
| 					if stream.CodecData != nil { | ||||
| 						n++ | ||||
| 					} | ||||
| 				} | ||||
| 				if n == len(self.streams) { | ||||
| 					break | ||||
| 				} | ||||
| 			} | ||||
| 			if err = self.poll(); err != nil { | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 		self.stage++ | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (self *Demuxer) ReadPacket() (pkt av.Packet, err error) { | ||||
| 	if err = self.probe(); err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	for len(self.pkts) == 0 { | ||||
| 		if err = self.poll(); err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	pkt = self.pkts[0] | ||||
| 	self.pkts = self.pkts[1:] | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (self *Demuxer) poll() (err error) { | ||||
| 	if err = self.readTSPacket(); err == io.EOF { | ||||
| 		var n int | ||||
| 		if n, err = self.payloadEnd(); err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 		if n == 0 { | ||||
| 			err = io.EOF | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (self *Demuxer) initPMT(payload []byte) (err error) { | ||||
| 	var psihdrlen int | ||||
| 	var datalen int | ||||
| 	if _, _, psihdrlen, datalen, err = tsio.ParsePSI(payload); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	self.pmt = &tsio.PMT{} | ||||
| 	if _, err = self.pmt.Unmarshal(payload[psihdrlen : psihdrlen+datalen]); err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	self.streams = []*Stream{} | ||||
| 	for i, info := range self.pmt.ElementaryStreamInfos { | ||||
| 		stream := &Stream{} | ||||
| 		stream.idx = i | ||||
| 		stream.demuxer = self | ||||
| 		stream.pid = info.ElementaryPID | ||||
| 		stream.streamType = info.StreamType | ||||
| 		switch info.StreamType { | ||||
| 		case tsio.ElementaryStreamTypeH264: | ||||
| 			self.streams = append(self.streams, stream) | ||||
| 		case tsio.ElementaryStreamTypeAdtsAAC: | ||||
| 			self.streams = append(self.streams, stream) | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (self *Demuxer) payloadEnd() (n int, err error) { | ||||
| 	for _, stream := range self.streams { | ||||
| 		var i int | ||||
| 		if i, err = stream.payloadEnd(); err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 		n += i | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (self *Demuxer) readTSPacket() (err error) { | ||||
| 	var hdrlen int | ||||
| 	var pid uint16 | ||||
| 	var start bool | ||||
| 	var iskeyframe bool | ||||
|  | ||||
| 	if _, err = io.ReadFull(self.r, self.tshdr); err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if pid, start, iskeyframe, hdrlen, err = tsio.ParseTSHeader(self.tshdr); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	payload := self.tshdr[hdrlen:] | ||||
|  | ||||
| 	if self.pat == nil { | ||||
| 		if pid == 0 { | ||||
| 			var psihdrlen int | ||||
| 			var datalen int | ||||
| 			if _, _, psihdrlen, datalen, err = tsio.ParsePSI(payload); err != nil { | ||||
| 				return | ||||
| 			} | ||||
| 			self.pat = &tsio.PAT{} | ||||
| 			if _, err = self.pat.Unmarshal(payload[psihdrlen : psihdrlen+datalen]); err != nil { | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 	} else if self.pmt == nil { | ||||
| 		for _, entry := range self.pat.Entries { | ||||
| 			if entry.ProgramMapPID == pid { | ||||
| 				if err = self.initPMT(payload); err != nil { | ||||
| 					return | ||||
| 				} | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 	} else { | ||||
| 		for _, stream := range self.streams { | ||||
| 			if pid == stream.pid { | ||||
| 				if err = stream.handleTSPacket(start, iskeyframe, payload); err != nil { | ||||
| 					return | ||||
| 				} | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (self *Stream) addPacket(payload []byte, timedelta time.Duration) { | ||||
| 	dts := self.dts | ||||
| 	pts := self.pts | ||||
| 	if dts == 0 { | ||||
| 		dts = pts | ||||
| 	} | ||||
|  | ||||
| 	demuxer := self.demuxer | ||||
| 	pkt := av.Packet{ | ||||
| 		Idx:        int8(self.idx), | ||||
| 		IsKeyFrame: self.iskeyframe, | ||||
| 		Time:       dts + timedelta, | ||||
| 		Data:       payload, | ||||
| 	} | ||||
| 	if pts != dts { | ||||
| 		pkt.CompositionTime = pts - dts | ||||
| 	} | ||||
| 	demuxer.pkts = append(demuxer.pkts, pkt) | ||||
| } | ||||
|  | ||||
| func (self *Stream) payloadEnd() (n int, err error) { | ||||
| 	payload := self.data | ||||
| 	if payload == nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if self.datalen != 0 && len(payload) != self.datalen { | ||||
| 		err = fmt.Errorf("ts: packet size mismatch size=%d correct=%d", len(payload), self.datalen) | ||||
| 		return | ||||
| 	} | ||||
| 	self.data = nil | ||||
|  | ||||
| 	switch self.streamType { | ||||
| 	case tsio.ElementaryStreamTypeAdtsAAC: | ||||
| 		var config aacparser.MPEG4AudioConfig | ||||
|  | ||||
| 		delta := time.Duration(0) | ||||
| 		for len(payload) > 0 { | ||||
| 			var hdrlen, framelen, samples int | ||||
| 			if config, hdrlen, framelen, samples, err = aacparser.ParseADTSHeader(payload); err != nil { | ||||
| 				return | ||||
| 			} | ||||
| 			if self.CodecData == nil { | ||||
| 				if self.CodecData, err = aacparser.NewCodecDataFromMPEG4AudioConfig(config); err != nil { | ||||
| 					return | ||||
| 				} | ||||
| 			} | ||||
| 			self.addPacket(payload[hdrlen:framelen], delta) | ||||
| 			n++ | ||||
| 			delta += time.Duration(samples) * time.Second / time.Duration(config.SampleRate) | ||||
| 			payload = payload[framelen:] | ||||
| 		} | ||||
|  | ||||
| 	case tsio.ElementaryStreamTypeH264: | ||||
| 		nalus, _ := h264parser.SplitNALUs(payload) | ||||
| 		var sps, pps []byte | ||||
| 		for _, nalu := range nalus { | ||||
| 			if len(nalu) > 0 { | ||||
| 				naltype := nalu[0] & 0x1f | ||||
| 				switch { | ||||
| 				case naltype == 7: | ||||
| 					sps = nalu | ||||
| 				case naltype == 8: | ||||
| 					pps = nalu | ||||
| 				case h264parser.IsDataNALU(nalu): | ||||
| 					// raw nalu to avcc | ||||
| 					b := make([]byte, 4+len(nalu)) | ||||
| 					pio.PutU32BE(b[0:4], uint32(len(nalu))) | ||||
| 					copy(b[4:], nalu) | ||||
| 					self.addPacket(b, time.Duration(0)) | ||||
| 					n++ | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if self.CodecData == nil && len(sps) > 0 && len(pps) > 0 { | ||||
| 			if self.CodecData, err = h264parser.NewCodecDataFromSPSAndPPS(sps, pps); err != nil { | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (self *Stream) handleTSPacket(start bool, iskeyframe bool, payload []byte) (err error) { | ||||
| 	if start { | ||||
| 		if _, err = self.payloadEnd(); err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 		var hdrlen int | ||||
| 		if hdrlen, _, self.datalen, self.pts, self.dts, err = tsio.ParsePESHeader(payload); err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 		self.iskeyframe = iskeyframe | ||||
| 		if self.datalen == 0 { | ||||
| 			self.data = make([]byte, 0, 4096) | ||||
| 		} else { | ||||
| 			self.data = make([]byte, 0, self.datalen) | ||||
| 		} | ||||
| 		self.data = append(self.data, payload[hdrlen:]...) | ||||
| 	} else { | ||||
| 		self.data = append(self.data, payload...) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										26
									
								
								format/ts/handler.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								format/ts/handler.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| package ts | ||||
|  | ||||
| import ( | ||||
| 	"io" | ||||
|  | ||||
| 	"github.com/deepch/vdk/av" | ||||
| 	"github.com/deepch/vdk/av/avutil" | ||||
| ) | ||||
|  | ||||
| func Handler(h *avutil.RegisterHandler) { | ||||
| 	h.Ext = ".ts" | ||||
|  | ||||
| 	h.Probe = func(b []byte) bool { | ||||
| 		return b[0] == 0x47 && b[188] == 0x47 | ||||
| 	} | ||||
|  | ||||
| 	h.ReaderDemuxer = func(r io.Reader) av.Demuxer { | ||||
| 		return NewDemuxer(r) | ||||
| 	} | ||||
|  | ||||
| 	h.WriterMuxer = func(w io.Writer) av.Muxer { | ||||
| 		return NewMuxer(w) | ||||
| 	} | ||||
|  | ||||
| 	h.CodecTypes = CodecTypes | ||||
| } | ||||
							
								
								
									
										206
									
								
								format/ts/muxer.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										206
									
								
								format/ts/muxer.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,206 @@ | ||||
| package ts | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/deepch/vdk/av" | ||||
| 	"github.com/deepch/vdk/codec/aacparser" | ||||
| 	"github.com/deepch/vdk/codec/h264parser" | ||||
| 	"github.com/deepch/vdk/format/ts/tsio" | ||||
| ) | ||||
|  | ||||
| var CodecTypes = []av.CodecType{av.H264, av.AAC} | ||||
|  | ||||
| type Muxer struct { | ||||
| 	w                        io.Writer | ||||
| 	streams                  []*Stream | ||||
| 	PaddingToMakeCounterCont bool | ||||
|  | ||||
| 	psidata []byte | ||||
| 	peshdr  []byte | ||||
| 	tshdr   []byte | ||||
| 	adtshdr []byte | ||||
| 	datav   [][]byte | ||||
| 	nalus   [][]byte | ||||
|  | ||||
| 	tswpat, tswpmt *tsio.TSWriter | ||||
| } | ||||
|  | ||||
| func NewMuxer(w io.Writer) *Muxer { | ||||
| 	return &Muxer{ | ||||
| 		w:       w, | ||||
| 		psidata: make([]byte, 188), | ||||
| 		peshdr:  make([]byte, tsio.MaxPESHeaderLength), | ||||
| 		tshdr:   make([]byte, tsio.MaxTSHeaderLength), | ||||
| 		adtshdr: make([]byte, aacparser.ADTSHeaderLength), | ||||
| 		nalus:   make([][]byte, 16), | ||||
| 		datav:   make([][]byte, 16), | ||||
| 		tswpmt:  tsio.NewTSWriter(tsio.PMT_PID), | ||||
| 		tswpat:  tsio.NewTSWriter(tsio.PAT_PID), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (self *Muxer) newStream(codec av.CodecData) (err error) { | ||||
| 	ok := false | ||||
| 	for _, c := range CodecTypes { | ||||
| 		if codec.Type() == c { | ||||
| 			ok = true | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	if !ok { | ||||
| 		err = fmt.Errorf("ts: codec type=%s is not supported", codec.Type()) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	pid := uint16(len(self.streams) + 0x100) | ||||
| 	stream := &Stream{ | ||||
| 		muxer:     self, | ||||
| 		CodecData: codec, | ||||
| 		pid:       pid, | ||||
| 		tsw:       tsio.NewTSWriter(pid), | ||||
| 	} | ||||
| 	self.streams = append(self.streams, stream) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (self *Muxer) writePaddingTSPackets(tsw *tsio.TSWriter) (err error) { | ||||
| 	for tsw.ContinuityCounter&0xf != 0x0 { | ||||
| 		if err = tsw.WritePackets(self.w, self.datav[:0], 0, false, true); err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (self *Muxer) WriteTrailer() (err error) { | ||||
| 	if self.PaddingToMakeCounterCont { | ||||
| 		for _, stream := range self.streams { | ||||
| 			if err = self.writePaddingTSPackets(stream.tsw); err != nil { | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (self *Muxer) SetWriter(w io.Writer) { | ||||
| 	self.w = w | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (self *Muxer) WritePATPMT() (err error) { | ||||
| 	pat := tsio.PAT{ | ||||
| 		Entries: []tsio.PATEntry{ | ||||
| 			{ProgramNumber: 1, ProgramMapPID: tsio.PMT_PID}, | ||||
| 		}, | ||||
| 	} | ||||
| 	patlen := pat.Marshal(self.psidata[tsio.PSIHeaderLength:]) | ||||
| 	n := tsio.FillPSI(self.psidata, tsio.TableIdPAT, tsio.TableExtPAT, patlen) | ||||
| 	self.datav[0] = self.psidata[:n] | ||||
| 	if err = self.tswpat.WritePackets(self.w, self.datav[:1], 0, false, true); err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	var elemStreams []tsio.ElementaryStreamInfo | ||||
| 	for _, stream := range self.streams { | ||||
| 		switch stream.Type() { | ||||
| 		case av.AAC: | ||||
| 			elemStreams = append(elemStreams, tsio.ElementaryStreamInfo{ | ||||
| 				StreamType:    tsio.ElementaryStreamTypeAdtsAAC, | ||||
| 				ElementaryPID: stream.pid, | ||||
| 			}) | ||||
| 		case av.H264: | ||||
| 			elemStreams = append(elemStreams, tsio.ElementaryStreamInfo{ | ||||
| 				StreamType:    tsio.ElementaryStreamTypeH264, | ||||
| 				ElementaryPID: stream.pid, | ||||
| 			}) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	pmt := tsio.PMT{ | ||||
| 		PCRPID:                0x100, | ||||
| 		ElementaryStreamInfos: elemStreams, | ||||
| 	} | ||||
| 	pmtlen := pmt.Len() | ||||
| 	if pmtlen+tsio.PSIHeaderLength > len(self.psidata) { | ||||
| 		err = fmt.Errorf("ts: pmt too large") | ||||
| 		return | ||||
| 	} | ||||
| 	pmt.Marshal(self.psidata[tsio.PSIHeaderLength:]) | ||||
| 	n = tsio.FillPSI(self.psidata, tsio.TableIdPMT, tsio.TableExtPMT, pmtlen) | ||||
| 	self.datav[0] = self.psidata[:n] | ||||
| 	if err = self.tswpmt.WritePackets(self.w, self.datav[:1], 0, false, true); err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (self *Muxer) WriteHeader(streams []av.CodecData) (err error) { | ||||
| 	self.streams = []*Stream{} | ||||
| 	for _, stream := range streams { | ||||
| 		if err = self.newStream(stream); err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if err = self.WritePATPMT(); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (self *Muxer) WritePacket(pkt av.Packet) (err error) { | ||||
| 	stream := self.streams[pkt.Idx] | ||||
| 	pkt.Time += time.Second | ||||
|  | ||||
| 	switch stream.Type() { | ||||
| 	case av.AAC: | ||||
| 		codec := stream.CodecData.(aacparser.CodecData) | ||||
|  | ||||
| 		n := tsio.FillPESHeader(self.peshdr, tsio.StreamIdAAC, len(self.adtshdr)+len(pkt.Data), pkt.Time, 0) | ||||
| 		self.datav[0] = self.peshdr[:n] | ||||
| 		aacparser.FillADTSHeader(self.adtshdr, codec.Config, 1024, len(pkt.Data)) | ||||
| 		self.datav[1] = self.adtshdr | ||||
| 		self.datav[2] = pkt.Data | ||||
|  | ||||
| 		if err = stream.tsw.WritePackets(self.w, self.datav[:3], pkt.Time, true, false); err != nil { | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 	case av.H264: | ||||
| 		codec := stream.CodecData.(h264parser.CodecData) | ||||
|  | ||||
| 		nalus := self.nalus[:0] | ||||
| 		if pkt.IsKeyFrame { | ||||
| 			nalus = append(nalus, codec.SPS()) | ||||
| 			nalus = append(nalus, codec.PPS()) | ||||
| 		} | ||||
| 		pktnalus, _ := h264parser.SplitNALUs(pkt.Data) | ||||
| 		for _, nalu := range pktnalus { | ||||
| 			nalus = append(nalus, nalu) | ||||
| 		} | ||||
|  | ||||
| 		datav := self.datav[:1] | ||||
| 		for i, nalu := range nalus { | ||||
| 			if i == 0 { | ||||
| 				datav = append(datav, h264parser.AUDBytes) | ||||
| 			} else { | ||||
| 				datav = append(datav, h264parser.StartCodeBytes) | ||||
| 			} | ||||
| 			datav = append(datav, nalu) | ||||
| 		} | ||||
|  | ||||
| 		n := tsio.FillPESHeader(self.peshdr, tsio.StreamIdH264, -1, pkt.Time+pkt.CompositionTime, pkt.Time) | ||||
| 		datav[0] = self.peshdr[:n] | ||||
|  | ||||
| 		if err = stream.tsw.WritePackets(self.w, datav, pkt.Time, pkt.IsKeyFrame, false); err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										27
									
								
								format/ts/stream.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								format/ts/stream.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| package ts | ||||
|  | ||||
| import ( | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/deepch/vdk/av" | ||||
| 	"github.com/deepch/vdk/format/ts/tsio" | ||||
| ) | ||||
|  | ||||
| type Stream struct { | ||||
| 	av.CodecData | ||||
|  | ||||
| 	demuxer *Demuxer | ||||
| 	muxer   *Muxer | ||||
|  | ||||
| 	pid        uint16 | ||||
| 	streamId   uint8 | ||||
| 	streamType uint8 | ||||
|  | ||||
| 	tsw *tsio.TSWriter | ||||
| 	idx int | ||||
|  | ||||
| 	iskeyframe bool | ||||
| 	pts, dts   time.Duration | ||||
| 	data       []byte | ||||
| 	datalen    int | ||||
| } | ||||
							
								
								
									
										55
									
								
								format/ts/tsio/checksum.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								format/ts/tsio/checksum.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| package tsio | ||||
|  | ||||
| var ieeeCrc32Tbl = []uint32{ | ||||
| 	0x00000000, 0xB71DC104, 0x6E3B8209, 0xD926430D, 0xDC760413, 0x6B6BC517, | ||||
| 	0xB24D861A, 0x0550471E, 0xB8ED0826, 0x0FF0C922, 0xD6D68A2F, 0x61CB4B2B, | ||||
| 	0x649B0C35, 0xD386CD31, 0x0AA08E3C, 0xBDBD4F38, 0x70DB114C, 0xC7C6D048, | ||||
| 	0x1EE09345, 0xA9FD5241, 0xACAD155F, 0x1BB0D45B, 0xC2969756, 0x758B5652, | ||||
| 	0xC836196A, 0x7F2BD86E, 0xA60D9B63, 0x11105A67, 0x14401D79, 0xA35DDC7D, | ||||
| 	0x7A7B9F70, 0xCD665E74, 0xE0B62398, 0x57ABE29C, 0x8E8DA191, 0x39906095, | ||||
| 	0x3CC0278B, 0x8BDDE68F, 0x52FBA582, 0xE5E66486, 0x585B2BBE, 0xEF46EABA, | ||||
| 	0x3660A9B7, 0x817D68B3, 0x842D2FAD, 0x3330EEA9, 0xEA16ADA4, 0x5D0B6CA0, | ||||
| 	0x906D32D4, 0x2770F3D0, 0xFE56B0DD, 0x494B71D9, 0x4C1B36C7, 0xFB06F7C3, | ||||
| 	0x2220B4CE, 0x953D75CA, 0x28803AF2, 0x9F9DFBF6, 0x46BBB8FB, 0xF1A679FF, | ||||
| 	0xF4F63EE1, 0x43EBFFE5, 0x9ACDBCE8, 0x2DD07DEC, 0x77708634, 0xC06D4730, | ||||
| 	0x194B043D, 0xAE56C539, 0xAB068227, 0x1C1B4323, 0xC53D002E, 0x7220C12A, | ||||
| 	0xCF9D8E12, 0x78804F16, 0xA1A60C1B, 0x16BBCD1F, 0x13EB8A01, 0xA4F64B05, | ||||
| 	0x7DD00808, 0xCACDC90C, 0x07AB9778, 0xB0B6567C, 0x69901571, 0xDE8DD475, | ||||
| 	0xDBDD936B, 0x6CC0526F, 0xB5E61162, 0x02FBD066, 0xBF469F5E, 0x085B5E5A, | ||||
| 	0xD17D1D57, 0x6660DC53, 0x63309B4D, 0xD42D5A49, 0x0D0B1944, 0xBA16D840, | ||||
| 	0x97C6A5AC, 0x20DB64A8, 0xF9FD27A5, 0x4EE0E6A1, 0x4BB0A1BF, 0xFCAD60BB, | ||||
| 	0x258B23B6, 0x9296E2B2, 0x2F2BAD8A, 0x98366C8E, 0x41102F83, 0xF60DEE87, | ||||
| 	0xF35DA999, 0x4440689D, 0x9D662B90, 0x2A7BEA94, 0xE71DB4E0, 0x500075E4, | ||||
| 	0x892636E9, 0x3E3BF7ED, 0x3B6BB0F3, 0x8C7671F7, 0x555032FA, 0xE24DF3FE, | ||||
| 	0x5FF0BCC6, 0xE8ED7DC2, 0x31CB3ECF, 0x86D6FFCB, 0x8386B8D5, 0x349B79D1, | ||||
| 	0xEDBD3ADC, 0x5AA0FBD8, 0xEEE00C69, 0x59FDCD6D, 0x80DB8E60, 0x37C64F64, | ||||
| 	0x3296087A, 0x858BC97E, 0x5CAD8A73, 0xEBB04B77, 0x560D044F, 0xE110C54B, | ||||
| 	0x38368646, 0x8F2B4742, 0x8A7B005C, 0x3D66C158, 0xE4408255, 0x535D4351, | ||||
| 	0x9E3B1D25, 0x2926DC21, 0xF0009F2C, 0x471D5E28, 0x424D1936, 0xF550D832, | ||||
| 	0x2C769B3F, 0x9B6B5A3B, 0x26D61503, 0x91CBD407, 0x48ED970A, 0xFFF0560E, | ||||
| 	0xFAA01110, 0x4DBDD014, 0x949B9319, 0x2386521D, 0x0E562FF1, 0xB94BEEF5, | ||||
| 	0x606DADF8, 0xD7706CFC, 0xD2202BE2, 0x653DEAE6, 0xBC1BA9EB, 0x0B0668EF, | ||||
| 	0xB6BB27D7, 0x01A6E6D3, 0xD880A5DE, 0x6F9D64DA, 0x6ACD23C4, 0xDDD0E2C0, | ||||
| 	0x04F6A1CD, 0xB3EB60C9, 0x7E8D3EBD, 0xC990FFB9, 0x10B6BCB4, 0xA7AB7DB0, | ||||
| 	0xA2FB3AAE, 0x15E6FBAA, 0xCCC0B8A7, 0x7BDD79A3, 0xC660369B, 0x717DF79F, | ||||
| 	0xA85BB492, 0x1F467596, 0x1A163288, 0xAD0BF38C, 0x742DB081, 0xC3307185, | ||||
| 	0x99908A5D, 0x2E8D4B59, 0xF7AB0854, 0x40B6C950, 0x45E68E4E, 0xF2FB4F4A, | ||||
| 	0x2BDD0C47, 0x9CC0CD43, 0x217D827B, 0x9660437F, 0x4F460072, 0xF85BC176, | ||||
| 	0xFD0B8668, 0x4A16476C, 0x93300461, 0x242DC565, 0xE94B9B11, 0x5E565A15, | ||||
| 	0x87701918, 0x306DD81C, 0x353D9F02, 0x82205E06, 0x5B061D0B, 0xEC1BDC0F, | ||||
| 	0x51A69337, 0xE6BB5233, 0x3F9D113E, 0x8880D03A, 0x8DD09724, 0x3ACD5620, | ||||
| 	0xE3EB152D, 0x54F6D429, 0x7926A9C5, 0xCE3B68C1, 0x171D2BCC, 0xA000EAC8, | ||||
| 	0xA550ADD6, 0x124D6CD2, 0xCB6B2FDF, 0x7C76EEDB, 0xC1CBA1E3, 0x76D660E7, | ||||
| 	0xAFF023EA, 0x18EDE2EE, 0x1DBDA5F0, 0xAAA064F4, 0x738627F9, 0xC49BE6FD, | ||||
| 	0x09FDB889, 0xBEE0798D, 0x67C63A80, 0xD0DBFB84, 0xD58BBC9A, 0x62967D9E, | ||||
| 	0xBBB03E93, 0x0CADFF97, 0xB110B0AF, 0x060D71AB, 0xDF2B32A6, 0x6836F3A2, | ||||
| 	0x6D66B4BC, 0xDA7B75B8, 0x035D36B5, 0xB440F7B1, 0x00000001, | ||||
| } | ||||
|  | ||||
| func calcCRC32(crc uint32, data []byte) uint32 { | ||||
| 	for _, b := range data { | ||||
| 		crc = ieeeCrc32Tbl[b^byte(crc)] ^ (crc >> 8) | ||||
| 	} | ||||
| 	return crc | ||||
| } | ||||
|  | ||||
							
								
								
									
										589
									
								
								format/ts/tsio/tsio.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										589
									
								
								format/ts/tsio/tsio.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,589 @@ | ||||
| package tsio | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/deepch/vdk/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 | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 Andrey Semochkin
					Andrey Semochkin