streaming

Media streaming and broadcast systems in Go
Log | Files | Refs | README | LICENSE

commit 0e74c5d7cb52573c3d0988c9a5dd196b5059d6b1
parent 5fa40b193f6b829e6554034f883cb61bca64a38a
Author: Oliver Lowe <o@olowe.co>
Date:   Sat,  8 Jun 2024 19:53:35 +1000

mpegts: implement packet encoding, adaptation field parsing

Diffstat:
Mmpegts/codec.go | 209++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mmpegts/mpegts.go | 28++++++++++++++++++----------
2 files changed, 225 insertions(+), 12 deletions(-)

diff --git a/mpegts/codec.go b/mpegts/codec.go @@ -44,10 +44,215 @@ func Decode(r io.Reader) (*Packet, error) { switch afc { case 0x01: p.Payload = buf[4:] + case 0x02: + p.Adaptation = parseAdaptationField(buf[4:]) case 0x03: - p.Adaptation = buf[4:] + p.Adaptation = parseAdaptationField(buf[4:]) + if p.Adaptation == nil { + p.emptyAdaptation = true + } + alen := int(buf[4]) + p.Payload = buf[4+1+alen:] default: - return &p, fmt.Errorf("cannot decode both payload and adaptation field") + return nil, fmt.Errorf("neither adaptation field or payload present") } return &p, nil } + +func parseAdaptationField(buf []byte) *Adaptation { + length := int(buf[0]) + if length == 0 { + return nil + } + buf = buf[1 : length+1] + var af Adaptation + flags := buf[0] + buf = buf[1:] + if flags&0x80 > 0 { + af.Discontinuous = true + } + if flags&0x40 > 0 { + af.RandomAccess = true + } + if flags&0x20 > 0 { + af.Priority = true + } + if flags&0x10 > 0 { + var p [6]byte + copy(p[:], buf[:6]) + pcr := parsePCR(p) + af.PCR = &pcr + buf = buf[6:] + } + if flags&0x08 > 0 { + var p [6]byte + copy(p[:], buf[:6]) + pcr := parsePCR(p) + af.OPCR = &pcr + buf = buf[6:] + } + if flags&0x04 > 0 { + af.SpliceCountdownSet = true + af.SpliceCountdown = buf[0] + buf = buf[1:] + } + if flags&0x02 > 0 { + tlen := int(buf[0]) + af.Private = buf[1:tlen] + buf = buf[tlen:] + } + if flags&0x01 > 0 { + extlen := int(buf[0]) + af.Extension = buf[1:extlen] + buf = buf[extlen:] + } + if len(buf) > 0 { + af.Stuffing = buf + } + return &af +} + +func parsePCR(a [6]byte) PCR { + b := []byte{0, 0, 0, a[4] & 0x80, a[3], a[2], a[1], a[0]} + //copy(b[:4], a[:4]) + // b[4] &= 0x80 // only want left-most bit + //var base uint64 = uint64(a[0] >> + base := binary.BigEndian.Uint64(b) + // next 6 bits reserved, so remaining 1 bit in a[5] and all of a[6] have the extension. + ext := binary.BigEndian.Uint16([]byte{a[4] & 0x01, a[5]}) + return PCR{base, ext} +} + +func Encode(w io.Writer, p *Packet) error { + buf := make([]byte, 4) + buf[0] = Sync + if p.Error { + buf[1] |= 0x80 + } + if p.PayloadStart { + buf[1] |= 0x40 + } + if p.Priority { + buf[1] |= 0x20 + } + binary.BigEndian.PutUint16(buf[1:3], uint16(p.PID)) + buf[3] |= byte(p.Scrambling) + if p.Adaptation != nil || p.emptyAdaptation { + buf[3] |= 0x20 + } + if p.Payload != nil { + buf[3] |= 0x10 + } + if p.Continuity > 15 { + return fmt.Errorf("continuity %d larger than max 4-bit integer %d", p.Continuity, 15) + } + buf[3] |= p.Continuity + + if p.Adaptation != nil { + alen := 1 // just flags + if p.Adaptation.PCR != nil { + alen += 6 + } + if p.Adaptation.OPCR != nil { + alen += 6 + } + if p.Adaptation.SpliceCountdownSet { + alen++ // single byte + } + if p.Adaptation.Private != nil { + alen++ // 1 byte to store length of private + alen += len(p.Adaptation.Private) + } + alen += len(p.Adaptation.Extension) + alen += len(p.Adaptation.Stuffing) + if alen > 255 { + return fmt.Errorf("adaptation field too long: have %d bytes, max %d", alen, 255) + } + + abuf := make([]byte, 1+alen) // length + total + abuf[0] = uint8(alen) + var i int = 2 // cursor; after length and flags + if p.Adaptation.Discontinuous { + abuf[1] |= 0x80 + } + if p.Adaptation.RandomAccess { + abuf[1] |= 0x40 + } + if p.Adaptation.Priority { + abuf[1] |= 0x20 + } + if p.Adaptation.PCR != nil { + abuf[1] |= 0x10 + if err := putPCR(abuf[i:i+6], p.Adaptation.PCR); err != nil { + return fmt.Errorf("pack PCR: %w", err) + } + i += 6 + } + if p.Adaptation.OPCR != nil { + abuf[1] |= 0x08 + if err := putPCR(abuf[i:i+6], p.Adaptation.OPCR); err != nil { + return fmt.Errorf("pack OPCR: %w", err) + } + i += 6 + } + if p.Adaptation.SpliceCountdownSet { + abuf[1] |= 0x04 + abuf[i] = p.Adaptation.SpliceCountdown + i++ + } + if p.Adaptation.Private != nil { + abuf[1] |= 0x02 + if len(p.Adaptation.Private) > 255 { + return fmt.Errorf("private data length %d longer than max %d", len(p.Adaptation.Private), 255) + } + abuf[i] = byte(len(p.Adaptation.Private)) + i++ + copy(abuf[i:], p.Adaptation.Private) + i += len(p.Adaptation.Private) + } + if p.Adaptation.Extension != nil { + abuf[1] |= 0x01 + copy(abuf[i:], p.Adaptation.Extension) + i += len(p.Adaptation.Extension) + } + if p.Adaptation.Stuffing != nil { + copy(abuf[i:], p.Adaptation.Stuffing) + } + buf = append(buf, abuf...) + } else if p.emptyAdaptation { + // no adaptation field to encode, but we need to store an adaptation field length of 0. + buf = append(buf, 0) + } + if p.Payload != nil { + buf = append(buf, p.Payload...) + } + if len(buf) != PacketSize { + return fmt.Errorf("bad encoded packet length %d", len(buf)) + } + _, err := w.Write(buf) + return err +} + +func putPCR(b []byte, pcr *PCR) error { + if len(b) != 6 { + return fmt.Errorf("need %d bytes, got %d", 6, len(b)) + } + max := uint64(8589934592 - 1) // max 33-bit int + if pcr.Base > max { + return fmt.Errorf("base %d larger than max %d", pcr.Base, max) + } + b[0] = byte(pcr.Base) + b[1] = byte(pcr.Base >> 8) + b[2] = byte(pcr.Base >> 16) + b[3] = byte(pcr.Base >> 24) + b[4] = byte(pcr.Base >> 32) + // next 6 bits are reserved + + var emax uint16 = 512 - 1 // max 9-bit int + if pcr.Extension > emax { + return fmt.Errorf("extension %d larger than max %d", pcr.Extension, emax) + } + b[4] |= byte(pcr.Extension >> 8) + b[5] = byte(pcr.Extension) + return nil +} diff --git a/mpegts/mpegts.go b/mpegts/mpegts.go @@ -18,10 +18,11 @@ type Packet struct { // Identifies the type of the packet. PID PacketID // Specifies which algorithm, if any, is used to encrypt the payload. - Scrambling Scramble - Continuity uint8 - Adaptation []byte - Payload []byte + Scrambling Scramble + Continuity uint8 + Adaptation *Adaptation + Payload []byte + emptyAdaptation bool } type Scramble uint8 @@ -69,8 +70,9 @@ type Adaptation struct { // randomly access other points in the stream. RandomAccess bool // Whether this stream is high priority. - Priority bool - PCR *PCR + Priority bool + SpliceCountdownSet bool + PCR *PCR // Original program clock reference. OPCR *PCR // The number of packets remaining until a splicing point. @@ -84,10 +86,16 @@ type Adaptation struct { Stuffing []byte } -// PCR represents a Program Clock Reference +// PCR represents a Program Clock Reference. type PCR struct { - // Number of ticks of a 27MHz clock as a 33-bit integer. - Ticks uint64 - // 9-bit integer + // 33-bit integer holding the number of ticks of a 90KHz clock. + Base uint64 + // 9-bit integer holding 27MHz ticks, intended as a + // constant addition to Base. Extension uint16 } + +// Returns the number of ticks of a 27MHz clock in p. +func (p PCR) Ticks() uint64 { + return p.Base*300 + uint64(p.Extension) +}