2017-12-11 08:20:24 +03:00
|
|
|
/*
|
|
|
|
NAME
|
2018-08-19 13:59:22 +03:00
|
|
|
mpegts.go - provides a data structure intended to encapsulate the properties
|
2018-01-06 08:00:45 +03:00
|
|
|
of an MpegTs packet and also functions to allow manipulation of these packets.
|
2017-12-11 08:20:24 +03:00
|
|
|
|
|
|
|
DESCRIPTION
|
|
|
|
See Readme.md
|
|
|
|
|
|
|
|
AUTHOR
|
2018-01-07 17:32:56 +03:00
|
|
|
Saxon A. Nelson-Milton <saxon.milton@gmail.com>
|
2017-12-11 08:20:24 +03:00
|
|
|
|
|
|
|
LICENSE
|
2018-08-19 13:59:22 +03:00
|
|
|
mpegts.go is Copyright (C) 2017 the Australian Ocean Lab (AusOcean)
|
2017-12-11 08:20:24 +03:00
|
|
|
|
|
|
|
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.
|
2017-12-11 07:54:49 +03:00
|
|
|
|
2017-12-11 08:20:24 +03:00
|
|
|
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 [GNU licenses](http://www.gnu.org/licenses).
|
|
|
|
*/
|
|
|
|
|
2018-08-19 13:59:22 +03:00
|
|
|
package mts
|
2018-01-07 17:32:56 +03:00
|
|
|
|
2018-12-14 06:05:56 +03:00
|
|
|
import (
|
|
|
|
"errors"
|
2019-02-27 09:42:52 +03:00
|
|
|
"fmt"
|
2019-02-15 04:31:07 +03:00
|
|
|
|
|
|
|
"github.com/Comcast/gots/packet"
|
2018-12-14 06:05:56 +03:00
|
|
|
)
|
|
|
|
|
2019-02-07 07:36:54 +03:00
|
|
|
// General mpegts packet properties.
|
2018-01-16 08:06:51 +03:00
|
|
|
const (
|
2019-01-12 10:06:35 +03:00
|
|
|
PacketSize = 188
|
2019-01-02 02:53:02 +03:00
|
|
|
PayloadSize = 176
|
2018-01-16 08:06:51 +03:00
|
|
|
)
|
|
|
|
|
2019-02-07 07:36:54 +03:00
|
|
|
// Program ID for various types of ts packets.
|
2019-01-25 08:55:01 +03:00
|
|
|
const (
|
2019-02-07 07:36:54 +03:00
|
|
|
SdtPid = 17
|
|
|
|
PatPid = 0
|
|
|
|
PmtPid = 4096
|
|
|
|
VideoPid = 256
|
2019-01-25 08:55:01 +03:00
|
|
|
)
|
|
|
|
|
2019-02-07 07:36:54 +03:00
|
|
|
// StreamID is the id of the first stream.
|
|
|
|
const StreamID = 0xe0
|
|
|
|
|
|
|
|
// HeadSize is the size of an mpegts packet header.
|
|
|
|
const HeadSize = 4
|
|
|
|
|
|
|
|
// Consts relating to adaptation field.
|
2019-01-25 08:55:01 +03:00
|
|
|
const (
|
2019-02-15 04:31:07 +03:00
|
|
|
AdaptationIdx = 4 // Index to the adaptation field (index of AFL).
|
|
|
|
AdaptationControlIdx = 3 // Index to octet with adaptation field control.
|
|
|
|
AdaptationFieldsIdx = AdaptationIdx + 1 // Adaptation field index is the index of the adaptation fields.
|
|
|
|
DefaultAdaptationSize = 2 // Default size of the adaptation field.
|
|
|
|
AdaptationControlMask = 0x30 // Mask for the adaptation field control in octet 3.
|
|
|
|
DefaultAdaptationBodySize = 1
|
|
|
|
DiscontinuityIndicatorMask = 0x80
|
|
|
|
DiscontinuityIndicatorIdx = AdaptationIdx + 1
|
2019-01-25 08:55:01 +03:00
|
|
|
)
|
|
|
|
|
2019-02-07 07:36:54 +03:00
|
|
|
// TODO: make this better - currently doesn't make sense.
|
2019-01-25 08:55:01 +03:00
|
|
|
const (
|
|
|
|
HasPayload = 0x1
|
|
|
|
HasAdaptationField = 0x2
|
|
|
|
)
|
|
|
|
|
2018-01-06 08:00:45 +03:00
|
|
|
/*
|
|
|
|
The below data struct encapsulates the fields of an MPEG-TS packet. Below is
|
|
|
|
the formatting of an MPEG-TS packet for reference!
|
2017-12-11 09:38:09 +03:00
|
|
|
|
2018-01-06 08:00:45 +03:00
|
|
|
MPEG-TS Packet Formatting
|
|
|
|
============================================================================
|
|
|
|
| octet no | bit 0 | bit 1 | bit 2 | bit 3 | bit 4 | bit 5 | bit 6 | bit 7 |
|
|
|
|
============================================================================
|
|
|
|
| octet 0 | sync byte (0x47) |
|
|
|
|
----------------------------------------------------------------------------
|
|
|
|
| octet 1 | TEI | PUSI | Prior | PID |
|
|
|
|
----------------------------------------------------------------------------
|
|
|
|
| octet 2 | PID cont. |
|
|
|
|
----------------------------------------------------------------------------
|
|
|
|
| octet 3 | TSC | AFC | CC |
|
|
|
|
----------------------------------------------------------------------------
|
|
|
|
| octet 4 | AFL |
|
|
|
|
----------------------------------------------------------------------------
|
|
|
|
| octet 5 | DI | RAI | ESPI | PCRF | OPCRF | SPF | TPDF | AFEF |
|
|
|
|
----------------------------------------------------------------------------
|
|
|
|
| optional | PCR (48 bits => 6 bytes) |
|
|
|
|
----------------------------------------------------------------------------
|
|
|
|
| - | PCR cont. |
|
|
|
|
----------------------------------------------------------------------------
|
|
|
|
| - | PCR cont. |
|
|
|
|
----------------------------------------------------------------------------
|
|
|
|
| - | PCR cont. |
|
|
|
|
----------------------------------------------------------------------------
|
|
|
|
| - | PCR cont. |
|
|
|
|
----------------------------------------------------------------------------
|
|
|
|
| - | PCR cont. |
|
|
|
|
----------------------------------------------------------------------------
|
|
|
|
| optional | OPCR (48 bits => 6 bytes) |
|
|
|
|
----------------------------------------------------------------------------
|
|
|
|
| - | OPCR cont. |
|
|
|
|
----------------------------------------------------------------------------
|
|
|
|
| - | OPCR cont. |
|
|
|
|
----------------------------------------------------------------------------
|
|
|
|
| - | OPCR cont. |
|
|
|
|
----------------------------------------------------------------------------
|
|
|
|
| - | OPCR cont. |
|
|
|
|
----------------------------------------------------------------------------
|
|
|
|
| - | OPCR cont. |
|
|
|
|
----------------------------------------------------------------------------
|
|
|
|
| optional | SC |
|
|
|
|
----------------------------------------------------------------------------
|
|
|
|
| optional | TPDL |
|
|
|
|
----------------------------------------------------------------------------
|
|
|
|
| optional | TPD (variable length) |
|
|
|
|
----------------------------------------------------------------------------
|
|
|
|
| - | ... |
|
|
|
|
----------------------------------------------------------------------------
|
|
|
|
| optional | Extension (variable length) |
|
|
|
|
----------------------------------------------------------------------------
|
|
|
|
| - | ... |
|
|
|
|
----------------------------------------------------------------------------
|
|
|
|
| optional | Stuffing (variable length) |
|
|
|
|
----------------------------------------------------------------------------
|
|
|
|
| - | ... |
|
|
|
|
----------------------------------------------------------------------------
|
|
|
|
| optional | Payload (variable length) |
|
|
|
|
----------------------------------------------------------------------------
|
|
|
|
| - | ... |
|
|
|
|
----------------------------------------------------------------------------
|
|
|
|
*/
|
2018-08-18 04:57:36 +03:00
|
|
|
type Packet struct {
|
2018-01-07 17:32:56 +03:00
|
|
|
TEI bool // Transport Error Indicator
|
|
|
|
PUSI bool // Payload Unit Start Indicator
|
|
|
|
Priority bool // Tranposrt priority indicator
|
|
|
|
PID uint16 // Packet identifier
|
|
|
|
TSC byte // Transport Scrambling Control
|
|
|
|
AFC byte // Adaption Field Control
|
|
|
|
CC byte // Continuity Counter
|
|
|
|
DI bool // Discontinouty indicator
|
|
|
|
RAI bool // random access indicator
|
|
|
|
ESPI bool // Elementary stream priority indicator
|
|
|
|
PCRF bool // PCR flag
|
|
|
|
OPCRF bool // OPCR flag
|
|
|
|
SPF bool // Splicing point flag
|
|
|
|
TPDF bool // Transport private data flag
|
|
|
|
AFEF bool // Adaptation field extension flag
|
|
|
|
PCR uint64 // Program clock reference
|
|
|
|
OPCR uint64 // Original program clock reference
|
|
|
|
SC byte // Splice countdown
|
|
|
|
TPDL byte // Tranposrt private data length
|
|
|
|
TPD []byte // Private data
|
|
|
|
Ext []byte // Adaptation field extension
|
2018-03-13 07:33:31 +03:00
|
|
|
Payload []byte // Mpeg ts Payload
|
2017-12-11 08:20:24 +03:00
|
|
|
}
|
|
|
|
|
2019-02-28 03:36:27 +03:00
|
|
|
// FindPmt will take a clip of mpegts and try to find a PMT table - if one
|
2019-01-22 03:57:24 +03:00
|
|
|
// is found, then it is returned along with its index, otherwise nil, -1 and an error is returned.
|
2019-02-28 03:34:40 +03:00
|
|
|
func FindPmt(d []byte) ([]byte, int, error) {
|
|
|
|
return FindPid(d, PmtPid)
|
2019-02-13 07:10:58 +03:00
|
|
|
}
|
|
|
|
|
2019-02-28 03:36:27 +03:00
|
|
|
// FindPat will take a clip of mpegts and try to find a PAT table - if one
|
2019-02-13 07:10:58 +03:00
|
|
|
// is found, then it is returned along with its index, otherwise nil, -1 and an error is returned.
|
2019-02-28 03:34:40 +03:00
|
|
|
func FindPat(d []byte) ([]byte, int, error) {
|
|
|
|
return FindPid(d, PatPid)
|
2019-02-13 07:10:58 +03:00
|
|
|
}
|
|
|
|
|
2019-02-28 03:36:27 +03:00
|
|
|
// FindPid will take a clip of mpegts and try to find a packet with given PID - if one
|
2019-02-13 07:10:58 +03:00
|
|
|
// is found, then it is returned along with its index, otherwise nil, -1 and an error is returned.
|
2019-02-28 03:34:40 +03:00
|
|
|
func FindPid(d []byte, pid uint16) (pkt []byte, i int, err error) {
|
2019-01-12 10:06:35 +03:00
|
|
|
if len(d) < PacketSize {
|
2019-01-22 03:51:12 +03:00
|
|
|
return nil, -1, errors.New("Mmpegts data not of valid length")
|
2018-12-14 08:45:02 +03:00
|
|
|
}
|
2019-01-22 03:51:12 +03:00
|
|
|
for i = 0; i < len(d); i += PacketSize {
|
2019-02-13 07:10:58 +03:00
|
|
|
p := (uint16(d[i+1]&0x1f) << 8) | uint16(d[i+2])
|
|
|
|
if p == pid {
|
|
|
|
pkt = d[i+4 : i+PacketSize]
|
2018-12-14 06:05:56 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2019-02-28 03:34:40 +03:00
|
|
|
return nil, -1, fmt.Errorf("could not find packet with pid: %d", pid)
|
2018-12-14 06:05:56 +03:00
|
|
|
}
|
|
|
|
|
2018-03-13 07:33:31 +03:00
|
|
|
// FillPayload takes a channel and fills the packets Payload field until the
|
2018-03-13 03:54:37 +03:00
|
|
|
// channel is empty or we've the packet reaches capacity
|
2018-08-18 04:57:36 +03:00
|
|
|
func (p *Packet) FillPayload(data []byte) int {
|
2019-01-02 02:56:34 +03:00
|
|
|
currentPktLen := 6 + asInt(p.PCRF)*6 + asInt(p.OPCRF)*6 +
|
2018-08-18 04:57:36 +03:00
|
|
|
asInt(p.SPF)*1 + asInt(p.TPDF)*1 + len(p.TPD)
|
2019-01-02 02:56:34 +03:00
|
|
|
p.Payload = make([]byte, PayloadSize-currentPktLen)
|
2018-08-18 04:57:36 +03:00
|
|
|
return copy(p.Payload, data)
|
|
|
|
}
|
|
|
|
|
|
|
|
func asInt(b bool) int {
|
|
|
|
if b {
|
|
|
|
return 1
|
2018-01-08 04:12:26 +03:00
|
|
|
}
|
2018-08-18 04:57:36 +03:00
|
|
|
return 0
|
2018-01-08 04:12:26 +03:00
|
|
|
}
|
|
|
|
|
2018-08-18 04:57:36 +03:00
|
|
|
func asByte(b bool) byte {
|
|
|
|
if b {
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
return 0
|
2018-02-28 16:46:59 +03:00
|
|
|
}
|
|
|
|
|
2018-03-13 03:54:37 +03:00
|
|
|
// ToByteSlice interprets the fields of the ts packet instance and outputs a
|
2018-03-13 07:33:31 +03:00
|
|
|
// corresponding byte slice
|
2018-12-27 05:31:38 +03:00
|
|
|
func (p *Packet) Bytes(buf []byte) []byte {
|
2019-01-12 10:06:35 +03:00
|
|
|
if buf == nil || cap(buf) != PacketSize {
|
|
|
|
buf = make([]byte, 0, PacketSize)
|
2018-12-27 05:31:38 +03:00
|
|
|
}
|
2018-12-27 05:49:11 +03:00
|
|
|
buf = buf[:0]
|
2018-08-18 04:57:36 +03:00
|
|
|
stuffingLength := 182 - len(p.Payload) - len(p.TPD) - asInt(p.PCRF)*6 -
|
|
|
|
asInt(p.OPCRF)*6 - asInt(p.SPF)
|
2018-01-16 10:17:38 +03:00
|
|
|
var stuffing []byte
|
|
|
|
if stuffingLength > 0 {
|
2018-03-14 04:18:03 +03:00
|
|
|
stuffing = make([]byte, stuffingLength)
|
2018-01-16 10:17:38 +03:00
|
|
|
}
|
2018-01-08 04:12:26 +03:00
|
|
|
for i := range stuffing {
|
|
|
|
stuffing[i] = 0xFF
|
|
|
|
}
|
2018-08-18 04:57:36 +03:00
|
|
|
afl := 1 + asInt(p.PCRF)*6 + asInt(p.OPCRF)*6 + asInt(p.SPF) + asInt(p.TPDF) + len(p.TPD) + len(stuffing)
|
|
|
|
buf = append(buf, []byte{
|
2018-01-07 17:32:56 +03:00
|
|
|
0x47,
|
2018-08-18 04:57:36 +03:00
|
|
|
(asByte(p.TEI)<<7 | asByte(p.PUSI)<<6 | asByte(p.Priority)<<5 | byte((p.PID&0xFF00)>>8)),
|
2018-01-07 17:32:56 +03:00
|
|
|
byte(p.PID & 0x00FF),
|
2018-02-28 17:10:52 +03:00
|
|
|
(p.TSC<<6 | p.AFC<<4 | p.CC),
|
|
|
|
}...)
|
2018-01-16 08:49:18 +03:00
|
|
|
|
|
|
|
if p.AFC == 3 || p.AFC == 2 {
|
2018-08-18 04:57:36 +03:00
|
|
|
buf = append(buf, []byte{
|
|
|
|
byte(afl), (asByte(p.DI)<<7 | asByte(p.RAI)<<6 | asByte(p.ESPI)<<5 |
|
|
|
|
asByte(p.PCRF)<<4 | asByte(p.OPCRF)<<3 | asByte(p.SPF)<<2 |
|
|
|
|
asByte(p.TPDF)<<1 | asByte(p.AFEF)),
|
2018-01-16 08:49:18 +03:00
|
|
|
}...)
|
2018-03-14 04:18:03 +03:00
|
|
|
for i := 40; p.PCRF && i >= 0; i -= 8 {
|
2018-08-18 04:57:36 +03:00
|
|
|
buf = append(buf, byte((p.PCR<<15)>>uint(i)))
|
2018-01-16 08:49:18 +03:00
|
|
|
}
|
2018-03-14 04:18:03 +03:00
|
|
|
for i := 40; p.OPCRF && i >= 0; i -= 8 {
|
2018-08-18 04:57:36 +03:00
|
|
|
buf = append(buf, byte(p.OPCR>>uint(i)))
|
2018-01-16 08:49:18 +03:00
|
|
|
}
|
|
|
|
if p.SPF {
|
2018-08-18 04:57:36 +03:00
|
|
|
buf = append(buf, p.SC)
|
2018-01-16 08:49:18 +03:00
|
|
|
}
|
|
|
|
if p.TPDF {
|
2018-08-18 04:57:36 +03:00
|
|
|
buf = append(buf, append([]byte{p.TPDL}, p.TPD...)...)
|
2018-01-16 08:49:18 +03:00
|
|
|
}
|
2018-08-18 04:57:36 +03:00
|
|
|
buf = append(buf, p.Ext...)
|
|
|
|
buf = append(buf, stuffing...)
|
2018-01-07 06:55:13 +03:00
|
|
|
}
|
2018-08-18 04:57:36 +03:00
|
|
|
buf = append(buf, p.Payload...)
|
|
|
|
return buf
|
2017-12-11 07:54:49 +03:00
|
|
|
}
|
2019-02-15 04:31:07 +03:00
|
|
|
|
|
|
|
type Option func(p *[PacketSize]byte)
|
|
|
|
|
|
|
|
// addAdaptationField adds an adaptation field to p, and applys the passed options to this field.
|
|
|
|
// TODO: this will probably break if we already have adaptation field.
|
|
|
|
func addAdaptationField(p *[PacketSize]byte, options ...Option) error {
|
2019-02-15 06:13:01 +03:00
|
|
|
if packet.ContainsAdaptationField((*packet.Packet)(p)) {
|
2019-02-15 04:31:07 +03:00
|
|
|
return errors.New("Adaptation field is already present in packet")
|
|
|
|
}
|
|
|
|
// Create space for adaptation field.
|
|
|
|
copy(p[HeadSize+DefaultAdaptationSize:], p[HeadSize:len(p)-DefaultAdaptationSize])
|
|
|
|
|
|
|
|
// TODO: seperate into own function
|
|
|
|
// Update adaptation field control.
|
|
|
|
p[AdaptationControlIdx] &= 0xff ^ AdaptationControlMask
|
|
|
|
p[AdaptationControlIdx] |= AdaptationControlMask
|
|
|
|
// Default the adaptationfield.
|
|
|
|
resetAdaptation(p)
|
|
|
|
|
|
|
|
// Apply and options that have bee passed.
|
|
|
|
for _, option := range options {
|
|
|
|
option(p)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// resetAdaptation sets fields in ps adaptation field to 0 if the adaptation field
|
|
|
|
// exists, otherwise an error is returned.
|
|
|
|
func resetAdaptation(p *[PacketSize]byte) error {
|
2019-02-15 06:13:01 +03:00
|
|
|
if !packet.ContainsAdaptationField((*packet.Packet)(p)) {
|
2019-02-15 04:31:07 +03:00
|
|
|
return errors.New("No adaptation field in this packet")
|
|
|
|
}
|
|
|
|
p[AdaptationIdx] = DefaultAdaptationBodySize
|
|
|
|
p[AdaptationIdx+1] = 0x00
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// DiscontinuityIndicator returns and Option that will set p's discontinuity
|
|
|
|
// indicator according to f.
|
|
|
|
func DiscontinuityIndicator(f bool) Option {
|
|
|
|
return func(p *[PacketSize]byte) {
|
|
|
|
set := byte(DiscontinuityIndicatorMask)
|
|
|
|
if !f {
|
|
|
|
set = 0x00
|
|
|
|
}
|
|
|
|
p[DiscontinuityIndicatorIdx] &= 0xff ^ DiscontinuityIndicatorMask
|
|
|
|
p[DiscontinuityIndicatorIdx] |= DiscontinuityIndicatorMask & set
|
|
|
|
}
|
|
|
|
}
|