init
This commit is contained in:
21
vendor/github.com/asticode/go-astits/LICENSE
generated
vendored
Normal file
21
vendor/github.com/asticode/go-astits/LICENSE
generated
vendored
Normal 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
123
vendor/github.com/asticode/go-astits/README.md
generated
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
[](http://goreportcard.com/report/github.com/asticode/go-astits)
|
||||
[](https://godoc.org/github.com/asticode/go-astits)
|
||||
[](https://travis-ci.org/asticode/go-astits#)
|
||||
[](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
|
29
vendor/github.com/asticode/go-astits/clock_reference.go
generated
vendored
Normal file
29
vendor/github.com/asticode/go-astits/clock_reference.go
generated
vendored
Normal 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
95
vendor/github.com/asticode/go-astits/data.go
generated
vendored
Normal 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
73
vendor/github.com/asticode/go-astits/data_eit.go
generated
vendored
Normal 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
49
vendor/github.com/asticode/go-astits/data_nit.go
generated
vendored
Normal 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
30
vendor/github.com/asticode/go-astits/data_pat.go
generated
vendored
Normal 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
322
vendor/github.com/asticode/go-astits/data_pes.go
generated
vendored
Normal 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
57
vendor/github.com/asticode/go-astits/data_pmt.go
generated
vendored
Normal 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
356
vendor/github.com/asticode/go-astits/data_psi.go
generated
vendored
Normal 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
70
vendor/github.com/asticode/go-astits/data_sdt.go
generated
vendored
Normal 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
23
vendor/github.com/asticode/go-astits/data_tot.go
generated
vendored
Normal 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
163
vendor/github.com/asticode/go-astits/demuxer.go
generated
vendored
Normal 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
918
vendor/github.com/asticode/go-astits/descriptor.go
generated
vendored
Normal 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
53
vendor/github.com/asticode/go-astits/dvb.go
generated
vendored
Normal 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
207
vendor/github.com/asticode/go-astits/packet.go
generated
vendored
Normal 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
111
vendor/github.com/asticode/go-astits/packet_buffer.go
generated
vendored
Normal 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
101
vendor/github.com/asticode/go-astits/packet_pool.go
generated
vendored
Normal 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
32
vendor/github.com/asticode/go-astits/program_map.go
generated
vendored
Normal 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
|
||||
}
|
Reference in New Issue
Block a user