|
|
|
package ts
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"gitlab.com/ics_cinnamon/joy4/av"
|
|
|
|
"gitlab.com/ics_cinnamon/joy4/codec/aacparser"
|
|
|
|
"gitlab.com/ics_cinnamon/joy4/codec/h264parser"
|
|
|
|
"gitlab.com/ics_cinnamon/joy4/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
|
|
|
|
}
|