/* NAME mpegts_generator.go DESCRIPTION See Readme.md AUTHOR Saxon Nelson-Milton LICENSE mpegts_generator.go is Copyright (C) 2017 the Australian Ocean Lab (AusOcean) It is free software: you can redistribute it and/or modify them under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. It is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with revid in gpl.txt. If not, see http://www.gnu.org/licenses. */ package generator import ( "bitbucket.org/ausocean/av/mpegts" "bitbucket.org/ausocean/av/pes" ) const psiPacketSize = 184 // TODO: really need to finish the at and pmt stuff - this is too hacky var ( patTable = []byte{0, 0, 176, 13, 0, 1, 193, 0, 0, 0, 1, 240, 0, 42, 177, 4, 178} pmtTable = []byte{0, 2, 176, 18, 0, 1, 193, 0, 0, 0xE1, 0x00, 0xF0, 0, 0x1B, 0xE1, 0, 0xF0, 0, 0x15, 0xBD, 0x4D, 0x56} ) // genPatAndPmt generates the rest of the pat and pmt tables i.e. fills them // with 0xFFs - because it looks ugly to hardcode above. This is called through // NewMpegtsgenerator func genPatAndPmt() { for len(patTable) < psiPacketSize { patTable = append(patTable, 255) } for len(pmtTable) < psiPacketSize { pmtTable = append(pmtTable, 255) } } const ( SdtPid = 17 PatPid = 0 pmtPid = 4096 videoPid = 256 streamID = 0xE0 outputChanSize = 100 inputChanSize = 10000 pesPktChanSize = 1000 payloadByteChanSize = 200000 ptsOffset = .7 maxCC = 15 ) // tsGenerator encapsulates properties of an mpegts generator. type tsGenerator struct { outputChan chan []byte nalInputChan chan []byte currentTsPacket *mpegts.MpegTsPacket payloadByteChan chan byte currentCC byte currentPtsTime float64 currentPcrTime float64 fps uint pesPktChan chan []byte ccMap map[int]int isGenerating bool } // InputChan returns a handle to the nalInputChan (inputChan) so that nal units // can be passed to the generator and processed func (g *tsGenerator) InputChan() chan []byte { return g.nalInputChan } // OutputChan returns a handle to the generator output chan where the mpegts // packets will show up once ready to go func (g *tsGenerator) OutputChan() <-chan []byte { return g.outputChan } // NewTsGenerator returns an instance of the tsGenerator struct func NewTsGenerator(fps uint) (g *tsGenerator) { g = new(tsGenerator) g.outputChan = make(chan []byte, outputChanSize) g.nalInputChan = make(chan []byte, inputChanSize) g.currentCC = 0 g.fps = fps g.currentPcrTime = 0.0 g.currentPtsTime = ptsOffset g.pesPktChan = make(chan []byte, pesPktChanSize) g.payloadByteChan = make(chan byte, payloadByteChanSize) g.ccMap = make(map[int]int, 4) g.ccMap[SdtPid] = 0 g.ccMap[PatPid] = 0 g.ccMap[pmtPid] = 0 g.ccMap[videoPid] = 0 genPatAndPmt() g.isGenerating = false return } // getPts retuns the next presentation timestamp for the tsGenerator t. func (g *tsGenerator) genPts() (pts uint64) { pts = uint64(g.currentPtsTime * float64(90000)) g.currentPtsTime += 1.0 / float64(g.fps) return } // genPcr returns the next program clock reference for the tsGenerator g func (g *tsGenerator) genPcr() (pcr uint64) { pcr = uint64(g.currentPcrTime * float64(90000)) g.currentPcrTime += 1.0 / float64(g.fps) return } // Start is called when we would like generation to begin, i.e. we would like // the generator to start taking input data and creating mpegts packets func (g *tsGenerator) Start() { g.isGenerating = true go g.generate() } func (g *tsGenerator) Stop() { g.isGenerating = false } // getCC returns the next continuity counter for a particular pid func (g *tsGenerator) getCC(pid int) int { temp := g.ccMap[pid] if g.ccMap[pid]++; g.ccMap[pid] > maxCC { g.ccMap[pid] = 0 } return temp } // generate handles the incoming data and generates equivalent mpegts packets - // sending them to the output channel func (g *tsGenerator) generate() { for { select { case nalUnit := <-g.nalInputChan: pesPkt := pes.PESPacket{ StreamID: streamID, PDI: byte(2), PTS: g.genPts(), Data: nalUnit, HeaderLength: 5, } pesPktSlice := pesPkt.ToByteSlice() for ii := range pesPktSlice { g.payloadByteChan <- pesPktSlice[ii] } pusi := true for len(g.payloadByteChan) > 0 { pkt := mpegts.MpegTsPacket{ PUSI: pusi, PID: videoPid, RAI: pusi, CC: byte(g.getCC(videoPid)), AFC: byte(3), PCRF: pusi, } pkt.FillPayload(g.payloadByteChan) // TODO: create consts for AFC parameters if pusi { // Create pat table patPkt := mpegts.MpegTsPacket{ PUSI: pusi, PID: PatPid, CC: byte(g.getCC(PatPid)), AFC: 1, Payload: patTable, } g.outputChan <- patPkt.ToByteSlice() // Create pmt table pmtPkt := mpegts.MpegTsPacket{ PUSI: pusi, PID: pmtPid, CC: byte(g.getCC(pmtPid)), AFC: 1, Payload: pmtTable, } g.outputChan <- pmtPkt.ToByteSlice() // If pusi then we need to gen a pcr pkt.PCR = g.genPcr() pusi = false } g.outputChan <- pkt.ToByteSlice() } } } }