This commit is contained in:
2018-11-04 15:58:15 +01:00
commit f956bcee28
1178 changed files with 584552 additions and 0 deletions

21
vendor/github.com/asticode/go-astits/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Quentin Renard
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

123
vendor/github.com/asticode/go-astits/README.md generated vendored Normal file
View File

@@ -0,0 +1,123 @@
[![GoReportCard](http://goreportcard.com/badge/github.com/asticode/go-astits)](http://goreportcard.com/report/github.com/asticode/go-astits)
[![GoDoc](https://godoc.org/github.com/asticode/go-astits?status.svg)](https://godoc.org/github.com/asticode/go-astits)
[![Travis](https://travis-ci.org/asticode/go-astits.svg?branch=master)](https://travis-ci.org/asticode/go-astits#)
[![Coveralls](https://coveralls.io/repos/github/asticode/go-astits/badge.svg?branch=master)](https://coveralls.io/github/asticode/go-astits)
This is a Golang library to natively parse and demux MPEG Transport Streams (ts) in GO.
WARNING: this library is not yet production ready. For instance, while parsing a slice of bytes, it doesn't check whether
the length of the slice is sufficient and rather panic on purpose. Use at your own risks!
# Installation
To install the library use the following:
go get -u github.com/asticode/go-astits/...
# Before looking at the code...
The transport stream is made of packets.<br>
Each packet has a header, an optional adaptation field and a payload.<br>
Several payloads can be appended and parsed as a data.
```
TRANSPORT STREAM
+--------------------------------------------------------------------------------------------------+
| |
PACKET PACKET
+----------------------------------------------+----------------------------------------------+----
| | |
+--------+---------------------------+---------+--------+---------------------------+---------+
| HEADER | OPTIONAL ADAPTATION FIELD | PAYLOAD | HEADER | OPTIONAL ADAPTATION FIELD | PAYLOAD | ...
+--------+---------------------------+---------+--------+---------------------------+---------+
| | | |
+---------+ +---------+
| |
+----------------------------------------------+
DATA
```
# Using the library in your code
WARNING: the code below doesn't handle errors for readability purposes. However you SHOULD!
```go
// Create a cancellable context in case you want to stop reading packets/data any time you want
ctx, cancel := context.WithCancel(context.Background())
// Handle SIGTERM signal
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGTERM)
go func() {
<-ch
cancel()
}()
// Open your file or initialize any kind of io.Reader
f, _ := os.Open("/path/to/file.ts")
defer f.Close()
// Create the demuxer
dmx := astits.New(ctx, f)
for {
// Get the next data
d, _ := dmx.NextData()
// Data is a PMT data
if d.PMT != nil {
// Loop through elementary streams
for _, es := range d.PMT.ElementaryStreams {
fmt.Printf("Stream detected: %d\n", es.ElementaryPID)
}
return
}
}
```
## Options
In order to pass options to the demuxer, look for the methods prefixed with `Opt` and add them upon calling `New`:
```go
// This is your custom packets parser
p := func(ps []*astits.Packet) (ds []*astits.Data, skip bool, err error) {
// This is your logic
skip = true
return
}
// Now you can create a demuxer with the proper options
dmx := New(ctx, f, OptPacketSize(192), OptPacketsParser(p))
```
# CLI
This library provides a CLI that will automatically get installed in `GOPATH/bin` on `go get` execution.
## List streams
$ astits -i <path to your file> -f <format: text|json (default: text)>
## List data
$ astits data -i <path to your file> -d <data type: eit|nit|... (repeatable argument | if empty, all data types are shown)>
# Features and roadmap
- [x] Parse PES packets
- [x] Parse PAT packets
- [x] Parse PMT packets
- [x] Parse EIT packets
- [x] Parse NIT packets
- [x] Parse SDT packets
- [x] Parse TOT packets
- [ ] Parse BAT packets
- [ ] Parse DIT packets
- [ ] Parse RST packets
- [ ] Parse SIT packets
- [ ] Parse ST packets
- [ ] Parse TDT packets
- [ ] Parse TSDT packets

View File

@@ -0,0 +1,29 @@
package astits
import (
"time"
)
// ClockReference represents a clock reference
// Base is based on a 90 kHz clock and extension is based on a 27 MHz clock
type ClockReference struct {
Base, Extension int
}
// newClockReference builds a new clock reference
func newClockReference(base, extension int) *ClockReference {
return &ClockReference{
Base: base,
Extension: extension,
}
}
// Duration converts the clock reference into duration
func (p ClockReference) Duration() time.Duration {
return time.Duration(p.Base*1e9/90000) + time.Duration(p.Extension*1e9/27000000)
}
// Time converts the clock reference into time
func (p ClockReference) Time() time.Time {
return time.Unix(0, p.Duration().Nanoseconds())
}

95
vendor/github.com/asticode/go-astits/data.go generated vendored Normal file
View File

@@ -0,0 +1,95 @@
package astits
import (
"github.com/pkg/errors"
)
// PIDs
const (
PIDPAT = 0x0 // Program Association Table (PAT) contains a directory listing of all Program Map Tables.
PIDCAT = 0x1 // Conditional Access Table (CAT) contains a directory listing of all ITU-T Rec. H.222 entitlement management message streams used by Program Map Tables.
PIDTSDT = 0x2 // Transport Stream Description Table (TSDT) contains descriptors related to the overall transport stream
PIDNull = 0x1fff // Null Packet (used for fixed bandwidth padding)
)
// Data represents a data
type Data struct {
EIT *EITData
FirstPacket *Packet
NIT *NITData
PAT *PATData
PES *PESData
PID uint16
PMT *PMTData
SDT *SDTData
TOT *TOTData
}
// parseData parses a payload spanning over multiple packets and returns a set of data
func parseData(ps []*Packet, prs PacketsParser, pm programMap) (ds []*Data, err error) {
// Use custom parser first
if prs != nil {
var skip bool
if ds, skip, err = prs(ps); err != nil {
err = errors.Wrap(err, "astits: custom packets parsing failed")
return
} else if skip {
return
}
}
// Reconstruct payload
var l int
for _, p := range ps {
l += len(p.Payload)
}
var payload = make([]byte, l)
var c int
for _, p := range ps {
c += copy(payload[c:], p.Payload)
}
// Parse PID
var pid = ps[0].Header.PID
// Parse payload
if pid == PIDCAT {
// Information in a CAT payload is private and dependent on the CA system. Use the PacketsParser
// to parse this type of payload
} else if isPSIPayload(pid, pm) {
var psiData *PSIData
if psiData, err = parsePSIData(payload); err != nil {
err = errors.Wrap(err, "astits: parsing PSI data failed")
return
}
ds = psiData.toData(ps[0], pid)
} else if isPESPayload(payload) {
d, err := parsePESData(payload)
if err == nil {
ds = append(ds, &Data{
FirstPacket: ps[0],
PES: d,
PID: pid,
})
}
}
return
}
// isPSIPayload checks whether the payload is a PSI one
func isPSIPayload(pid uint16, pm programMap) bool {
return pid == PIDPAT || // PAT
pm.exists(pid) || // PMT
((pid >= 0x10 && pid <= 0x14) || (pid >= 0x1e && pid <= 0x1f)) //DVB
}
// isPESPayload checks whether the payload is a PES one
func isPESPayload(i []byte) bool {
// Packet is not big enough
if len(i) < 3 {
return false
}
// Check prefix
return uint32(i[0])<<16|uint32(i[1])<<8|uint32(i[2]) == 1
}

73
vendor/github.com/asticode/go-astits/data_eit.go generated vendored Normal file
View File

@@ -0,0 +1,73 @@
package astits
import "time"
// EITData represents an EIT data
// Page: 36 | Chapter: 5.2.4 | Link: https://www.dvb.org/resources/public/standards/a38_dvb-si_specification.pdf
type EITData struct {
Events []*EITDataEvent
LastTableID uint8
OriginalNetworkID uint16
SegmentLastSectionNumber uint8
ServiceID uint16
TransportStreamID uint16
}
// EITDataEvent represents an EIT data event
type EITDataEvent struct {
Descriptors []*Descriptor
Duration time.Duration
EventID uint16
HasFreeCSAMode bool // When true indicates that access to one or more streams may be controlled by a CA system.
RunningStatus uint8
StartTime time.Time
}
// parseEITSection parses an EIT section
func parseEITSection(i []byte, offset *int, offsetSectionsEnd int, tableIDExtension uint16) (d *EITData) {
// Init
d = &EITData{ServiceID: tableIDExtension}
// Transport stream ID
d.TransportStreamID = uint16(i[*offset])<<8 | uint16(i[*offset+1])
*offset += 2
// Original network ID
d.OriginalNetworkID = uint16(i[*offset])<<8 | uint16(i[*offset+1])
*offset += 2
// Segment last section number
d.SegmentLastSectionNumber = uint8(i[*offset])
*offset += 1
// Last table ID
d.LastTableID = uint8(i[*offset])
*offset += 1
// Loop until end of section data is reached
for *offset < offsetSectionsEnd {
// Event ID
var e = &EITDataEvent{}
e.EventID = uint16(i[*offset])<<8 | uint16(i[*offset+1])
*offset += 2
// Start time
e.StartTime = parseDVBTime(i, offset)
// Duration
e.Duration = parseDVBDurationSeconds(i, offset)
// Running status
e.RunningStatus = uint8(i[*offset]) >> 5
// Free CA mode
e.HasFreeCSAMode = uint8(i[*offset]&0x10) > 0
// Descriptors
e.Descriptors = parseDescriptors(i, offset)
// Add event
d.Events = append(d.Events, e)
}
return
}

49
vendor/github.com/asticode/go-astits/data_nit.go generated vendored Normal file
View File

@@ -0,0 +1,49 @@
package astits
// NITData represents a NIT data
// Page: 29 | Chapter: 5.2.1 | Link: https://www.dvb.org/resources/public/standards/a38_dvb-si_specification.pdf
type NITData struct {
NetworkDescriptors []*Descriptor
NetworkID uint16
TransportStreams []*NITDataTransportStream
}
// NITDataTransportStream represents a NIT data transport stream
type NITDataTransportStream struct {
OriginalNetworkID uint16
TransportDescriptors []*Descriptor
TransportStreamID uint16
}
// parseNITSection parses a NIT section
func parseNITSection(i []byte, offset *int, tableIDExtension uint16) (d *NITData) {
// Init
d = &NITData{NetworkID: tableIDExtension}
// Network descriptors
d.NetworkDescriptors = parseDescriptors(i, offset)
// Transport stream loop length
var transportStreamLoopLength = int(uint16(i[*offset]&0xf)<<8 | uint16(i[*offset+1]))
*offset += 2
// Transport stream loop
transportStreamLoopLength += *offset
for *offset < transportStreamLoopLength {
// Transport stream ID
var ts = &NITDataTransportStream{}
ts.TransportStreamID = uint16(i[*offset])<<8 | uint16(i[*offset+1])
*offset += 2
// Original network ID
ts.OriginalNetworkID = uint16(i[*offset])<<8 | uint16(i[*offset+1])
*offset += 2
// Transport descriptors
ts.TransportDescriptors = parseDescriptors(i, offset)
// Append transport stream
d.TransportStreams = append(d.TransportStreams, ts)
}
return
}

30
vendor/github.com/asticode/go-astits/data_pat.go generated vendored Normal file
View File

@@ -0,0 +1,30 @@
package astits
// PATData represents a PAT data
// https://en.wikipedia.org/wiki/Program-specific_information
type PATData struct {
Programs []*PATProgram
TransportStreamID uint16
}
// PATProgram represents a PAT program
type PATProgram struct {
ProgramMapID uint16 // The packet identifier that contains the associated PMT
ProgramNumber uint16 // Relates to the Table ID extension in the associated PMT. A value of 0 is reserved for a NIT packet identifier.
}
// parsePATSection parses a PAT section
func parsePATSection(i []byte, offset *int, offsetSectionsEnd int, tableIDExtension uint16) (d *PATData) {
// Init
d = &PATData{TransportStreamID: tableIDExtension}
// Loop until end of section data is reached
for *offset < offsetSectionsEnd {
d.Programs = append(d.Programs, &PATProgram{
ProgramMapID: uint16(i[*offset+2]&0x1f)<<8 | uint16(i[*offset+3]),
ProgramNumber: uint16(i[*offset])<<8 | uint16(i[*offset+1]),
})
*offset += 4
}
return
}

322
vendor/github.com/asticode/go-astits/data_pes.go generated vendored Normal file
View File

@@ -0,0 +1,322 @@
package astits
import (
"fmt"
"github.com/pkg/errors"
)
// P-STD buffer scales
const (
PSTDBufferScale128Bytes = 0
PSTDBufferScale1024Bytes = 1
)
// PTS DTS indicator
const (
PTSDTSIndicatorBothPresent = 3
PTSDTSIndicatorIsForbidden = 1
PTSDTSIndicatorNoPTSOrDTS = 0
PTSDTSIndicatorOnlyPTS = 2
)
// Stream IDs
const (
StreamIDPrivateStream1 = 189
StreamIDPaddingStream = 190
StreamIDPrivateStream2 = 191
)
// Trick mode controls
const (
TrickModeControlFastForward = 0
TrickModeControlFastReverse = 3
TrickModeControlFreezeFrame = 2
TrickModeControlSlowMotion = 1
TrickModeControlSlowReverse = 4
)
// PESData represents a PES data
// https://en.wikipedia.org/wiki/Packetized_elementary_stream
// http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
// http://happy.emu.id.au/lab/tut/dttb/dtbtut4b.htm
type PESData struct {
Data []byte
Header *PESHeader
}
// PESHeader represents a packet PES header
type PESHeader struct {
OptionalHeader *PESOptionalHeader
PacketLength uint16 // 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.
StreamID uint8 // Examples: Audio streams (0xC0-0xDF), Video streams (0xE0-0xEF)
}
// PESOptionalHeader represents a PES optional header
type PESOptionalHeader struct {
AdditionalCopyInfo uint8
CRC uint16
DataAlignmentIndicator bool // True indicates that the PES packet header is immediately followed by the video start code or audio syncword
DSMTrickMode *DSMTrickMode
DTS *ClockReference
ESCR *ClockReference
ESRate uint32
Extension2Data []byte
Extension2Length uint8
HasAdditionalCopyInfo bool
HasCRC bool
HasDSMTrickMode bool
HasESCR bool
HasESRate bool
HasExtension bool
HasExtension2 bool
HasOptionalFields bool
HasPackHeaderField bool
HasPrivateData bool
HasProgramPacketSequenceCounter bool
HasPSTDBuffer bool
HeaderLength uint8
IsCopyrighted bool
IsOriginal bool
MarkerBits uint8
MPEG1OrMPEG2ID uint8
OriginalStuffingLength uint8
PacketSequenceCounter uint8
PackField uint8
Priority bool
PrivateData []byte
PSTDBufferScale uint8
PSTDBufferSize uint16
PTS *ClockReference
PTSDTSIndicator uint8
ScramblingControl uint8
}
// DSMTrickMode represents a DSM trick mode
// https://books.google.fr/books?id=vwUrAwAAQBAJ&pg=PT501&lpg=PT501&dq=dsm+trick+mode+control&source=bl&ots=fI-9IHXMRL&sig=PWnhxrsoMWNQcl1rMCPmJGNO9Ds&hl=fr&sa=X&ved=0ahUKEwjogafD8bjXAhVQ3KQKHeHKD5oQ6AEINDAB#v=onepage&q=dsm%20trick%20mode%20control&f=false
type DSMTrickMode struct {
FieldID uint8
FrequencyTruncation uint8
IntraSliceRefresh uint8
RepeatControl uint8
TrickModeControl uint8
}
// parsePESData parses a PES data
func parsePESData(i []byte) (d *PESData, err error) {
// Init
d = &PESData{}
// Parse header
var offset, dataStart, dataEnd = 3, 0, 0
if d.Header, dataStart, dataEnd, err = parsePESHeader(i, &offset); err != nil {
err = errors.Wrap(err, "astits: parsing PES header failed")
return
}
// Parse data
d.Data = i[dataStart:dataEnd]
return
}
// hasPESOptionalHeader checks whether the data has a PES optional header
func hasPESOptionalHeader(streamID uint8) bool {
return streamID != StreamIDPaddingStream && streamID != StreamIDPrivateStream2
}
// parsePESData parses a PES header
func parsePESHeader(i []byte, offset *int) (h *PESHeader, dataStart, dataEnd int, err error) {
// Init
h = &PESHeader{}
// Stream ID
h.StreamID = uint8(i[*offset])
*offset += 1
// Length
h.PacketLength = uint16(i[*offset])<<8 | uint16(i[*offset+1])
*offset += 2
// Data end
if h.PacketLength > 0 {
dataEnd = *offset + int(h.PacketLength)
} else {
dataEnd = len(i)
}
// Check for incomplete data
if dataEnd > len(i) {
err = fmt.Errorf("astits: pes dataEnd (%d) > len(i) (%d)", dataEnd, len(i))
return
}
// Optional header
if hasPESOptionalHeader(h.StreamID) {
h.OptionalHeader, dataStart = parsePESOptionalHeader(i, offset)
} else {
dataStart = *offset
}
return
}
// parsePESOptionalHeader parses a PES optional header
func parsePESOptionalHeader(i []byte, offset *int) (h *PESOptionalHeader, dataStart int) {
// Init
h = &PESOptionalHeader{}
// Marker bits
h.MarkerBits = uint8(i[*offset]) >> 6
// Scrambling control
h.ScramblingControl = uint8(i[*offset]) >> 4 & 0x3
// Priority
h.Priority = uint8(i[*offset])&0x8 > 0
// Data alignment indicator
h.DataAlignmentIndicator = uint8(i[*offset])&0x4 > 0
// Copyrighted
h.IsCopyrighted = uint(i[*offset])&0x2 > 0
// Original or copy
h.IsOriginal = uint8(i[*offset])&0x1 > 0
*offset += 1
// PTS DST indicator
h.PTSDTSIndicator = uint8(i[*offset]) >> 6 & 0x3
// Flags
h.HasESCR = uint8(i[*offset])&0x20 > 0
h.HasESRate = uint8(i[*offset])&0x10 > 0
h.HasDSMTrickMode = uint8(i[*offset])&0x8 > 0
h.HasAdditionalCopyInfo = uint8(i[*offset])&0x4 > 0
h.HasCRC = uint8(i[*offset])&0x2 > 0
h.HasExtension = uint8(i[*offset])&0x1 > 0
*offset += 1
// Header length
h.HeaderLength = uint8(i[*offset])
*offset += 1
// Data start
dataStart = *offset + int(h.HeaderLength)
// PTS/DTS
if h.PTSDTSIndicator == PTSDTSIndicatorOnlyPTS {
h.PTS = parsePTSOrDTS(i[*offset:])
*offset += 5
} else if h.PTSDTSIndicator == PTSDTSIndicatorBothPresent {
h.PTS = parsePTSOrDTS(i[*offset:])
*offset += 5
h.DTS = parsePTSOrDTS(i[*offset:])
*offset += 5
}
// ESCR
if h.HasESCR {
h.ESCR = parseESCR(i[*offset:])
*offset += 6
}
// ES rate
if h.HasESRate {
h.ESRate = uint32(i[*offset])&0x7f<<15 | uint32(i[*offset+1])<<7 | uint32(i[*offset+2])>>1
*offset += 3
}
// Trick mode
if h.HasDSMTrickMode {
h.DSMTrickMode = parseDSMTrickMode(i[*offset])
*offset += 1
}
// Additional copy info
if h.HasAdditionalCopyInfo {
h.AdditionalCopyInfo = i[*offset] & 0x7f
*offset += 1
}
// CRC
if h.HasCRC {
h.CRC = uint16(i[*offset])>>8 | uint16(i[*offset+1])
*offset += 2
}
// Extension
if h.HasExtension {
// Flags
h.HasPrivateData = i[*offset]&0x80 > 0
h.HasPackHeaderField = i[*offset]&0x40 > 0
h.HasProgramPacketSequenceCounter = i[*offset]&0x20 > 0
h.HasPSTDBuffer = i[*offset]&0x10 > 0
h.HasExtension2 = i[*offset]&0x1 > 0
*offset += 1
// Private data
if h.HasPrivateData {
h.PrivateData = i[*offset : *offset+16]
*offset += 16
}
// Pack field length
if h.HasPackHeaderField {
h.PackField = uint8(i[*offset])
*offset += 1
}
// Program packet sequence counter
if h.HasProgramPacketSequenceCounter {
h.PacketSequenceCounter = uint8(i[*offset]) & 0x7f
h.MPEG1OrMPEG2ID = uint8(i[*offset+1]) >> 6 & 0x1
h.OriginalStuffingLength = uint8(i[*offset+1]) & 0x3f
*offset += 2
}
// P-STD buffer
if h.HasPSTDBuffer {
h.PSTDBufferScale = i[*offset] >> 5 & 0x1
h.PSTDBufferSize = uint16(i[*offset])&0x1f<<8 | uint16(i[*offset+1])
*offset += 2
}
// Extension 2
if h.HasExtension2 {
// Length
h.Extension2Length = uint8(i[*offset]) & 0x7f
*offset += 2
// Data
h.Extension2Data = i[*offset : *offset+int(h.Extension2Length)]
*offset += int(h.Extension2Length)
}
}
return
}
// parseDSMTrickMode parses a DSM trick mode
func parseDSMTrickMode(i byte) (m *DSMTrickMode) {
m = &DSMTrickMode{}
m.TrickModeControl = i >> 5
if m.TrickModeControl == TrickModeControlFastForward || m.TrickModeControl == TrickModeControlFastReverse {
m.FieldID = i >> 3 & 0x3
m.IntraSliceRefresh = i >> 2 & 0x1
m.FrequencyTruncation = i & 0x3
} else if m.TrickModeControl == TrickModeControlFreezeFrame {
m.FieldID = i >> 3 & 0x3
} else if m.TrickModeControl == TrickModeControlSlowMotion || m.TrickModeControl == TrickModeControlSlowReverse {
m.RepeatControl = i & 0x1f
}
return
}
// parsePTSOrDTS parses a PTS or a DTS
func parsePTSOrDTS(i []byte) *ClockReference {
return newClockReference(int(uint64(i[0])>>1&0x7<<30|uint64(i[1])<<22|uint64(i[2])>>1&0x7f<<15|uint64(i[3])<<7|uint64(i[4])>>1&0x7f), 0)
}
// parseESCR parses an ESCR
func parseESCR(i []byte) *ClockReference {
var escr = uint64(i[0])>>3&0x7<<39 | uint64(i[0])&0x3<<37 | uint64(i[1])<<29 | uint64(i[2])>>3<<24 | uint64(i[2])&0x3<<22 | uint64(i[3])<<14 | uint64(i[4])>>3<<9 | uint64(i[4])&0x3<<7 | uint64(i[5])>>1
return newClockReference(int(escr>>9), int(escr&0x1ff))
}

57
vendor/github.com/asticode/go-astits/data_pmt.go generated vendored Normal file
View File

@@ -0,0 +1,57 @@
package astits
// Stream types
const (
StreamTypeLowerBitrateVideo = 27 // ITU-T Rec. H.264 and ISO/IEC 14496-10
StreamTypeMPEG1Audio = 3 // ISO/IEC 11172-3
StreamTypeMPEG2HalvedSampleRateAudio = 4 // ISO/IEC 13818-3
StreamTypeMPEG2PacketizedData = 6 // ITU-T Rec. H.222 and ISO/IEC 13818-1 i.e., DVB subtitles/VBI and AC-3
)
// PMTData represents a PMT data
// https://en.wikipedia.org/wiki/Program-specific_information
type PMTData struct {
ElementaryStreams []*PMTElementaryStream
PCRPID uint16 // The packet identifier that contains the program clock reference used to improve the random access accuracy of the stream's timing that is derived from the program timestamp. If this is unused. then it is set to 0x1FFF (all bits on).
ProgramDescriptors []*Descriptor // Program descriptors
ProgramNumber uint16
}
// PMTElementaryStream represents a PMT elementary stream
type PMTElementaryStream struct {
ElementaryPID uint16 // The packet identifier that contains the stream type data.
ElementaryStreamDescriptors []*Descriptor // Elementary stream descriptors
StreamType uint8 // This defines the structure of the data contained within the elementary packet identifier.
}
// parsePMTSection parses a PMT section
func parsePMTSection(i []byte, offset *int, offsetSectionsEnd int, tableIDExtension uint16) (d *PMTData) {
// Init
d = &PMTData{ProgramNumber: tableIDExtension}
// PCR PID
d.PCRPID = uint16(i[*offset]&0x1f)<<8 | uint16(i[*offset+1])
*offset += 2
// Program descriptors
d.ProgramDescriptors = parseDescriptors(i, offset)
// Loop until end of section data is reached
for *offset < offsetSectionsEnd {
// Stream type
var e = &PMTElementaryStream{}
e.StreamType = uint8(i[*offset])
*offset += 1
// Elementary PID
e.ElementaryPID = uint16(i[*offset]&0x1f)<<8 | uint16(i[*offset+1])
*offset += 2
// Elementary descriptors
e.ElementaryStreamDescriptors = parseDescriptors(i, offset)
// Add elementary stream
d.ElementaryStreams = append(d.ElementaryStreams, e)
}
return
}

356
vendor/github.com/asticode/go-astits/data_psi.go generated vendored Normal file
View File

@@ -0,0 +1,356 @@
package astits
import (
"fmt"
"github.com/asticode/go-astilog"
"github.com/pkg/errors"
)
// PSI table IDs
const (
PSITableTypeBAT = "BAT"
PSITableTypeDIT = "DIT"
PSITableTypeEIT = "EIT"
PSITableTypeNIT = "NIT"
PSITableTypeNull = "Null"
PSITableTypePAT = "PAT"
PSITableTypePMT = "PMT"
PSITableTypeRST = "RST"
PSITableTypeSDT = "SDT"
PSITableTypeSIT = "SIT"
PSITableTypeST = "ST"
PSITableTypeTDT = "TDT"
PSITableTypeTOT = "TOT"
PSITableTypeUnknown = "Unknown"
)
// PSIData represents a PSI data
// https://en.wikipedia.org/wiki/Program-specific_information
type PSIData struct {
PointerField int // Present at the start of the TS packet payload signaled by the payload_unit_start_indicator bit in the TS header. Used to set packet alignment bytes or content before the start of tabled payload data.
Sections []*PSISection
}
// PSISection represents a PSI section
type PSISection struct {
CRC32 uint32 // A checksum of the entire table excluding the pointer field, pointer filler bytes and the trailing CRC32.
Header *PSISectionHeader
Syntax *PSISectionSyntax
}
// PSISectionHeader represents a PSI section header
type PSISectionHeader struct {
PrivateBit bool // The PAT, PMT, and CAT all set this to 0. Other tables set this to 1.
SectionLength uint16 // The number of bytes that follow for the syntax section (with CRC value) and/or table data. These bytes must not exceed a value of 1021.
SectionSyntaxIndicator bool // A flag that indicates if the syntax section follows the section length. The PAT, PMT, and CAT all set this to 1.
TableID int // Table Identifier, that defines the structure of the syntax section and other contained data. As an exception, if this is the byte that immediately follow previous table section and is set to 0xFF, then it indicates that the repeat of table section end here and the rest of TS data payload shall be stuffed with 0xFF. Consequently the value 0xFF shall not be used for the Table Identifier.
TableType string
}
// PSISectionSyntax represents a PSI section syntax
type PSISectionSyntax struct {
Data *PSISectionSyntaxData
Header *PSISectionSyntaxHeader
}
// PSISectionSyntaxHeader represents a PSI section syntax header
type PSISectionSyntaxHeader struct {
CurrentNextIndicator bool // Indicates if data is current in effect or is for future use. If the bit is flagged on, then the data is to be used at the present moment.
LastSectionNumber uint8 // This indicates which table is the last table in the sequence of tables.
SectionNumber uint8 // This is an index indicating which table this is in a related sequence of tables. The first table starts from 0.
TableIDExtension uint16 // Informational only identifier. The PAT uses this for the transport stream identifier and the PMT uses this for the Program number.
VersionNumber uint8 // Syntax version number. Incremented when data is changed and wrapped around on overflow for values greater than 32.
}
// PSISectionSyntaxData represents a PSI section syntax data
type PSISectionSyntaxData struct {
EIT *EITData
NIT *NITData
PAT *PATData
PMT *PMTData
SDT *SDTData
TOT *TOTData
}
// parsePSIData parses a PSI data
func parsePSIData(i []byte) (d *PSIData, err error) {
// Init data
d = &PSIData{}
var offset int
// Pointer field
d.PointerField = int(i[offset])
offset += 1
// Pointer filler bytes
offset += d.PointerField
// Parse sections
var s *PSISection
var stop bool
for offset < len(i) && !stop {
if s, stop, err = parsePSISection(i, &offset); err != nil {
err = errors.Wrap(err, "astits: parsing PSI table failed")
return
}
d.Sections = append(d.Sections, s)
}
return
}
// parsePSISection parses a PSI section
func parsePSISection(i []byte, offset *int) (s *PSISection, stop bool, err error) {
// Init section
s = &PSISection{}
// Parse header
var offsetStart, offsetSectionsEnd, offsetEnd int
s.Header, offsetStart, _, offsetSectionsEnd, offsetEnd = parsePSISectionHeader(i, offset)
// Check whether we need to stop the parsing
if shouldStopPSIParsing(s.Header.TableType) {
stop = true
return
}
// Check whether there's a syntax section
if s.Header.SectionLength > 0 {
// Parse syntax
s.Syntax = parsePSISectionSyntax(i, offset, s.Header, offsetSectionsEnd)
// Process CRC32
if hasCRC32(s.Header.TableType) {
// Parse CRC32
s.CRC32 = parseCRC32(i[offsetSectionsEnd:offsetEnd])
*offset += 4
// Check CRC32
var c = computeCRC32(i[offsetStart:offsetSectionsEnd])
if c != s.CRC32 {
err = fmt.Errorf("astits: Table CRC32 %x != computed CRC32 %x", s.CRC32, c)
return
}
}
}
return
}
// parseCRC32 parses a CRC32
func parseCRC32(i []byte) uint32 {
return uint32(i[len(i)-4])<<24 | uint32(i[len(i)-3])<<16 | uint32(i[len(i)-2])<<8 | uint32(i[len(i)-1])
}
// computeCRC32 computes a CRC32
// https://stackoverflow.com/questions/35034042/how-to-calculate-crc32-in-psi-si-packet
func computeCRC32(i []byte) (o uint32) {
o = uint32(0xffffffff)
for _, b := range i {
for i := 0; i < 8; i++ {
if (o >= uint32(0x80000000)) != (b >= uint8(0x80)) {
o = (o << 1) ^ 0x04C11DB7
} else {
o = o << 1
}
b <<= 1
}
}
return
}
// shouldStopPSIParsing checks whether the PSI parsing should be stopped
func shouldStopPSIParsing(tableType string) bool {
return tableType == PSITableTypeNull || tableType == PSITableTypeUnknown
}
// parsePSISectionHeader parses a PSI section header
func parsePSISectionHeader(i []byte, offset *int) (h *PSISectionHeader, offsetStart, offsetSectionsStart, offsetSectionsEnd, offsetEnd int) {
// Init
h = &PSISectionHeader{}
offsetStart = *offset
// Table ID
h.TableID = int(i[*offset])
*offset += 1
// Table type
h.TableType = psiTableType(h.TableID)
// Check whether we need to stop the parsing
if shouldStopPSIParsing(h.TableType) {
return
}
// Section syntax indicator
h.SectionSyntaxIndicator = i[*offset]&0x80 > 0
// Private bit
h.PrivateBit = i[*offset]&0x40 > 0
// Section length
h.SectionLength = uint16(i[*offset]&0xf)<<8 | uint16(i[*offset+1])
*offset += 2
// Offsets
offsetSectionsStart = *offset
offsetEnd = offsetSectionsStart + int(h.SectionLength)
offsetSectionsEnd = offsetEnd
if hasCRC32(h.TableType) {
offsetSectionsEnd -= 4
}
return
}
// hasCRC32 checks whether the table has a CRC32
func hasCRC32(tableType string) bool {
return tableType == PSITableTypePAT ||
tableType == PSITableTypePMT ||
tableType == PSITableTypeEIT ||
tableType == PSITableTypeNIT ||
tableType == PSITableTypeTOT ||
tableType == PSITableTypeSDT
}
// psiTableType returns the psi table type based on the table id
// Page: 28 | https://www.dvb.org/resources/public/standards/a38_dvb-si_specification.pdf
func psiTableType(tableID int) string {
switch {
case tableID == 0x4a:
return PSITableTypeBAT
case tableID >= 0x4e && tableID <= 0x6f:
return PSITableTypeEIT
case tableID == 0x7e:
return PSITableTypeDIT
case tableID == 0x40, tableID == 0x41:
return PSITableTypeNIT
case tableID == 0xff:
return PSITableTypeNull
case tableID == 0:
return PSITableTypePAT
case tableID == 2:
return PSITableTypePMT
case tableID == 0x71:
return PSITableTypeRST
case tableID == 0x42, tableID == 0x46:
return PSITableTypeSDT
case tableID == 0x7f:
return PSITableTypeSIT
case tableID == 0x72:
return PSITableTypeST
case tableID == 0x70:
return PSITableTypeTDT
case tableID == 0x73:
return PSITableTypeTOT
}
// TODO Remove this log
astilog.Debugf("astits: unlisted PSI table ID %d", tableID)
return PSITableTypeUnknown
}
// parsePSISectionSyntax parses a PSI section syntax
func parsePSISectionSyntax(i []byte, offset *int, h *PSISectionHeader, offsetSectionsEnd int) (s *PSISectionSyntax) {
// Init
s = &PSISectionSyntax{}
// Header
if hasPSISyntaxHeader(h.TableType) {
s.Header = parsePSISectionSyntaxHeader(i, offset)
}
// Parse data
s.Data = parsePSISectionSyntaxData(i, offset, h, s.Header, offsetSectionsEnd)
return
}
// hasPSISyntaxHeader checks whether the section has a syntax header
func hasPSISyntaxHeader(tableType string) bool {
return tableType == PSITableTypeEIT ||
tableType == PSITableTypeNIT ||
tableType == PSITableTypePAT ||
tableType == PSITableTypePMT ||
tableType == PSITableTypeSDT
}
// parsePSISectionSyntaxHeader parses a PSI section syntax header
func parsePSISectionSyntaxHeader(i []byte, offset *int) (h *PSISectionSyntaxHeader) {
// Init
h = &PSISectionSyntaxHeader{}
// Table ID extension
h.TableIDExtension = uint16(i[*offset])<<8 | uint16(i[*offset+1])
*offset += 2
// Version number
h.VersionNumber = uint8(i[*offset]&0x3f) >> 1
// Current/Next indicator
h.CurrentNextIndicator = i[*offset]&0x1 > 0
*offset += 1
// Section number
h.SectionNumber = uint8(i[*offset])
*offset += 1
// Last section number
h.LastSectionNumber = uint8(i[*offset])
*offset += 1
return
}
// parsePSISectionSyntaxData parses a PSI section data
func parsePSISectionSyntaxData(i []byte, offset *int, h *PSISectionHeader, sh *PSISectionSyntaxHeader, offsetSectionsEnd int) (d *PSISectionSyntaxData) {
// Init
d = &PSISectionSyntaxData{}
// Switch on table type
switch h.TableType {
case PSITableTypeBAT:
// TODO Parse BAT
case PSITableTypeDIT:
// TODO Parse DIT
case PSITableTypeEIT:
d.EIT = parseEITSection(i, offset, offsetSectionsEnd, sh.TableIDExtension)
case PSITableTypeNIT:
d.NIT = parseNITSection(i, offset, sh.TableIDExtension)
case PSITableTypePAT:
d.PAT = parsePATSection(i, offset, offsetSectionsEnd, sh.TableIDExtension)
case PSITableTypePMT:
d.PMT = parsePMTSection(i, offset, offsetSectionsEnd, sh.TableIDExtension)
case PSITableTypeRST:
// TODO Parse RST
case PSITableTypeSDT:
d.SDT = parseSDTSection(i, offset, offsetSectionsEnd, sh.TableIDExtension)
case PSITableTypeSIT:
// TODO Parse SIT
case PSITableTypeST:
// TODO Parse ST
case PSITableTypeTOT:
d.TOT = parseTOTSection(i, offset)
case PSITableTypeTDT:
// TODO Parse TDT
}
return
}
// toData parses the PSI tables and returns a set of Data
func (d *PSIData) toData(firstPacket *Packet, pid uint16) (ds []*Data) {
// Loop through sections
for _, s := range d.Sections {
// Switch on table type
switch s.Header.TableType {
case PSITableTypeEIT:
ds = append(ds, &Data{EIT: s.Syntax.Data.EIT, FirstPacket: firstPacket, PID: pid})
case PSITableTypeNIT:
ds = append(ds, &Data{FirstPacket: firstPacket, NIT: s.Syntax.Data.NIT, PID: pid})
case PSITableTypePAT:
ds = append(ds, &Data{FirstPacket: firstPacket, PAT: s.Syntax.Data.PAT, PID: pid})
case PSITableTypePMT:
ds = append(ds, &Data{FirstPacket: firstPacket, PID: pid, PMT: s.Syntax.Data.PMT})
case PSITableTypeSDT:
ds = append(ds, &Data{FirstPacket: firstPacket, PID: pid, SDT: s.Syntax.Data.SDT})
case PSITableTypeTOT:
ds = append(ds, &Data{FirstPacket: firstPacket, PID: pid, TOT: s.Syntax.Data.TOT})
}
}
return
}

70
vendor/github.com/asticode/go-astits/data_sdt.go generated vendored Normal file
View File

@@ -0,0 +1,70 @@
package astits
// Running statuses
const (
RunningStatusNotRunning = 1
RunningStatusPausing = 3
RunningStatusRunning = 4
RunningStatusServiceOffAir = 5
RunningStatusStartsInAFewSeconds = 2
RunningStatusUndefined = 0
)
// SDTData represents an SDT data
// Page: 33 | Chapter: 5.2.3 | Link: https://www.dvb.org/resources/public/standards/a38_dvb-si_specification.pdf
type SDTData struct {
OriginalNetworkID uint16
Services []*SDTDataService
TransportStreamID uint16
}
// SDTDataService represents an SDT data service
type SDTDataService struct {
Descriptors []*Descriptor
HasEITPresentFollowing bool // When true indicates that EIT present/following information for the service is present in the current TS
HasEITSchedule bool // When true indicates that EIT schedule information for the service is present in the current TS
HasFreeCSAMode bool // When true indicates that access to one or more streams may be controlled by a CA system.
RunningStatus uint8
ServiceID uint16
}
// parseSDTSection parses an SDT section
func parseSDTSection(i []byte, offset *int, offsetSectionsEnd int, tableIDExtension uint16) (d *SDTData) {
// Init
d = &SDTData{TransportStreamID: tableIDExtension}
// Original network ID
d.OriginalNetworkID = uint16(i[*offset])<<8 | uint16(i[*offset+1])
*offset += 2
// Reserved for future use
*offset += 1
// Loop until end of section data is reached
for *offset < offsetSectionsEnd {
// Service ID
var s = &SDTDataService{}
s.ServiceID = uint16(i[*offset])<<8 | uint16(i[*offset+1])
*offset += 2
// EIT schedule flag
s.HasEITSchedule = uint8(i[*offset]&0x2) > 0
// EIT present/following flag
s.HasEITPresentFollowing = uint8(i[*offset]&0x1) > 0
*offset += 1
// Running status
s.RunningStatus = uint8(i[*offset]) >> 5
// Free CA mode
s.HasFreeCSAMode = uint8(i[*offset]&0x10) > 0
// Descriptors
s.Descriptors = parseDescriptors(i, offset)
// Append service
d.Services = append(d.Services, s)
}
return
}

23
vendor/github.com/asticode/go-astits/data_tot.go generated vendored Normal file
View File

@@ -0,0 +1,23 @@
package astits
import "time"
// TOTData represents a TOT data
// Page: 39 | Chapter: 5.2.6 | Link: https://www.dvb.org/resources/public/standards/a38_dvb-si_specification.pdf
type TOTData struct {
Descriptors []*Descriptor
UTCTime time.Time
}
// parseTOTSection parses a TOT section
func parseTOTSection(i []byte, offset *int) (d *TOTData) {
// Init
d = &TOTData{}
// UTC time
d.UTCTime = parseDVBTime(i, offset)
// Descriptors
d.Descriptors = parseDescriptors(i, offset)
return
}

163
vendor/github.com/asticode/go-astits/demuxer.go generated vendored Normal file
View File

@@ -0,0 +1,163 @@
package astits
import (
"context"
"io"
"github.com/pkg/errors"
)
// Sync byte
const syncByte = '\x47'
// Errors
var (
ErrNoMorePackets = errors.New("astits: no more packets")
ErrPacketMustStartWithASyncByte = errors.New("astits: packet must start with a sync byte")
)
// Demuxer represents a demuxer
// https://en.wikipedia.org/wiki/MPEG_transport_stream
// http://seidl.cs.vsb.cz/download/dvb/DVB_Poster.pdf
// http://www.etsi.org/deliver/etsi_en/300400_300499/300468/01.13.01_40/en_300468v011301o.pdf
type Demuxer struct {
ctx context.Context
dataBuffer []*Data
optPacketSize int
optPacketsParser PacketsParser
packetBuffer *packetBuffer
packetPool *packetPool
programMap programMap
r io.Reader
}
// PacketsParser represents an object capable of parsing a set of packets containing a unique payload spanning over those packets
// Use the skip returned argument to indicate whether the default process should still be executed on the set of packets
type PacketsParser func(ps []*Packet) (ds []*Data, skip bool, err error)
// New creates a new transport stream based on a reader
func New(ctx context.Context, r io.Reader, opts ...func(*Demuxer)) (d *Demuxer) {
// Init
d = &Demuxer{
ctx: ctx,
packetPool: newPacketPool(),
programMap: newProgramMap(),
r: r,
}
// Apply options
for _, opt := range opts {
opt(d)
}
return
}
// OptPacketSize returns the option to set the packet size
func OptPacketSize(packetSize int) func(*Demuxer) {
return func(d *Demuxer) {
d.optPacketSize = packetSize
}
}
// OptPacketsParser returns the option to set the packets parser
func OptPacketsParser(p PacketsParser) func(*Demuxer) {
return func(d *Demuxer) {
d.optPacketsParser = p
}
}
// NextPacket retrieves the next packet
func (dmx *Demuxer) NextPacket() (p *Packet, err error) {
// Check ctx error
// TODO Handle ctx error another way since if the read blocks, everything blocks
// Maybe execute everything in a goroutine and listen the ctx channel in the same for loop
if err = dmx.ctx.Err(); err != nil {
return
}
// Create packet buffer if not exists
if dmx.packetBuffer == nil {
if dmx.packetBuffer, err = newPacketBuffer(dmx.r, dmx.optPacketSize); err != nil {
err = errors.Wrap(err, "astits: creating packet buffer failed")
return
}
}
// Fetch next packet from buffer
if p, err = dmx.packetBuffer.next(); err != nil {
if err != ErrNoMorePackets {
err = errors.Wrap(err, "astits: fetching next packet from buffer failed")
}
return
}
return
}
// NextData retrieves the next data
func (dmx *Demuxer) NextData() (d *Data, err error) {
// Check data buffer
if len(dmx.dataBuffer) > 0 {
d = dmx.dataBuffer[0]
dmx.dataBuffer = dmx.dataBuffer[1:]
return
}
// Loop through packets
var p *Packet
var ps []*Packet
var ds []*Data
for {
// Get next packet
if p, err = dmx.NextPacket(); err != nil {
// We don't dump the packet pool since we don't want incomplete data
if err == ErrNoMorePackets {
return
}
err = errors.Wrap(err, "astits: fetching next packet failed")
return
}
// Add packet to the pool
if ps = dmx.packetPool.add(p); len(ps) == 0 {
continue
}
// Parse data
if ds, err = parseData(ps, dmx.optPacketsParser, dmx.programMap); err != nil {
err = errors.Wrap(err, "astits: building new data failed")
return
}
// Check whether there is data to be processed
if len(ds) > 0 {
// Process data
d = ds[0]
dmx.dataBuffer = append(dmx.dataBuffer, ds[1:]...)
// Update program map
for _, v := range ds {
if v.PAT != nil {
for _, pgm := range v.PAT.Programs {
// Program number 0 is reserved to NIT
if pgm.ProgramNumber > 0 {
dmx.programMap.set(pgm.ProgramMapID, pgm.ProgramNumber)
}
}
}
}
return
}
}
}
// Rewind rewinds the demuxer reader
func (dmx *Demuxer) Rewind() (n int64, err error) {
dmx.dataBuffer = []*Data{}
dmx.packetBuffer = nil
dmx.packetPool = newPacketPool()
if n, err = rewind(dmx.r); err != nil {
err = errors.Wrap(err, "astits: rewinding reader failed")
return
}
return
}

918
vendor/github.com/asticode/go-astits/descriptor.go generated vendored Normal file
View File

@@ -0,0 +1,918 @@
package astits
import (
"time"
"github.com/asticode/go-astilog"
)
// Audio types
// Page: 683 | https://books.google.fr/books?id=6dgWB3-rChYC&printsec=frontcover&hl=fr
const (
AudioTypeCleanEffects = 0x1
AudioTypeHearingImpaired = 0x2
AudioTypeVisualImpairedCommentary = 0x3
)
// Data stream alignments
// Page: 85 | Chapter:2.6.11 | Link: http://ecee.colorado.edu/~ecen5653/ecen5653/papers/iso13818-1.pdf
const (
DataStreamAligmentAudioSyncWord = 0x1
DataStreamAligmentVideoSliceOrAccessUnit = 0x1
DataStreamAligmentVideoAccessUnit = 0x2
DataStreamAligmentVideoGOPOrSEQ = 0x3
DataStreamAligmentVideoSEQ = 0x4
)
// Descriptor tags
// Page: 42 | Chapter: 6.1 | Link: https://www.dvb.org/resources/public/standards/a38_dvb-si_specification.pdf
const (
DescriptorTagAC3 = 0x6a
DescriptorTagAVCVideo = 0x28
DescriptorTagComponent = 0x50
DescriptorTagContent = 0x54
DescriptorTagDataStreamAlignment = 0x6
DescriptorTagEnhancedAC3 = 0x7a
DescriptorTagExtendedEvent = 0x4e
DescriptorTagExtension = 0x7f
DescriptorTagISO639LanguageAndAudioType = 0xa
DescriptorTagLocalTimeOffset = 0x58
DescriptorTagMaximumBitrate = 0xe
DescriptorTagNetworkName = 0x40
DescriptorTagParentalRating = 0x55
DescriptorTagPrivateDataIndicator = 0xf
DescriptorTagPrivateDataSpecifier = 0x5f
DescriptorTagRegistration = 0x5
DescriptorTagService = 0x48
DescriptorTagShortEvent = 0x4d
DescriptorTagStreamIdentifier = 0x52
DescriptorTagSubtitling = 0x59
DescriptorTagTeletext = 0x56
DescriptorTagVBIData = 0x45
DescriptorTagVBITeletext = 0x46
)
// Descriptor extension tags
// Page: 111 | Chapter: 6.1 | Link: https://www.dvb.org/resources/public/standards/a38_dvb-si_specification.pdf
const (
DescriptorTagExtensionSupplementaryAudio = 0x6
)
// Service types
// Page: 97 | Chapter: 6.2.33 | Link: https://www.dvb.org/resources/public/standards/a38_dvb-si_specification.pdf
// https://www.dvb.org/resources/public/standards/a38_dvb-si_specification.pdf / page 97
const (
ServiceTypeDigitalTelevisionService = 0x1
)
// Teletext types
// Page: 106 | Chapter: 6.2.43 | Link: https://www.dvb.org/resources/public/standards/a38_dvb-si_specification.pdf
const (
TeletextTypeAdditionalInformationPage = 0x3
TeletextTypeInitialTeletextPage = 0x1
TeletextTypeProgramSchedulePage = 0x4
TeletextTypeTeletextSubtitlePage = 0x2
TeletextTypeTeletextSubtitlePageForHearingImpairedPeople = 0x5
)
// VBI data service id
// Page: 109 | Link: https://www.dvb.org/resources/public/standards/a38_dvb-si_specification.pdf
const (
VBIDataServiceIDClosedCaptioning = 0x6
VBIDataServiceIDEBUTeletext = 0x1
VBIDataServiceIDInvertedTeletext = 0x2
VBIDataServiceIDMonochrome442Samples = 0x7
VBIDataServiceIDVPS = 0x4
VBIDataServiceIDWSS = 0x5
)
// Descriptor represents a descriptor
// TODO Handle UTF8
type Descriptor struct {
AC3 *DescriptorAC3
AVCVideo *DescriptorAVCVideo
Component *DescriptorComponent
Content *DescriptorContent
DataStreamAlignment *DescriptorDataStreamAlignment
EnhancedAC3 *DescriptorEnhancedAC3
ExtendedEvent *DescriptorExtendedEvent
Extension *DescriptorExtension
ISO639LanguageAndAudioType *DescriptorISO639LanguageAndAudioType
Length uint8
LocalTimeOffset *DescriptorLocalTimeOffset
MaximumBitrate *DescriptorMaximumBitrate
NetworkName *DescriptorNetworkName
ParentalRating *DescriptorParentalRating
PrivateDataIndicator *DescriptorPrivateDataIndicator
PrivateDataSpecifier *DescriptorPrivateDataSpecifier
Registration *DescriptorRegistration
Service *DescriptorService
ShortEvent *DescriptorShortEvent
StreamIdentifier *DescriptorStreamIdentifier
Subtitling *DescriptorSubtitling
Tag uint8 // the tag defines the structure of the contained data following the descriptor length.
Teletext *DescriptorTeletext
UserDefined []byte
VBIData *DescriptorVBIData
VBITeletext *DescriptorTeletext
}
// DescriptorAC3 represents an AC3 descriptor
// Page: 165 | https://www.dvb.org/resources/public/standards/a38_dvb-si_specification.pdf
type DescriptorAC3 struct {
AdditionalInfo []byte
ASVC uint8
BSID uint8
ComponentType uint8
HasASVC bool
HasBSID bool
HasComponentType bool
HasMainID bool
MainID uint8
}
func newDescriptorAC3(i []byte) (d *DescriptorAC3) {
var offset int
d = &DescriptorAC3{}
d.HasComponentType = uint8(i[offset]&0x80) > 0
d.HasBSID = uint8(i[offset]&0x40) > 0
d.HasMainID = uint8(i[offset]&0x20) > 0
d.HasASVC = uint8(i[offset]&0x10) > 0
offset += 1
if d.HasComponentType {
d.ComponentType = uint8(i[offset])
offset += 1
}
if d.HasBSID {
d.BSID = uint8(i[offset])
offset += 1
}
if d.HasMainID {
d.MainID = uint8(i[offset])
offset += 1
}
if d.HasASVC {
d.ASVC = uint8(i[offset])
offset += 1
}
for offset < len(i) {
d.AdditionalInfo = append(d.AdditionalInfo, i[offset])
offset += 1
}
return
}
// DescriptorAVCVideo represents an AVC video descriptor
// No doc found unfortunately, basing the implementation on https://github.com/gfto/bitstream/blob/master/mpeg/psi/desc_28.h
type DescriptorAVCVideo struct {
AVC24HourPictureFlag bool
AVCStillPresent bool
CompatibleFlags uint8
ConstraintSet0Flag bool
ConstraintSet1Flag bool
ConstraintSet2Flag bool
LevelIDC uint8
ProfileIDC uint8
}
func newDescriptorAVCVideo(i []byte) (d *DescriptorAVCVideo) {
// Init
d = &DescriptorAVCVideo{}
var offset int
// Profile idc
d.ProfileIDC = uint8(i[offset])
offset += 1
// Flags
d.ConstraintSet0Flag = i[offset]&0x80 > 0
d.ConstraintSet1Flag = i[offset]&0x40 > 0
d.ConstraintSet2Flag = i[offset]&0x20 > 0
d.CompatibleFlags = i[offset] & 0x1f
offset += 1
// Level idc
d.LevelIDC = uint8(i[offset])
offset += 1
// AVC still present
d.AVCStillPresent = i[offset]&0x80 > 0
// AVC 24 hour picture flag
d.AVC24HourPictureFlag = i[offset]&0x40 > 0
return
}
// DescriptorComponent represents a component descriptor
// Page: 51 | https://www.dvb.org/resources/public/standards/a38_dvb-si_specification.pdf
type DescriptorComponent struct {
ComponentTag uint8
ComponentType uint8
ISO639LanguageCode []byte
StreamContent uint8
StreamContentExt uint8
Text []byte
}
func newDescriptorComponent(i []byte) (d *DescriptorComponent) {
// Init
d = &DescriptorComponent{}
var offset int
// Stream content ext
d.StreamContentExt = uint8(i[offset] >> 4)
// Stream content
d.StreamContent = uint8(i[offset] & 0xf)
offset += 1
// Component type
d.ComponentType = uint8(i[offset])
offset += 1
// Component tag
d.ComponentTag = uint8(i[offset])
offset += 1
// ISO639 language code
d.ISO639LanguageCode = i[offset : offset+3]
offset += 3
// Text
for offset < len(i) {
d.Text = append(d.Text, i[offset])
offset += 1
}
return
}
// DescriptorContent represents a content descriptor
// Page: 58 | https://www.dvb.org/resources/public/standards/a38_dvb-si_specification.pdf
type DescriptorContent struct {
Items []*DescriptorContentItem
}
// DescriptorContentItem represents a content item descriptor
// Check page 59 of https://www.dvb.org/resources/public/standards/a38_dvb-si_specification.pdf for content nibble
// levels associations
type DescriptorContentItem struct {
ContentNibbleLevel1 uint8
ContentNibbleLevel2 uint8
UserByte uint8
}
func newDescriptorContent(i []byte) (d *DescriptorContent) {
// Init
d = &DescriptorContent{}
var offset int
// Add items
for offset < len(i) {
d.Items = append(d.Items, &DescriptorContentItem{
ContentNibbleLevel1: uint8(i[offset] >> 4),
ContentNibbleLevel2: uint8(i[offset] & 0xf),
UserByte: uint8(i[offset+1]),
})
offset += 2
}
return
}
// DescriptorDataStreamAlignment represents a data stream alignment descriptor
type DescriptorDataStreamAlignment struct {
Type uint8
}
func newDescriptorDataStreamAlignment(i []byte) *DescriptorDataStreamAlignment {
return &DescriptorDataStreamAlignment{Type: uint8(i[0])}
}
// DescriptorEnhancedAC3 represents an enhanced AC3 descriptor
// Page: 166 | https://www.dvb.org/resources/public/standards/a38_dvb-si_specification.pdf
type DescriptorEnhancedAC3 struct {
AdditionalInfo []byte
ASVC uint8
BSID uint8
ComponentType uint8
HasASVC bool
HasBSID bool
HasComponentType bool
HasMainID bool
HasSubStream1 bool
HasSubStream2 bool
HasSubStream3 bool
MainID uint8
MixInfoExists bool
SubStream1 uint8
SubStream2 uint8
SubStream3 uint8
}
func newDescriptorEnhancedAC3(i []byte) (d *DescriptorEnhancedAC3) {
var offset int
d = &DescriptorEnhancedAC3{}
d.HasComponentType = uint8(i[offset]&0x80) > 0
d.HasBSID = uint8(i[offset]&0x40) > 0
d.HasMainID = uint8(i[offset]&0x20) > 0
d.HasASVC = uint8(i[offset]&0x10) > 0
d.MixInfoExists = uint8(i[offset]&0x8) > 0
d.HasSubStream1 = uint8(i[offset]&0x4) > 0
d.HasSubStream2 = uint8(i[offset]&0x2) > 0
d.HasSubStream3 = uint8(i[offset]&0x1) > 0
offset += 1
if d.HasComponentType {
d.ComponentType = uint8(i[offset])
offset += 1
}
if d.HasBSID {
d.BSID = uint8(i[offset])
offset += 1
}
if d.HasMainID {
d.MainID = uint8(i[offset])
offset += 1
}
if d.HasASVC {
d.ASVC = uint8(i[offset])
offset += 1
}
if d.HasSubStream1 {
d.SubStream1 = uint8(i[offset])
offset += 1
}
if d.HasSubStream2 {
d.SubStream2 = uint8(i[offset])
offset += 1
}
if d.HasSubStream3 {
d.SubStream3 = uint8(i[offset])
offset += 1
}
for offset < len(i) {
d.AdditionalInfo = append(d.AdditionalInfo, i[offset])
offset += 1
}
return
}
// DescriptorExtendedEvent represents an extended event descriptor
type DescriptorExtendedEvent struct {
ISO639LanguageCode []byte
Items []*DescriptorExtendedEventItem
LastDescriptorNumber uint8
Number uint8
Text []byte
}
// DescriptorExtendedEventItem represents an extended event item descriptor
type DescriptorExtendedEventItem struct {
Content []byte
Description []byte
}
func newDescriptorExtendedEvent(i []byte) (d *DescriptorExtendedEvent) {
// Init
d = &DescriptorExtendedEvent{}
var offset int
// Number
d.Number = uint8(i[offset] >> 4)
// Last descriptor number
d.LastDescriptorNumber = uint8(i[offset] & 0xf)
offset += 1
// ISO 639 language code
d.ISO639LanguageCode = i[offset : offset+3]
offset += 3
// Items length
var itemsLength = int(i[offset])
offset += 1
// Items
var offsetEnd = offset + itemsLength
for offset < offsetEnd {
d.Items = append(d.Items, newDescriptorExtendedEventItem(i, &offset))
}
// Text length
var textLength = int(i[offset])
offset += 1
// Text
offsetEnd = offset + textLength
for offset < offsetEnd {
d.Text = append(d.Text, i[offset])
offset += 1
}
return
}
func newDescriptorExtendedEventItem(i []byte, offset *int) (d *DescriptorExtendedEventItem) {
// Init
d = &DescriptorExtendedEventItem{}
// Description length
var descriptionLength = int(i[*offset])
*offset += 1
// Description
var offsetEnd = *offset + descriptionLength
for *offset < offsetEnd {
d.Description = append(d.Description, i[*offset])
*offset += 1
}
// Content length
var contentLength = int(i[*offset])
*offset += 1
// Content
offsetEnd = *offset + contentLength
for *offset < offsetEnd {
d.Content = append(d.Content, i[*offset])
*offset += 1
}
return
}
// DescriptorExtension represents an extension descriptor
// Page: 72 | https://www.dvb.org/resources/public/standards/a38_dvb-si_specification.pdf
type DescriptorExtension struct {
SupplementaryAudio *DescriptorExtensionSupplementaryAudio
Tag uint8
}
func newDescriptorExtension(i []byte) (d *DescriptorExtension) {
// Init
d = &DescriptorExtension{Tag: uint8(i[0])}
// Switch on tag
var b = i[1:]
switch d.Tag {
case DescriptorTagExtensionSupplementaryAudio:
d.SupplementaryAudio = newDescriptorExtensionSupplementaryAudio(b)
default:
// TODO Remove this log
astilog.Debugf("astits: unlisted extension tag 0x%x", d.Tag)
}
return
}
// DescriptorExtensionSupplementaryAudio represents a supplementary audio extension descriptor
// Page: 130 | https://www.dvb.org/resources/public/standards/a38_dvb-si_specification.pdf
type DescriptorExtensionSupplementaryAudio struct {
EditorialClassification uint8
HasLanguageCode bool
LanguageCode []byte
MixType bool
PrivateData []byte
}
func newDescriptorExtensionSupplementaryAudio(i []byte) (d *DescriptorExtensionSupplementaryAudio) {
// Init
d = &DescriptorExtensionSupplementaryAudio{}
var offset int
// Mix type
d.MixType = i[offset]&0x80 > 0
// Editorial classification
d.EditorialClassification = uint8(i[offset] >> 2 & 0x1f)
// Language code flag
d.HasLanguageCode = i[offset]&0x1 > 0
offset += 1
// Language code
if d.HasLanguageCode {
d.LanguageCode = i[offset : offset+3]
offset += 3
}
// Private data
for offset < len(i) {
d.PrivateData = append(d.PrivateData, i[offset])
offset += 1
}
return
}
// DescriptorISO639LanguageAndAudioType represents an ISO639 language descriptor
type DescriptorISO639LanguageAndAudioType struct {
Language []byte
Type uint8
}
func newDescriptorISO639LanguageAndAudioType(i []byte) *DescriptorISO639LanguageAndAudioType {
return &DescriptorISO639LanguageAndAudioType{
Language: i[0:3],
Type: uint8(i[3]),
}
}
// DescriptorLocalTimeOffset represents a local time offset descriptor
// Page: 84 | Link: https://www.dvb.org/resources/public/standards/a38_dvb-si_specification.pdf
type DescriptorLocalTimeOffset struct {
Items []*DescriptorLocalTimeOffsetItem
}
// DescriptorLocalTimeOffsetItem represents a local time offset item descriptor
type DescriptorLocalTimeOffsetItem struct {
CountryCode []byte
CountryRegionID uint8
LocalTimeOffset time.Duration
LocalTimeOffsetPolarity bool
NextTimeOffset time.Duration
TimeOfChange time.Time
}
func newDescriptorLocalTimeOffset(i []byte) (d *DescriptorLocalTimeOffset) {
// Init
d = &DescriptorLocalTimeOffset{}
var offset int
// Add items
for offset < len(i) {
// Init
var itm = &DescriptorLocalTimeOffsetItem{}
d.Items = append(d.Items, itm)
// Country code
itm.CountryCode = i[offset : offset+3]
offset += 3
// Country region ID
itm.CountryRegionID = uint8(i[offset] >> 2)
// Local time offset polarity
itm.LocalTimeOffsetPolarity = i[offset]&0x1 > 0
offset += 1
// Local time offset
itm.LocalTimeOffset = parseDVBDurationMinutes(i, &offset)
// Time of change
itm.TimeOfChange = parseDVBTime(i, &offset)
// Next time offset
itm.NextTimeOffset = parseDVBDurationMinutes(i, &offset)
}
return
}
// DescriptorMaximumBitrate represents a maximum bitrate descriptor
type DescriptorMaximumBitrate struct {
Bitrate uint32 // In bytes/second
}
func newDescriptorMaximumBitrate(i []byte) *DescriptorMaximumBitrate {
return &DescriptorMaximumBitrate{Bitrate: (uint32(i[0]&0x3f)<<16 | uint32(i[1])<<8 | uint32(i[2])) * 50}
}
// DescriptorNetworkName represents a network name descriptor
// Page: 93 | Chapter: 6.2.27 | Link: https://www.dvb.org/resources/public/standards/a38_dvb-si_specification.pdf
type DescriptorNetworkName struct {
Name []byte
}
func newDescriptorNetworkName(i []byte) *DescriptorNetworkName {
return &DescriptorNetworkName{Name: i}
}
// DescriptorParentalRating represents a parental rating descriptor
// Page: 93 | Link: https://www.dvb.org/resources/public/standards/a38_dvb-si_specification.pdf
type DescriptorParentalRating struct {
Items []*DescriptorParentalRatingItem
}
// DescriptorParentalRatingItem represents a parental rating item descriptor
type DescriptorParentalRatingItem struct {
CountryCode []byte
Rating uint8
}
// MinimumAge returns the minimum age for the parental rating
func (d DescriptorParentalRatingItem) MinimumAge() int {
// Undefined or user defined ratings
if d.Rating == 0 || d.Rating > 0x10 {
return 0
}
return int(d.Rating) + 3
}
func newDescriptorParentalRating(i []byte) (d *DescriptorParentalRating) {
// Init
d = &DescriptorParentalRating{}
var offset int
// Add items
for offset < len(i) {
d.Items = append(d.Items, &DescriptorParentalRatingItem{
CountryCode: i[offset : offset+3],
Rating: uint8(i[offset+3]),
})
offset += 4
}
return
}
// DescriptorPrivateDataIndicator represents a private data Indicator descriptor
type DescriptorPrivateDataIndicator struct {
Indicator uint32
}
func newDescriptorPrivateDataIndicator(i []byte) *DescriptorPrivateDataIndicator {
return &DescriptorPrivateDataIndicator{Indicator: uint32(i[0])<<24 | uint32(i[1])<<16 | uint32(i[2])<<8 | uint32(i[3])}
}
// DescriptorPrivateDataSpecifier represents a private data specifier descriptor
type DescriptorPrivateDataSpecifier struct {
Specifier uint32
}
func newDescriptorPrivateDataSpecifier(i []byte) *DescriptorPrivateDataSpecifier {
return &DescriptorPrivateDataSpecifier{Specifier: uint32(i[0])<<24 | uint32(i[1])<<16 | uint32(i[2])<<8 | uint32(i[3])}
}
// DescriptorRegistration represents a registration descriptor
// Page: 84 | http://ecee.colorado.edu/~ecen5653/ecen5653/papers/iso13818-1.pdf
type DescriptorRegistration struct {
AdditionalIdentificationInfo []byte
FormatIdentifier uint32
}
func newDescriptorRegistration(i []byte) (d *DescriptorRegistration) {
d = &DescriptorRegistration{}
d.FormatIdentifier = uint32(i[0])<<24 | uint32(i[1])<<16 | uint32(i[2])<<8 | uint32(i[3])
var offset = 4
for offset < len(i) {
d.AdditionalIdentificationInfo = append(d.AdditionalIdentificationInfo, i[offset])
offset += 1
}
return
}
// DescriptorService represents a service descriptor
// Page: 96 | Chapter: 6.2.33 | Link: https://www.dvb.org/resources/public/standards/a38_dvb-si_specification.pdf
type DescriptorService struct {
Name []byte
Provider []byte
Type uint8
}
func newDescriptorService(i []byte) (d *DescriptorService) {
var offset int
d = &DescriptorService{Type: uint8(i[offset])}
offset += 1
var providerLength = int(i[offset])
offset += 1
d.Provider = i[offset : offset+providerLength]
offset += providerLength
var nameLength = int(i[offset])
offset += 1
d.Name = i[offset : offset+nameLength]
return
}
// DescriptorShortEvent represents a short event descriptor
// Page: 99 | Chapter: 6.2.37 | Link: https://www.dvb.org/resources/public/standards/a38_dvb-si_specification.pdf
type DescriptorShortEvent struct {
EventName []byte
Language []byte
Text []byte
}
func newDescriptorShortEvent(i []byte) (d *DescriptorShortEvent) {
var offset int
d = &DescriptorShortEvent{}
d.Language = i[:3]
offset += 3
var length = int(i[offset])
offset += 1
d.EventName = i[offset : offset+length]
offset += length
length = int(i[offset])
offset += 1
d.Text = i[offset : offset+length]
return
}
// DescriptorStreamIdentifier represents a stream identifier descriptor
// Page: 102 | Chapter: 6.2.39 | Link: https://www.dvb.org/resources/public/standards/a38_dvb-si_specification.pdf
type DescriptorStreamIdentifier struct{ ComponentTag uint8 }
func newDescriptorStreamIdentifier(i []byte) *DescriptorStreamIdentifier {
return &DescriptorStreamIdentifier{ComponentTag: uint8(i[0])}
}
// DescriptorSubtitling represents a subtitling descriptor
// Page: 103 | Chapter: 6.2.41 | Link: https://www.dvb.org/resources/public/standards/a38_dvb-si_specification.pdf
type DescriptorSubtitling struct {
Items []*DescriptorSubtitlingItem
}
// DescriptorSubtitlingItem represents subtitling descriptor item
type DescriptorSubtitlingItem struct {
AncillaryPageID uint16
CompositionPageID uint16
Language []byte
Type uint8
}
func newDescriptorSubtitling(i []byte) (d *DescriptorSubtitling) {
d = &DescriptorSubtitling{}
var offset int
for offset < len(i) {
itm := &DescriptorSubtitlingItem{}
itm.Language = i[offset : offset+3]
offset += 3
itm.Type = uint8(i[offset])
offset += 1
itm.CompositionPageID = uint16(i[offset])<<8 | uint16(i[offset+1])
offset += 2
itm.AncillaryPageID = uint16(i[offset])<<8 | uint16(i[offset+1])
offset += 2
d.Items = append(d.Items, itm)
}
return
}
// DescriptorTeletext represents a teletext descriptor
// Page: 105 | Chapter: 6.2.43 | Link: https://www.dvb.org/resources/public/standards/a38_dvb-si_specification.pdf
type DescriptorTeletext struct {
Items []*DescriptorTeletextItem
}
// DescriptorTeletextItem represents a teletext descriptor item
type DescriptorTeletextItem struct {
Language []byte
Magazine uint8
Page uint8
Type uint8
}
func newDescriptorTeletext(i []byte) (d *DescriptorTeletext) {
var offset int
d = &DescriptorTeletext{}
for offset < len(i) {
itm := &DescriptorTeletextItem{}
itm.Language = i[offset : offset+3]
offset += 3
itm.Type = uint8(i[offset]) >> 3
itm.Magazine = uint8(i[offset] & 0x7)
offset += 1
itm.Page = uint8(i[offset])>>4*10 + uint8(i[offset]&0xf)
offset += 1
d.Items = append(d.Items, itm)
}
return
}
// DescriptorVBIData represents a VBI data descriptor
// Page: 108 | Link: https://www.dvb.org/resources/public/standards/a38_dvb-si_specification.pdf
type DescriptorVBIData struct {
Services []*DescriptorVBIDataService
}
// DescriptorVBIDataService represents a vbi data service descriptor
type DescriptorVBIDataService struct {
DataServiceID uint8
Descriptors []*DescriptorVBIDataDescriptor
}
// DescriptorVBIDataItem represents a vbi data descriptor item
type DescriptorVBIDataDescriptor struct {
FieldParity bool
LineOffset uint8
}
func newDescriptorVBIData(i []byte) (d *DescriptorVBIData) {
// Init
d = &DescriptorVBIData{}
var offset int
// Items
for offset < len(i) {
// Init
var srv = &DescriptorVBIDataService{}
// Data service ID
srv.DataServiceID = uint8(i[offset])
offset += 1
// Data service descriptor length
var dataServiceDescriptorLength = int(i[offset])
offset += 1
// Data service descriptor
var offsetEnd = offset + dataServiceDescriptorLength
for offset < offsetEnd {
if srv.DataServiceID == VBIDataServiceIDClosedCaptioning ||
srv.DataServiceID == VBIDataServiceIDEBUTeletext ||
srv.DataServiceID == VBIDataServiceIDInvertedTeletext ||
srv.DataServiceID == VBIDataServiceIDMonochrome442Samples ||
srv.DataServiceID == VBIDataServiceIDVPS ||
srv.DataServiceID == VBIDataServiceIDWSS {
srv.Descriptors = append(srv.Descriptors, &DescriptorVBIDataDescriptor{
FieldParity: i[offset]&0x20 > 0,
LineOffset: uint8(i[offset] & 0x1f),
})
offset += 1
}
}
// Append service
d.Services = append(d.Services, srv)
}
return
}
// parseDescriptors parses descriptors
func parseDescriptors(i []byte, offset *int) (o []*Descriptor) {
// Get length
var length = int(uint16(i[*offset]&0xf)<<8 | uint16(i[*offset+1]))
*offset += 2
// Loop
if length > 0 {
length += *offset
for *offset < length {
// Init
var d = &Descriptor{
Length: uint8(i[*offset+1]),
Tag: uint8(i[*offset]),
}
*offset += 2
// Parse data
if d.Length > 0 {
// Get descriptor content
var b = i[*offset : *offset+int(d.Length)]
// User defined
if d.Tag >= 0x80 && d.Tag <= 0xfe {
d.UserDefined = make([]byte, len(b))
copy(d.UserDefined, b)
} else {
// Switch on tag
switch d.Tag {
case DescriptorTagAC3:
d.AC3 = newDescriptorAC3(b)
case DescriptorTagAVCVideo:
d.AVCVideo = newDescriptorAVCVideo(b)
case DescriptorTagComponent:
d.Component = newDescriptorComponent(b)
case DescriptorTagContent:
d.Content = newDescriptorContent(b)
case DescriptorTagDataStreamAlignment:
d.DataStreamAlignment = newDescriptorDataStreamAlignment(b)
case DescriptorTagEnhancedAC3:
d.EnhancedAC3 = newDescriptorEnhancedAC3(b)
case DescriptorTagExtendedEvent:
d.ExtendedEvent = newDescriptorExtendedEvent(b)
case DescriptorTagExtension:
d.Extension = newDescriptorExtension(b)
case DescriptorTagISO639LanguageAndAudioType:
d.ISO639LanguageAndAudioType = newDescriptorISO639LanguageAndAudioType(b)
case DescriptorTagLocalTimeOffset:
d.LocalTimeOffset = newDescriptorLocalTimeOffset(b)
case DescriptorTagMaximumBitrate:
d.MaximumBitrate = newDescriptorMaximumBitrate(b)
case DescriptorTagNetworkName:
d.NetworkName = newDescriptorNetworkName(b)
case DescriptorTagParentalRating:
d.ParentalRating = newDescriptorParentalRating(b)
case DescriptorTagPrivateDataIndicator:
d.PrivateDataIndicator = newDescriptorPrivateDataIndicator(b)
case DescriptorTagPrivateDataSpecifier:
d.PrivateDataSpecifier = newDescriptorPrivateDataSpecifier(b)
case DescriptorTagRegistration:
d.Registration = newDescriptorRegistration(b)
case DescriptorTagService:
d.Service = newDescriptorService(b)
case DescriptorTagShortEvent:
d.ShortEvent = newDescriptorShortEvent(b)
case DescriptorTagStreamIdentifier:
d.StreamIdentifier = newDescriptorStreamIdentifier(b)
case DescriptorTagSubtitling:
d.Subtitling = newDescriptorSubtitling(b)
case DescriptorTagTeletext:
d.Teletext = newDescriptorTeletext(b)
case DescriptorTagVBIData:
d.VBIData = newDescriptorVBIData(b)
case DescriptorTagVBITeletext:
d.VBITeletext = newDescriptorTeletext(b)
default:
// TODO Remove this log
astilog.Debugf("astits: unlisted descriptor tag 0x%x", d.Tag)
}
}
*offset += int(d.Length)
}
o = append(o, d)
}
}
return
}

53
vendor/github.com/asticode/go-astits/dvb.go generated vendored Normal file
View File

@@ -0,0 +1,53 @@
package astits
import (
"fmt"
"time"
)
// parseDVBTime parses a DVB time
// This field is coded as 16 bits giving the 16 LSBs of MJD followed by 24 bits coded as 6 digits in 4 - bit Binary
// Coded Decimal (BCD). If the start time is undefined (e.g. for an event in a NVOD reference service) all bits of the
// field are set to "1".
// I apologize for the computation which is really messy but details are given in the documentation
// Page: 160 | Annex C | Link: https://www.dvb.org/resources/public/standards/a38_dvb-si_specification.pdf
func parseDVBTime(i []byte, offset *int) (t time.Time) {
// Date
var mjd = uint16(i[*offset])<<8 | uint16(i[*offset+1])
var yt = int((float64(mjd) - 15078.2) / 365.25)
var mt = int((float64(mjd) - 14956.1 - float64(int(float64(yt)*365.25))) / 30.6001)
var d = int(float64(mjd) - 14956 - float64(int(float64(yt)*365.25)) - float64(int(float64(mt)*30.6001)))
var k int
if mt == 14 || mt == 15 {
k = 1
}
var y = yt + k
var m = mt - 1 - k*12
t, _ = time.Parse("06-01-02", fmt.Sprintf("%d-%d-%d", y, m, d))
*offset += 2
// Time
t = t.Add(parseDVBDurationSeconds(i, offset))
return
}
// parseDVBDurationMinutes parses a minutes duration
// 16 bit field containing the duration of the event in hours, minutes. format: 4 digits, 4 - bit BCD = 18 bit
func parseDVBDurationMinutes(i []byte, offset *int) (d time.Duration) {
d = parseDVBDurationByte(i[*offset])*time.Hour + parseDVBDurationByte(i[*offset+1])*time.Minute
*offset += 2
return
}
// parseDVBDurationSeconds parses a seconds duration
// 24 bit field containing the duration of the event in hours, minutes, seconds. format: 6 digits, 4 - bit BCD = 24 bit
func parseDVBDurationSeconds(i []byte, offset *int) (d time.Duration) {
d = parseDVBDurationByte(i[*offset])*time.Hour + parseDVBDurationByte(i[*offset+1])*time.Minute + parseDVBDurationByte(i[*offset+2])*time.Second
*offset += 3
return
}
// parseDVBDurationByte parses a duration byte
func parseDVBDurationByte(i byte) time.Duration {
return time.Duration(uint8(i)>>4*10 + uint8(i)&0xf)
}

207
vendor/github.com/asticode/go-astits/packet.go generated vendored Normal file
View File

@@ -0,0 +1,207 @@
package astits
// Scrambling Controls
const (
ScramblingControlNotScrambled = 0
ScramblingControlReservedForFutureUse = 1
ScramblingControlScrambledWithEvenKey = 2
ScramblingControlScrambledWithOddKey = 3
)
// Packet represents a packet
// https://en.wikipedia.org/wiki/MPEG_transport_stream
type Packet struct {
AdaptationField *PacketAdaptationField
Bytes []byte // This is the whole packet content
Header *PacketHeader
Payload []byte // This is only the payload content
}
// PacketHeader represents a packet header
type PacketHeader struct {
ContinuityCounter uint8 // Sequence number of payload packets (0x00 to 0x0F) within each stream (except PID 8191)
HasAdaptationField bool
HasPayload bool
PayloadUnitStartIndicator bool // Set when a PES, PSI, or DVB-MIP packet begins immediately following the header.
PID uint16 // Packet Identifier, describing the payload data.
TransportErrorIndicator bool // Set when a demodulator can't correct errors from FEC data; indicating the packet is corrupt.
TransportPriority bool // Set when the current packet has a higher priority than other packets with the same PID.
TransportScramblingControl uint8
}
// PacketAdaptationField represents a packet adaptation field
type PacketAdaptationField struct {
AdaptationExtensionField *PacketAdaptationExtensionField
DiscontinuityIndicator bool // Set if current TS packet is in a discontinuity state with respect to either the continuity counter or the program clock reference
ElementaryStreamPriorityIndicator bool // Set when this stream should be considered "high priority"
HasAdaptationExtensionField bool
HasOPCR bool
HasPCR bool
HasTransportPrivateData bool
HasSplicingCountdown bool
Length int
OPCR *ClockReference // Original Program clock reference. Helps when one TS is copied into another
PCR *ClockReference // Program clock reference
RandomAccessIndicator bool // Set when the stream may be decoded without errors from this point
SpliceCountdown int // Indicates how many TS packets from this one a splicing point occurs (Two's complement signed; may be negative)
TransportPrivateDataLength int
TransportPrivateData []byte
}
// PacketAdaptationExtensionField represents a packet adaptation extension field
type PacketAdaptationExtensionField struct {
DTSNextAccessUnit *ClockReference // The PES DTS of the splice point. Split up as 3 bits, 1 marker bit (0x1), 15 bits, 1 marker bit, 15 bits, and 1 marker bit, for 33 data bits total.
HasLegalTimeWindow bool
HasPiecewiseRate bool
HasSeamlessSplice bool
LegalTimeWindowIsValid bool
LegalTimeWindowOffset uint16 // Extra information for rebroadcasters to determine the state of buffers when packets may be missing.
Length int
PiecewiseRate uint32 // The rate of the stream, measured in 188-byte packets, to define the end-time of the LTW.
SpliceType uint8 // Indicates the parameters of the H.262 splice.
}
// parsePacket parses a packet
func parsePacket(i []byte) (p *Packet, err error) {
// Packet must start with a sync byte
if i[0] != syncByte {
err = ErrPacketMustStartWithASyncByte
return
}
// Init
p = &Packet{Bytes: i}
// In case packet size is bigger than 188 bytes, we don't care for the first bytes
i = i[len(i)-188+1:]
// Parse header
p.Header = parsePacketHeader(i)
// Parse adaptation field
if p.Header.HasAdaptationField {
p.AdaptationField = parsePacketAdaptationField(i[3:])
}
// Build payload
if p.Header.HasPayload {
p.Payload = i[payloadOffset(p.Header, p.AdaptationField):]
}
return
}
// payloadOffset returns the payload offset
func payloadOffset(h *PacketHeader, a *PacketAdaptationField) (offset int) {
offset = 3
if h.HasAdaptationField {
offset += 1 + a.Length
}
return
}
// parsePacketHeader parses the packet header
func parsePacketHeader(i []byte) *PacketHeader {
return &PacketHeader{
ContinuityCounter: uint8(i[2] & 0xf),
HasAdaptationField: i[2]&0x20 > 0,
HasPayload: i[2]&0x10 > 0,
PayloadUnitStartIndicator: i[0]&0x40 > 0,
PID: uint16(i[0]&0x1f)<<8 | uint16(i[1]),
TransportErrorIndicator: i[0]&0x80 > 0,
TransportPriority: i[0]&0x20 > 0,
TransportScramblingControl: uint8(i[2]) >> 6 & 0x3,
}
}
// parsePacketAdaptationField parses the packet adaptation field
func parsePacketAdaptationField(i []byte) (a *PacketAdaptationField) {
// Init
a = &PacketAdaptationField{}
var offset int
// Length
a.Length = int(i[offset])
offset += 1
// Valid length
if a.Length > 0 {
// Flags
a.DiscontinuityIndicator = i[offset]&0x80 > 0
a.RandomAccessIndicator = i[offset]&0x40 > 0
a.ElementaryStreamPriorityIndicator = i[offset]&0x20 > 0
a.HasPCR = i[offset]&0x10 > 0
a.HasOPCR = i[offset]&0x08 > 0
a.HasSplicingCountdown = i[offset]&0x04 > 0
a.HasTransportPrivateData = i[offset]&0x02 > 0
a.HasAdaptationExtensionField = i[offset]&0x01 > 0
offset += 1
// PCR
if a.HasPCR {
a.PCR = parsePCR(i[offset:])
offset += 6
}
// OPCR
if a.HasOPCR {
a.OPCR = parsePCR(i[offset:])
offset += 6
}
// Splicing countdown
if a.HasSplicingCountdown {
a.SpliceCountdown = int(i[offset])
offset += 1
}
// Transport private data
if a.HasTransportPrivateData {
a.TransportPrivateDataLength = int(i[offset])
offset += 1
if a.TransportPrivateDataLength > 0 {
a.TransportPrivateData = i[offset : offset+a.TransportPrivateDataLength]
offset += a.TransportPrivateDataLength
}
}
// Adaptation extension
if a.HasAdaptationExtensionField {
a.AdaptationExtensionField = &PacketAdaptationExtensionField{Length: int(i[offset])}
offset += 1
if a.AdaptationExtensionField.Length > 0 {
// Basic
a.AdaptationExtensionField.HasLegalTimeWindow = i[offset]&0x80 > 0
a.AdaptationExtensionField.HasPiecewiseRate = i[offset]&0x40 > 0
a.AdaptationExtensionField.HasSeamlessSplice = i[offset]&0x20 > 0
offset += 1
// Legal time window
if a.AdaptationExtensionField.HasLegalTimeWindow {
a.AdaptationExtensionField.LegalTimeWindowIsValid = i[offset]&0x80 > 0
a.AdaptationExtensionField.LegalTimeWindowOffset = uint16(i[offset]&0x7f)<<8 | uint16(i[offset+1])
offset += 2
}
// Piecewise rate
if a.AdaptationExtensionField.HasPiecewiseRate {
a.AdaptationExtensionField.PiecewiseRate = uint32(i[offset]&0x3f)<<16 | uint32(i[offset+1])<<8 | uint32(i[offset+2])
offset += 3
}
// Seamless splice
if a.AdaptationExtensionField.HasSeamlessSplice {
a.AdaptationExtensionField.SpliceType = uint8(i[offset]&0xf0) >> 4
a.AdaptationExtensionField.DTSNextAccessUnit = parsePTSOrDTS(i[offset:])
}
}
}
}
return
}
// parsePCR parses a Program Clock Reference
// Program clock reference, stored as 33 bits base, 6 bits reserved, 9 bits extension.
func parsePCR(i []byte) *ClockReference {
var pcr = uint64(i[0])<<40 | uint64(i[1])<<32 | uint64(i[2])<<24 | uint64(i[3])<<16 | uint64(i[4])<<8 | uint64(i[5])
return newClockReference(int(pcr>>15), int(pcr&0x1ff))
}

111
vendor/github.com/asticode/go-astits/packet_buffer.go generated vendored Normal file
View File

@@ -0,0 +1,111 @@
package astits
import (
"fmt"
"io"
"github.com/pkg/errors"
)
// packetBuffer represents a packet buffer
type packetBuffer struct {
b []*Packet
packetSize int
r io.Reader
}
// newPacketBuffer creates a new packet buffer
func newPacketBuffer(r io.Reader, packetSize int) (pb *packetBuffer, err error) {
// Init
pb = &packetBuffer{
packetSize: packetSize,
r: r,
}
// Packet size is not set
if pb.packetSize == 0 {
// Auto detect packet size
if pb.packetSize, err = autoDetectPacketSize(r); err != nil {
err = errors.Wrap(err, "astits: auto detecting packet size failed")
return
}
}
return
}
// autoDetectPacketSize updates the packet size based on the first bytes
// Minimum packet size is 188 and is bounded by 2 sync bytes
// Assumption is made that the first byte of the reader is a sync byte
func autoDetectPacketSize(r io.Reader) (packetSize int, err error) {
// Read first bytes
const l = 193
var b = make([]byte, l)
if _, err = r.Read(b); err != nil {
err = errors.Wrapf(err, "astits: reading first %d bytes failed", l)
return
}
// Packet must start with a sync byte
if b[0] != syncByte {
err = ErrPacketMustStartWithASyncByte
return
}
// Look for sync bytes
for idx, b := range b {
if b == syncByte && idx >= 188 {
// Update packet size
packetSize = idx
// Rewind or sync reader
var n int64
if n, err = rewind(r); err != nil {
err = errors.Wrap(err, "astits: rewinding failed")
return
} else if n == -1 {
var ls = packetSize - (l - packetSize)
if _, err = r.Read(make([]byte, ls)); err != nil {
err = errors.Wrapf(err, "astits: reading %d bytes to sync reader failed", ls)
return
}
}
return
}
}
err = fmt.Errorf("astits: only one sync byte detected in first %d bytes", l)
return
}
// rewind rewinds the reader if possible, otherwise n = -1
func rewind(r io.Reader) (n int64, err error) {
if s, ok := r.(io.Seeker); ok {
if n, err = s.Seek(0, 0); err != nil {
err = errors.Wrap(err, "astits: seeking to 0 failed")
return
}
return
}
n = -1
return
}
// next fetches the next packet from the buffer
func (pb *packetBuffer) next() (p *Packet, err error) {
// Read
var b = make([]byte, pb.packetSize)
if _, err = io.ReadFull(pb.r, b); err != nil {
if err == io.EOF || err == io.ErrUnexpectedEOF {
err = ErrNoMorePackets
} else {
err = errors.Wrapf(err, "astits: reading %d bytes failed", pb.packetSize)
}
return
}
// Parse packet
if p, err = parsePacket(b); err != nil {
err = errors.Wrap(err, "astits: building packet failed")
return
}
return
}

101
vendor/github.com/asticode/go-astits/packet_pool.go generated vendored Normal file
View File

@@ -0,0 +1,101 @@
package astits
import (
"sort"
"sync"
)
// packetPool represents a pool of packets
type packetPool struct {
b map[uint16][]*Packet // Indexed by PID
m *sync.Mutex
}
// newPacketPool creates a new packet pool
func newPacketPool() *packetPool {
return &packetPool{
b: make(map[uint16][]*Packet),
m: &sync.Mutex{},
}
}
// add adds a new packet to the pool
func (b *packetPool) add(p *Packet) (ps []*Packet) {
// Throw away packet if error indicator
if p.Header.TransportErrorIndicator {
return
}
// Throw away packets that don't have a payload until we figure out what we're going to do with them
// TODO figure out what we're going to do with them :D
if !p.Header.HasPayload {
return
}
// Lock
b.m.Lock()
defer b.m.Unlock()
// Init buffer
var mps []*Packet
var ok bool
if mps, ok = b.b[p.Header.PID]; !ok {
mps = []*Packet{}
}
// Empty buffer if we detect a discontinuity
if hasDiscontinuity(mps, p) {
mps = []*Packet{}
}
// Throw away packet if it's the same as the previous one
if isSameAsPrevious(mps, p) {
return
}
// Add packet
if len(mps) > 0 || (len(mps) == 0 && p.Header.PayloadUnitStartIndicator) {
mps = append(mps, p)
}
// Check payload unit start indicator
if p.Header.PayloadUnitStartIndicator && len(mps) > 1 {
ps = mps[:len(mps)-1]
mps = []*Packet{p}
}
// Assign
b.b[p.Header.PID] = mps
return
}
// dump dumps the packet pool by looking for the first item with packets inside
func (b *packetPool) dump() (ps []*Packet) {
b.m.Lock()
defer b.m.Unlock()
var keys []int
for k := range b.b {
keys = append(keys, int(k))
}
sort.Ints(keys)
for _, k := range keys {
ps = b.b[uint16(k)]
delete(b.b, uint16(k))
if len(ps) > 0 {
return
}
}
return
}
// hasDiscontinuity checks whether a packet is discontinuous with a set of packets
func hasDiscontinuity(ps []*Packet, p *Packet) bool {
return (p.Header.HasAdaptationField && p.AdaptationField.DiscontinuityIndicator) ||
(len(ps) > 0 && p.Header.HasPayload && p.Header.ContinuityCounter != (ps[len(ps)-1].Header.ContinuityCounter+1)%16) ||
(len(ps) > 0 && !p.Header.HasPayload && p.Header.ContinuityCounter != ps[len(ps)-1].Header.ContinuityCounter)
}
// isSameAsPrevious checks whether a packet is the same as the last packet of a set of packets
func isSameAsPrevious(ps []*Packet, p *Packet) bool {
return len(ps) > 0 && p.Header.HasPayload && p.Header.ContinuityCounter == ps[len(ps)-1].Header.ContinuityCounter
}

32
vendor/github.com/asticode/go-astits/program_map.go generated vendored Normal file
View File

@@ -0,0 +1,32 @@
package astits
import "sync"
// programMap represents a program ids map
type programMap struct {
m *sync.Mutex
p map[uint16]uint16 // map[ProgramMapID]ProgramNumber
}
// newProgramMap creates a new program ids map
func newProgramMap() programMap {
return programMap{
m: &sync.Mutex{},
p: make(map[uint16]uint16),
}
}
// exists checks whether the program with this pid exists
func (m programMap) exists(pid uint16) (ok bool) {
m.m.Lock()
defer m.m.Unlock()
_, ok = m.p[pid]
return
}
// set sets a new program id
func (m programMap) set(pid, number uint16) {
m.m.Lock()
defer m.m.Unlock()
m.p[pid] = number
}