├── .gitignore ├── testdata └── fuzz │ ├── FuzzDescriptor │ ├── 6ae8bb726335d15a │ └── c18ae01ae2349b75 │ └── FuzzDemuxer │ ├── ea43ffbf0b7f89ff4ab10dbe942e8cabfebc714d0c727db405a5764eba605ba4 │ ├── 675671b6f2ad170dc8c87ab80a3765a8e0820ae74bec632b695e8c3a3c68f448 │ ├── 835769742227666f0ebf4de0f9d65117a5cb135ee6e1039bad1a1703b8e4dd66 │ ├── ac216932987a199d86aeef773034d8334af53358d27f15f175eac97d89fc6861 │ ├── ee47e15faac5a6c6806aa0fb1ffbb9dd32a569ed4503a38147a4ca9470e3497b │ ├── d1e578c13225ac8013c841910c3eb5e89150b114deca770e3afb6acdc8036a91 │ ├── 1acf6c5d01716faefd01ee027121ab635b343d5423e9c20e27dd4e27619eaf31 │ ├── 49be436174e9321657131246dd602da216b479eb7a247017b104614f11f3e50f │ ├── 3613c76a2489ae2ec5c2eee87f57b611119b55adcf10be45687b39d89ae38205 │ ├── c9386b0717370f896e7baa2c62d5ae36346e4253b8883cdff93c016105516711 │ └── 9f12197b2060d80d1f1c7710f44bcdade975fd7660f9d85d79536a34d904cf70 ├── doc └── en_300468v011501p.pdf ├── go.mod ├── program_map_test.go ├── clock_reference_test.go ├── wrapping_counter.go ├── .devcontainer ├── devcontainer.json └── Dockerfile ├── crc32.go ├── data_tot_test.go ├── .github └── workflows │ └── test.yml ├── clock_reference.go ├── packet_buffer_test.go ├── data_tot.go ├── crc32_test.go ├── LICENSE ├── internal └── cmd │ └── crc32_table │ └── main.go ├── program_map.go ├── data_nit_test.go ├── go.sum ├── data_sdt_test.go ├── pools.go ├── data_eit_test.go ├── data_pat_test.go ├── data_pat.go ├── data_pmt_test.go ├── dvb_test.go ├── data_test.go ├── data_nit.go ├── crc32_table.go ├── data_sdt.go ├── data_eit.go ├── packet_buffer.go ├── packet_pool.go ├── dvb.go ├── packet_pool_test.go ├── data.go ├── cmd ├── astits-es-split │ └── main.go └── astits-probe │ └── main.go ├── demuxer.go ├── README.md ├── demuxer_test.go ├── data_pmt.go ├── packet_test.go ├── muxer_test.go ├── muxer.go ├── data_psi_test.go └── data_pes_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Thumbs.db 3 | .idea/ 4 | cover* 5 | test 6 | -------------------------------------------------------------------------------- /testdata/fuzz/FuzzDescriptor/6ae8bb726335d15a: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("0\x060\x02000\x00") 3 | -------------------------------------------------------------------------------- /testdata/fuzz/FuzzDescriptor/c18ae01ae2349b75: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("0\x060\x0200X\x00") 3 | -------------------------------------------------------------------------------- /doc/en_300468v011501p.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asticode/go-astits/HEAD/doc/en_300468v011501p.pdf -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/asticode/go-astits 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/asticode/go-astikit v0.30.0 7 | github.com/pkg/profile v1.4.0 8 | github.com/stretchr/testify v1.4.0 9 | ) 10 | -------------------------------------------------------------------------------- /testdata/fuzz/FuzzDemuxer/ea43ffbf0b7f89ff4ab10dbe942e8cabfebc714d0c727db405a5764eba605ba4: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("G000070000000\xc5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") 3 | -------------------------------------------------------------------------------- /testdata/fuzz/FuzzDemuxer/675671b6f2ad170dc8c87ab80a3765a8e0820ae74bec632b695e8c3a3c68f448: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("G \x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000A0\x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000") 3 | -------------------------------------------------------------------------------- /testdata/fuzz/FuzzDemuxer/835769742227666f0ebf4de0f9d65117a5cb135ee6e1039bad1a1703b8e4dd66: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("G \x00X\x00X0\x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") 3 | -------------------------------------------------------------------------------- /testdata/fuzz/FuzzDemuxer/ac216932987a199d86aeef773034d8334af53358d27f15f175eac97d89fc6861: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("G \x00X\x00B0\x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") 3 | -------------------------------------------------------------------------------- /testdata/fuzz/FuzzDemuxer/ee47e15faac5a6c6806aa0fb1ffbb9dd32a569ed4503a38147a4ca9470e3497b: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("G00X\x00\x00\x010\x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") 3 | -------------------------------------------------------------------------------- /program_map_test.go: -------------------------------------------------------------------------------- 1 | package astits 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestProgramMap(t *testing.T) { 10 | pm := newProgramMap() 11 | assert.False(t, pm.existsUnlocked(1)) 12 | pm.setUnlocked(1, 1) 13 | assert.True(t, pm.existsUnlocked(1)) 14 | pm.unsetUnlocked(1) 15 | assert.False(t, pm.existsUnlocked(1)) 16 | } 17 | -------------------------------------------------------------------------------- /clock_reference_test.go: -------------------------------------------------------------------------------- 1 | package astits 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | var clockReference = newClockReference(3271034319, 58) 11 | 12 | func TestClockReference(t *testing.T) { 13 | assert.Equal(t, 36344825768814*time.Nanosecond, clockReference.Duration()) 14 | assert.Equal(t, int64(36344), clockReference.Time().Unix()) 15 | } 16 | -------------------------------------------------------------------------------- /wrapping_counter.go: -------------------------------------------------------------------------------- 1 | package astits 2 | 3 | type wrappingCounter struct { 4 | value int 5 | wrapAt int 6 | } 7 | 8 | func newWrappingCounter(wrapAt int) wrappingCounter { 9 | return wrappingCounter{ 10 | value: wrapAt + 1, 11 | wrapAt: wrapAt, 12 | } 13 | } 14 | 15 | func (c *wrappingCounter) get() int { 16 | return c.value 17 | } 18 | 19 | func (c *wrappingCounter) inc() int { 20 | c.value++ 21 | if c.value > c.wrapAt { 22 | c.value = 0 23 | } 24 | return c.value 25 | } 26 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": { 3 | "args": { 4 | "GO_VERSION": "1.25.3" 5 | }, 6 | "dockerfile": "Dockerfile" 7 | }, 8 | "customizations": { 9 | "vscode": { 10 | "extensions": [ 11 | "golang.go" 12 | ], 13 | "settings": { 14 | "remote.autoForwardPorts": false 15 | } 16 | } 17 | }, 18 | "mounts": [ 19 | "source=${localEnv:HOME}${localEnv:USERPROFILE}/.ssh,target=/root/.ssh,readonly,type=bind" 20 | ], 21 | "name": "asticode/go-astits" 22 | } 23 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | 3 | RUN apk add --update 4 | 5 | ## 6 | # git 7 | ## 8 | 9 | RUN apk add git openssh-client nano less 10 | 11 | ## 12 | # go 13 | ## 14 | 15 | ARG GO_VERSION 16 | 17 | RUN <<_EOF_ sh 18 | arch=$(apk --print-arch) 19 | goArch="amd64" 20 | case \${arch} in 21 | aarch64) goArch="arm64" ;; 22 | esac 23 | wget -O /tmp/go.tar.gz https://dl.google.com/go/go${GO_VERSION}.linux-\${goArch}.tar.gz 24 | tar -C /opt -xzf /tmp/go.tar.gz 25 | _EOF_ 26 | ENV PATH="$PATH:/opt/go/bin" -------------------------------------------------------------------------------- /crc32.go: -------------------------------------------------------------------------------- 1 | package astits 2 | 3 | const ( 4 | crc32Polynomial = uint32(0xffffffff) 5 | ) 6 | 7 | func computeCRC32(bs []byte) uint32 { 8 | return updateCRC32(crc32Polynomial, bs) 9 | } 10 | 11 | // Based on VLC implementation using a static CRC table (1kb additional memory on start, without 12 | // reallocations): https://github.com/videolan/vlc/blob/master/modules/mux/mpeg/ps.c 13 | func updateCRC32(crc32 uint32, bs []byte) uint32 { 14 | for _, b := range bs { 15 | crc32 = (crc32 << 8) ^ tableCRC32[((crc32>>24)^uint32(b))&0xff] 16 | } 17 | return crc32 18 | } 19 | -------------------------------------------------------------------------------- /data_tot_test.go: -------------------------------------------------------------------------------- 1 | package astits 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/asticode/go-astikit" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | var tot = &TOTData{ 12 | Descriptors: descriptors, 13 | UTCTime: dvbTime, 14 | } 15 | 16 | func totBytes() []byte { 17 | buf := &bytes.Buffer{} 18 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: buf}) 19 | w.Write(dvbTimeBytes) // UTC time 20 | w.Write("0000") // Reserved 21 | descriptorsBytes(w) // Service #1 descriptors 22 | return buf.Bytes() 23 | } 24 | 25 | func TestParseTOTSection(t *testing.T) { 26 | d, err := parseTOTSection(astikit.NewBytesIterator(totBytes())) 27 | assert.Equal(t, d, tot) 28 | assert.NoError(t, err) 29 | } 30 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v4 18 | with: 19 | go-version: '1.20' 20 | 21 | - name: Install dependencies 22 | run: go mod download 23 | 24 | - name: Run tests 25 | run: go test -race -covermode atomic -coverprofile=covprofile ./... 26 | 27 | - if: github.event_name != 'pull_request' 28 | name: Send coverage 29 | env: 30 | COVERALLS_TOKEN: ${{ secrets.COVERALLS_TOKEN }} 31 | run: | 32 | go install github.com/mattn/goveralls@latest 33 | goveralls -coverprofile=covprofile -service=github 34 | -------------------------------------------------------------------------------- /clock_reference.go: -------------------------------------------------------------------------------- 1 | package astits 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // ClockReference represents a clock reference 8 | // Base is based on a 90 kHz clock and extension is based on a 27 MHz clock 9 | type ClockReference struct { 10 | Base, Extension int64 11 | } 12 | 13 | // newClockReference builds a new clock reference 14 | func newClockReference(base, extension int64) *ClockReference { 15 | return &ClockReference{ 16 | Base: base, 17 | Extension: extension, 18 | } 19 | } 20 | 21 | // Duration converts the clock reference into duration 22 | func (p ClockReference) Duration() time.Duration { 23 | return time.Duration(p.Base*1e9/90000) + time.Duration(p.Extension*1e9/27000000) 24 | } 25 | 26 | // Time converts the clock reference into time 27 | func (p ClockReference) Time() time.Time { 28 | return time.Unix(0, p.Duration().Nanoseconds()) 29 | } 30 | -------------------------------------------------------------------------------- /packet_buffer_test.go: -------------------------------------------------------------------------------- 1 | package astits 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/asticode/go-astikit" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestAutoDetectPacketSize(t *testing.T) { 12 | // Packet should start with a sync byte 13 | buf := &bytes.Buffer{} 14 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: buf}) 15 | w.Write(uint8(2)) 16 | w.Write(byte(syncByte)) 17 | _, err := autoDetectPacketSize(bytes.NewReader(buf.Bytes())) 18 | assert.EqualError(t, err, ErrPacketMustStartWithASyncByte.Error()) 19 | 20 | // Valid packet size 21 | buf.Reset() 22 | w.Write(byte(syncByte)) 23 | w.Write(make([]byte, 20)) 24 | w.Write(byte(syncByte)) 25 | w.Write(make([]byte, 166)) 26 | w.Write(byte(syncByte)) 27 | w.Write(make([]byte, 187)) 28 | w.Write([]byte("test")) 29 | r := bytes.NewReader(buf.Bytes()) 30 | p, err := autoDetectPacketSize(r) 31 | assert.NoError(t, err) 32 | assert.Equal(t, MpegTsPacketSize, p) 33 | assert.Equal(t, 380, r.Len()) 34 | } 35 | -------------------------------------------------------------------------------- /data_tot.go: -------------------------------------------------------------------------------- 1 | package astits 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/asticode/go-astikit" 8 | ) 9 | 10 | // TOTData represents a TOT data 11 | // Page: 39 | Chapter: 5.2.6 | Link: https://www.dvb.org/resources/public/standards/a38_dvb-si_specification.pdf 12 | // (barbashov) the link above can be broken, alternative: https://dvb.org/wp-content/uploads/2019/12/a038_tm1217r37_en300468v1_17_1_-_rev-134_-_si_specification.pdf 13 | type TOTData struct { 14 | Descriptors []*Descriptor 15 | UTCTime time.Time 16 | } 17 | 18 | // parseTOTSection parses a TOT section 19 | func parseTOTSection(i *astikit.BytesIterator) (d *TOTData, err error) { 20 | // Create data 21 | d = &TOTData{} 22 | 23 | // UTC time 24 | if d.UTCTime, err = parseDVBTime(i); err != nil { 25 | err = fmt.Errorf("astits: parsing DVB time failed: %w", err) 26 | return 27 | } 28 | 29 | // Descriptors 30 | if d.Descriptors, err = parseDescriptors(i); err != nil { 31 | err = fmt.Errorf("astits: parsing descriptors failed: %w", err) 32 | return 33 | } 34 | return 35 | } 36 | -------------------------------------------------------------------------------- /crc32_test.go: -------------------------------------------------------------------------------- 1 | package astits 2 | 3 | import ( 4 | "encoding/binary" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | var ( 10 | testDataPat = []byte{0x00, 0xb0, 0x0d, 0x00, 0x01, 0xe1, 0x00, 0x00, 0x00, 0x01, 0xf0, 0x00, 0xe2, 0x95, 0xf6, 0x9d} 11 | testDataPmt = []byte{0x02, 0xb0, 0x1d, 0x00, 0x01, 0xf5, 0x00, 0x00, 0xe1, 0x00, 0xf0, 0x00, 0x1b, 0xe1, 0x00, 0x00, 12 | 0x00, 0x0f, 0xe1, 0x04, 0x00, 0x06, 0x0a, 0x04, 0x72, 0x75, 0x73, 0x00, 0x38, 0x92, 0x85, 0xac} 13 | ) 14 | 15 | func Test_updateCRC32(t *testing.T) { 16 | tests := []struct { 17 | name string 18 | crc uint32 19 | data []byte 20 | }{ 21 | { 22 | name: "Calc PAT crc32", 23 | crc: binary.BigEndian.Uint32(testDataPat[len(testDataPat)-4:]), 24 | data: testDataPat[:len(testDataPat)-4], 25 | }, { 26 | name: "Calc PMT crc32", 27 | crc: binary.BigEndian.Uint32(testDataPmt[len(testDataPmt)-4:]), 28 | data: testDataPmt[:len(testDataPmt)-4], 29 | }, 30 | } 31 | 32 | for _, test := range tests { 33 | t.Run(test.name, func(t *testing.T) { 34 | assert.Equal(t, test.crc, computeCRC32(test.data)) 35 | }) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Quentin Renard 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /internal/cmd/crc32_table/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log" 7 | "os" 8 | ) 9 | 10 | const ( 11 | disclaimer = `// Code generated by astits using internal/cmd/crc32_table. DO NOT EDIT` 12 | packet = `package astits` 13 | filename = `crc32_table.go` 14 | ) 15 | 16 | func main() { 17 | file, err := os.Create(filename) 18 | if err != nil { 19 | log.Fatal(err) 20 | } 21 | defer file.Close() 22 | 23 | write(fmt.Fprintf(file, "%s\n%s\n\n", disclaimer, packet)) 24 | generateTable(file) 25 | } 26 | 27 | func generateTable(w io.Writer) { 28 | write(fmt.Fprintf(w, `var tableCRC32 = [256]uint32{`)) 29 | 30 | for i := uint32(0); i < 256; i++ { 31 | var k uint32 32 | for j := (i << 24) | 0x800000; j != 0x80000000; j <<= 1 { 33 | if (k^j)&0x80000000 != 0 { 34 | k = (k << 1) ^ 0x04c11db7 35 | } else { 36 | k = (k << 1) ^ 0 37 | } 38 | } 39 | 40 | if i%8 == 0 { 41 | write(fmt.Fprintf(w, "\n\t")) 42 | } 43 | write(fmt.Fprintf(w, "%#08X, ", k)) 44 | } 45 | write(fmt.Fprintf(w, "\n%s", `}`)) 46 | } 47 | 48 | func write(_ int, err error) { 49 | if err != nil { 50 | log.Fatal(err) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /program_map.go: -------------------------------------------------------------------------------- 1 | package astits 2 | 3 | // programMap represents a program ids map 4 | type programMap struct { 5 | // We use map[uint32] instead map[uint16] as go runtime provide optimized hash functions for (u)int32/64 keys 6 | p map[uint32]uint16 // map[ProgramMapID]ProgramNumber 7 | } 8 | 9 | // newProgramMap creates a new program ids map 10 | func newProgramMap() *programMap { 11 | return &programMap{ 12 | p: make(map[uint32]uint16), 13 | } 14 | } 15 | 16 | // existsUnlocked checks whether the program with this pid exists 17 | func (m programMap) existsUnlocked(pid uint16) (ok bool) { 18 | _, ok = m.p[uint32(pid)] 19 | return 20 | } 21 | 22 | // setUnlocked sets a new program id 23 | func (m programMap) setUnlocked(pid, number uint16) { 24 | m.p[uint32(pid)] = number 25 | } 26 | 27 | func (m programMap) unsetUnlocked(pid uint16) { 28 | delete(m.p, uint32(pid)) 29 | } 30 | 31 | func (m programMap) toPATDataUnlocked() *PATData { 32 | d := &PATData{ 33 | Programs: make([]*PATProgram, 0, len(m.p)), 34 | TransportStreamID: uint16(PSITableIDPAT), 35 | } 36 | 37 | for pid, pnr := range m.p { 38 | d.Programs = append(d.Programs, &PATProgram{ 39 | ProgramMapID: uint16(pid), 40 | ProgramNumber: pnr, 41 | }) 42 | } 43 | 44 | return d 45 | } 46 | -------------------------------------------------------------------------------- /data_nit_test.go: -------------------------------------------------------------------------------- 1 | package astits 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/asticode/go-astikit" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | var nit = &NITData{ 12 | NetworkDescriptors: descriptors, 13 | NetworkID: 1, 14 | TransportStreams: []*NITDataTransportStream{{ 15 | OriginalNetworkID: 3, 16 | TransportDescriptors: descriptors, 17 | TransportStreamID: 2, 18 | }}, 19 | } 20 | 21 | func nitBytes() []byte { 22 | buf := &bytes.Buffer{} 23 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: buf}) 24 | w.Write("0000") // Reserved for future use 25 | descriptorsBytes(w) // Network descriptors 26 | w.Write("0000") // Reserved for future use 27 | w.Write("000000001001") // Transport stream loop length 28 | w.Write(uint16(2)) // Transport stream #1 id 29 | w.Write(uint16(3)) // Transport stream #1 original network id 30 | w.Write("0000") // Transport stream #1 reserved for future use 31 | descriptorsBytes(w) // Transport stream #1 descriptors 32 | return buf.Bytes() 33 | } 34 | 35 | func TestParseNITSection(t *testing.T) { 36 | var b = nitBytes() 37 | d, err := parseNITSection(astikit.NewBytesIterator(b), uint16(1)) 38 | assert.Equal(t, d, nit) 39 | assert.NoError(t, err) 40 | } 41 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/asticode/go-astikit v0.30.0 h1:DkBkRQRIxYcknlaU7W7ksNfn4gMFsB0tqMJflxkRsZA= 2 | github.com/asticode/go-astikit v0.30.0/go.mod h1:h4ly7idim1tNhaVkdVBeXQZEE3L0xblP7fCWbgwipF0= 3 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/pkg/profile v1.4.0 h1:uCmaf4vVbWAOZz36k1hrQD7ijGRzLwaME8Am/7a4jZI= 6 | github.com/pkg/profile v1.4.0/go.mod h1:NWz/XGvpEW1FyYQ7fCx4dqYBLlfTcE+A9FLAkNKqjFE= 7 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 8 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 9 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 10 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 11 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 12 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 13 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 14 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 15 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 16 | -------------------------------------------------------------------------------- /data_sdt_test.go: -------------------------------------------------------------------------------- 1 | package astits 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/asticode/go-astikit" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | var sdt = &SDTData{ 12 | OriginalNetworkID: 2, 13 | Services: []*SDTDataService{{ 14 | Descriptors: descriptors, 15 | HasEITPresentFollowing: true, 16 | HasEITSchedule: true, 17 | HasFreeCSAMode: true, 18 | RunningStatus: 5, 19 | ServiceID: 3, 20 | }}, 21 | TransportStreamID: 1, 22 | } 23 | 24 | func sdtBytes() []byte { 25 | buf := &bytes.Buffer{} 26 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: buf}) 27 | w.Write(uint16(2)) // Original network ID 28 | w.Write(uint8(0)) // Reserved for future use 29 | w.Write(uint16(3)) // Service #1 id 30 | w.Write("000000") // Service #1 reserved for future use 31 | w.Write("1") // Service #1 EIT schedule flag 32 | w.Write("1") // Service #1 EIT present/following flag 33 | w.Write("101") // Service #1 running status 34 | w.Write("1") // Service #1 free CA mode 35 | descriptorsBytes(w) // Service #1 descriptors 36 | return buf.Bytes() 37 | } 38 | 39 | func TestParseSDTSection(t *testing.T) { 40 | var b = sdtBytes() 41 | d, err := parseSDTSection(astikit.NewBytesIterator(b), len(b), uint16(1)) 42 | assert.Equal(t, d, sdt) 43 | assert.NoError(t, err) 44 | } 45 | -------------------------------------------------------------------------------- /pools.go: -------------------------------------------------------------------------------- 1 | package astits 2 | 3 | import "sync" 4 | 5 | // bytesPool global variable is used to ease access to pool from any place of the code 6 | var bytesPool = &bytesPooler{ 7 | sp: sync.Pool{ 8 | New: func() interface{} { 9 | // Prepare the slice of somewhat sensible initial size to minimize calls to runtime.growslice 10 | return &bytesPoolItem{ 11 | s: make([]byte, 0, 1024), 12 | } 13 | }, 14 | }, 15 | } 16 | 17 | // bytesPoolItem is an object containing payload slice 18 | type bytesPoolItem struct { 19 | s []byte 20 | } 21 | 22 | // bytesPooler is a pool for temporary payload in parseData() 23 | // Don't use it anywhere else to avoid pool pollution 24 | type bytesPooler struct { 25 | sp sync.Pool 26 | } 27 | 28 | // get returns the bytesPoolItem object with byte slice of a 'size' length 29 | func (bp *bytesPooler) get(size int) (payload *bytesPoolItem) { 30 | payload = bp.sp.Get().(*bytesPoolItem) 31 | // Reset slice length or grow it to requested size for use with copy 32 | if cap(payload.s) >= size { 33 | payload.s = payload.s[:size] 34 | } else { 35 | n := size - cap(payload.s) 36 | payload.s = append(payload.s[:cap(payload.s)], make([]byte, n)...)[:size] 37 | } 38 | return 39 | } 40 | 41 | // put returns reference to the payload slice back to pool 42 | // Don't use the payload after a call to put 43 | func (bp *bytesPooler) put(payload *bytesPoolItem) { 44 | bp.sp.Put(payload) 45 | } 46 | -------------------------------------------------------------------------------- /data_eit_test.go: -------------------------------------------------------------------------------- 1 | package astits 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/asticode/go-astikit" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | var eit = &EITData{ 12 | Events: []*EITDataEvent{{ 13 | Descriptors: descriptors, 14 | Duration: dvbDurationSeconds, 15 | EventID: 6, 16 | HasFreeCSAMode: true, 17 | RunningStatus: 7, 18 | StartTime: dvbTime, 19 | }}, 20 | LastTableID: 5, 21 | OriginalNetworkID: 3, 22 | SegmentLastSectionNumber: 4, 23 | ServiceID: 1, 24 | TransportStreamID: 2, 25 | } 26 | 27 | func eitBytes() []byte { 28 | buf := &bytes.Buffer{} 29 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: buf}) 30 | w.Write(uint16(2)) // Transport stream ID 31 | w.Write(uint16(3)) // Original network ID 32 | w.Write(uint8(4)) // Segment last section number 33 | w.Write(uint8(5)) // Last table id 34 | w.Write(uint16(6)) // Event #1 id 35 | w.Write(dvbTimeBytes) // Event #1 start time 36 | w.Write(dvbDurationSecondsBytes) // Event #1 duration 37 | w.Write("111") // Event #1 running status 38 | w.Write("1") // Event #1 free CA mode 39 | descriptorsBytes(w) // Event #1 descriptors 40 | return buf.Bytes() 41 | } 42 | 43 | func TestParseEITSection(t *testing.T) { 44 | var b = eitBytes() 45 | d, err := parseEITSection(astikit.NewBytesIterator(b), len(b), uint16(1)) 46 | assert.Equal(t, d, eit) 47 | assert.NoError(t, err) 48 | } 49 | -------------------------------------------------------------------------------- /data_pat_test.go: -------------------------------------------------------------------------------- 1 | package astits 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/asticode/go-astikit" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | var pat = &PATData{ 12 | Programs: []*PATProgram{ 13 | {ProgramMapID: 3, ProgramNumber: 2}, 14 | {ProgramMapID: 5, ProgramNumber: 4}, 15 | }, 16 | TransportStreamID: 1, 17 | } 18 | 19 | func patBytes() []byte { 20 | buf := &bytes.Buffer{} 21 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: buf}) 22 | w.Write(uint16(2)) // Program #1 number 23 | w.Write("111") // Program #1 reserved bits 24 | w.Write("0000000000011") // Program #1 map ID 25 | w.Write(uint16(4)) // Program #2 number 26 | w.Write("111") // Program #2 reserved bits 27 | w.Write("0000000000101") // Program #3 map ID 28 | return buf.Bytes() 29 | } 30 | 31 | func TestParsePATSection(t *testing.T) { 32 | var b = patBytes() 33 | d, err := parsePATSection(astikit.NewBytesIterator(b), len(b), uint16(1)) 34 | assert.Equal(t, d, pat) 35 | assert.NoError(t, err) 36 | } 37 | 38 | func TestWritePATSection(t *testing.T) { 39 | bw := &bytes.Buffer{} 40 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: bw}) 41 | n, err := writePATSection(w, pat) 42 | assert.NoError(t, err) 43 | assert.Equal(t, n, 8) 44 | assert.Equal(t, n, bw.Len()) 45 | assert.Equal(t, patBytes(), bw.Bytes()) 46 | } 47 | 48 | func BenchmarkParsePATSection(b *testing.B) { 49 | b.ReportAllocs() 50 | bs := patBytes() 51 | 52 | for i := 0; i < b.N; i++ { 53 | parsePATSection(astikit.NewBytesIterator(bs), len(bs), uint16(1)) 54 | } 55 | } 56 | 57 | func BenchmarkWritePATSection(b *testing.B) { 58 | b.ReportAllocs() 59 | 60 | bw := &bytes.Buffer{} 61 | bw.Grow(1024) 62 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: bw}) 63 | 64 | for i := 0; i < b.N; i++ { 65 | bw.Reset() 66 | writePATSection(w, pat) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /data_pat.go: -------------------------------------------------------------------------------- 1 | package astits 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/asticode/go-astikit" 7 | ) 8 | 9 | const ( 10 | patSectionEntryBytesSize = 4 // 16 bits + 3 reserved + 13 bits = 32 bits 11 | ) 12 | 13 | // PATData represents a PAT data 14 | // https://en.wikipedia.org/wiki/Program-specific_information 15 | type PATData struct { 16 | Programs []*PATProgram 17 | TransportStreamID uint16 18 | } 19 | 20 | // PATProgram represents a PAT program 21 | type PATProgram struct { 22 | ProgramMapID uint16 // The packet identifier that contains the associated PMT 23 | ProgramNumber uint16 // Relates to the Table ID extension in the associated PMT. A value of 0 is reserved for a NIT packet identifier. 24 | } 25 | 26 | // parsePATSection parses a PAT section 27 | func parsePATSection(i *astikit.BytesIterator, offsetSectionsEnd int, tableIDExtension uint16) (d *PATData, err error) { 28 | // Create data 29 | d = &PATData{TransportStreamID: tableIDExtension} 30 | 31 | // Loop until end of section data is reached 32 | for i.Offset() < offsetSectionsEnd { 33 | // Get next bytes 34 | var bs []byte 35 | if bs, err = i.NextBytesNoCopy(4); err != nil { 36 | err = fmt.Errorf("astits: fetching next bytes failed: %w", err) 37 | return 38 | } 39 | 40 | // Append program 41 | d.Programs = append(d.Programs, &PATProgram{ 42 | ProgramMapID: uint16(bs[2]&0x1f)<<8 | uint16(bs[3]), 43 | ProgramNumber: uint16(bs[0])<<8 | uint16(bs[1]), 44 | }) 45 | } 46 | return 47 | } 48 | 49 | func calcPATSectionLength(d *PATData) uint16 { 50 | return uint16(4 * len(d.Programs)) 51 | } 52 | 53 | func writePATSection(w *astikit.BitsWriter, d *PATData) (int, error) { 54 | b := astikit.NewBitsWriterBatch(w) 55 | 56 | for _, p := range d.Programs { 57 | b.Write(p.ProgramNumber) 58 | b.WriteN(uint8(0xff), 3) 59 | b.WriteN(p.ProgramMapID, 13) 60 | } 61 | 62 | return len(d.Programs) * patSectionEntryBytesSize, b.Err() 63 | } 64 | -------------------------------------------------------------------------------- /data_pmt_test.go: -------------------------------------------------------------------------------- 1 | package astits 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/asticode/go-astikit" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | var pmt = &PMTData{ 12 | ElementaryStreams: []*PMTElementaryStream{{ 13 | ElementaryPID: 2730, 14 | ElementaryStreamDescriptors: descriptors, 15 | StreamType: StreamTypeMPEG1Audio, 16 | }}, 17 | PCRPID: 5461, 18 | ProgramDescriptors: descriptors, 19 | ProgramNumber: 1, 20 | } 21 | 22 | func pmtBytes() []byte { 23 | buf := &bytes.Buffer{} 24 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: buf}) 25 | w.Write("111") // Reserved bits 26 | w.Write("1010101010101") // PCR PID 27 | w.Write("1111") // Reserved 28 | descriptorsBytes(w) // Program descriptors 29 | w.Write(uint8(StreamTypeMPEG1Audio)) // Stream #1 stream type 30 | w.Write("111") // Stream #1 reserved 31 | w.Write("0101010101010") // Stream #1 PID 32 | w.Write("1111") // Stream #1 reserved 33 | descriptorsBytes(w) // Stream #1 descriptors 34 | return buf.Bytes() 35 | } 36 | 37 | func TestParsePMTSection(t *testing.T) { 38 | var b = pmtBytes() 39 | d, err := parsePMTSection(astikit.NewBytesIterator(b), len(b), uint16(1)) 40 | assert.Equal(t, d, pmt) 41 | assert.NoError(t, err) 42 | } 43 | 44 | func TestWritePMTSection(t *testing.T) { 45 | buf := bytes.Buffer{} 46 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: &buf}) 47 | n, err := writePMTSection(w, pmt) 48 | assert.NoError(t, err) 49 | assert.Equal(t, n, buf.Len()) 50 | assert.Equal(t, pmtBytes(), buf.Bytes()) 51 | } 52 | 53 | func BenchmarkParsePMTSection(b *testing.B) { 54 | b.ReportAllocs() 55 | bs := pmtBytes() 56 | 57 | for i := 0; i < b.N; i++ { 58 | parsePMTSection(astikit.NewBytesIterator(bs), len(bs), uint16(1)) 59 | } 60 | } 61 | 62 | func BenchmarkWritePMTSection(b *testing.B) { 63 | b.ReportAllocs() 64 | 65 | bw := &bytes.Buffer{} 66 | bw.Grow(1024) 67 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: bw}) 68 | 69 | for i := 0; i < b.N; i++ { 70 | bw.Reset() 71 | writePMTSection(w, pmt) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /dvb_test.go: -------------------------------------------------------------------------------- 1 | package astits 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | "time" 7 | 8 | "github.com/asticode/go-astikit" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | var ( 13 | dvbDurationMinutes = time.Hour + 45*time.Minute 14 | dvbDurationMinutesBytes = []byte{0x1, 0x45} // 0145 15 | dvbDurationSeconds = time.Hour + 45*time.Minute + 30*time.Second 16 | dvbDurationSecondsBytes = []byte{0x1, 0x45, 0x30} // 014530 17 | dvbTime, _ = time.Parse("2006-01-02 15:04:05", "1993-10-13 12:45:00") 18 | dvbTimeBytes = []byte{0xc0, 0x79, 0x12, 0x45, 0x0} // C079124500 19 | ) 20 | 21 | func TestParseDVBTime(t *testing.T) { 22 | d, err := parseDVBTime(astikit.NewBytesIterator(dvbTimeBytes)) 23 | assert.Equal(t, dvbTime, d) 24 | assert.NoError(t, err) 25 | } 26 | 27 | func TestParseDVBDurationMinutes(t *testing.T) { 28 | d, err := parseDVBDurationMinutes(astikit.NewBytesIterator(dvbDurationMinutesBytes)) 29 | assert.Equal(t, dvbDurationMinutes, d) 30 | assert.NoError(t, err) 31 | } 32 | 33 | func TestParseDVBDurationSeconds(t *testing.T) { 34 | d, err := parseDVBDurationSeconds(astikit.NewBytesIterator(dvbDurationSecondsBytes)) 35 | assert.Equal(t, dvbDurationSeconds, d) 36 | assert.NoError(t, err) 37 | } 38 | 39 | func TestWriteDVBTime(t *testing.T) { 40 | buf := &bytes.Buffer{} 41 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: buf}) 42 | n, err := writeDVBTime(w, dvbTime) 43 | assert.NoError(t, err) 44 | assert.Equal(t, n, buf.Len()) 45 | assert.Equal(t, dvbTimeBytes, buf.Bytes()) 46 | } 47 | 48 | func TestWriteDVBDurationMinutes(t *testing.T) { 49 | buf := &bytes.Buffer{} 50 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: buf}) 51 | n, err := writeDVBDurationMinutes(w, dvbDurationMinutes) 52 | assert.NoError(t, err) 53 | assert.Equal(t, n, buf.Len()) 54 | assert.Equal(t, dvbDurationMinutesBytes, buf.Bytes()) 55 | } 56 | 57 | func TestWriteDVBDurationSeconds(t *testing.T) { 58 | buf := &bytes.Buffer{} 59 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: buf}) 60 | n, err := writeDVBDurationSeconds(w, dvbDurationSeconds) 61 | assert.NoError(t, err) 62 | assert.Equal(t, n, buf.Len()) 63 | assert.Equal(t, dvbDurationSecondsBytes, buf.Bytes()) 64 | } 65 | 66 | func BenchmarkDVBTime(b *testing.B) { 67 | for i := 0; i < b.N; i++ { 68 | parseDVBTime(astikit.NewBytesIterator(dvbTimeBytes)) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /data_test.go: -------------------------------------------------------------------------------- 1 | package astits 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/asticode/go-astikit" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestParseData(t *testing.T) { 12 | // Init 13 | pm := newProgramMap() 14 | ps := []*Packet{} 15 | 16 | // Custom parser 17 | cds := []*DemuxerData{{PID: 1}} 18 | var c = func(ps []*Packet) (o []*DemuxerData, skip bool, err error) { 19 | o = cds 20 | skip = true 21 | return 22 | } 23 | ds, err := parseData(ps, c, pm) 24 | assert.NoError(t, err) 25 | assert.Equal(t, cds, ds) 26 | 27 | // Do nothing for CAT 28 | ps = []*Packet{{Header: PacketHeader{PID: PIDCAT}}} 29 | ds, err = parseData(ps, nil, pm) 30 | assert.NoError(t, err) 31 | assert.Empty(t, ds) 32 | 33 | // PES 34 | p := pesWithHeaderBytes() 35 | ps = []*Packet{ 36 | { 37 | Header: PacketHeader{PID: uint16(256)}, 38 | Payload: p[:33], 39 | }, 40 | { 41 | Header: PacketHeader{PID: uint16(256)}, 42 | Payload: p[33:], 43 | }, 44 | } 45 | ds, err = parseData(ps, nil, pm) 46 | assert.NoError(t, err) 47 | assert.Equal(t, []*DemuxerData{ 48 | { 49 | FirstPacket: &Packet{Header: ps[0].Header, AdaptationField: ps[0].AdaptationField}, 50 | PES: pesWithHeader(), 51 | PID: uint16(256), 52 | }}, ds) 53 | 54 | // PSI 55 | pm.setUnlocked(uint16(256), uint16(1)) 56 | p = psiBytes() 57 | ps = []*Packet{ 58 | { 59 | Header: PacketHeader{PID: uint16(256)}, 60 | Payload: p[:33], 61 | }, 62 | { 63 | Header: PacketHeader{PID: uint16(256)}, 64 | Payload: p[33:], 65 | }, 66 | } 67 | ds, err = parseData(ps, nil, pm) 68 | assert.NoError(t, err) 69 | assert.Equal(t, psi.toData( 70 | &Packet{Header: ps[0].Header, AdaptationField: ps[0].AdaptationField}, 71 | uint16(256), 72 | ), ds) 73 | } 74 | 75 | func TestIsPSIPayload(t *testing.T) { 76 | pm := newProgramMap() 77 | var pids []int 78 | for i := 0; i <= 255; i++ { 79 | if isPSIPayload(uint16(i), pm) { 80 | pids = append(pids, i) 81 | } 82 | } 83 | assert.Equal(t, []int{0, 16, 17, 18, 19, 20, 30, 31}, pids) 84 | pm.setUnlocked(uint16(1), uint16(0)) 85 | assert.True(t, isPSIPayload(uint16(1), pm)) 86 | } 87 | 88 | func TestIsPESPayload(t *testing.T) { 89 | buf := &bytes.Buffer{} 90 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: buf}) 91 | w.Write("0000000000000001") 92 | assert.False(t, isPESPayload(buf.Bytes())) 93 | buf.Reset() 94 | w.Write("000000000000000000000001") 95 | assert.True(t, isPESPayload(buf.Bytes())) 96 | } 97 | -------------------------------------------------------------------------------- /data_nit.go: -------------------------------------------------------------------------------- 1 | package astits 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/asticode/go-astikit" 7 | ) 8 | 9 | // NITData represents a NIT data 10 | // Page: 29 | Chapter: 5.2.1 | Link: https://www.dvb.org/resources/public/standards/a38_dvb-si_specification.pdf 11 | // (barbashov) the link above can be broken, alternative: https://dvb.org/wp-content/uploads/2019/12/a038_tm1217r37_en300468v1_17_1_-_rev-134_-_si_specification.pdf 12 | type NITData struct { 13 | NetworkDescriptors []*Descriptor 14 | NetworkID uint16 15 | TransportStreams []*NITDataTransportStream 16 | } 17 | 18 | // NITDataTransportStream represents a NIT data transport stream 19 | type NITDataTransportStream struct { 20 | OriginalNetworkID uint16 21 | TransportDescriptors []*Descriptor 22 | TransportStreamID uint16 23 | } 24 | 25 | // parseNITSection parses a NIT section 26 | func parseNITSection(i *astikit.BytesIterator, tableIDExtension uint16) (d *NITData, err error) { 27 | // Create data 28 | d = &NITData{NetworkID: tableIDExtension} 29 | 30 | // Network descriptors 31 | if d.NetworkDescriptors, err = parseDescriptors(i); err != nil { 32 | err = fmt.Errorf("astits: parsing descriptors failed: %w", err) 33 | return 34 | } 35 | 36 | // Get next bytes 37 | var bs []byte 38 | if bs, err = i.NextBytesNoCopy(2); err != nil { 39 | err = fmt.Errorf("astits: fetching next bytes failed: %w", err) 40 | return 41 | } 42 | 43 | // Transport stream loop length 44 | transportStreamLoopLength := int(uint16(bs[0]&0xf)<<8 | uint16(bs[1])) 45 | 46 | // Transport stream loop 47 | offsetEnd := i.Offset() + transportStreamLoopLength 48 | for i.Offset() < offsetEnd { 49 | // Create transport stream 50 | ts := &NITDataTransportStream{} 51 | 52 | // Get next bytes 53 | if bs, err = i.NextBytesNoCopy(2); err != nil { 54 | err = fmt.Errorf("astits: fetching next bytes failed: %w", err) 55 | return 56 | } 57 | 58 | // Transport stream ID 59 | ts.TransportStreamID = uint16(bs[0])<<8 | uint16(bs[1]) 60 | 61 | // Get next bytes 62 | if bs, err = i.NextBytesNoCopy(2); err != nil { 63 | err = fmt.Errorf("astits: fetching next bytes failed: %w", err) 64 | return 65 | } 66 | 67 | // Original network ID 68 | ts.OriginalNetworkID = uint16(bs[0])<<8 | uint16(bs[1]) 69 | 70 | // Transport descriptors 71 | if ts.TransportDescriptors, err = parseDescriptors(i); err != nil { 72 | err = fmt.Errorf("astits: parsing descriptors failed: %w", err) 73 | return 74 | } 75 | 76 | // Append transport stream 77 | d.TransportStreams = append(d.TransportStreams, ts) 78 | } 79 | return 80 | } 81 | -------------------------------------------------------------------------------- /crc32_table.go: -------------------------------------------------------------------------------- 1 | // Code generated by astits using internal/cmd/crc32_table. DO NOT EDIT 2 | package astits 3 | 4 | var tableCRC32 = [256]uint32{ 5 | 0x00000000, 0x04C11DB7, 0x09823B6E, 0x0D4326D9, 0x130476DC, 0x17C56B6B, 0x1A864DB2, 0x1E475005, 6 | 0x2608EDB8, 0x22C9F00F, 0x2F8AD6D6, 0x2B4BCB61, 0x350C9B64, 0x31CD86D3, 0x3C8EA00A, 0x384FBDBD, 7 | 0x4C11DB70, 0x48D0C6C7, 0x4593E01E, 0x4152FDA9, 0x5F15ADAC, 0x5BD4B01B, 0x569796C2, 0x52568B75, 8 | 0x6A1936C8, 0x6ED82B7F, 0x639B0DA6, 0x675A1011, 0x791D4014, 0x7DDC5DA3, 0x709F7B7A, 0x745E66CD, 9 | 0x9823B6E0, 0x9CE2AB57, 0x91A18D8E, 0x95609039, 0x8B27C03C, 0x8FE6DD8B, 0x82A5FB52, 0x8664E6E5, 10 | 0xBE2B5B58, 0xBAEA46EF, 0xB7A96036, 0xB3687D81, 0xAD2F2D84, 0xA9EE3033, 0xA4AD16EA, 0xA06C0B5D, 11 | 0xD4326D90, 0xD0F37027, 0xDDB056FE, 0xD9714B49, 0xC7361B4C, 0xC3F706FB, 0xCEB42022, 0xCA753D95, 12 | 0xF23A8028, 0xF6FB9D9F, 0xFBB8BB46, 0xFF79A6F1, 0xE13EF6F4, 0xE5FFEB43, 0xE8BCCD9A, 0xEC7DD02D, 13 | 0x34867077, 0x30476DC0, 0x3D044B19, 0x39C556AE, 0x278206AB, 0x23431B1C, 0x2E003DC5, 0x2AC12072, 14 | 0x128E9DCF, 0x164F8078, 0x1B0CA6A1, 0x1FCDBB16, 0x018AEB13, 0x054BF6A4, 0x0808D07D, 0x0CC9CDCA, 15 | 0x7897AB07, 0x7C56B6B0, 0x71159069, 0x75D48DDE, 0x6B93DDDB, 0x6F52C06C, 0x6211E6B5, 0x66D0FB02, 16 | 0x5E9F46BF, 0x5A5E5B08, 0x571D7DD1, 0x53DC6066, 0x4D9B3063, 0x495A2DD4, 0x44190B0D, 0x40D816BA, 17 | 0xACA5C697, 0xA864DB20, 0xA527FDF9, 0xA1E6E04E, 0xBFA1B04B, 0xBB60ADFC, 0xB6238B25, 0xB2E29692, 18 | 0x8AAD2B2F, 0x8E6C3698, 0x832F1041, 0x87EE0DF6, 0x99A95DF3, 0x9D684044, 0x902B669D, 0x94EA7B2A, 19 | 0xE0B41DE7, 0xE4750050, 0xE9362689, 0xEDF73B3E, 0xF3B06B3B, 0xF771768C, 0xFA325055, 0xFEF34DE2, 20 | 0xC6BCF05F, 0xC27DEDE8, 0xCF3ECB31, 0xCBFFD686, 0xD5B88683, 0xD1799B34, 0xDC3ABDED, 0xD8FBA05A, 21 | 0x690CE0EE, 0x6DCDFD59, 0x608EDB80, 0x644FC637, 0x7A089632, 0x7EC98B85, 0x738AAD5C, 0x774BB0EB, 22 | 0x4F040D56, 0x4BC510E1, 0x46863638, 0x42472B8F, 0x5C007B8A, 0x58C1663D, 0x558240E4, 0x51435D53, 23 | 0x251D3B9E, 0x21DC2629, 0x2C9F00F0, 0x285E1D47, 0x36194D42, 0x32D850F5, 0x3F9B762C, 0x3B5A6B9B, 24 | 0x0315D626, 0x07D4CB91, 0x0A97ED48, 0x0E56F0FF, 0x1011A0FA, 0x14D0BD4D, 0x19939B94, 0x1D528623, 25 | 0xF12F560E, 0xF5EE4BB9, 0xF8AD6D60, 0xFC6C70D7, 0xE22B20D2, 0xE6EA3D65, 0xEBA91BBC, 0xEF68060B, 26 | 0xD727BBB6, 0xD3E6A601, 0xDEA580D8, 0xDA649D6F, 0xC423CD6A, 0xC0E2D0DD, 0xCDA1F604, 0xC960EBB3, 27 | 0xBD3E8D7E, 0xB9FF90C9, 0xB4BCB610, 0xB07DABA7, 0xAE3AFBA2, 0xAAFBE615, 0xA7B8C0CC, 0xA379DD7B, 28 | 0x9B3660C6, 0x9FF77D71, 0x92B45BA8, 0x9675461F, 0x8832161A, 0x8CF30BAD, 0x81B02D74, 0x857130C3, 29 | 0x5D8A9099, 0x594B8D2E, 0x5408ABF7, 0x50C9B640, 0x4E8EE645, 0x4A4FFBF2, 0x470CDD2B, 0x43CDC09C, 30 | 0x7B827D21, 0x7F436096, 0x7200464F, 0x76C15BF8, 0x68860BFD, 0x6C47164A, 0x61043093, 0x65C52D24, 31 | 0x119B4BE9, 0x155A565E, 0x18197087, 0x1CD86D30, 0x029F3D35, 0x065E2082, 0x0B1D065B, 0x0FDC1BEC, 32 | 0x3793A651, 0x3352BBE6, 0x3E119D3F, 0x3AD08088, 0x2497D08D, 0x2056CD3A, 0x2D15EBE3, 0x29D4F654, 33 | 0xC5A92679, 0xC1683BCE, 0xCC2B1D17, 0xC8EA00A0, 0xD6AD50A5, 0xD26C4D12, 0xDF2F6BCB, 0xDBEE767C, 34 | 0xE3A1CBC1, 0xE760D676, 0xEA23F0AF, 0xEEE2ED18, 0xF0A5BD1D, 0xF464A0AA, 0xF9278673, 0xFDE69BC4, 35 | 0x89B8FD09, 0x8D79E0BE, 0x803AC667, 0x84FBDBD0, 0x9ABC8BD5, 0x9E7D9662, 0x933EB0BB, 0x97FFAD0C, 36 | 0xAFB010B1, 0xAB710D06, 0xA6322BDF, 0xA2F33668, 0xBCB4666D, 0xB8757BDA, 0xB5365D03, 0xB1F740B4, 37 | } 38 | -------------------------------------------------------------------------------- /data_sdt.go: -------------------------------------------------------------------------------- 1 | package astits 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/asticode/go-astikit" 7 | ) 8 | 9 | // Running statuses 10 | const ( 11 | RunningStatusNotRunning = 1 12 | RunningStatusPausing = 3 13 | RunningStatusRunning = 4 14 | RunningStatusServiceOffAir = 5 15 | RunningStatusStartsInAFewSeconds = 2 16 | RunningStatusUndefined = 0 17 | ) 18 | 19 | // SDTData represents an SDT data 20 | // Page: 33 | Chapter: 5.2.3 | Link: https://www.dvb.org/resources/public/standards/a38_dvb-si_specification.pdf 21 | // (barbashov) the link above can be broken, alternative: https://dvb.org/wp-content/uploads/2019/12/a038_tm1217r37_en300468v1_17_1_-_rev-134_-_si_specification.pdf 22 | type SDTData struct { 23 | OriginalNetworkID uint16 24 | Services []*SDTDataService 25 | TransportStreamID uint16 26 | } 27 | 28 | // SDTDataService represents an SDT data service 29 | type SDTDataService struct { 30 | Descriptors []*Descriptor 31 | HasEITPresentFollowing bool // When true indicates that EIT present/following information for the service is present in the current TS 32 | HasEITSchedule bool // When true indicates that EIT schedule information for the service is present in the current TS 33 | HasFreeCSAMode bool // When true indicates that access to one or more streams may be controlled by a CA system. 34 | RunningStatus uint8 35 | ServiceID uint16 36 | } 37 | 38 | // parseSDTSection parses an SDT section 39 | func parseSDTSection(i *astikit.BytesIterator, offsetSectionsEnd int, tableIDExtension uint16) (d *SDTData, err error) { 40 | // Create data 41 | d = &SDTData{TransportStreamID: tableIDExtension} 42 | 43 | // Get next bytes 44 | var bs []byte 45 | if bs, err = i.NextBytesNoCopy(2); err != nil { 46 | err = fmt.Errorf("astits: fetching next bytes failed: %w", err) 47 | return 48 | } 49 | 50 | // Original network ID 51 | d.OriginalNetworkID = uint16(bs[0])<<8 | uint16(bs[1]) 52 | 53 | // Reserved for future use 54 | i.Skip(1) 55 | 56 | // Loop until end of section data is reached 57 | for i.Offset() < offsetSectionsEnd { 58 | // Create service 59 | s := &SDTDataService{} 60 | 61 | // Get next bytes 62 | if bs, err = i.NextBytesNoCopy(2); err != nil { 63 | err = fmt.Errorf("astits: fetching next bytes failed: %w", err) 64 | return 65 | } 66 | 67 | // Service ID 68 | s.ServiceID = uint16(bs[0])<<8 | uint16(bs[1]) 69 | 70 | // Get next byte 71 | var b byte 72 | if b, err = i.NextByte(); err != nil { 73 | err = fmt.Errorf("astits: fetching next byte failed: %w", err) 74 | return 75 | } 76 | 77 | // EIT schedule flag 78 | s.HasEITSchedule = uint8(b&0x2) > 0 79 | 80 | // EIT present/following flag 81 | s.HasEITPresentFollowing = uint8(b&0x1) > 0 82 | 83 | // Get next byte 84 | if b, err = i.NextByte(); err != nil { 85 | err = fmt.Errorf("astits: fetching next byte failed: %w", err) 86 | return 87 | } 88 | 89 | // Running status 90 | s.RunningStatus = uint8(b) >> 5 91 | 92 | // Free CA mode 93 | s.HasFreeCSAMode = uint8(b&0x10) > 0 94 | 95 | // We need to rewind since the current byte is used by the descriptor as well 96 | i.Skip(-1) 97 | 98 | // Descriptors 99 | if s.Descriptors, err = parseDescriptors(i); err != nil { 100 | err = fmt.Errorf("astits: parsing descriptors failed: %w", err) 101 | return 102 | } 103 | 104 | // Append service 105 | d.Services = append(d.Services, s) 106 | } 107 | return 108 | } 109 | -------------------------------------------------------------------------------- /data_eit.go: -------------------------------------------------------------------------------- 1 | package astits 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/asticode/go-astikit" 8 | ) 9 | 10 | // EITData represents an EIT data 11 | // Page: 36 | Chapter: 5.2.4 | Link: https://www.dvb.org/resources/public/standards/a38_dvb-si_specification.pdf 12 | // (barbashov) the link above can be broken, alternative: https://dvb.org/wp-content/uploads/2019/12/a038_tm1217r37_en300468v1_17_1_-_rev-134_-_si_specification.pdf 13 | type EITData struct { 14 | Events []*EITDataEvent 15 | LastTableID uint8 16 | OriginalNetworkID uint16 17 | SegmentLastSectionNumber uint8 18 | ServiceID uint16 19 | TransportStreamID uint16 20 | } 21 | 22 | // EITDataEvent represents an EIT data event 23 | type EITDataEvent struct { 24 | Descriptors []*Descriptor 25 | Duration time.Duration 26 | EventID uint16 27 | HasFreeCSAMode bool // When true indicates that access to one or more streams may be controlled by a CA system. 28 | RunningStatus uint8 29 | StartTime time.Time 30 | } 31 | 32 | // parseEITSection parses an EIT section 33 | func parseEITSection(i *astikit.BytesIterator, offsetSectionsEnd int, tableIDExtension uint16) (d *EITData, err error) { 34 | // Create data 35 | d = &EITData{ServiceID: tableIDExtension} 36 | 37 | // Get next 2 bytes 38 | var bs []byte 39 | if bs, err = i.NextBytesNoCopy(2); err != nil { 40 | err = fmt.Errorf("astits: fetching next bytes failed: %w", err) 41 | return 42 | } 43 | 44 | // Transport stream ID 45 | d.TransportStreamID = uint16(bs[0])<<8 | uint16(bs[1]) 46 | 47 | // Get next 2 bytes 48 | if bs, err = i.NextBytesNoCopy(2); err != nil { 49 | err = fmt.Errorf("astits: fetching next bytes failed: %w", err) 50 | return 51 | } 52 | 53 | // Original network ID 54 | d.OriginalNetworkID = uint16(bs[0])<<8 | uint16(bs[1]) 55 | 56 | // Get next byte 57 | var b byte 58 | if b, err = i.NextByte(); err != nil { 59 | err = fmt.Errorf("astits: fetching next byte failed: %w", err) 60 | return 61 | } 62 | 63 | // Segment last section number 64 | d.SegmentLastSectionNumber = uint8(b) 65 | 66 | // Get next byte 67 | if b, err = i.NextByte(); err != nil { 68 | err = fmt.Errorf("astits: fetching next byte failed: %w", err) 69 | return 70 | } 71 | 72 | // Last table ID 73 | d.LastTableID = uint8(b) 74 | 75 | // Loop until end of section data is reached 76 | for i.Offset() < offsetSectionsEnd { 77 | // Get next 2 bytes 78 | if bs, err = i.NextBytesNoCopy(2); err != nil { 79 | err = fmt.Errorf("astits: fetching next bytes failed: %w", err) 80 | return 81 | } 82 | 83 | // Event ID 84 | var e = &EITDataEvent{} 85 | e.EventID = uint16(bs[0])<<8 | uint16(bs[1]) 86 | 87 | // Start time 88 | if e.StartTime, err = parseDVBTime(i); err != nil { 89 | err = fmt.Errorf("astits: parsing DVB time") 90 | return 91 | } 92 | 93 | // Duration 94 | if e.Duration, err = parseDVBDurationSeconds(i); err != nil { 95 | err = fmt.Errorf("astits: parsing DVB duration seconds failed: %w", err) 96 | return 97 | } 98 | 99 | // Get next byte 100 | if b, err = i.NextByte(); err != nil { 101 | err = fmt.Errorf("astits: fetching next byte failed: %w", err) 102 | return 103 | } 104 | 105 | // Running status 106 | e.RunningStatus = uint8(b) >> 5 107 | 108 | // Free CA mode 109 | e.HasFreeCSAMode = uint8(b&0x10) > 0 110 | 111 | // We need to rewind since the current byte is used by the descriptor as well 112 | i.Skip(-1) 113 | 114 | // Descriptors 115 | if e.Descriptors, err = parseDescriptors(i); err != nil { 116 | err = fmt.Errorf("astits: parsing descriptors failed: %w", err) 117 | return 118 | } 119 | 120 | // Add event 121 | d.Events = append(d.Events, e) 122 | } 123 | return 124 | } 125 | -------------------------------------------------------------------------------- /testdata/fuzz/FuzzDemuxer/d1e578c13225ac8013c841910c3eb5e89150b114deca770e3afb6acdc8036a91: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("G_\xff\x10#SEGMENT\x00#TAG:AD\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xffG@\x00\x10!\x19\x8f\xff\xff\xff\xff\xf3\x16ZM\x92\x94\xc3@\xb0PL\x1d\x10\x00@\x05\xd4&\x86\\=\x81\x1e\xbe\x91\x9b\x1cm\xf1\xdch\xd1\xe8.H<\xbbb\xf9b\x1ax\x87\xdc\x04>|5\xa2\x12\xfd,\xacE\x15V\x02\b\xd5ui\x94\x90\r\xfcI\xe8\xbbYv\x03\x12\x1a\b\xdeN#$\xaa#a\x92\x99\f\x8edʚ\x81n\xd3h\xf3\xad\x0ege\xd1D*\v\xe9j\xd8U5-\x9e\xe58\xfcc\x02C9J\x99\x1c\xbc˫|s2\xce<\xaa\x17\x93\x87\x81\\Łd\x9b){\x80\xd8\xe9\xab;\x1an\x92C\x11\x03r\fA \x19\x14\x02G\x01\x011a\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x92\xe9\x00\x90v\x00Q-\x017\xcbӬH\x06s\xb6N\x19\x8c\xb7-\x00*\x03M\xf8\xcf\xcdy\tV\xa4\xa8i\xe3L)\b-\x9a6\tN\x02f\xec\v\xd6SB4\x1b\x93\x80\xfc\xfc\xbc3\xa9\\\xd5\xdc*\xa8*7\xf8&\xddV\xe93\xc9\xd7\xe7\x03dʀ\x16\x81\xf2\xe8\x9bA\xc0GA\x000\a\x10\x00\x00\x05\xdc~\x00\x00\x00\x01\xe0\x00\x00\x84\xc0\n1\x00\x01FQ\x11\x00\x01\x17q\x00\x00\x00\x01\t\xf0\x00\x00\x00\x01gd\x00\x1f\xac\xd9@P\x05\xbb\x01\x10\x00\x00\x03\x00\x10\x00\x00\x03\x03\xc0\xf1\x83\x19`\x00\x00\x00\x01h\xeb\xec\xb2,\x00\x00\x01e\x88\x84\x00\x9f\x99=\x94](lϬo\x13,\x9f\xdd\xfb\xa5@\x9e\xf4\xaeihrH\x1f\x93X\xbe=@\x88\xd5D\x88p\x14\x9c\n\x98\x13E\xc7D'[E\x1bo}z\xe7`\x03|\xacsY\x12Y\xe6H\xa5\xf5}a\xe1v\xa3#I\xc1\xabQ\x10\x1cl\xe0o\x17\xd4\xcfW>\x83\b\x13㹺4AyV/B\x99\xa9\xc5\x11\x81ͷK\x8b\xe3G\x01\x00\x11JB\xa2M\x8e\x92\x94p\xe8\x93\xdem\xa1\xad\xef9M\xd6\x1d4\x86\x83\xa6\xfc*\x12\xe7\x92\xf9@\xfdH.\xaf\ue1f9\xc1\xe4\x1c\x87#\xd3\xd5\x14\xc3\xffj\x81\x14kx\x97\xd2\xf9\xe74wâ\x1d\xd3Σ\xc10 \x90\x8bD\xda\xcf\xc0\x91\xf9\xab\xe6&\x044\x1aw\xa7\xe4D\x8c\xe6\xa7ҟ\x99\xa5\\\x92<\xc5\xc7/sF\xdal\xd1Y\xcb\xc8\xfeB5\x19\x89\x91\xf6mO\xcbG\xdf'H\x1bW\xbe\xbd*\xbeY6/\xb1\x9b\x89\x89\xa4\x02n\r\xe3`\xd5V\xe4\x88\xfah\xd0oVS\x17\x16\xc0+5@(%\xb6\xd3#lr\xa5\xab\x01\xa8\x1e۴\x9af\xd4\xf5\x1f\x85\xdb\xc0\xb7\x1705\x13\xda\xc3G\x01\x00\x12\x9a\xf3\xe8\xea K֥\x91ީr \xfaݔ\x94\x92\x87\x02\xe3EQ\x1d:\xb4A\x9dFg-P\v\x97\xa3\x14d\xa4!,F\xa6\x0e܆Նu\xf1\v\x94\x19\a\xe2\xf8%:\x06\x1c\r$\xd1\xc1\b\x7f\x01\xe3Ry/\xd5\x11\xeb\x16\x94\xf7\n<ވ\xc9\xfa\x93\x1f\x86y\x8f\xe2PW\x15\x05H;ɇp&J\xddR>\xfeH\x8d\xe3T\x00\x84\x99\xf5\xb4A\x94\xc0\x87\xf3\x8a\xdf\x7fL\xbf^\xca\xe3\xa3s\x1c\x1d\xa5\xa7\x16F\xb0\x81\xa5\xeeQ\xe1\x16\xde\xe6\x05\x98\x83\x83G\xab\x9e\xeb+\x86dK\xe2\x18\xb3\x17\x11\x90\xa7}H\xf7Q!\xdc!㉱+\xedin\x97ėJ&\xedG\xce\xe2G\x01\x00\x13`\xa0V\xbb\xd5\xc8\x1ef\x10\x98\x04\x81裺\x16\u07b7auz\xbf\xcd\xcbP\x8bYv\xb5\x0f\x1b\x8a\xf6\xe7\xce\x15\xe7\xd8\xc3V\v\xbfSD\xff\xb0\r\x1f\xa8}4$5\xc8\xd5QE&\x99H\xc1q\xec.-\xf1\xf5\xb0q\xb8ps\x8d\xb7\xf7\xb6\x13+\x93\x7fa\x02\x89\x13r\xefޑ\xd7\x12T\xb7++\xfa\x16>\xa0\x18\x00'\xfdEF\xed\x84&\r\xbe\xe8f\xe1\xc0\xccu\xa3\xb1\xb9HZ\xc8ܧ\xeb\xab\x04S\xf0\x17\x13\xd9\x0e\xe3Å\x15J2\xeb\xe2u\xa6\xf9/-\x9b\b\x1b\xbf\xb18\xac\xb7\xfd\fB\xa8\xbd\x12\xe8\ru\xe9\x13\x13⎒\x11\xad\xe1=5\x01\xb9*6\xd7V\x1a\x9a\x8b\xbb:G\x01\x00\x14\x9a\xc0C\xaf\a\x06\x9f\xbd$\x95#\x87J\x9c\xb2\xba(O\x89N\xfa\x95\xe7\x9b.\x9e\xeb\xa8\xce;u\xa8v\x0f@,\x0fx;\xf4\x19-P%v\xe9c)\xe9\x95\xeb\xfbE\xb7\x10}\xf6\xb5.\x93$\x17\xf5\x12:\xce+\x85\x924\xf1\x8c\xed\x97\xce\xff\x8d2\xb6\xbf-<\x9agHO\xba\xaa\xc3PY/u\x04\x9f\x9b\x1b7\xe0:\x80\x10\xc5\xfd\xe1CA\xb2 \xff\xb66\x7f\xec\x10))\xb19\x98\xb9\xcf\xe0\xea\xe7e<\x14\x86M\xac\r1}\xae9S\xaaٻmHBM\xdf\xc3m\xb0l@\xb5\xc7\xe5\rt\xdco7\xf5\xc2\xe8\xf8s\x97m\x91l\x12yB=\xbd\xe4Ft$4\xf8HbeD\xf7") 3 | -------------------------------------------------------------------------------- /packet_buffer.go: -------------------------------------------------------------------------------- 1 | package astits 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "fmt" 7 | "io" 8 | 9 | "github.com/asticode/go-astikit" 10 | ) 11 | 12 | // packetBuffer represents a packet buffer 13 | type packetBuffer struct { 14 | packetSize int 15 | s PacketSkipper 16 | r io.Reader 17 | packetReadBuffer []byte 18 | } 19 | 20 | // newPacketBuffer creates a new packet buffer 21 | func newPacketBuffer(r io.Reader, packetSize int, s PacketSkipper) (pb *packetBuffer, err error) { 22 | // Init 23 | pb = &packetBuffer{ 24 | packetSize: packetSize, 25 | s: s, 26 | r: r, 27 | } 28 | 29 | // Packet size is not set 30 | if pb.packetSize == 0 { 31 | // Auto detect packet size 32 | if pb.packetSize, err = autoDetectPacketSize(r); err != nil { 33 | err = fmt.Errorf("astits: auto detecting packet size failed: %w", err) 34 | return 35 | } 36 | } 37 | return 38 | } 39 | 40 | // autoDetectPacketSize updates the packet size based on the first bytes 41 | // Minimum packet size is 188 and is bounded by 2 sync bytes 42 | // Assumption is made that the first byte of the reader is a sync byte 43 | func autoDetectPacketSize(r io.Reader) (packetSize int, err error) { 44 | // Read first bytes 45 | const l = 193 46 | var b = make([]byte, l) 47 | shouldRewind, rerr := peek(r, b) 48 | if rerr != nil { 49 | err = fmt.Errorf("astits: reading first %d bytes failed: %w", l, rerr) 50 | return 51 | } 52 | 53 | // Packet must start with a sync byte 54 | if b[0] != syncByte { 55 | err = ErrPacketMustStartWithASyncByte 56 | return 57 | } 58 | 59 | // Look for sync bytes 60 | for idx, b := range b { 61 | if b == syncByte && idx >= MpegTsPacketSize { 62 | // Update packet size 63 | packetSize = idx 64 | 65 | if !shouldRewind { 66 | return 67 | } 68 | 69 | // Rewind or sync reader 70 | var n int64 71 | if n, err = rewind(r); err != nil { 72 | err = fmt.Errorf("astits: rewinding failed: %w", err) 73 | return 74 | } else if n == -1 { 75 | var ls = packetSize - (l - packetSize) 76 | if _, err = r.Read(make([]byte, ls)); err != nil { 77 | err = fmt.Errorf("astits: reading %d bytes to sync reader failed: %w", ls, err) 78 | return 79 | } 80 | } 81 | return 82 | } 83 | } 84 | err = fmt.Errorf("astits: only one sync byte detected in first %d bytes", l) 85 | return 86 | } 87 | 88 | // bufio.Reader can't be rewinded, which leads to packet loss on packet size autodetection 89 | // but it has handy Peek() method 90 | // so what we do here is peeking bytes for bufio.Reader and falling back to rewinding/syncing for all other readers 91 | func peek(r io.Reader, b []byte) (shouldRewind bool, err error) { 92 | if br, ok := r.(*bufio.Reader); ok { 93 | var bs []byte 94 | bs, err = br.Peek(len(b)) 95 | if err != nil { 96 | return 97 | } 98 | copy(b, bs) 99 | return false, nil 100 | } 101 | 102 | _, err = r.Read(b) 103 | shouldRewind = true 104 | return 105 | } 106 | 107 | // rewind rewinds the reader if possible, otherwise n = -1 108 | func rewind(r io.Reader) (n int64, err error) { 109 | if s, ok := r.(io.Seeker); ok { 110 | if n, err = s.Seek(0, 0); err != nil { 111 | err = fmt.Errorf("astits: seeking to 0 failed: %w", err) 112 | return 113 | } 114 | return 115 | } 116 | n = -1 117 | return 118 | } 119 | 120 | // next fetches the next packet from the buffer 121 | func (pb *packetBuffer) next() (p *Packet, err error) { 122 | // Read 123 | if pb.packetReadBuffer == nil || len(pb.packetReadBuffer) != pb.packetSize { 124 | pb.packetReadBuffer = make([]byte, pb.packetSize) 125 | } 126 | 127 | // Loop to make sure we return a packet even if first packets are skipped 128 | for p == nil { 129 | if _, err = io.ReadFull(pb.r, pb.packetReadBuffer); err != nil { 130 | if err == io.EOF || err == io.ErrUnexpectedEOF { 131 | err = ErrNoMorePackets 132 | } else { 133 | err = fmt.Errorf("astits: reading %d bytes failed: %w", pb.packetSize, err) 134 | } 135 | return 136 | } 137 | 138 | // Parse packet 139 | if p, err = parsePacket(astikit.NewBytesIterator(pb.packetReadBuffer), pb.s); err != nil { 140 | if !errors.Is(err, errSkippedPacket) { 141 | err = fmt.Errorf("astits: building packet failed: %w", err) 142 | return 143 | } 144 | } 145 | } 146 | 147 | return 148 | } 149 | -------------------------------------------------------------------------------- /packet_pool.go: -------------------------------------------------------------------------------- 1 | package astits 2 | 3 | import ( 4 | "sort" 5 | ) 6 | 7 | // packetAccumulator keeps track of packets for a single PID and decides when to flush them 8 | type packetAccumulator struct { 9 | pid uint16 10 | programMap *programMap 11 | q []*Packet 12 | } 13 | 14 | // newPacketAccumulator creates a new packet queue for a single PID 15 | func newPacketAccumulator(pid uint16, programMap *programMap) *packetAccumulator { 16 | return &packetAccumulator{ 17 | pid: pid, 18 | programMap: programMap, 19 | } 20 | } 21 | 22 | // add adds a new packet for this PID to the queue 23 | func (b *packetAccumulator) add(p *Packet) (ps []*Packet) { 24 | mps := b.q 25 | 26 | // Empty buffer if we detect a discontinuity 27 | if hasDiscontinuity(mps, p) { 28 | // Reset current slice or make new 29 | if cap(mps) > 0 { 30 | mps = mps[:0] 31 | } else { 32 | mps = make([]*Packet, 0, 10) 33 | } 34 | } 35 | 36 | // Throw away packet if it's the same as the previous one 37 | if isSameAsPrevious(mps, p) { 38 | return 39 | } 40 | 41 | // Flush buffer if new payload starts here 42 | if p.Header.PayloadUnitStartIndicator { 43 | ps = mps 44 | mps = make([]*Packet, 0, cap(mps)) 45 | } 46 | 47 | mps = append(mps, p) 48 | 49 | // Check if PSI payload is complete 50 | if b.programMap != nil && 51 | (b.pid == PIDPAT || b.programMap.existsUnlocked(b.pid)) && 52 | isPSIComplete(mps) { 53 | ps = mps 54 | mps = nil 55 | } else if isPESPayload(mps[0].Payload) && isPESComplete(mps) { // Check if PES payload is complete 56 | ps = mps 57 | mps = nil 58 | } 59 | 60 | b.q = mps 61 | return 62 | } 63 | 64 | // packetPool represents a queue of packets for each PID in the stream 65 | type packetPool struct { 66 | // We use map[uint32] instead map[uint16] as go runtime provide optimized hash functions for (u)int32/64 keys 67 | b map[uint32]*packetAccumulator // Indexed by PID 68 | 69 | programMap *programMap 70 | } 71 | 72 | // newPacketPool creates a new packet pool with an optional parser and programMap 73 | func newPacketPool(programMap *programMap) *packetPool { 74 | return &packetPool{ 75 | b: make(map[uint32]*packetAccumulator), 76 | 77 | programMap: programMap, 78 | } 79 | } 80 | 81 | // addUnlocked adds a new packet to the pool 82 | func (b *packetPool) addUnlocked(p *Packet) (ps []*Packet) { 83 | // Throw away packet if error indicator 84 | if p.Header.TransportErrorIndicator { 85 | return 86 | } 87 | 88 | // Throw away packets that don't have a payload until we figure out what we're going to do with them 89 | // TODO figure out what we're going to do with them :D 90 | if !p.Header.HasPayload { 91 | return 92 | } 93 | 94 | // Make sure accumulator exists 95 | acc, ok := b.b[uint32(p.Header.PID)] 96 | if !ok { 97 | acc = newPacketAccumulator(p.Header.PID, b.programMap) 98 | b.b[uint32(p.Header.PID)] = acc 99 | } 100 | 101 | // Add to the accumulator 102 | return acc.add(p) 103 | } 104 | 105 | // dumpUnlocked dumps the packet pool by looking for the first item with packets inside 106 | func (b *packetPool) dumpUnlocked() (ps []*Packet) { 107 | var keys []int 108 | for k := range b.b { 109 | keys = append(keys, int(k)) 110 | } 111 | sort.Ints(keys) 112 | for _, k := range keys { 113 | ps = b.b[uint32(k)].q 114 | delete(b.b, uint32(k)) 115 | if len(ps) > 0 { 116 | return 117 | } 118 | } 119 | return 120 | } 121 | 122 | // hasDiscontinuity checks whether a packet is discontinuous with a set of packets 123 | func hasDiscontinuity(ps []*Packet, p *Packet) bool { 124 | if p.Header.PID == PIDNull { 125 | return false 126 | } 127 | 128 | isDiscontinuityIndicator := p.Header.HasAdaptationField && p.AdaptationField.DiscontinuityIndicator 129 | if isDiscontinuityIndicator { 130 | return false 131 | } 132 | 133 | l := len(ps) 134 | if l == 0 { 135 | return false 136 | } 137 | 138 | var expectedContinuityCounter uint8 139 | if p.Header.HasPayload { 140 | expectedContinuityCounter = (ps[l-1].Header.ContinuityCounter + 1) & 0x0f 141 | } else { 142 | expectedContinuityCounter = ps[l-1].Header.ContinuityCounter 143 | } 144 | if expectedContinuityCounter == p.Header.ContinuityCounter { 145 | return false 146 | } 147 | 148 | return true 149 | } 150 | 151 | // isSameAsPrevious checks whether a packet is the same as the last packet of a set of packets 152 | func isSameAsPrevious(ps []*Packet, p *Packet) bool { 153 | l := len(ps) 154 | return l > 0 && p.Header.HasPayload && p.Header.ContinuityCounter == ps[l-1].Header.ContinuityCounter 155 | } 156 | -------------------------------------------------------------------------------- /dvb.go: -------------------------------------------------------------------------------- 1 | package astits 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/asticode/go-astikit" 8 | ) 9 | 10 | // parseDVBTime parses a DVB time 11 | // 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 12 | // Coded Decimal (BCD). If the start time is undefined (e.g. for an event in a NVOD reference service) all bits of the 13 | // field are set to "1". 14 | // I apologize for the computation which is really messy but details are given in the documentation 15 | // Page: 160 | Annex C | Link: https://www.dvb.org/resources/public/standards/a38_dvb-si_specification.pdf 16 | // (barbashov) the link above can be broken, alternative: https://dvb.org/wp-content/uploads/2019/12/a038_tm1217r37_en300468v1_17_1_-_rev-134_-_si_specification.pdf 17 | func parseDVBTime(i *astikit.BytesIterator) (t time.Time, err error) { 18 | // Get next 2 bytes 19 | var bs []byte 20 | if bs, err = i.NextBytesNoCopy(2); err != nil { 21 | err = fmt.Errorf("astits: fetching next bytes failed: %w", err) 22 | return 23 | } 24 | 25 | // Date 26 | var mjd = uint16(bs[0])<<8 | uint16(bs[1]) 27 | var yt = int((float64(mjd) - 15078.2) / 365.25) 28 | var mt = int((float64(mjd) - 14956.1 - float64(int(float64(yt)*365.25))) / 30.6001) 29 | var d = int(float64(mjd) - 14956 - float64(int(float64(yt)*365.25)) - float64(int(float64(mt)*30.6001))) 30 | var k int 31 | if mt == 14 || mt == 15 { 32 | k = 1 33 | } 34 | var y = 1900 + yt + k 35 | var m = mt - 1 - k*12 36 | t = time.Date(y, time.Month(m), d, 0, 0, 0, 0, time.UTC) 37 | 38 | // Time 39 | var s time.Duration 40 | if s, err = parseDVBDurationSeconds(i); err != nil { 41 | err = fmt.Errorf("astits: parsing DVB duration seconds failed: %w", err) 42 | return 43 | } 44 | t = t.Add(s) 45 | return 46 | } 47 | 48 | // parseDVBDurationMinutes parses a minutes duration 49 | // 16 bit field containing the duration of the event in hours, minutes. format: 4 digits, 4 - bit BCD = 18 bit 50 | func parseDVBDurationMinutes(i *astikit.BytesIterator) (d time.Duration, err error) { 51 | var bs []byte 52 | if bs, err = i.NextBytesNoCopy(2); err != nil { 53 | err = fmt.Errorf("astits: fetching next bytes failed: %w", err) 54 | return 55 | } 56 | d = parseDVBDurationByte(bs[0])*time.Hour + parseDVBDurationByte(bs[1])*time.Minute 57 | return 58 | } 59 | 60 | // parseDVBDurationSeconds parses a seconds duration 61 | // 24 bit field containing the duration of the event in hours, minutes, seconds. format: 6 digits, 4 - bit BCD = 24 bit 62 | func parseDVBDurationSeconds(i *astikit.BytesIterator) (d time.Duration, err error) { 63 | var bs []byte 64 | if bs, err = i.NextBytesNoCopy(3); err != nil { 65 | err = fmt.Errorf("astits: fetching next bytes failed: %w", err) 66 | return 67 | } 68 | d = parseDVBDurationByte(bs[0])*time.Hour + parseDVBDurationByte(bs[1])*time.Minute + parseDVBDurationByte(bs[2])*time.Second 69 | return 70 | } 71 | 72 | // parseDVBDurationByte parses a duration byte 73 | func parseDVBDurationByte(i byte) time.Duration { 74 | return time.Duration(uint8(i)>>4*10 + uint8(i)&0xf) 75 | } 76 | 77 | func writeDVBTime(w *astikit.BitsWriter, t time.Time) (int, error) { 78 | year := t.Year() - 1900 79 | month := t.Month() 80 | day := t.Day() 81 | 82 | l := 0 83 | if month <= time.February { 84 | l = 1 85 | } 86 | 87 | mjd := 14956 + day + int(float64(year-l)*365.25) + int(float64(int(month)+1+l*12)*30.6001) 88 | 89 | d := t.Sub(t.Truncate(24 * time.Hour)) 90 | 91 | b := astikit.NewBitsWriterBatch(w) 92 | 93 | b.Write(uint16(mjd)) 94 | bytesWritten, err := writeDVBDurationSeconds(w, d) 95 | if err != nil { 96 | return 2, err 97 | } 98 | 99 | return bytesWritten + 2, b.Err() 100 | } 101 | 102 | func writeDVBDurationMinutes(w *astikit.BitsWriter, d time.Duration) (int, error) { 103 | b := astikit.NewBitsWriterBatch(w) 104 | 105 | hours := uint8(d.Hours()) 106 | minutes := uint8(int(d.Minutes()) % 60) 107 | 108 | b.Write(dvbDurationByteRepresentation(hours)) 109 | b.Write(dvbDurationByteRepresentation(minutes)) 110 | 111 | return 2, b.Err() 112 | } 113 | 114 | func writeDVBDurationSeconds(w *astikit.BitsWriter, d time.Duration) (int, error) { 115 | b := astikit.NewBitsWriterBatch(w) 116 | 117 | hours := uint8(d.Hours()) 118 | minutes := uint8(int(d.Minutes()) % 60) 119 | seconds := uint8(int(d.Seconds()) % 60) 120 | 121 | b.Write(dvbDurationByteRepresentation(hours)) 122 | b.Write(dvbDurationByteRepresentation(minutes)) 123 | b.Write(dvbDurationByteRepresentation(seconds)) 124 | 125 | return 3, b.Err() 126 | } 127 | 128 | func dvbDurationByteRepresentation(n uint8) uint8 { 129 | return (n/10)<<4 | n%10 130 | } 131 | -------------------------------------------------------------------------------- /testdata/fuzz/FuzzDemuxer/1acf6c5d01716faefd01ee027121ab635b343d5423e9c20e27dd4e27619eaf31: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("G@\x00\x10\x00\x00\xb0\r\f7\xc1\x00\x00\x00\x01\xe00\x8a|\\\xa4\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xffG@0\x1c\x00\x02\xb0u\x00\x01\xe7\x00\x00\xe01\xf0\x1b\x05\x04GA94\x87\x13\xc1\x01\x01\x00\xf4\r\x01eng\x01\x00\x00\x05TV-14\x02\xe01\xf0\x0e\x11\x01\xff\x10\x06\xc0\xbdb\xc0\b\x00\x06\x01\x02\x81\xe04\xf0\x18\x05\x04AC-3\x81\n\b8\x05\xff\x0f\x01\xbfeng\n\x04eng\x00\x81\xe05\xf0\x18\x05\x04AC-3\x81\n\b(E\xff\x00\x01\xbfspa\n\x04spa\x00\x8c\xc7f^\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xffG\x001\x13љ\xa2\xb7Cs4j\xa0\xc1\xff\xecU\x8a\xb4V\x01\x8b\xff\xd8h$\x80\xc5\xff\xed\x01\x88\x00X\xa80\x7f\xfb@b\x00\x16\x01\x8b\xff\xd8h\xe01\x00\v\x1a4\x90\x05V\x00\xc4\x00,i\xc0\x03\xa6\x18h\xd8kEiZ0\xccg\x81\t\xa3e\fh\xad\x06\xb4\x18\xacF\x1a9aLQ\xa5\xda\x006b\xa1\xac\x8a`\x00~\x054a\x19\xab\x05\rcY\r`\x03f\x8cә\x87\xb2\xda1\x9a\xb0\xcc\x1f\x80\x05\xa35\x8d\xad-ck\f\xda\tC3Eb\xac\x99\x83\x17\xff\xb4b\xb3Pa\x00\x16\x15\x9a\x83\b\x00\xb2f\xd0M\x18\xabFfL4a\x9a\xb4f\xac\x98h%\x16\xb1\xb5\x8d\xa0\xc0\x00,m\x06\x00G\x001\x14\x01d͠\x9a+\x15b\xc5A\x83\xff\xd93h%6\x83\a\xff\xb1\xb5\xa5\xa0\xc1\xff\xecֲf\f_\xfe\xd0Lk\x1aұa\x9bA)f\xac\xacՓ6\x8c\xd6fZ\f \x02\xcdA\x84\x00Y3hڳU\x05\xf0\x02e\xac\xd4\x18@\x05\x88\xcc\x18\xbf\xfd\xa3j\f \x02Ŗ\xb3V@\xc5\xff\xec\xc1\x8b\xff\xda6\x06/\xffck\x00b\xff\xf64\x18\x7f\xfe2b\xb6V\xcb\xc0}DțGڭ\x1aZѳ@\x1a5\u058c\xd0a\x00\x16-(0\x80\vJ\xd3\x19\xb4f\xb1\xac\xa0\xc4\x00,Ն\x1a9X\xb4\xa0\xc2\x00,A\x88\x00Y@a\x00\x16`\xc4\x00-\x18b\f_\xfe\xc0\x18\x80\x05G\x001\x15\x8de#0b\x00\x16\x8c\xb4hц+A4hѣ&m\x04\x91\xa3F\x8c0\xd0M\x181`\f_\xfeɩ\xa3\x8c\x18\xb0a\x86\x8e0b\xc22a\xa0\x96F\fA\x88\x00X0\xc01\x7f\xfbG\x19A\x88\x00XƑ\x86\x1a5\x06 \x01h\f@\x02ŀ1\x00\v\f4G5%\xe6\xa6*\xd4i\xbdZh\x1cS҂!\x94G,.}\x0f{\x9d(:ibS\x00\x00\x00\x01DR\x8a9\x14\x839\x14\x83<\x8d\x82\x04\x9dր\xc5\xff\xed\x1a7Cr4\x12\xb5\x06\x10\x01\xa46 \xc4\x00,(0\x80\rcA\x84\x00k\f4`\x18\x80\x05\x84f\x06 \x01e\x8c0ц\x10\x18\x7f\xfd\x8bG\x001\x16\b\f?\xfe\xcc\x18\x80\x05\x86\x82d\x8dG\x00\xc3\xff\xed~6~\t\x003eA\x88\x00Y4G\x05\x8e\x8c\f\f\x00~\b\x7f\xe8\x1a#\x11\xbc\x18Q\xf1@\x0fA\x0f\xfc\x89\xa1\xc4Pa\x00\x180\xa3\xf4\x82\xa2\xc3m\x06M\x10c\xff\xf8\x18\xff\xfe\x88֣\xc0\xc7\xff\xf4>\xf5\x11\xf7f\\\x9b\xacu~\xe8%\xf9\x88\xc6js(\xc6js`\xdd-\x1b4hєgZ\t#M9\xa3\x1as(\f?\xfe͠\x94\xcaѤh\xc8\x18\xbf\xfd\x86\x88\xc0\xb2j\xfa#\xb1\xa6\x8b\xed\x14$6\xd7D\x8e81\x00\r\x83\x10\x00\xd0K\xf3\x90b\x00\x16 \xc4\x00,\x01\x88\x00X\x83\x10\x00\xb7%<\x00vݭG\x001\x17\x1c\xa7\x80\x0e\x9a@a\x00\x16\x94\xf0\x01\xd3H\f \x02\xcc\x18\xbf\xfd\x94=\xa3\x15\x849\x9a\xb2Ø\xac\x81\x88\x00ZAeo\xa3\xf9\xb49\x8da\x0ec[\x99\xba\xe8ƈ\xe0\xb1Vp\xaa\x82_\x9d\xd0a\x00\x16 \xc4\x00,(0\x80\v\x10b\x00\x16\xe4\x8d\xd9A\x84\x00Z\"\x02\xcbف\x15\xa9\xb5\x83\x10\x00ѳ\x83\b\x00\xd1\x15\x8aD\x94Q\x11\x1a\x88\x83\x1f\xff\xd01\x7f\xfc\f\x7f\xffTFc<\x18\xff\xfe\x9e\x06?\xff\x8a\x06/\xff\xb4.z\xea\xda9*\x8d\xb4\x11ʙ\xe3渧\xa2/@\xc6\x004Fa\x10\x9b]\x11h\x81\xaa\xba\xbe=\x83\x17\xff҂1\x82O\x98\x00\xc8\ni(0\x00G\x001\x18\v@b\x00\x1a\xd2\x1e\x00:lA\x8b\xff\xdbv\x8dF4\x06/\xffh1\xa3\x185\x9dh\xd8\x18\xbf\xfd\xa3\nŕ9\x98\xc0\xb0b\xff\xf6\x829A\x8b\xff\xd8\xd0a\x00\x16\x14\x18\xbf\xfd\x9a\x83\b\x00\xb3\x8ci\xd0\x03p-\xa4\x163\x82O\x99\xb1V\x83Z7;t\xb4\x82ǙG\xf3E\x06\x00\x01h\f@\x02Ѡ1\xff\xfbs\x14\x8d\xd2\f@\x02ъ0\x90\x84\xa8Kfӂǀ\v\xe0\v\x98Q\x84\x84%B[\x1d8\xf1\xa3\xd0\x00g\x19\x83\x17\xff\xb01\xa3z\x7f\x88\x0f\xc0\xb8Ɯ\x00:\xa46\x14\xf0\x01\xf8\x17\x18Ӏ\aT\x86\xc8`\x16\x05\xb3hJiPm\x1f\xcfM\b\xf4\x02\xda\x1aG@\x00\x11\x00\x00\xb0\r\f7\xc1\x00\x00\x00\x01\xe00\x8a|\\\xa4\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xffG\x001\x19\f\x00\x04\xcdA\x87\xff\xd8\xc0a\x00\x1a\xdcHݠ\xc5\xff\xed\x1a\x1a\xa0h\x92\xd1Y\xa0\r@\x05\xec\x18\xac(0\x7f\xfbG-\xa0\x03f*\x03BC\n\f \x02ś\x00\xc7\xff\xed\x11\x81cW\x03\x18\x00\xc0\xc6\x0002\x00\r\x04\xbe\x05\xa3F\x8d\xc7\x1b\xb5\xa3m\x06\x1f\xfffe\xd0a\xff\xf6f#6\x8c\x03\x17\xff\xb1\xa0\xc5\xff\xec\x01\x8b\xff\xd8\xd0b\xff\xf6L\xa3D`X\xff\xf01\x80\r\xc0\xc6\x004\x12\xf89A\x84\x00Z(0\x7f\xfb\x10c\x00\x16\x80\xc7\xff\xed\xc4ݭ\x11\xc1c\x9fM\"`a\xff\xfa# P'\x06i>\xe4PI\xe0XF, <\x7f\xfbq\rn\xe8њ\xa0>$\xd2") -------------------------------------------------------------------------------- /testdata/fuzz/FuzzDemuxer/49be436174e9321657131246dd602da216b479eb7a247017b104614f11f3e50f: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("G@\x00\x10\x00\x00\xb0\r\x00\x01\xc1\x00\x00\x00\x03\xe00\xedc\xab?\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xffG@0\x10\x00\x02\xb0?\x00\x03\xc3\x00\x00\xe01\xf0\x00$\xe01\xf0\x00\x06\xe02\xf0\f\x05\x04AC-4\n\x04eng\x00\x06\xe03\xf0\f\x05\x04AC-4\n\x04spa\x00\x06\xe09\xf0\x06\x05\x04STPP\x92\f\x18H\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xffG@3\x10\x00\x00\x01\xbd\x01\x80\x87\x80\x05%\xc7W\n٬@\x01t\xb9]\xe6\x98@\x04\x85\x00.\f \x10W\x80`\x83\x10\xa6@^A\x00\x02\xb4]\xf0\xab\x1d6\b\xc5 \x80lt\xd8;\x05D\x01j\x0f\x80\v\x85\xc0JI5\xc5E\x95\xf8u\x13\xc21\x15ý\xc2b\xb1\xef\xe4p\x8f\x01\xc4\x00\xf8\x7f\fP+3\x89\xe1\xdd\x1d\xcd\xe6Gʗ\x13N\xc7\x03\x87\x95\x13\xba:\xb3\xfb=\xa1~\xa3\xbb\xf2\xe2\xccd\xc1\xda۱_\xe2z\xe4\xa5\xdcݰ=\x97p\xd7^\xb7[\xbc\x1a\xa3n\xd1 C)F`ٔ\xc4|;af\x14\xa0\xde\x0f\x01\x03\x19\xea\x16%2\xfe\fJT\x92p\\P\xb1\x86\xf0'\xc6t\x82\x03\xd9^l\xf0D\t\xf2m-Ǭ\x89\xa5\xce\x0e\xe5\x1a\xb2t\xce\xc1\xb5\xf9\x16\">\xc5#YΙ\xe8\xf8\xf4\xed\x0fU\xb3\xc5\x1d\x95?\xbf\xb2\xa5މ\xf1\xa1@d\xd9\x0eC\xbe\r\x1a\x8cϪC\xb9\xaa\xbf2\x10\xcbJ O\x03\x7fh,>7\x8fΌ\x96\xef\xc5F\x95\xe4 \x85\x15/G\x1aK\x13\xcf\x00B\x1e\xac6\xb5Kk\xdb3TG\x003\x14z\x1c:up\xf5@\xb2[\x9f\xe6\x81up\x90\xff-\xee\xb7\xcc\xe7\x90M\xd6\xf21\xcdz{\xb7Y\xc2AEq<\xe8\x8f\xfb\xd82\x9f\x9a\x9e\xec\xaf|'z\xfdP\xc6\xf2\x8b_\x85Uo\xd7by\x95\xa2ˤ\xde\xdeZӧ\xdet\xa0\x88\xd7O\xfb\x99\xa5\xa6/[g\xbb1\xb2\x9aioc\xd63\xd9v\x1f\xbb\xcegNE\x1aw\xcd\x10\x8c\xa3z\xb2\x00\xe4\x01qv\xa1\x1a\x16o\x89\bh\xa0\xb0\xd0\xd9\xc3L4i\x15\f\x0eI\xbe\x89\x84\xf7\xe9\xc8i\x8eH\xf4\x98\xce\xf8\xab\xae\xfaXV|\xceN\x93\xe4(\xad\x06G\x05Y\x80\x03P@\x04\xee\x80\x19\x82\a\xe9v\x00\xa4\x01\xe8\xb1b];\xaf\x82\xb9\xdf7\x1b\x9c\xaeD{\xac\xb1\xb0\x00\x18\x11\x84|c\x85t\xec\xe1\xdcb&d\x92V\x02L\x9b\xcdH\x03\x9d\xac@\x17s/\xd5\xeect\xc6,\xb6[\xb5Qɍ\x1a\x7f\xf2c\x9f\xcb\xe9\x8e\xc7Sv\x94a\x92\xebw\xc1\x03\xe3A\x03\xe8y\xf6\xf9p\x03\x00\x06\xde\xfb\x1d\x04\xce\x17\x8a\x9a\x84\xff\xce\xdb\x1f\x9b\x16b\xcdn@ }\b\x01\x1b\x80\b\x80\b>\xfd\x93\xcc\x16G\x17\xbc\x17'\xaez\xe2ʖ[\xc5[\x81\xb8\xde\xc4\x00\x8c\x01\x99\x14\x01\x88\x02\xbe}\xf9\xdd|\xdfw\xc7M\xb6\xe4\x9bt\xc5\x01U&\x16\xb9\x1b0\x06\x84l\xb4\x02\xbd\x90\xa3\t\xc6\xc3b\xd5\xf5\xca\xdb\x00'\x00\x7f\xb9\x93\xd8S\xcc\xd4\xfc\xd9k\x00\x00\x00\x01!\x12?\x8d{\x93^3\xf2\xf2\x1dk\xf9?Z\xa1o\xbeoP\tu\xad\xef*\xd6ˢ`I+\x15\xb6\x17C\xebF\\\xce\xf9\x7f!\xb6\xb5n\xc6m\x87\xb4\xb3\xdc)Z;\x1e\x04F\x93&2\xee\x16s6\xc6MZ{\xda2\xf6g\xba\x9e˪\xa8Ŗ31\xa3n\x8a{!\x1c\xa71\x99\xf9`EdW\x9cc\xf4\xd1\xdc\xf9\xb1\x9f\x9c\U000cc3fa\xc7G\x17\xbc\x18\x8b\rdk3\x1a\b\xdb\x1c\x91\x9a\x9e\xd5\xf2\xcfc&v4v?\x19u8\x11\xbb\xb4\xc31\xa3n\x04\x0f\xc8\xf7\xec\x88\x00@\x00\xff\x91\x1b]ι\xdc\x1a59~;Y\xf6\xff'n\xb2vy\xecƈ\xe1\xce\x06\xe3\b@T\x048\x04\xe0\fɼ1\xb93\x14\x94>7\xeaJ\x15A\x80\x18\x14\x05\x06\x01]\xba\x12\x92\x9b\xa0\x8e\x9a+$4\xbc\x8d\x00fLv~o\x80\x1e\x10\xd8h\x8d\xa8$\xe5\xf5\xc7n\xe7\xf2\xedn0\x03/\xa1\x06\xf9\xee6\x00\a\xc0\t\xa7?\x12\tɱ\x00\x89u\xb0\x91\x80\x17g\xc6\x1b\xc6\xc7\xe49\xdaMnM~7v;u\xa3q}G\x11\t\x86S\x1b\xa1\xd7ȱ\x02G\x17\xbc\x19W\x87\xbe\x19\x7fV\xb6`\a\xffw\x9aN3f\x13\x1bH\xa4\xddj\x8d-\x11\x88@\xa50\x01\xe0\t\x80\x1d\xf1\x8cM\xc3q>!\x86r`\x0e\xca\x1a\x19\xd8Q\xb0\x01`b\x1f\xaa!\x01Bg+fT\x05R\xd0\b\x11\xf8\x9b|\a\xb3\nH\f\x13a\x80S\x14\xba(\x06-\xf2\v\v\x80v\x9e\xdb\xf1O\x16\xde&a\x87\xeb]\v\x8c\x00d\x92h\x18\x01\x8b\xa3\xb2\xce\x1f\xa2ёיp\x02@\xdc\x02\x94\x97Ǫ\x02\x80P4!\x02\xb5:\xe9\xa8$\xf99\x1cV8\xde\xf6\xe7S\v\xdaݰ\x02߯\xc7~\xce\x15м\xe5\xf3V\x99U\xb9\x00\b\xc5\xf1\x80\r\x85ݥ\xbf\x13\x1b\xd7n\x15\xad\xcf\xfc") -------------------------------------------------------------------------------- /testdata/fuzz/FuzzDemuxer/c9386b0717370f896e7baa2c62d5ae36346e4253b8883cdff93c016105516711: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("G_\xff\x10#SEGMENT\x00#TAG:AD\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xffG@\x00\x10\x00\x00\xb0\r\x00\x00\xc1\x00\x00\x00\x01\xf0\x00q\x10\xd8x\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xffGP\x00\x10\x00\x02\xb0\x17\x00\x01\xc1\x00\x00\xe1\x00\xf0\x00\x0f\xe1\x01\xf0\x00\x1b\xe1\x00\xf0\x00\xf2\xd9\x15c\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xffGA\x01\x10\x00\x00\x01\xc0\x01\b\x84\x80\x05!\x00\x01\x1e\xf1\xff\xf1L\x80 \x1f\xfc!\x19\x8f\xff\xff\xff\xff\xf3\x16ZM\x92\x94\xc3@\xb0PL\x1d\x10\x00@\x05\xd4&\x86\\=\x81\x1e\xbe\x91\x9b\x1cm\xf1\xdch\xd1\xe8.H<\xbbb\xf9b\x1ax\x87\xdc\x04>|5\xa2\x12\xfd,\xacE\x15V\x02\b\xd5ui\x94\x90\r\xfcI\xe8\xbbYv\x03\x12\x1a\b\xdeN#$\xaa#a\x92\x99\f\x8edʚ\x81n\xd3h\xf3\xad\x0ege\xd1D*\v\xe9j\xd8U5-\x9e\xe58\xfcc\x02C9J\x99\x1c\xbc˫|s2\xce<\xaa\x17\x93\x87\x81\\Łd\x9b){\x80\xd8\xe9\xab;\x1an\x92C\x11\x03r\fA \x19\x14\x02G\x01\x011a\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x92\xe9\x00\x90v\x00Q-\x017\xcbӬH\x06s\xb6N\x19\x8c\xb7-\x00*\x03M\xf8\xcfy\tV\xa4\xa8i\xe3L)\b-\x9a6\tN\x02f\xec\v\xd6SB4\x1b\x93\x80\xfc\xfc\xbc3\xa9\\\xd5\xdc*\xa8*7\xf8&\xddV\xe93\xc9\xd7\xe7\x03dʀ\x16\x81\xf2\xe8\x9bA\xc0GA\x000\a\x10\x00\x00\x05\xdc~\x00\x00\x00\x01\xe0\x00\x00\x84\xc0\n1\x00\x01FQ\x11\x00\x01\x17q\x00\x00\x00\x01\t\xf0\x00\x00\x00\x01gd\x00\x1f\xac\xd9@P\x05\xbb\x01\x10\x00\x00\x03\x00\x10\x00\x00\x03\x03\xc0\xf1\x83\x19`\x00\x00\x00\x01h\xeb\xec\xb2,\x00\x00\x01e\x88\x84\x00\x9f\x99=\x94](lϬo\x13,\x9f\xdd\xfb\xa5@\x9e\xf4\xaeihrH\x1f\x93X\xbe=@\x88\xd5D\x88p\x14\x9c\n\x98\x13E\xc7D'[E\x1bo}z\xe7`\x03|\xacsY\x12Y\xe6H\xa5\xf5}a\xe1v\xa3#I\xc1\xabQ\x10\x1cl\xe0o\x17\xd4\xcfW>\x83\b\x13㹺4AyV/B\x99\xa9\xc5\x11\x81ͷK\x8b\xe3G\x01\x00\x11JB\xa2M\x8e\x92\x94p\xe8\x93\xdem\xa1\xad\xef9M\xd6\x1d4\x86\x83\xa6\xfc*\x12\xe7\x92\xf9@\xfdH.\xaf\ue1f9\xc1\xe4\x1c\x87#\xd3\xd5\x14\xc3\xffj\x81\x14kx\x97\xd2\xf9\xe74wâ\x1d\xd3Σ\xc10 \x90\x8bD\xda\xcf\xc0\x91\xf9\xab\xe6&\x044\x1aw\xa7\xe4D\x8c\xe6\xa7ҟ\x99\xa5\\\x92<\xc5\xc7/sF\xdal\xd1Y\xcb\xc8\xfeB5\x19\x89\x91\xf6mO\xcbG\xdf'H\x1bW\xbe\xbd*\xbeY6/\xb1\x9b\x89\x89\xa4\x02n\r\xe3`\xd5V\xe4\x88\xfah\xd0oVS\x17\x16\xc0+5@(%\xb6\xd3#lr\xa5\xab\x01\xa8\x1e۴\x9af\xd4\xf5\x1f\x85\xdb\xc0\xb7\x1705\x13\xda\xc3G\x01\x00\x12\x9a\xf3\xe8\xea K֥\x91ީr \xfaݔ\x94\x92\x87\x02\xe3EQ\x1d:\xb4A\x9dFg-P\v\x97\xa3\x14d\xa4!,F\xa6\x0e܆Նu\xf1\v\x94\x19\a\xe2\xf8%:\x06\x1c\r$\xd1\xc1\b\x7f\x01\xe3Ry/\xd5\x11\xeb\x16\x94\xf7\n<ވ\xc9\xfa\x93\x1f\x86y\x8f\xe2PW\x15\x05H;ɇp&J\xddR>\xfeH\x8d\xe3T\x00\x84\x99\xf5\xb4A\x94\xc0\x87\xf3\x8a\xdf\x7fL\xbf^\xca\xe3\xa3s\x1c\x1d\xa5\xa7\x16F\xb0\x81\xa5\xeeQ\xe1\x16\xde\xe6\x05\x98\x83\x83G\xab\x9e\xeb+\x86dK\xe2\x18\xb3\x17\x11\x90\xa7}H\xf7Q!\xdc!㉱+\xedin\x97ėJ&\xedG\xce\xe2G\x01\x00\x13`\xa0V\xbb\xd5\xc8\x1ef\x10\x98\x04\x81裺\x16\u07b7auz\xbf\xcd\xcbP\x8bYv\xb5\x0f\x1b\x8a\xf6\xe7\xce\x15\xe7\xd8\xc3V\v\xbfSD\xff\xb0\r\x1f\xa8}4$5\xc8\xd5QE&\x99H\xc1q\xec.-\xf1\xf5\xb0q\xb8ps\x8d\xb7\xf7\xb6\x13+\x93\x7fa\x02\x89\x13r\xefޑ\xd7\x12T\xb7++\xfa\x16>\xa0\x18\x00'\xfdEF\xed\x84&\r\xbe\xe8f\xe1\xc0\xccu\xa3\xb1\xb9HZ\xc8ܧ\xeb\xab\x04S\xf0\x17\x13\xd9\x0e\xe3Å\x15J2\xeb\xe2u\xa6\xf9/-\x9b\b\x1b\xbf\xb18\xac\xb7\xfd\fB\xa8\xbd\x12\xe8\ru\xe9\x13\x13⎒\x11\xad\xe1=5\x01\xb9*6\xd7V\x1a\x9a\x8b\xbb:G\x01\x00\x14\x9a\xc0C\xaf\a\x06\x9f\xbd$\x95#\x87J\x9c\xb2\xba(O\x89N\xfa\x95\xe7\x9b.\x9e\xeb\xa8\xce;u\xa8v\x0f@,\x0fx;\xf4\x19-P%v\xe9c)\xe9\x95\xeb\xfbE\xb7\x10}\xf6\xb5.\x93$\x17\xf5\x12:\xce+\x85\x924\xf1\x8c\xed\x97\xce\xff\x8d2\xb6\xbf-<\x9agHO\xba\xaa\xc3PY/u\x04\x9f\x9b\x1b7\xe0:\x80\x10\xc5\xfd\xe1CA\xb2 \xff\xb66\x7f\xec\x10))\xb19\x98\xb9\xcf\xe0\xea\xe7e<\x14\x86M\xac\r1}\xae9S\xaaٻmHBM\xdf\xc3m\xb0l@\xb5\xc7\xe5\rt\xdco7\xf5\xc2\xe8\xf8s\x97m\x91l\x12yB=\xbd\xe4Ft$4\xf8HbeD\xb3\x92") -------------------------------------------------------------------------------- /testdata/fuzz/FuzzDemuxer/9f12197b2060d80d1f1c7710f44bcdade975fd7660f9d85d79536a34d904cf70: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("G_\xff\x10#SEGMENT\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xffG@\x11\x10\x00B\xf0%\x00\x01\xc1\x00\x00\xff\x01\xff\x00\x01\xfc\x80\x14H\x12\x01\x06FFmpeg\tService01w|C\xca\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xffG@\x00\x10\x00\x00\xb0\r\x00\x01\xc1\x00\x00\x00\x01\xf0\x00*\xb1\x04\xb2\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xffGP\x00\x10\x00\x02\xb0+\x00\x01\xc1\x00\x00\xe1\x00\xf0\x00\x1b\xe1\x00\xf0\x00\x0f\xe1\x01\xf0\x00\x15\xe1\x02\xf0\x0f&\r\xff\xffID3 \xffID3 \x00\x0f^\xe9Տ\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xffGA\x000\aP\x00\x00\x05\xdd\xfe\x00\x00\x00\x01\xe0\x00\x00\x80\xc0\n1\x00\x01Fc\x11\x00\x01\x17w\x00\x00\x00\x01\t\x10\x00\x00\x00\x01gM@\x1f\xec\xa0(\x02\xdd\b\x00\x00\x1fH\x00\aS\x04x\xc1\x8c\xb0\x00\x00\x00\x01h\xea\xef \x00\x00\x01\x06\x04G\xb5\x001GA94\x03\xd4\xff\xfc I\xffC\"\xfe I\xfe\x00\x00\xfd\x80\x80\xfa\x00\x00\xfa\x00\x00\xfa\x00\x00\xfa\x00\x00\xfa\x00\x00\xfa\x00\x00\xfa\x00\x00\xfa\x00\x00\xfa\x00\x00\xfa\x00\x00\xfa\x00\x00\xfa\x00\x00\xfa\x00\x00\xfa\x00\x00\xfa\x00\x00\xff\x80\x00\x00\x01e\x88\x82\x00\x0e\xff\xd2^\x1b\xd9#\xe1\xe7?\x83\xf7x\x8d\x01\xf9\x8c\xbf\xba\xb2O:To*`\xc1\x9d\xc3\x1d>\xb7G\x01\x00\x11\xa8\x01\x83\xf1\xdb\xf0\xb0\xf3\xc3\x05\xff\xf6T\xf3\x02\xa9\xd0\xc6K\x17\xb6\xee\xc9\xfeJ\x97I\x15\xbfl%\xa7\xe5\x0f\xdf\xd8\x03ye\xb4!}\xbc0\x90\x90\x11#\xdbT\x92\x8eJ\xf0%\xfb\xeb\nH\xf2\x1fG\x9fߓ\x14vm\xc4\xc8\x04\x9e\x95\xe2\xbajYI8\x99\x990\x83O3\xf8\x8c\xb5\x99;\x03q\xcf>\x00\xd9\xdc\xfe\t\x89u\xa7du\xdepK.7\x87\xb8\xa3!l*P\u05ec\xeaS\x8d%/\xfb\x9e\xe8\x00\xb9\x8d02\x166\xe9\xddj\xc7T\x13\xc3\a\xa7\xef]\xb2A=Ф\x98\x01O\xed \x82\x94D\xbe7wA\x10\xe5y\xe7\x01\x18\x13ֈS\xf3Z\xe7'VX\xe3\x1f\xb0\xbf\xc6I\xf7\x9f\xf9{\u07bc3Y\xc3\r\x98\xea\x91\xd4\x04b\x9bR\xa23\xac\xb5\xe9a\xf0\xe80\xb4Y\xf8̦~\xc4\x1e\xb9\xde\xee\xa7\U0010c756\x1d1s\xc0\xde4\x02\xb5\xf1G\x01\x00\x15\xba$P~\x8e5u\vf1\x82@9\xdcK\xa3S\xa1\xd0\xc1N\xd5Uj\xe8X\xdb\x16\xa8Byf\xdb1\b\xf1\x0e:\x90\xbd~[\x9aσ\xea\xbe\xf1\xa0\xf8d\xcb/\xbdU\xb0\xa0g\xd1\xe1\xb6\x13B\x06\xe5\xcaB\xe3}l\x8f\xca!\xa2ȱW\x9elm\xfb\xd0\x02\x0fَj\xd7\xd2@\x90\x17\x90h\x98\xf8\xc8\xd9\xc5\x17\x8d\x0eH\xf4g\x0f\xb2\x8d\x85\x900\xc2\xefBx)\xf5eX\x00z\x9c\xb8\f\a\x17\xfd\xc1\xf8\x80\xa5\x96Ւ\x13\xb1\xdel'\xbe\xc8T'\xbd\x9e\xa9h\x066\xb3GX\xd1x\x93b̉s\xcd\xfa\x92~\xf2v\xe3\x10\xa4\x03\x1cs\x9bu\x1c\\\x11\xe8\xdb\x19\xcd\xeb\x84\x01\xb4") -------------------------------------------------------------------------------- /packet_pool_test.go: -------------------------------------------------------------------------------- 1 | package astits 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestHasDiscontinuity(t *testing.T) { 10 | assert.False(t, hasDiscontinuity([]*Packet{{Header: PacketHeader{ContinuityCounter: 15}}}, &Packet{Header: PacketHeader{ContinuityCounter: 0, HasPayload: true}})) 11 | assert.False(t, hasDiscontinuity([]*Packet{{Header: PacketHeader{ContinuityCounter: 15}}}, &Packet{Header: PacketHeader{ContinuityCounter: 15}})) 12 | assert.False(t, hasDiscontinuity([]*Packet{{Header: PacketHeader{ContinuityCounter: 15}}}, &Packet{AdaptationField: &PacketAdaptationField{DiscontinuityIndicator: true}, Header: PacketHeader{ContinuityCounter: 0, HasAdaptationField: true, HasPayload: true}})) 13 | assert.False(t, hasDiscontinuity([]*Packet{{Header: PacketHeader{ContinuityCounter: 15}}}, &Packet{Header: PacketHeader{PID: PIDNull}})) 14 | assert.False(t, hasDiscontinuity([]*Packet{}, &Packet{Header: PacketHeader{ContinuityCounter: 5}})) 15 | assert.True(t, hasDiscontinuity([]*Packet{{Header: PacketHeader{ContinuityCounter: 15}}}, &Packet{Header: PacketHeader{ContinuityCounter: 1, HasPayload: true}})) 16 | assert.True(t, hasDiscontinuity([]*Packet{{Header: PacketHeader{ContinuityCounter: 15}}}, &Packet{Header: PacketHeader{ContinuityCounter: 0}})) 17 | } 18 | 19 | func TestIsSameAsPrevious(t *testing.T) { 20 | assert.False(t, isSameAsPrevious([]*Packet{{Header: PacketHeader{ContinuityCounter: 1}}}, &Packet{Header: PacketHeader{ContinuityCounter: 1}})) 21 | assert.False(t, isSameAsPrevious([]*Packet{{Header: PacketHeader{ContinuityCounter: 1}}}, &Packet{Header: PacketHeader{ContinuityCounter: 2, HasPayload: true}})) 22 | assert.True(t, isSameAsPrevious([]*Packet{{Header: PacketHeader{ContinuityCounter: 1}}}, &Packet{Header: PacketHeader{ContinuityCounter: 1, HasPayload: true}})) 23 | } 24 | 25 | func TestPacketPool(t *testing.T) { 26 | b := newPacketPool(nil) 27 | ps := b.addUnlocked(&Packet{Header: PacketHeader{ContinuityCounter: 0, HasPayload: true, PID: 1}}) 28 | assert.Len(t, ps, 0) 29 | ps = b.addUnlocked(&Packet{Header: PacketHeader{ContinuityCounter: 1, HasPayload: true, PayloadUnitStartIndicator: true, PID: 1}}) 30 | assert.Len(t, ps, 1) 31 | ps = b.addUnlocked(&Packet{Header: PacketHeader{ContinuityCounter: 1, HasPayload: true, PayloadUnitStartIndicator: true, PID: 2}}) 32 | assert.Len(t, ps, 0) 33 | ps = b.addUnlocked(&Packet{Header: PacketHeader{ContinuityCounter: 2, HasPayload: true, PID: 1}}) 34 | assert.Len(t, ps, 0) 35 | ps = b.addUnlocked(&Packet{Header: PacketHeader{ContinuityCounter: 3, HasPayload: true, PayloadUnitStartIndicator: true, PID: 1}}) 36 | assert.Len(t, ps, 2) 37 | ps = b.addUnlocked(&Packet{Header: PacketHeader{ContinuityCounter: 5, HasPayload: true, PID: 1}}) 38 | assert.Len(t, ps, 0) 39 | ps = b.addUnlocked(&Packet{Header: PacketHeader{ContinuityCounter: 6, HasPayload: true, PayloadUnitStartIndicator: true, PID: 1}}) 40 | assert.Len(t, ps, 1) 41 | ps = b.addUnlocked(&Packet{Header: PacketHeader{ContinuityCounter: 7, HasPayload: true, PID: 1}}) 42 | assert.Len(t, ps, 0) 43 | ps = b.dumpUnlocked() 44 | assert.Len(t, ps, 2) 45 | assert.Equal(t, uint16(1), ps[0].Header.PID) 46 | ps = b.dumpUnlocked() 47 | assert.Len(t, ps, 1) 48 | assert.Equal(t, uint16(2), ps[0].Header.PID) 49 | ps = b.dumpUnlocked() 50 | assert.Len(t, ps, 0) 51 | } 52 | 53 | func TestPacketPoolWithRarePackets(t *testing.T) { 54 | payloadDVBTeletext := hexToBytes(`000001bd00b2848024293c972af5ffffffffffffffffffffffffffffffff 55 | ffffffffffffffffffffffffffffff10032cf5e4a8a80b0ba80ba80b2692 56 | 040404040404040404040404040404040404040404040404040404040404 57 | 0404032cd5e4a8a85757a8a8a80b26a80404040404040404040404040404 58 | 040404040404040404040404040404040404ff2cffffffffffffffffffff 59 | ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 60 | ffffffff`) 61 | b := newPacketPool(nil) 62 | ps := b.addUnlocked(&Packet{Header: PacketHeader{ContinuityCounter: 0, HasPayload: true, PayloadUnitStartIndicator: true, PID: 1004}, Payload: payloadDVBTeletext}) 63 | assert.Len(t, ps, 1) 64 | ps = b.addUnlocked(&Packet{Header: PacketHeader{ContinuityCounter: 1, HasPayload: true, PayloadUnitStartIndicator: true, PID: 1004}, Payload: payloadDVBTeletext}) 65 | assert.Len(t, ps, 1) 66 | ps = b.addUnlocked(&Packet{Header: PacketHeader{ContinuityCounter: 2, HasPayload: true, PayloadUnitStartIndicator: true, PID: 1004}, Payload: payloadDVBTeletext}) 67 | assert.Len(t, ps, 1) 68 | ps = b.addUnlocked(&Packet{Header: PacketHeader{ContinuityCounter: 3, HasPayload: true, PayloadUnitStartIndicator: true, PID: 1004}, Payload: payloadDVBTeletext}) 69 | assert.Len(t, ps, 1) 70 | ps = b.addUnlocked(&Packet{Header: PacketHeader{ContinuityCounter: 3, HasPayload: true, PayloadUnitStartIndicator: true, PID: 1004}, Payload: payloadDVBTeletext}) 71 | assert.Len(t, ps, 1) 72 | ps = b.addUnlocked(&Packet{Header: PacketHeader{ContinuityCounter: 4, HasPayload: true, PayloadUnitStartIndicator: true, PID: 1004}, Payload: payloadDVBTeletext}) 73 | assert.Len(t, ps, 1) 74 | ps = b.addUnlocked(&Packet{Header: PacketHeader{ContinuityCounter: 6, HasPayload: true, PayloadUnitStartIndicator: true, PID: 1004}, Payload: payloadDVBTeletext}) 75 | assert.Len(t, ps, 1) 76 | ps = b.addUnlocked(&Packet{Header: PacketHeader{ContinuityCounter: 7, HasPayload: true, PayloadUnitStartIndicator: true, PID: 1004}, Payload: payloadDVBTeletext}) 77 | assert.Len(t, ps, 1) 78 | ps = b.addUnlocked(&Packet{Header: PacketHeader{ContinuityCounter: 7, HasPayload: true, PayloadUnitStartIndicator: true, PID: 1004}, Payload: payloadDVBTeletext}) 79 | assert.Len(t, ps, 1) 80 | ps = b.addUnlocked(&Packet{Header: PacketHeader{ContinuityCounter: 9, HasPayload: true, PayloadUnitStartIndicator: true, PID: 1004}, Payload: payloadDVBTeletext}) 81 | assert.Len(t, ps, 1) 82 | ps = b.addUnlocked(&Packet{Header: PacketHeader{ContinuityCounter: 10, HasPayload: true, PayloadUnitStartIndicator: true, PID: 1004}, Payload: payloadDVBTeletext}) 83 | assert.Len(t, ps, 1) 84 | ps = b.dumpUnlocked() 85 | assert.Len(t, ps, 0) 86 | } 87 | -------------------------------------------------------------------------------- /data.go: -------------------------------------------------------------------------------- 1 | package astits 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "github.com/asticode/go-astikit" 7 | ) 8 | 9 | // PIDs 10 | const ( 11 | PIDPAT uint16 = 0x0 // Program Association Table (PAT) contains a directory listing of all Program Map Tables. 12 | PIDCAT uint16 = 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. 13 | PIDTSDT uint16 = 0x2 // Transport Stream Description Table (TSDT) contains descriptors related to the overall transport stream 14 | PIDNull uint16 = 0x1fff // Null Packet (used for fixed bandwidth padding) 15 | ) 16 | 17 | // DemuxerData represents a data parsed by Demuxer 18 | type DemuxerData struct { 19 | EIT *EITData 20 | FirstPacket *Packet 21 | NIT *NITData 22 | PAT *PATData 23 | PES *PESData 24 | PID uint16 25 | PMT *PMTData 26 | SDT *SDTData 27 | TOT *TOTData 28 | } 29 | 30 | // MuxerData represents a data to be written by Muxer 31 | type MuxerData struct { 32 | PID uint16 33 | AdaptationField *PacketAdaptationField 34 | PES *PESData 35 | } 36 | 37 | // parseData parses a payload spanning over multiple packets and returns a set of data 38 | func parseData(ps []*Packet, prs PacketsParser, pm *programMap) (ds []*DemuxerData, err error) { 39 | // Use custom parser first 40 | if prs != nil { 41 | var skip bool 42 | if ds, skip, err = prs(ps); err != nil { 43 | err = fmt.Errorf("astits: custom packets parsing failed: %w", err) 44 | return 45 | } else if skip { 46 | return 47 | } 48 | } 49 | 50 | // Get payload length 51 | var l int 52 | for _, p := range ps { 53 | l += len(p.Payload) 54 | } 55 | 56 | // Get the slice for payload from pool 57 | payload := bytesPool.get(l) 58 | defer bytesPool.put(payload) 59 | 60 | // Append payload 61 | var c int 62 | for _, p := range ps { 63 | c += copy(payload.s[c:], p.Payload) 64 | } 65 | 66 | // Create reader 67 | i := astikit.NewBytesIterator(payload.s) 68 | 69 | // Parse PID 70 | pid := ps[0].Header.PID 71 | 72 | // Copy first packet headers, so we can safely deallocate original payload 73 | fp := &Packet{ 74 | Header: ps[0].Header, 75 | AdaptationField: ps[0].AdaptationField, 76 | } 77 | 78 | // Parse payload 79 | if pid == PIDCAT { 80 | // Information in a CAT payload is private and dependent on the CA system. Use the PacketsParser 81 | // to parse this type of payload 82 | } else if isPSIPayload(pid, pm) { 83 | // Parse PSI data 84 | var psiData *PSIData 85 | if psiData, err = parsePSIData(i); err != nil { 86 | err = fmt.Errorf("astits: parsing PSI data failed: %w", err) 87 | return 88 | } 89 | 90 | // Append data 91 | ds = psiData.toData(fp, pid) 92 | } else if isPESPayload(payload.s) { 93 | // Parse PES data 94 | var pesData *PESData 95 | if pesData, err = parsePESData(i); err != nil { 96 | err = fmt.Errorf("astits: parsing PES data failed: %w", err) 97 | return 98 | } 99 | 100 | // Append data 101 | ds = []*DemuxerData{ 102 | { 103 | FirstPacket: fp, 104 | PES: pesData, 105 | PID: pid, 106 | }, 107 | } 108 | } 109 | return 110 | } 111 | 112 | // isPSIPayload checks whether the payload is a PSI one 113 | func isPSIPayload(pid uint16, pm *programMap) bool { 114 | return pid == PIDPAT || // PAT 115 | pm.existsUnlocked(pid) || // PMT 116 | ((pid >= 0x10 && pid <= 0x14) || (pid >= 0x1e && pid <= 0x1f)) //DVB 117 | } 118 | 119 | // isPESPayload checks whether the payload is a PES one 120 | func isPESPayload(i []byte) bool { 121 | // Packet is not big enough 122 | if len(i) < 3 { 123 | return false 124 | } 125 | 126 | // Check prefix 127 | return uint32(i[0])<<16|uint32(i[1])<<8|uint32(i[2]) == 1 128 | } 129 | 130 | // isPSIComplete checks whether we have sufficient amount of packets to parse PSI 131 | func isPSIComplete(ps []*Packet) bool { 132 | // Get payload length 133 | var l int 134 | for _, p := range ps { 135 | l += len(p.Payload) 136 | } 137 | 138 | // Get the slice for payload from pool 139 | payload := bytesPool.get(l) 140 | defer bytesPool.put(payload) 141 | 142 | // Append payload 143 | var o int 144 | for _, p := range ps { 145 | o += copy(payload.s[o:], p.Payload) 146 | } 147 | 148 | // Create reader 149 | i := astikit.NewBytesIterator(payload.s) 150 | 151 | // Get next byte 152 | b, err := i.NextByte() 153 | if err != nil { 154 | return false 155 | } 156 | 157 | // Pointer filler bytes 158 | i.Skip(int(b)) 159 | 160 | for i.HasBytesLeft() { 161 | 162 | // Get PSI table ID 163 | b, err = i.NextByte() 164 | if err != nil { 165 | return false 166 | } 167 | 168 | // Check whether we need to stop the parsing 169 | if shouldStopPSIParsing(PSITableID(b)) { 170 | break 171 | } 172 | 173 | // Get PSI section length 174 | var bs []byte 175 | bs, err = i.NextBytesNoCopy(2) 176 | if err != nil { 177 | return false 178 | } 179 | 180 | i.Skip(int(binary.BigEndian.Uint16(bs) & 0x0fff)) 181 | } 182 | 183 | return i.Len() >= i.Offset() 184 | } 185 | 186 | // isPESComplete checks whether payload fully contains PES packet 187 | func isPESComplete(ps []*Packet) bool { 188 | // Get payload length 189 | var l int 190 | for _, p := range ps { 191 | l += len(p.Payload) 192 | } 193 | 194 | // Get the slice for payload from pool 195 | payload := bytesPool.get(l) 196 | defer bytesPool.put(payload) 197 | 198 | // Append payload 199 | var o int 200 | for _, p := range ps { 201 | o += copy(payload.s[o:], p.Payload) 202 | } 203 | 204 | // Create reader 205 | i := astikit.NewBytesIterator(payload.s) 206 | 207 | // Skip first 3 bytes that are there to identify the PES payload 208 | i.Seek(3) 209 | 210 | // Parse header 211 | h, _, dataEnd, err := parsePESHeader(i) 212 | if err != nil { 213 | err = fmt.Errorf("astits: parsing PES header failed: %w", err) 214 | return false 215 | } 216 | 217 | if h.PacketLength == 0 { 218 | // There's no other way to know whether the packet is complete 219 | return false 220 | } 221 | 222 | return i.Len() >= dataEnd 223 | } 224 | -------------------------------------------------------------------------------- /cmd/astits-es-split/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "context" 6 | "errors" 7 | "flag" 8 | "fmt" 9 | "github.com/pkg/profile" 10 | "io" 11 | "log" 12 | "os" 13 | "path" 14 | "time" 15 | 16 | "github.com/asticode/go-astikit" 17 | "github.com/asticode/go-astits" 18 | ) 19 | 20 | const ( 21 | ioBufSize = 10 * 1024 * 1024 22 | ) 23 | 24 | type muxerOut struct { 25 | name string 26 | closer io.Closer 27 | *bufio.Writer 28 | } 29 | 30 | func newMuxerOut(name string, discard bool) (*muxerOut, error) { 31 | var w io.Writer 32 | var c io.Closer 33 | if !discard { 34 | f, err := os.Create(name) 35 | if err != nil { 36 | return nil, err 37 | } 38 | name = f.Name() 39 | c = f 40 | w = f 41 | } else { 42 | name += " --discard--" 43 | w = io.Discard 44 | } 45 | return &muxerOut{name, c, bufio.NewWriterSize(w, ioBufSize)}, nil 46 | } 47 | 48 | func (m *muxerOut) Close() error { 49 | if err := m.Flush(); err != nil { 50 | log.Printf("Error flushing %s: %v", m.name, err) 51 | } 52 | if m.closer != nil { 53 | if err := m.closer.Close(); err != nil { 54 | return fmt.Errorf("error closing %s: %w", m.name, err) 55 | } 56 | } 57 | return nil 58 | } 59 | 60 | func main() { 61 | flag.Usage = func() { 62 | fmt.Fprintf(flag.CommandLine.Output(), "Split TS file into multiple files each holding one elementary stream") 63 | fmt.Fprintf(flag.CommandLine.Output(), "%s INPUT_FILE [FLAGS]:\n", os.Args[0]) 64 | flag.PrintDefaults() 65 | } 66 | 67 | memoryProfiling := flag.Bool("mp", false, "if yes, memory profiling is enabled") 68 | cpuProfiling := flag.Bool("cp", false, "if yes, cpu profiling is enabled") 69 | discard := flag.Bool("discard", false, "if yes, output will be passed to discard (profiling/debug only)") 70 | outDir := flag.String("o", "out", "Output dir, 'out' by default") 71 | inputFile := astikit.FlagCmd() 72 | flag.Parse() 73 | 74 | if *cpuProfiling { 75 | defer profile.Start(profile.CPUProfile, profile.ProfilePath(".")).Stop() 76 | } else if *memoryProfiling { 77 | defer profile.Start(profile.MemProfile, profile.ProfilePath(".")).Stop() 78 | } 79 | 80 | infile, err := os.Open(inputFile) 81 | if err != nil { 82 | log.Fatalf("%v", err) 83 | } 84 | defer infile.Close() 85 | 86 | if !*discard { 87 | if _, err = os.Stat(*outDir); !os.IsNotExist(err) { 88 | log.Fatalf("can't write to '%s': already exists", *outDir) 89 | } 90 | 91 | if err = os.MkdirAll(*outDir, os.ModePerm); err != nil { 92 | log.Fatalf("%v", err) 93 | } 94 | } 95 | 96 | demux := astits.NewDemuxer( 97 | context.Background(), 98 | bufio.NewReaderSize(infile, ioBufSize), 99 | ) 100 | 101 | var pat *astits.PATData 102 | // key is program number 103 | pmts := map[uint16]*astits.PMTData{} 104 | gotAllPMTs := false 105 | // key is pid 106 | muxers := map[uint16]*astits.Muxer{} 107 | 108 | pmtsPrinted := false 109 | 110 | timeStarted := time.Now() 111 | bytesWritten := 0 112 | 113 | var d *astits.DemuxerData 114 | for { 115 | if d, err = demux.NextData(); err != nil { 116 | if errors.Is(err, astits.ErrNoMorePackets) { 117 | break 118 | } 119 | log.Fatalf("%v", err) 120 | } 121 | 122 | if d.PAT != nil { 123 | pat = d.PAT 124 | gotAllPMTs = false 125 | continue 126 | } 127 | 128 | if d.PMT != nil { 129 | pmts[d.PMT.ProgramNumber] = d.PMT 130 | 131 | gotAllPMTs = true 132 | for _, p := range pat.Programs { 133 | if _, ok := pmts[p.ProgramNumber]; !ok { 134 | gotAllPMTs = false 135 | break 136 | } 137 | } 138 | 139 | if !gotAllPMTs { 140 | continue 141 | } 142 | 143 | if !pmtsPrinted { 144 | log.Printf("Got all PMTs") 145 | } 146 | for _, pmt := range pmts { 147 | if !pmtsPrinted { 148 | log.Printf("\tProgram %d PCR PID %d", pmt.ProgramNumber, pmt.PCRPID) 149 | } 150 | for _, es := range pmt.ElementaryStreams { 151 | if _, ok := muxers[es.ElementaryPID]; ok { 152 | continue 153 | } 154 | 155 | esFilename := path.Join(*outDir, fmt.Sprintf("%d.ts", es.ElementaryPID)) 156 | var outWriter *muxerOut 157 | if outWriter, err = newMuxerOut(esFilename, *discard); err != nil { 158 | log.Fatalf("%v", err) 159 | } 160 | defer func() { 161 | if err = outWriter.Close(); err != nil { 162 | log.Print(err) 163 | } 164 | }() 165 | 166 | mux := astits.NewMuxer(context.Background(), outWriter) 167 | if err = mux.AddElementaryStream(*es); err != nil { 168 | log.Fatalf("%v", err) 169 | } 170 | mux.SetPCRPID(es.ElementaryPID) 171 | muxers[es.ElementaryPID] = mux 172 | 173 | if !pmtsPrinted { 174 | log.Printf("\t\tES PID %d type %s", 175 | es.ElementaryPID, es.StreamType.String(), 176 | ) 177 | } 178 | } 179 | } 180 | 181 | pmtsPrinted = true 182 | continue 183 | } 184 | 185 | if !gotAllPMTs { 186 | continue 187 | } 188 | 189 | if d.PES == nil { 190 | continue 191 | } 192 | 193 | pid := d.FirstPacket.Header.PID 194 | mux, ok := muxers[pid] 195 | if !ok { 196 | log.Printf("Got payload for unknown PID %d", pid) 197 | continue 198 | } 199 | 200 | af := d.FirstPacket.AdaptationField 201 | 202 | if af != nil && af.HasPCR { 203 | af.HasPCR = false 204 | } 205 | 206 | var pcr *astits.ClockReference 207 | switch d.PES.Header.OptionalHeader.PTSDTSIndicator { 208 | case astits.PTSDTSIndicatorOnlyPTS: 209 | pcr = d.PES.Header.OptionalHeader.PTS 210 | case astits.PTSDTSIndicatorBothPresent: 211 | pcr = d.PES.Header.OptionalHeader.DTS 212 | } 213 | 214 | if pcr != nil { 215 | if af == nil { 216 | af = &astits.PacketAdaptationField{} 217 | } 218 | af.HasPCR = true 219 | af.PCR = pcr 220 | } 221 | 222 | var written int 223 | if written, err = mux.WriteData(&astits.MuxerData{ 224 | PID: pid, 225 | AdaptationField: af, 226 | PES: d.PES, 227 | }); err != nil { 228 | log.Fatalf("%v", err) 229 | } 230 | 231 | bytesWritten += written 232 | } 233 | 234 | timeDiff := time.Since(timeStarted) 235 | log.Printf("%d bytes written at rate %.02f mb/s", bytesWritten, (float64(bytesWritten)/1024.0/1024.0)/timeDiff.Seconds()) 236 | 237 | log.Printf("Done") 238 | } 239 | -------------------------------------------------------------------------------- /demuxer.go: -------------------------------------------------------------------------------- 1 | package astits 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "io" 8 | 9 | "github.com/asticode/go-astikit" 10 | ) 11 | 12 | // Sync byte 13 | const syncByte = '\x47' 14 | 15 | // Errors 16 | var ( 17 | ErrNoMorePackets = errors.New("astits: no more packets") 18 | ErrPacketMustStartWithASyncByte = errors.New("astits: packet must start with a sync byte") 19 | ) 20 | 21 | // Demuxer represents a demuxer 22 | // https://en.wikipedia.org/wiki/MPEG_transport_stream 23 | // http://seidl.cs.vsb.cz/download/dvb/DVB_Poster.pdf 24 | // http://www.etsi.org/deliver/etsi_en/300400_300499/300468/01.13.01_40/en_300468v011301o.pdf 25 | type Demuxer struct { 26 | ctx context.Context 27 | dataBuffer []*DemuxerData 28 | l astikit.CompleteLogger 29 | 30 | optPacketSize int 31 | optPacketsParser PacketsParser 32 | optPacketSkipper PacketSkipper 33 | 34 | packetBuffer *packetBuffer 35 | packetPool *packetPool 36 | programMap *programMap 37 | r io.Reader 38 | } 39 | 40 | // PacketsParser represents an object capable of parsing a set of packets containing a unique payload spanning over those packets 41 | // Use the skip returned argument to indicate whether the default process should still be executed on the set of packets 42 | type PacketsParser func(ps []*Packet) (ds []*DemuxerData, skip bool, err error) 43 | 44 | // PacketSkipper represents an object capable of skipping a packet before parsing its payload. Its header and adaptation field is parsed and provided to the object. 45 | // Use this option if you need to filter out unwanted packets from your pipeline. NextPacket() will return the next unskipped packet if any. 46 | type PacketSkipper func(p *Packet) (skip bool) 47 | 48 | // NewDemuxer creates a new transport stream based on a reader 49 | func NewDemuxer(ctx context.Context, r io.Reader, opts ...func(*Demuxer)) (d *Demuxer) { 50 | // Init 51 | d = &Demuxer{ 52 | ctx: ctx, 53 | l: astikit.AdaptStdLogger(nil), 54 | programMap: newProgramMap(), 55 | r: r, 56 | } 57 | d.packetPool = newPacketPool(d.programMap) 58 | 59 | // Apply options 60 | for _, opt := range opts { 61 | opt(d) 62 | } 63 | 64 | return 65 | } 66 | 67 | // DemuxerOptLogger returns the option to set the logger 68 | func DemuxerOptLogger(l astikit.StdLogger) func(*Demuxer) { 69 | return func(d *Demuxer) { 70 | d.l = astikit.AdaptStdLogger(l) 71 | } 72 | } 73 | 74 | // DemuxerOptPacketSize returns the option to set the packet size 75 | func DemuxerOptPacketSize(packetSize int) func(*Demuxer) { 76 | return func(d *Demuxer) { 77 | d.optPacketSize = packetSize 78 | } 79 | } 80 | 81 | // DemuxerOptPacketsParser returns the option to set the packets parser 82 | func DemuxerOptPacketsParser(p PacketsParser) func(*Demuxer) { 83 | return func(d *Demuxer) { 84 | d.optPacketsParser = p 85 | } 86 | } 87 | 88 | // DemuxerOptPacketSkipper returns the option to set the packet skipper 89 | func DemuxerOptPacketSkipper(s PacketSkipper) func(*Demuxer) { 90 | return func(d *Demuxer) { 91 | d.optPacketSkipper = s 92 | } 93 | } 94 | 95 | // NextPacket retrieves the next packet 96 | func (dmx *Demuxer) NextPacket() (p *Packet, err error) { 97 | // Check ctx error 98 | // TODO Handle ctx error another way since if the read blocks, everything blocks 99 | // Maybe execute everything in a goroutine and listen the ctx channel in the same for loop 100 | if err = dmx.ctx.Err(); err != nil { 101 | return 102 | } 103 | 104 | // Create packet buffer if not exists 105 | if dmx.packetBuffer == nil { 106 | if dmx.packetBuffer, err = newPacketBuffer(dmx.r, dmx.optPacketSize, dmx.optPacketSkipper); err != nil { 107 | err = fmt.Errorf("astits: creating packet buffer failed: %w", err) 108 | return 109 | } 110 | } 111 | 112 | // Fetch next packet from buffer 113 | if p, err = dmx.packetBuffer.next(); err != nil { 114 | if err != ErrNoMorePackets { 115 | err = fmt.Errorf("astits: fetching next packet from buffer failed: %w", err) 116 | } 117 | return 118 | } 119 | return 120 | } 121 | 122 | // NextData retrieves the next data 123 | func (dmx *Demuxer) NextData() (d *DemuxerData, err error) { 124 | // Check data buffer 125 | if len(dmx.dataBuffer) > 0 { 126 | d = dmx.dataBuffer[0] 127 | dmx.dataBuffer = dmx.dataBuffer[1:] 128 | return 129 | } 130 | 131 | // Loop through packets 132 | var p *Packet 133 | var ps []*Packet 134 | var ds []*DemuxerData 135 | for { 136 | // Get next packet 137 | if p, err = dmx.NextPacket(); err != nil { 138 | // If the end of the stream has been reached, we dump the packet pool 139 | if err == ErrNoMorePackets { 140 | for { 141 | // Dump packet pool 142 | if ps = dmx.packetPool.dumpUnlocked(); len(ps) == 0 { 143 | break 144 | } 145 | 146 | // Parse data 147 | var errParseData error 148 | if ds, errParseData = parseData(ps, dmx.optPacketsParser, dmx.programMap); errParseData != nil { 149 | // Log error as there may be some incomplete data here 150 | // We still want to try to parse all packets, in case final data is complete 151 | dmx.l.Error(fmt.Errorf("astits: parsing data failed: %w", errParseData)) 152 | continue 153 | } 154 | 155 | // Update data 156 | if d = dmx.updateData(ds); d != nil { 157 | err = nil 158 | return 159 | } 160 | } 161 | return 162 | } 163 | err = fmt.Errorf("astits: fetching next packet failed: %w", err) 164 | return 165 | } 166 | 167 | // Add packet to the pool 168 | if ps = dmx.packetPool.addUnlocked(p); len(ps) == 0 { 169 | continue 170 | } 171 | 172 | // Parse data 173 | if ds, err = parseData(ps, dmx.optPacketsParser, dmx.programMap); err != nil { 174 | err = fmt.Errorf("astits: building new data failed: %w", err) 175 | return 176 | } 177 | 178 | // Update data 179 | if d = dmx.updateData(ds); d != nil { 180 | return 181 | } 182 | } 183 | } 184 | 185 | func (dmx *Demuxer) updateData(ds []*DemuxerData) (d *DemuxerData) { 186 | // Check whether there is data to be processed 187 | if len(ds) > 0 { 188 | // Process data 189 | d = ds[0] 190 | dmx.dataBuffer = append(dmx.dataBuffer, ds[1:]...) 191 | 192 | // Update program map 193 | for _, v := range ds { 194 | if v.PAT != nil { 195 | for _, pgm := range v.PAT.Programs { 196 | // Program number 0 is reserved to NIT 197 | if pgm.ProgramNumber > 0 { 198 | dmx.programMap.setUnlocked(pgm.ProgramMapID, pgm.ProgramNumber) 199 | } 200 | } 201 | } 202 | } 203 | } 204 | return 205 | } 206 | 207 | // Rewind rewinds the demuxer reader 208 | func (dmx *Demuxer) Rewind() (n int64, err error) { 209 | dmx.dataBuffer = []*DemuxerData{} 210 | dmx.packetBuffer = nil 211 | dmx.packetPool = newPacketPool(dmx.programMap) 212 | if n, err = rewind(dmx.r); err != nil { 213 | err = fmt.Errorf("astits: rewinding reader failed: %w", err) 214 | return 215 | } 216 | return 217 | } 218 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![GoReportCard](http://goreportcard.com/badge/github.com/asticode/go-astits)](http://goreportcard.com/report/github.com/asticode/go-astits) 2 | [![GoDoc](https://godoc.org/github.com/asticode/go-astits?status.svg)](https://godoc.org/github.com/asticode/go-astits) 3 | [![Test](https://github.com/asticode/go-astits/actions/workflows/test.yml/badge.svg)](https://github.com/asticode/go-astits/actions/workflows/test.yml) 4 | [![Coveralls](https://coveralls.io/repos/github/asticode/go-astits/badge.svg?branch=master)](https://coveralls.io/github/asticode/go-astits) 5 | 6 | This is a Golang library to natively demux and mux MPEG Transport Streams (ts) in GO. 7 | 8 | WARNING: this library is not yet production ready. Use at your own risks! 9 | 10 | # Installation 11 | 12 | To install the library use the following: 13 | 14 | go get -u github.com/asticode/go-astits/... 15 | 16 | To install the executables use the following: 17 | 18 | go install github.com/asticode/go-astits/cmd 19 | 20 | # Before looking at the code... 21 | 22 | The transport stream is made of packets.
23 | Each packet has a header, an optional adaptation field and a payload.
24 | Several payloads can be appended and parsed as a data. 25 | 26 | ``` 27 | TRANSPORT STREAM 28 | +--------------------------------------------------------------------------------------------------+ 29 | | | 30 | 31 | PACKET PACKET 32 | +----------------------------------------------+----------------------------------------------+---- 33 | | | | 34 | 35 | +--------+---------------------------+---------+--------+---------------------------+---------+ 36 | | HEADER | OPTIONAL ADAPTATION FIELD | PAYLOAD | HEADER | OPTIONAL ADAPTATION FIELD | PAYLOAD | ... 37 | +--------+---------------------------+---------+--------+---------------------------+---------+ 38 | 39 | | | | | 40 | +---------+ +---------+ 41 | | | 42 | +----------------------------------------------+ 43 | DATA 44 | ``` 45 | 46 | # Using the library in your code 47 | 48 | WARNING: the code below doesn't handle errors for readability purposes. However you SHOULD! 49 | 50 | ## Demux 51 | 52 | ```go 53 | // Create a cancellable context in case you want to stop reading packets/data any time you want 54 | ctx, cancel := context.WithCancel(context.Background()) 55 | 56 | // Handle SIGTERM signal 57 | ch := make(chan os.Signal, 1) 58 | signal.Notify(ch, syscall.SIGTERM) 59 | go func() { 60 | <-ch 61 | cancel() 62 | }() 63 | 64 | // Open your file or initialize any kind of io.Reader 65 | // Buffering using bufio.Reader is recommended for performance 66 | f, _ := os.Open("/path/to/file.ts") 67 | defer f.Close() 68 | 69 | // Create the demuxer 70 | dmx := astits.NewDemuxer(ctx, f) 71 | for { 72 | // Get the next data 73 | d, _ := dmx.NextData() 74 | 75 | // Data is a PMT data 76 | if d.PMT != nil { 77 | // Loop through elementary streams 78 | for _, es := range d.PMT.ElementaryStreams { 79 | fmt.Printf("Stream detected: %d\n", es.ElementaryPID) 80 | } 81 | return 82 | } 83 | } 84 | ``` 85 | 86 | ## Mux 87 | 88 | ```go 89 | // Create a cancellable context in case you want to stop writing packets/data any time you want 90 | ctx, cancel := context.WithCancel(context.Background()) 91 | 92 | // Handle SIGTERM signal 93 | ch := make(chan os.Signal, 1) 94 | signal.Notify(ch, syscall.SIGTERM) 95 | go func() { 96 | <-ch 97 | cancel() 98 | }() 99 | 100 | // Create your file or initialize any kind of io.Writer 101 | // Buffering using bufio.Writer is recommended for performance 102 | f, _ := os.Create("/path/to/file.ts") 103 | defer f.Close() 104 | 105 | // Create the muxer 106 | mx := astits.NewMuxer(ctx, f) 107 | 108 | // Add an elementary stream 109 | mx.AddElementaryStream(astits.PMTElementaryStream{ 110 | ElementaryPID: 1, 111 | StreamType: astits.StreamTypeMetadata, 112 | }) 113 | 114 | // Write tables 115 | // Using that function is not mandatory, WriteData will retransmit tables from time to time 116 | mx.WriteTables() 117 | 118 | // Write data 119 | mx.WriteData(&astits.MuxerData{ 120 | PES: &astits.PESData{ 121 | Data: []byte("test"), 122 | }, 123 | PID: 1, 124 | }) 125 | ``` 126 | 127 | ## Options 128 | 129 | In order to pass options to the demuxer or the muxer, look for the methods prefixed with `DemuxerOpt` or `MuxerOpt` and add them upon calling `NewDemuxer` or `NewMuxer` : 130 | 131 | ```go 132 | // This is your custom packets parser 133 | p := func(ps []*astits.Packet) (ds []*astits.Data, skip bool, err error) { 134 | // This is your logic 135 | skip = true 136 | return 137 | } 138 | 139 | // Now you can create a demuxer with the proper options 140 | dmx := NewDemuxer(ctx, f, DemuxerOptPacketSize(192), DemuxerOptPacketsParser(p)) 141 | ``` 142 | 143 | # CLI 144 | 145 | This library provides 2 CLIs that will automatically get installed in `GOPATH/bin` on `go get` execution. 146 | 147 | ## astits-probe 148 | 149 | ### List streams 150 | 151 | $ astits-probe -i -f 152 | 153 | ### List packets 154 | 155 | $ astits-probe packets -i 156 | 157 | ### List data 158 | 159 | $ astits-probe data -i -d 160 | 161 | ## astits-es-split 162 | 163 | ### Split streams into separate .ts files 164 | 165 | $ astits-es-split -o 166 | 167 | # Features and roadmap 168 | 169 | - [x] Add demuxer 170 | - [x] Add muxer 171 | - [x] Demux PES packets 172 | - [x] Mux PES packets 173 | - [x] Demux PAT packets 174 | - [x] Mux PAT packets 175 | - [x] Demux PMT packets 176 | - [x] Mux PMT packets 177 | - [x] Demux EIT packets 178 | - [ ] Mux EIT packets 179 | - [x] Demux NIT packets 180 | - [ ] Mux NIT packets 181 | - [x] Demux SDT packets 182 | - [ ] Mux SDT packets 183 | - [x] Demux TOT packets 184 | - [ ] Mux TOT packets 185 | - [ ] Demux BAT packets 186 | - [ ] Mux BAT packets 187 | - [ ] Demux DIT packets 188 | - [ ] Mux DIT packets 189 | - [ ] Demux RST packets 190 | - [ ] Mux RST packets 191 | - [ ] Demux SIT packets 192 | - [ ] Mux SIT packets 193 | - [ ] Mux ST packets 194 | - [ ] Demux TDT packets 195 | - [ ] Mux TDT packets 196 | - [ ] Demux TSDT packets 197 | - [ ] Mux TSDT packets 198 | -------------------------------------------------------------------------------- /demuxer_test.go: -------------------------------------------------------------------------------- 1 | package astits 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/hex" 7 | "fmt" 8 | "io" 9 | "strings" 10 | "testing" 11 | "unicode" 12 | 13 | "github.com/asticode/go-astikit" 14 | "github.com/stretchr/testify/assert" 15 | ) 16 | 17 | func hexToBytes(in string) []byte { 18 | cin := strings.Map(func(r rune) rune { 19 | if unicode.IsSpace(r) { 20 | return -1 21 | } 22 | return r 23 | }, in) 24 | o, err := hex.DecodeString(cin) 25 | if err != nil { 26 | panic(err) 27 | } 28 | return o 29 | } 30 | 31 | func TestDemuxerNew(t *testing.T) { 32 | ps := 1 33 | pp := func(ps []*Packet) (ds []*DemuxerData, skip bool, err error) { return } 34 | sp := func(p *Packet) bool { return true } 35 | dmx := NewDemuxer(context.Background(), nil, DemuxerOptPacketSize(ps), DemuxerOptPacketsParser(pp), DemuxerOptPacketSkipper(sp)) 36 | assert.Equal(t, ps, dmx.optPacketSize) 37 | assert.Equal(t, fmt.Sprintf("%p", pp), fmt.Sprintf("%p", dmx.optPacketsParser)) 38 | assert.Equal(t, fmt.Sprintf("%p", sp), fmt.Sprintf("%p", dmx.optPacketSkipper)) 39 | } 40 | 41 | func TestDemuxerNextPacket(t *testing.T) { 42 | // Ctx error 43 | ctx, cancel := context.WithCancel(context.Background()) 44 | dmx := NewDemuxer(ctx, bytes.NewReader([]byte{})) 45 | cancel() 46 | _, err := dmx.NextPacket() 47 | assert.Error(t, err) 48 | 49 | // Valid 50 | buf := &bytes.Buffer{} 51 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: buf}) 52 | b1, p1 := packet(packetHeader, *packetAdaptationField, []byte("1"), true) 53 | w.Write(b1) 54 | b2, p2 := packet(packetHeader, *packetAdaptationField, []byte("2"), true) 55 | w.Write(b2) 56 | dmx = NewDemuxer(context.Background(), bytes.NewReader(buf.Bytes())) 57 | 58 | // First packet 59 | p, err := dmx.NextPacket() 60 | assert.NoError(t, err) 61 | assert.Equal(t, p1, p) 62 | assert.Equal(t, 192, dmx.packetBuffer.packetSize) 63 | 64 | // Second packet 65 | p, err = dmx.NextPacket() 66 | assert.NoError(t, err) 67 | assert.Equal(t, p2, p) 68 | 69 | // EOF 70 | _, err = dmx.NextPacket() 71 | assert.EqualError(t, err, ErrNoMorePackets.Error()) 72 | } 73 | 74 | func TestDemuxerNextData(t *testing.T) { 75 | // Init 76 | buf := &bytes.Buffer{} 77 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: buf}) 78 | b := psiBytes() 79 | b1, _ := packet(PacketHeader{ContinuityCounter: uint8(0), PayloadUnitStartIndicator: true, PID: PIDPAT}, PacketAdaptationField{}, b[:147], true) 80 | w.Write(b1) 81 | b2, _ := packet(PacketHeader{ContinuityCounter: uint8(1), PID: PIDPAT}, PacketAdaptationField{}, b[147:], true) 82 | w.Write(b2) 83 | dmx := NewDemuxer(context.Background(), bytes.NewReader(buf.Bytes())) 84 | p, err := dmx.NextPacket() 85 | assert.NoError(t, err) 86 | _, err = dmx.Rewind() 87 | assert.NoError(t, err) 88 | 89 | // Next data 90 | var ds []*DemuxerData 91 | for _, s := range psi.Sections { 92 | if !s.Header.TableID.isUnknown() { 93 | d, err := dmx.NextData() 94 | assert.NoError(t, err) 95 | ds = append(ds, d) 96 | } 97 | } 98 | assert.Equal(t, psi.toData( 99 | &Packet{Header: p.Header, AdaptationField: p.AdaptationField}, 100 | PIDPAT, 101 | ), ds) 102 | assert.Equal(t, map[uint32]uint16{0x3: 0x2, 0x5: 0x4}, dmx.programMap.p) 103 | 104 | // No more packets 105 | _, err = dmx.NextData() 106 | assert.EqualError(t, err, ErrNoMorePackets.Error()) 107 | } 108 | 109 | func TestDemuxerNextDataUnknownDataPackets(t *testing.T) { 110 | buf := &bytes.Buffer{} 111 | bufWriter := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: buf}) 112 | 113 | // Packet that isn't a data packet (PSI or PES) 114 | b1, _ := packet(PacketHeader{ 115 | ContinuityCounter: uint8(0), 116 | PID: 256, 117 | PayloadUnitStartIndicator: true, 118 | HasPayload: true, 119 | }, PacketAdaptationField{}, []byte{0x01, 0x02, 0x03, 0x04}, true) 120 | bufWriter.Write(b1) 121 | 122 | // The demuxer must return "no more packets" 123 | dmx := NewDemuxer(context.Background(), bytes.NewReader(buf.Bytes()), 124 | DemuxerOptPacketSize(188)) 125 | d, err := dmx.NextData() 126 | assert.Equal(t, (*DemuxerData)(nil), d) 127 | assert.EqualError(t, err, ErrNoMorePackets.Error()) 128 | } 129 | 130 | func TestDemuxerNextDataPATPMT(t *testing.T) { 131 | pat := hexToBytes(`474000100000b00d0001c100000001f0002ab104b2ffffffffffffffff 132 | ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 133 | ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 134 | ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 135 | ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 136 | ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 137 | ffffffffffffffffff`) 138 | pmt := hexToBytes(`475000100002b0170001c10000e100f0001be100f0000fe101f0002f44 139 | b99bffffffffffffffffffffffffffffffffffffffffffffffffffffffff 140 | ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 141 | ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 142 | ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 143 | ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 144 | ffffffffffffffffff`) 145 | r := bytes.NewReader(append(pat, pmt...)) 146 | dmx := NewDemuxer(context.Background(), r, DemuxerOptPacketSize(188)) 147 | assert.Equal(t, 188*2, r.Len()) 148 | 149 | d, err := dmx.NextData() 150 | assert.NoError(t, err) 151 | assert.Equal(t, uint16(0), d.FirstPacket.Header.PID) 152 | assert.NotNil(t, d.PAT) 153 | assert.Equal(t, 188, r.Len()) 154 | 155 | d, err = dmx.NextData() 156 | assert.NoError(t, err) 157 | assert.Equal(t, uint16(0x1000), d.FirstPacket.Header.PID) 158 | assert.NotNil(t, d.PMT) 159 | } 160 | 161 | func TestDemuxerRewind(t *testing.T) { 162 | r := bytes.NewReader([]byte("content")) 163 | dmx := NewDemuxer(context.Background(), r) 164 | dmx.packetPool.addUnlocked(&Packet{Header: PacketHeader{PID: 1}}) 165 | dmx.dataBuffer = append(dmx.dataBuffer, &DemuxerData{}) 166 | b := make([]byte, 2) 167 | _, err := r.Read(b) 168 | assert.NoError(t, err) 169 | n, err := dmx.Rewind() 170 | assert.NoError(t, err) 171 | assert.Equal(t, int64(0), n) 172 | assert.Equal(t, 7, r.Len()) 173 | assert.Equal(t, 0, len(dmx.dataBuffer)) 174 | assert.Equal(t, 0, len(dmx.packetPool.b)) 175 | assert.Nil(t, dmx.packetBuffer) 176 | } 177 | 178 | func BenchmarkDemuxer_NextData(b *testing.B) { 179 | b.ReportAllocs() 180 | 181 | buf := &bytes.Buffer{} 182 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: buf}) 183 | bs := psiBytes() 184 | b1, _ := packet(PacketHeader{ContinuityCounter: uint8(0), PayloadUnitStartIndicator: true, PID: PIDPAT}, PacketAdaptationField{}, bs[:147], true) 185 | w.Write(b1) 186 | b2, _ := packet(PacketHeader{ContinuityCounter: uint8(1), PID: PIDPAT}, PacketAdaptationField{}, bs[147:], true) 187 | w.Write(b2) 188 | 189 | r := bytes.NewReader(buf.Bytes()) 190 | dmx := NewDemuxer(context.Background(), r) 191 | 192 | for i := 0; i < b.N; i++ { 193 | r.Seek(0, io.SeekStart) 194 | for _, s := range psi.Sections { 195 | if !s.Header.TableID.isUnknown() { 196 | dmx.NextData() 197 | } 198 | } 199 | } 200 | } 201 | 202 | func FuzzDemuxer(f *testing.F) { 203 | f.Fuzz(func(t *testing.T, b []byte) { 204 | r := bytes.NewReader(b) 205 | dmx := NewDemuxer(context.Background(), r, DemuxerOptPacketSize(188)) 206 | for { 207 | _, err := dmx.NextData() 208 | if err == ErrNoMorePackets { 209 | break 210 | } 211 | } 212 | }) 213 | } 214 | -------------------------------------------------------------------------------- /data_pmt.go: -------------------------------------------------------------------------------- 1 | package astits 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/asticode/go-astikit" 7 | ) 8 | 9 | type StreamType uint8 10 | 11 | // Stream types 12 | const ( 13 | StreamTypeMPEG1Video StreamType = 0x01 14 | StreamTypeMPEG2Video StreamType = 0x02 15 | StreamTypeMPEG1Audio StreamType = 0x03 // ISO/IEC 11172-3 16 | StreamTypeMPEG2HalvedSampleRateAudio StreamType = 0x04 // ISO/IEC 13818-3 17 | StreamTypeMPEG2Audio StreamType = 0x04 18 | StreamTypePrivateSection StreamType = 0x05 19 | StreamTypePrivateData StreamType = 0x06 20 | StreamTypeMPEG2PacketizedData StreamType = 0x06 // Rec. ITU-T H.222 | ISO/IEC 13818-1 i.e., DVB subtitles/VBI and AC-3 21 | StreamTypeADTS StreamType = 0x0F // ISO/IEC 13818-7 Audio with ADTS transport syntax 22 | StreamTypeAACAudio StreamType = 0x0f 23 | StreamTypeMPEG4Video StreamType = 0x10 24 | StreamTypeAACLATMAudio StreamType = 0x11 25 | StreamTypeMetadata StreamType = 0x15 26 | StreamTypeH264Video StreamType = 0x1B // Rec. ITU-T H.264 | ISO/IEC 14496-10 27 | StreamTypeH265Video StreamType = 0x24 // Rec. ITU-T H.265 | ISO/IEC 23008-2 28 | StreamTypeHEVCVideo StreamType = 0x24 29 | StreamTypeCAVSVideo StreamType = 0x42 30 | StreamTypeVC1Video StreamType = 0xea 31 | StreamTypeDIRACVideo StreamType = 0xd1 32 | StreamTypeAC3Audio StreamType = 0x81 33 | StreamTypeDTSAudio StreamType = 0x82 34 | StreamTypeTRUEHDAudio StreamType = 0x83 35 | StreamTypeSCTE35 StreamType = 0x86 36 | StreamTypeEAC3Audio StreamType = 0x87 37 | ) 38 | 39 | // PMTData represents a PMT data 40 | // https://en.wikipedia.org/wiki/Program-specific_information 41 | type PMTData struct { 42 | ElementaryStreams []*PMTElementaryStream 43 | 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). 44 | ProgramDescriptors []*Descriptor // Program descriptors 45 | ProgramNumber uint16 46 | } 47 | 48 | // PMTElementaryStream represents a PMT elementary stream 49 | type PMTElementaryStream struct { 50 | ElementaryPID uint16 // The packet identifier that contains the stream type data. 51 | ElementaryStreamDescriptors []*Descriptor // Elementary stream descriptors 52 | StreamType StreamType // This defines the structure of the data contained within the elementary packet identifier. 53 | } 54 | 55 | // parsePMTSection parses a PMT section 56 | func parsePMTSection(i *astikit.BytesIterator, offsetSectionsEnd int, tableIDExtension uint16) (d *PMTData, err error) { 57 | // Create data 58 | d = &PMTData{ProgramNumber: tableIDExtension} 59 | 60 | // Get next bytes 61 | var bs []byte 62 | if bs, err = i.NextBytesNoCopy(2); err != nil { 63 | err = fmt.Errorf("astits: fetching next bytes failed: %w", err) 64 | return 65 | } 66 | 67 | // PCR PID 68 | d.PCRPID = uint16(bs[0]&0x1f)<<8 | uint16(bs[1]) 69 | 70 | // Program descriptors 71 | if d.ProgramDescriptors, err = parseDescriptors(i); err != nil { 72 | err = fmt.Errorf("astits: parsing descriptors failed: %w", err) 73 | return 74 | } 75 | 76 | // Loop until end of section data is reached 77 | for i.Offset() < offsetSectionsEnd { 78 | // Create stream 79 | e := &PMTElementaryStream{} 80 | 81 | // Get next byte 82 | var b byte 83 | if b, err = i.NextByte(); err != nil { 84 | err = fmt.Errorf("astits: fetching next byte failed: %w", err) 85 | return 86 | } 87 | 88 | // Stream type 89 | e.StreamType = StreamType(b) 90 | 91 | // Get next bytes 92 | if bs, err = i.NextBytesNoCopy(2); err != nil { 93 | err = fmt.Errorf("astits: fetching next bytes failed: %w", err) 94 | return 95 | } 96 | 97 | // Elementary PID 98 | e.ElementaryPID = uint16(bs[0]&0x1f)<<8 | uint16(bs[1]) 99 | 100 | // Elementary descriptors 101 | if e.ElementaryStreamDescriptors, err = parseDescriptors(i); err != nil { 102 | err = fmt.Errorf("astits: parsing descriptors failed: %w", err) 103 | return 104 | } 105 | 106 | // Add elementary stream 107 | d.ElementaryStreams = append(d.ElementaryStreams, e) 108 | } 109 | return 110 | } 111 | 112 | func calcPMTProgramInfoLength(d *PMTData) uint16 { 113 | ret := uint16(2) // program_info_length 114 | ret += calcDescriptorsLength(d.ProgramDescriptors) 115 | 116 | for _, es := range d.ElementaryStreams { 117 | ret += 5 // stream_type, elementary_pid, es_info_length 118 | ret += calcDescriptorsLength(es.ElementaryStreamDescriptors) 119 | } 120 | 121 | return ret 122 | } 123 | 124 | func calcPMTSectionLength(d *PMTData) uint16 { 125 | ret := uint16(4) 126 | ret += calcDescriptorsLength(d.ProgramDescriptors) 127 | 128 | for _, es := range d.ElementaryStreams { 129 | ret += 5 130 | ret += calcDescriptorsLength(es.ElementaryStreamDescriptors) 131 | } 132 | 133 | return ret 134 | } 135 | 136 | func writePMTSection(w *astikit.BitsWriter, d *PMTData) (int, error) { 137 | b := astikit.NewBitsWriterBatch(w) 138 | 139 | // TODO split into sections 140 | 141 | b.WriteN(uint8(0xff), 3) 142 | b.WriteN(d.PCRPID, 13) 143 | bytesWritten := 2 144 | 145 | n, err := writeDescriptorsWithLength(w, d.ProgramDescriptors) 146 | if err != nil { 147 | return 0, err 148 | } 149 | bytesWritten += n 150 | 151 | for _, es := range d.ElementaryStreams { 152 | b.Write(uint8(es.StreamType)) 153 | b.WriteN(uint8(0xff), 3) 154 | b.WriteN(es.ElementaryPID, 13) 155 | bytesWritten += 3 156 | 157 | n, err = writeDescriptorsWithLength(w, es.ElementaryStreamDescriptors) 158 | if err != nil { 159 | return 0, err 160 | } 161 | bytesWritten += n 162 | } 163 | 164 | return bytesWritten, b.Err() 165 | } 166 | 167 | func (t StreamType) IsVideo() bool { 168 | switch t { 169 | case StreamTypeMPEG1Video, 170 | StreamTypeMPEG2Video, 171 | StreamTypeMPEG4Video, 172 | StreamTypeH264Video, 173 | StreamTypeH265Video, 174 | StreamTypeCAVSVideo, 175 | StreamTypeVC1Video, 176 | StreamTypeDIRACVideo: 177 | return true 178 | } 179 | return false 180 | } 181 | 182 | func (t StreamType) IsAudio() bool { 183 | switch t { 184 | case StreamTypeMPEG1Audio, 185 | StreamTypeMPEG2Audio, 186 | StreamTypeAACAudio, 187 | StreamTypeAACLATMAudio, 188 | StreamTypeAC3Audio, 189 | StreamTypeDTSAudio, 190 | StreamTypeTRUEHDAudio, 191 | StreamTypeEAC3Audio: 192 | return true 193 | } 194 | return false 195 | } 196 | 197 | func (t StreamType) String() string { 198 | switch t { 199 | case StreamTypeMPEG1Video: 200 | return "MPEG1 Video" 201 | case StreamTypeMPEG2Video: 202 | return "MPEG2 Video" 203 | case StreamTypeMPEG1Audio: 204 | return "MPEG1 Audio" 205 | case StreamTypeMPEG2Audio: 206 | return "MPEG2 Audio" 207 | case StreamTypePrivateSection: 208 | return "Private Section" 209 | case StreamTypePrivateData: 210 | return "Private Data" 211 | case StreamTypeAACAudio: 212 | return "AAC Audio" 213 | case StreamTypeMPEG4Video: 214 | return "MPEG4 Video" 215 | case StreamTypeAACLATMAudio: 216 | return "AAC LATM Audio" 217 | case StreamTypeMetadata: 218 | return "Metadata" 219 | case StreamTypeH264Video: 220 | return "H264 Video" 221 | case StreamTypeH265Video: 222 | return "H265 Video" 223 | case StreamTypeCAVSVideo: 224 | return "CAVS Video" 225 | case StreamTypeVC1Video: 226 | return "VC1 Video" 227 | case StreamTypeDIRACVideo: 228 | return "DIRAC Video" 229 | case StreamTypeAC3Audio: 230 | return "AC3 Audio" 231 | case StreamTypeDTSAudio: 232 | return "DTS Audio" 233 | case StreamTypeTRUEHDAudio: 234 | return "TRUEHD Audio" 235 | case StreamTypeSCTE35: 236 | return "SCTE 35" 237 | case StreamTypeEAC3Audio: 238 | return "EAC3 Audio" 239 | } 240 | return "Unknown" 241 | } 242 | 243 | func (t StreamType) ToPESStreamID() uint8 { 244 | switch t { 245 | case StreamTypeMPEG1Video, StreamTypeMPEG2Video, StreamTypeMPEG4Video, StreamTypeH264Video, 246 | StreamTypeH265Video, StreamTypeCAVSVideo, StreamTypeVC1Video: 247 | return 0xe0 248 | case StreamTypeDIRACVideo: 249 | return 0xfd 250 | case StreamTypeMPEG2Audio, StreamTypeAACAudio, StreamTypeAACLATMAudio: 251 | return 0xc0 252 | case StreamTypeAC3Audio, StreamTypeEAC3Audio: // m2ts_mode??? 253 | return 0xfd 254 | case StreamTypePrivateSection, StreamTypePrivateData, StreamTypeMetadata: 255 | return 0xfc 256 | default: 257 | return 0xbd 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /packet_test.go: -------------------------------------------------------------------------------- 1 | package astits 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/asticode/go-astikit" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func packet(h PacketHeader, a PacketAdaptationField, i []byte, packet192bytes bool) ([]byte, *Packet) { 13 | buf := &bytes.Buffer{} 14 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: buf}) 15 | w.Write(uint8(syncByte)) // Sync byte 16 | if packet192bytes { 17 | w.Write([]byte("test")) // Sometimes packets are 192 bytes 18 | } 19 | w.Write(packetHeaderBytes(h, "11")) // Header 20 | w.Write(packetAdaptationFieldBytes(a)) // Adaptation field 21 | var payload = append(i, bytes.Repeat([]byte{0}, 147-len(i))...) // Payload 22 | w.Write(payload) 23 | return buf.Bytes(), &Packet{ 24 | AdaptationField: packetAdaptationField, 25 | Header: packetHeader, 26 | Payload: payload, 27 | } 28 | } 29 | 30 | func packetShort(h PacketHeader, payload []byte) ([]byte, *Packet) { 31 | buf := &bytes.Buffer{} 32 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: buf}) 33 | w.Write(uint8(syncByte)) // Sync byte 34 | w.Write(packetHeaderBytes(h, "01")) // Header 35 | p := append(payload, bytes.Repeat([]byte{0}, MpegTsPacketSize-buf.Len())...) 36 | w.Write(p) 37 | return buf.Bytes(), &Packet{ 38 | Header: h, 39 | Payload: payload, 40 | } 41 | } 42 | 43 | func TestParsePacket(t *testing.T) { 44 | // Packet not starting with a sync 45 | buf := &bytes.Buffer{} 46 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: buf}) 47 | w.Write(uint16(1)) // Invalid sync byte 48 | _, err := parsePacket(astikit.NewBytesIterator(buf.Bytes()), nil) 49 | assert.EqualError(t, err, ErrPacketMustStartWithASyncByte.Error()) 50 | 51 | // Valid 52 | b, ep := packet(packetHeader, *packetAdaptationField, []byte("payload"), true) 53 | p, err := parsePacket(astikit.NewBytesIterator(b), nil) 54 | assert.NoError(t, err) 55 | assert.Equal(t, p, ep) 56 | 57 | // Skip 58 | _, err = parsePacket(astikit.NewBytesIterator(b), func(p *Packet) bool { return true }) 59 | assert.EqualError(t, err, errSkippedPacket.Error()) 60 | } 61 | 62 | func TestPayloadOffset(t *testing.T) { 63 | assert.Equal(t, 3, payloadOffset(0, PacketHeader{}, nil)) 64 | assert.Equal(t, 7, payloadOffset(1, PacketHeader{HasAdaptationField: true}, &PacketAdaptationField{Length: 2})) 65 | } 66 | 67 | func TestWritePacket(t *testing.T) { 68 | eb, ep := packet(packetHeader, *packetAdaptationField, []byte("payload"), false) 69 | buf := &bytes.Buffer{} 70 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: buf}) 71 | n, err := writePacket(w, ep, MpegTsPacketSize) 72 | assert.NoError(t, err) 73 | assert.Equal(t, MpegTsPacketSize, n) 74 | assert.Equal(t, n, buf.Len()) 75 | assert.Equal(t, len(eb), buf.Len()) 76 | assert.Equal(t, eb, buf.Bytes()) 77 | } 78 | 79 | func TestWritePacket_HeaderOnly(t *testing.T) { 80 | shortPacketHeader := packetHeader 81 | shortPacketHeader.HasPayload = false 82 | shortPacketHeader.HasAdaptationField = false 83 | _, ep := packetShort(shortPacketHeader, nil) 84 | 85 | buf := &bytes.Buffer{} 86 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: buf}) 87 | 88 | n, err := writePacket(w, ep, MpegTsPacketSize) 89 | assert.NoError(t, err) 90 | assert.Equal(t, MpegTsPacketSize, n) 91 | assert.Equal(t, n, buf.Len()) 92 | 93 | // we can't just compare bytes returned by packetShort since they're not completely correct, 94 | // so we just cross-check writePacket with parsePacket 95 | i := astikit.NewBytesIterator(buf.Bytes()) 96 | p, err := parsePacket(i, nil) 97 | assert.NoError(t, err) 98 | assert.Equal(t, ep, p) 99 | } 100 | 101 | var packetHeader = PacketHeader{ 102 | ContinuityCounter: 10, 103 | HasAdaptationField: true, 104 | HasPayload: true, 105 | PayloadUnitStartIndicator: true, 106 | PID: 5461, 107 | TransportErrorIndicator: true, 108 | TransportPriority: true, 109 | TransportScramblingControl: ScramblingControlScrambledWithEvenKey, 110 | } 111 | 112 | func packetHeaderBytes(h PacketHeader, afControl string) []byte { 113 | buf := &bytes.Buffer{} 114 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: buf}) 115 | w.Write(h.TransportErrorIndicator) // Transport error indicator 116 | w.Write(h.PayloadUnitStartIndicator) // Payload unit start indicator 117 | w.Write("1") // Transport priority 118 | w.Write(fmt.Sprintf("%.13b", h.PID)) // PID 119 | w.Write("10") // Scrambling control 120 | w.Write(afControl) // Adaptation field control 121 | w.Write(fmt.Sprintf("%.4b", h.ContinuityCounter)) // Continuity counter 122 | return buf.Bytes() 123 | } 124 | 125 | func TestParsePacketHeader(t *testing.T) { 126 | v, err := parsePacketHeader(astikit.NewBytesIterator(packetHeaderBytes(packetHeader, "11"))) 127 | assert.Equal(t, packetHeader, v) 128 | assert.NoError(t, err) 129 | } 130 | 131 | func TestWritePacketHeader(t *testing.T) { 132 | buf := &bytes.Buffer{} 133 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: buf}) 134 | bytesWritten, err := writePacketHeader(w, packetHeader) 135 | assert.NoError(t, err) 136 | assert.Equal(t, bytesWritten, 3) 137 | assert.Equal(t, bytesWritten, buf.Len()) 138 | assert.Equal(t, packetHeaderBytes(packetHeader, "11"), buf.Bytes()) 139 | } 140 | 141 | var packetAdaptationField = &PacketAdaptationField{ 142 | AdaptationExtensionField: &PacketAdaptationExtensionField{ 143 | DTSNextAccessUnit: dtsClockReference, 144 | HasLegalTimeWindow: true, 145 | HasPiecewiseRate: true, 146 | HasSeamlessSplice: true, 147 | LegalTimeWindowIsValid: true, 148 | LegalTimeWindowOffset: 10922, 149 | Length: 11, 150 | PiecewiseRate: 2796202, 151 | SpliceType: 2, 152 | }, 153 | DiscontinuityIndicator: true, 154 | ElementaryStreamPriorityIndicator: true, 155 | HasAdaptationExtensionField: true, 156 | HasOPCR: true, 157 | HasPCR: true, 158 | HasTransportPrivateData: true, 159 | HasSplicingCountdown: true, 160 | Length: 36, 161 | OPCR: pcr, 162 | PCR: pcr, 163 | RandomAccessIndicator: true, 164 | SpliceCountdown: 2, 165 | TransportPrivateDataLength: 4, 166 | TransportPrivateData: []byte("test"), 167 | StuffingLength: 5, 168 | } 169 | 170 | func packetAdaptationFieldBytes(a PacketAdaptationField) []byte { 171 | buf := &bytes.Buffer{} 172 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: buf}) 173 | w.Write(uint8(36)) // Length 174 | w.Write(a.DiscontinuityIndicator) // Discontinuity indicator 175 | w.Write("1") // Random access indicator 176 | w.Write("1") // Elementary stream priority indicator 177 | w.Write("1") // PCR flag 178 | w.Write("1") // OPCR flag 179 | w.Write("1") // Splicing point flag 180 | w.Write("1") // Transport data flag 181 | w.Write("1") // Adaptation field extension flag 182 | w.Write(pcrBytes()) // PCR 183 | w.Write(pcrBytes()) // OPCR 184 | w.Write(uint8(2)) // Splice countdown 185 | w.Write(uint8(4)) // Transport private data length 186 | w.Write([]byte("test")) // Transport private data 187 | w.Write(uint8(11)) // Adaptation extension length 188 | w.Write("1") // LTW flag 189 | w.Write("1") // Piecewise rate flag 190 | w.Write("1") // Seamless splice flag 191 | w.Write("11111") // Reserved 192 | w.Write("1") // LTW valid flag 193 | w.Write("010101010101010") // LTW offset 194 | w.Write("11") // Piecewise rate reserved 195 | w.Write("1010101010101010101010") // Piecewise rate 196 | w.Write(dtsBytes("0010")) // Splice type + DTS next access unit 197 | w.WriteN(^uint64(0), 40) // Stuffing bytes 198 | return buf.Bytes() 199 | } 200 | 201 | func TestParsePacketAdaptationField(t *testing.T) { 202 | v, err := parsePacketAdaptationField(astikit.NewBytesIterator(packetAdaptationFieldBytes(*packetAdaptationField))) 203 | assert.Equal(t, packetAdaptationField, v) 204 | assert.NoError(t, err) 205 | } 206 | 207 | func TestWritePacketAdaptationField(t *testing.T) { 208 | buf := &bytes.Buffer{} 209 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: buf}) 210 | eb := packetAdaptationFieldBytes(*packetAdaptationField) 211 | bytesWritten, err := writePacketAdaptationField(w, packetAdaptationField) 212 | assert.NoError(t, err) 213 | assert.Equal(t, bytesWritten, buf.Len()) 214 | assert.Equal(t, len(eb), buf.Len()) 215 | assert.Equal(t, eb, buf.Bytes()) 216 | } 217 | 218 | var pcr = &ClockReference{ 219 | Base: 5726623061, 220 | Extension: 341, 221 | } 222 | 223 | func pcrBytes() []byte { 224 | buf := &bytes.Buffer{} 225 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: buf}) 226 | w.Write("101010101010101010101010101010101") // Base 227 | w.Write("111111") // Reserved 228 | w.Write("101010101") // Extension 229 | return buf.Bytes() 230 | } 231 | 232 | func TestParsePCR(t *testing.T) { 233 | v, err := parsePCR(astikit.NewBytesIterator(pcrBytes())) 234 | assert.Equal(t, pcr, v) 235 | assert.NoError(t, err) 236 | } 237 | 238 | func TestWritePCR(t *testing.T) { 239 | buf := &bytes.Buffer{} 240 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: buf}) 241 | bytesWritten, err := writePCR(w, pcr) 242 | assert.NoError(t, err) 243 | assert.Equal(t, bytesWritten, 6) 244 | assert.Equal(t, bytesWritten, buf.Len()) 245 | assert.Equal(t, pcrBytes(), buf.Bytes()) 246 | } 247 | 248 | func BenchmarkWritePCR(b *testing.B) { 249 | buf := &bytes.Buffer{} 250 | buf.Grow(6) 251 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: buf}) 252 | 253 | b.ReportAllocs() 254 | for i := 0; i < b.N; i++ { 255 | buf.Reset() 256 | writePCR(w, pcr) 257 | } 258 | } 259 | 260 | func BenchmarkParsePacket(b *testing.B) { 261 | bs, _ := packet(packetHeader, *packetAdaptationField, []byte("payload"), true) 262 | 263 | for i := 0; i < b.N; i++ { 264 | b.ReportAllocs() 265 | parsePacket(astikit.NewBytesIterator(bs), nil) 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /muxer_test.go: -------------------------------------------------------------------------------- 1 | package astits 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "testing" 7 | 8 | "github.com/asticode/go-astikit" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func patExpectedBytes(versionNumber uint8, cc uint8) []byte { 13 | buf := bytes.Buffer{} 14 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: &buf}) 15 | w.Write(uint8(syncByte)) 16 | w.Write("010") // no transport error, payload start, no priority 17 | w.WriteN(PIDPAT, 13) 18 | w.Write("0001") // no scrambling, no AF, payload present 19 | w.WriteN(cc, 4) 20 | 21 | w.Write(uint16(0)) // Table ID 22 | w.Write("1011") // Syntax section indicator, private bit, reserved 23 | w.WriteN(uint16(13), 12) // Section length 24 | 25 | w.Write(uint16(PSITableIDPAT)) 26 | w.Write("11") // Reserved bits 27 | w.WriteN(versionNumber, 5) // Version number 28 | w.Write("1") // Current/next indicator 29 | w.Write(uint8(0)) // Section number 30 | w.Write(uint8(0)) // Last section number 31 | 32 | w.Write(programNumberStart) 33 | w.Write("111") // reserved 34 | w.WriteN(pmtStartPID, 13) 35 | 36 | // CRC32 37 | if versionNumber == 0 { 38 | w.Write([]byte{0x71, 0x10, 0xd8, 0x78}) 39 | } else { 40 | w.Write([]byte{0xef, 0xbe, 0x08, 0x5a}) 41 | } 42 | 43 | w.Write(bytes.Repeat([]byte{0xff}, 167)) 44 | 45 | return buf.Bytes() 46 | } 47 | 48 | func TestMuxer_generatePAT(t *testing.T) { 49 | muxer := NewMuxer(context.Background(), nil) 50 | 51 | err := muxer.generatePAT() 52 | assert.NoError(t, err) 53 | assert.Equal(t, MpegTsPacketSize, muxer.patBytes.Len()) 54 | assert.Equal(t, patExpectedBytes(0, 0), muxer.patBytes.Bytes()) 55 | 56 | // Version number shouldn't change 57 | err = muxer.generatePAT() 58 | assert.NoError(t, err) 59 | assert.Equal(t, MpegTsPacketSize, muxer.patBytes.Len()) 60 | assert.Equal(t, patExpectedBytes(0, 1), muxer.patBytes.Bytes()) 61 | 62 | // Version number should change 63 | muxer.pmUpdated = true 64 | err = muxer.generatePAT() 65 | assert.NoError(t, err) 66 | assert.Equal(t, MpegTsPacketSize, muxer.patBytes.Len()) 67 | assert.Equal(t, patExpectedBytes(1, 2), muxer.patBytes.Bytes()) 68 | } 69 | 70 | func pmtExpectedBytesVideoOnly(versionNumber, cc uint8) []byte { 71 | buf := bytes.Buffer{} 72 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: &buf}) 73 | w.Write(uint8(syncByte)) 74 | w.Write("010") // no transport error, payload start, no priority 75 | w.WriteN(pmtStartPID, 13) 76 | w.Write("0001") // no scrambling, no AF, payload present 77 | w.WriteN(cc, 4) 78 | 79 | w.Write(uint16(PSITableIDPMT)) // Table ID 80 | w.Write("1011") // Syntax section indicator, private bit, reserved 81 | w.WriteN(uint16(18), 12) // Section length 82 | 83 | w.Write(programNumberStart) 84 | w.Write("11") // Reserved bits 85 | w.WriteN(versionNumber, 5) // Version number 86 | w.Write("1") // Current/next indicator 87 | w.Write(uint8(0)) // Section number 88 | w.Write(uint8(0)) // Last section number 89 | 90 | w.Write("111") // reserved 91 | w.WriteN(uint16(0x1234), 13) // PCR PID 92 | 93 | w.Write("1111") // reserved 94 | w.WriteN(uint16(0), 12) // program info length 95 | 96 | w.Write(uint8(StreamTypeH264Video)) 97 | w.Write("111") // reserved 98 | w.WriteN(uint16(0x1234), 13) 99 | 100 | w.Write("1111") // reserved 101 | w.WriteN(uint16(0), 12) // es info length 102 | 103 | w.Write([]byte{0x31, 0x48, 0x5b, 0xa2}) // CRC32 104 | 105 | w.Write(bytes.Repeat([]byte{0xff}, 162)) 106 | 107 | return buf.Bytes() 108 | } 109 | 110 | func pmtExpectedBytesVideoAndAudio(versionNumber uint8, cc uint8) []byte { 111 | buf := bytes.Buffer{} 112 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: &buf}) 113 | w.Write(uint8(syncByte)) 114 | w.Write("010") // no transport error, payload start, no priority 115 | w.WriteN(pmtStartPID, 13) 116 | w.Write("0001") // no scrambling, no AF, payload present 117 | w.WriteN(cc, 4) 118 | 119 | w.Write(uint16(PSITableIDPMT)) // Table ID 120 | w.Write("1011") // Syntax section indicator, private bit, reserved 121 | w.WriteN(uint16(23), 12) // Section length 122 | 123 | w.Write(programNumberStart) 124 | w.Write("11") // Reserved bits 125 | w.WriteN(versionNumber, 5) // Version number 126 | w.Write("1") // Current/next indicator 127 | w.Write(uint8(0)) // Section number 128 | w.Write(uint8(0)) // Last section number 129 | 130 | w.Write("111") // reserved 131 | w.WriteN(uint16(0x1234), 13) // PCR PID 132 | 133 | w.Write("1111") // reserved 134 | w.WriteN(uint16(0), 12) // program info length 135 | 136 | w.Write(uint8(StreamTypeH264Video)) 137 | w.Write("111") // reserved 138 | w.WriteN(uint16(0x1234), 13) 139 | w.Write("1111") // reserved 140 | w.WriteN(uint16(0), 12) // es info length 141 | 142 | w.Write(uint8(StreamTypeADTS)) 143 | w.Write("111") // reserved 144 | w.WriteN(uint16(0x0234), 13) 145 | w.Write("1111") // reserved 146 | w.WriteN(uint16(0), 12) // es info length 147 | 148 | // CRC32 149 | if versionNumber == 0 { 150 | w.Write([]byte{0x29, 0x52, 0xc4, 0x50}) 151 | } else { 152 | w.Write([]byte{0x06, 0xf4, 0xa6, 0xea}) 153 | } 154 | 155 | w.Write(bytes.Repeat([]byte{0xff}, 157)) 156 | 157 | return buf.Bytes() 158 | } 159 | 160 | func TestMuxer_generatePMT(t *testing.T) { 161 | muxer := NewMuxer(context.Background(), nil) 162 | err := muxer.AddElementaryStream(PMTElementaryStream{ 163 | ElementaryPID: 0x1234, 164 | StreamType: StreamTypeH264Video, 165 | }) 166 | muxer.SetPCRPID(0x1234) 167 | assert.NoError(t, err) 168 | 169 | err = muxer.generatePMT() 170 | assert.NoError(t, err) 171 | assert.Equal(t, MpegTsPacketSize, muxer.pmtBytes.Len()) 172 | assert.Equal(t, pmtExpectedBytesVideoOnly(0, 0), muxer.pmtBytes.Bytes()) 173 | 174 | // Version number shouldn't change 175 | err = muxer.generatePMT() 176 | assert.NoError(t, err) 177 | assert.Equal(t, MpegTsPacketSize, muxer.pmtBytes.Len()) 178 | assert.Equal(t, pmtExpectedBytesVideoOnly(0, 1), muxer.pmtBytes.Bytes()) 179 | 180 | err = muxer.AddElementaryStream(PMTElementaryStream{ 181 | ElementaryPID: 0x0234, 182 | StreamType: StreamTypeAACAudio, 183 | }) 184 | assert.NoError(t, err) 185 | 186 | // Version number should change 187 | err = muxer.generatePMT() 188 | assert.NoError(t, err) 189 | assert.Equal(t, MpegTsPacketSize, muxer.pmtBytes.Len()) 190 | assert.Equal(t, pmtExpectedBytesVideoAndAudio(1, 2), muxer.pmtBytes.Bytes()) 191 | } 192 | 193 | func TestMuxer_WriteTables(t *testing.T) { 194 | buf := bytes.Buffer{} 195 | muxer := NewMuxer(context.Background(), &buf) 196 | err := muxer.AddElementaryStream(PMTElementaryStream{ 197 | ElementaryPID: 0x1234, 198 | StreamType: StreamTypeH264Video, 199 | }) 200 | muxer.SetPCRPID(0x1234) 201 | assert.NoError(t, err) 202 | 203 | n, err := muxer.WriteTables() 204 | assert.NoError(t, err) 205 | assert.Equal(t, 2*MpegTsPacketSize, n) 206 | assert.Equal(t, n, buf.Len()) 207 | 208 | expectedBytes := append(patExpectedBytes(0, 0), pmtExpectedBytesVideoOnly(0, 0)...) 209 | assert.Equal(t, expectedBytes, buf.Bytes()) 210 | } 211 | 212 | func TestMuxer_WriteTables_Error(t *testing.T) { 213 | muxer := NewMuxer(context.Background(), nil) 214 | err := muxer.AddElementaryStream(PMTElementaryStream{ 215 | ElementaryPID: 0x1234, 216 | StreamType: StreamTypeH264Video, 217 | }) 218 | assert.NoError(t, err) 219 | 220 | _, err = muxer.WriteTables() 221 | assert.Equal(t, ErrPCRPIDInvalid, err) 222 | } 223 | 224 | func TestMuxer_AddElementaryStream(t *testing.T) { 225 | muxer := NewMuxer(context.Background(), nil) 226 | err := muxer.AddElementaryStream(PMTElementaryStream{ 227 | ElementaryPID: 0x1234, 228 | StreamType: StreamTypeH264Video, 229 | }) 230 | assert.NoError(t, err) 231 | 232 | err = muxer.AddElementaryStream(PMTElementaryStream{ 233 | ElementaryPID: 0x1234, 234 | StreamType: StreamTypeH264Video, 235 | }) 236 | assert.Equal(t, ErrPIDAlreadyExists, err) 237 | } 238 | 239 | func TestMuxer_RemoveElementaryStream(t *testing.T) { 240 | muxer := NewMuxer(context.Background(), nil) 241 | err := muxer.AddElementaryStream(PMTElementaryStream{ 242 | ElementaryPID: 0x1234, 243 | StreamType: StreamTypeH264Video, 244 | }) 245 | assert.NoError(t, err) 246 | 247 | err = muxer.RemoveElementaryStream(0x1234) 248 | assert.NoError(t, err) 249 | 250 | err = muxer.RemoveElementaryStream(0x1234) 251 | assert.Equal(t, ErrPIDNotFound, err) 252 | } 253 | 254 | func testPayload() []byte { 255 | ret := make([]byte, 0xff+1) 256 | for i := 0; i <= 0xff; i++ { 257 | ret[i] = byte(i) 258 | } 259 | return ret 260 | } 261 | 262 | func TestMuxer_WritePayload(t *testing.T) { 263 | buf := bytes.Buffer{} 264 | muxer := NewMuxer(context.Background(), &buf) 265 | 266 | err := muxer.AddElementaryStream(PMTElementaryStream{ 267 | ElementaryPID: 0x1234, 268 | StreamType: StreamTypeH264Video, 269 | }) 270 | muxer.SetPCRPID(0x1234) 271 | assert.NoError(t, err) 272 | 273 | err = muxer.AddElementaryStream(PMTElementaryStream{ 274 | ElementaryPID: 0x0234, 275 | StreamType: StreamTypeAACAudio, 276 | }) 277 | assert.NoError(t, err) 278 | 279 | payload := testPayload() 280 | pcr := ClockReference{ 281 | Base: 5726623061, 282 | Extension: 341, 283 | } 284 | pts := ClockReference{Base: 5726623060} 285 | 286 | n, err := muxer.WriteData(&MuxerData{ 287 | PID: 0x1234, 288 | AdaptationField: &PacketAdaptationField{ 289 | HasPCR: true, 290 | PCR: &pcr, 291 | RandomAccessIndicator: true, 292 | }, 293 | PES: &PESData{ 294 | Data: payload, 295 | Header: &PESHeader{ 296 | OptionalHeader: &PESOptionalHeader{ 297 | DTS: &pts, 298 | PTS: &pts, 299 | PTSDTSIndicator: PTSDTSIndicatorBothPresent, 300 | }, 301 | }, 302 | }, 303 | }) 304 | 305 | assert.NoError(t, err) 306 | assert.Equal(t, buf.Len(), n) 307 | 308 | bytesTotal := n 309 | 310 | n, err = muxer.WriteData(&MuxerData{ 311 | PID: 0x0234, 312 | AdaptationField: &PacketAdaptationField{ 313 | HasPCR: true, 314 | PCR: &pcr, 315 | RandomAccessIndicator: true, 316 | }, 317 | PES: &PESData{ 318 | Data: payload, 319 | Header: &PESHeader{ 320 | OptionalHeader: &PESOptionalHeader{ 321 | DTS: &pts, 322 | PTS: &pts, 323 | PTSDTSIndicator: PTSDTSIndicatorBothPresent, 324 | }, 325 | }, 326 | }, 327 | }) 328 | 329 | assert.NoError(t, err) 330 | assert.Equal(t, buf.Len(), bytesTotal+n) 331 | assert.Equal(t, 0, buf.Len()%MpegTsPacketSize) 332 | 333 | bs := buf.Bytes() 334 | assert.Equal(t, patExpectedBytes(0, 0), bs[:MpegTsPacketSize]) 335 | assert.Equal(t, pmtExpectedBytesVideoAndAudio(0, 0), bs[MpegTsPacketSize:MpegTsPacketSize*2]) 336 | } 337 | -------------------------------------------------------------------------------- /muxer.go: -------------------------------------------------------------------------------- 1 | package astits 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "errors" 7 | "io" 8 | 9 | "github.com/asticode/go-astikit" 10 | ) 11 | 12 | const ( 13 | startPID uint16 = 0x0100 14 | pmtStartPID uint16 = 0x1000 15 | programNumberStart uint16 = 1 16 | ) 17 | 18 | var ( 19 | ErrPIDNotFound = errors.New("astits: PID not found") 20 | ErrPIDAlreadyExists = errors.New("astits: PID already exists") 21 | ErrPCRPIDInvalid = errors.New("astits: PCR PID invalid") 22 | ) 23 | 24 | type Muxer struct { 25 | ctx context.Context 26 | w io.Writer 27 | bitsWriter *astikit.BitsWriter 28 | 29 | packetSize int 30 | tablesRetransmitPeriod int // period in PES packets 31 | 32 | pm *programMap // pid -> programNumber 33 | pmUpdated bool 34 | pmt PMTData 35 | pmtUpdated bool 36 | nextPID uint16 37 | patVersion wrappingCounter 38 | pmtVersion wrappingCounter 39 | patCC wrappingCounter 40 | pmtCC wrappingCounter 41 | 42 | patBytes bytes.Buffer 43 | pmtBytes bytes.Buffer 44 | 45 | buf bytes.Buffer 46 | bufWriter *astikit.BitsWriter 47 | 48 | // We use map[uint32] instead map[uint16] as go runtime provide optimized hash functions for (u)int32/64 keys 49 | esContexts map[uint32]*esContext 50 | tablesRetransmitCounter int 51 | } 52 | 53 | type esContext struct { 54 | es *PMTElementaryStream 55 | cc wrappingCounter 56 | } 57 | 58 | func newEsContext(es *PMTElementaryStream) *esContext { 59 | return &esContext{ 60 | es: es, 61 | cc: newWrappingCounter(0b1111), // CC is 4 bits 62 | } 63 | } 64 | 65 | func MuxerOptTablesRetransmitPeriod(newPeriod int) func(*Muxer) { 66 | return func(m *Muxer) { 67 | m.tablesRetransmitPeriod = newPeriod 68 | } 69 | } 70 | 71 | // TODO MuxerOptAutodetectPCRPID selecting first video PID for each PMT, falling back to first audio, falling back to any other 72 | 73 | func NewMuxer(ctx context.Context, w io.Writer, opts ...func(*Muxer)) *Muxer { 74 | m := &Muxer{ 75 | ctx: ctx, 76 | w: w, 77 | 78 | packetSize: MpegTsPacketSize, // no 192-byte packet support yet 79 | tablesRetransmitPeriod: 40, 80 | 81 | pm: newProgramMap(), 82 | pmt: PMTData{ 83 | ElementaryStreams: []*PMTElementaryStream{}, 84 | ProgramNumber: programNumberStart, 85 | }, 86 | 87 | // table version is 5-bit field 88 | patVersion: newWrappingCounter(0b11111), 89 | pmtVersion: newWrappingCounter(0b11111), 90 | 91 | patCC: newWrappingCounter(0b1111), 92 | pmtCC: newWrappingCounter(0b1111), 93 | 94 | esContexts: map[uint32]*esContext{}, 95 | } 96 | 97 | m.bufWriter = astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: &m.buf}) 98 | m.bitsWriter = astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: m.w}) 99 | 100 | // TODO multiple programs support 101 | m.pm.setUnlocked(pmtStartPID, programNumberStart) 102 | m.pmUpdated = true 103 | 104 | for _, opt := range opts { 105 | opt(m) 106 | } 107 | 108 | // to output tables at the very start 109 | m.tablesRetransmitCounter = m.tablesRetransmitPeriod 110 | 111 | return m 112 | } 113 | 114 | // if es.ElementaryPID is zero, it will be generated automatically 115 | func (m *Muxer) AddElementaryStream(es PMTElementaryStream) error { 116 | if es.ElementaryPID != 0 { 117 | for _, oes := range m.pmt.ElementaryStreams { 118 | if oes.ElementaryPID == es.ElementaryPID { 119 | return ErrPIDAlreadyExists 120 | } 121 | } 122 | } else { 123 | es.ElementaryPID = m.nextPID 124 | m.nextPID++ 125 | } 126 | 127 | m.pmt.ElementaryStreams = append(m.pmt.ElementaryStreams, &es) 128 | 129 | m.esContexts[uint32(es.ElementaryPID)] = newEsContext(&es) 130 | // invalidate pmt cache 131 | m.pmtBytes.Reset() 132 | m.pmtUpdated = true 133 | return nil 134 | } 135 | 136 | func (m *Muxer) RemoveElementaryStream(pid uint16) error { 137 | foundIdx := -1 138 | for i, oes := range m.pmt.ElementaryStreams { 139 | if oes.ElementaryPID == pid { 140 | foundIdx = i 141 | break 142 | } 143 | } 144 | 145 | if foundIdx == -1 { 146 | return ErrPIDNotFound 147 | } 148 | 149 | m.pmt.ElementaryStreams = append(m.pmt.ElementaryStreams[:foundIdx], m.pmt.ElementaryStreams[foundIdx+1:]...) 150 | delete(m.esContexts, uint32(pid)) 151 | m.pmtBytes.Reset() 152 | m.pmtUpdated = true 153 | return nil 154 | } 155 | 156 | // SetPCRPID marks pid as one to look PCRs in 157 | func (m *Muxer) SetPCRPID(pid uint16) { 158 | m.pmt.PCRPID = pid 159 | m.pmtUpdated = true 160 | } 161 | 162 | // WriteData writes MuxerData to TS stream 163 | // Currently only PES packets are supported 164 | // Be aware that after successful call WriteData will set d.AdaptationField.StuffingLength value to zero 165 | func (m *Muxer) WriteData(d *MuxerData) (int, error) { 166 | ctx, ok := m.esContexts[uint32(d.PID)] 167 | if !ok { 168 | return 0, ErrPIDNotFound 169 | } 170 | 171 | bytesWritten := 0 172 | 173 | forceTables := d.AdaptationField != nil && 174 | d.AdaptationField.RandomAccessIndicator && 175 | d.PID == m.pmt.PCRPID 176 | 177 | n, err := m.retransmitTables(forceTables) 178 | if err != nil { 179 | return n, err 180 | } 181 | 182 | bytesWritten += n 183 | 184 | payloadStart := true 185 | writeAf := d.AdaptationField != nil 186 | payloadBytesWritten := 0 187 | for payloadBytesWritten < len(d.PES.Data) { 188 | pktLen := 1 + mpegTsPacketHeaderSize // sync byte + header 189 | pkt := Packet{ 190 | Header: PacketHeader{ 191 | ContinuityCounter: uint8(ctx.cc.inc()), 192 | HasAdaptationField: writeAf, 193 | HasPayload: false, 194 | PayloadUnitStartIndicator: false, 195 | PID: d.PID, 196 | }, 197 | } 198 | 199 | if writeAf { 200 | pkt.AdaptationField = d.AdaptationField 201 | // one byte for adaptation field length field 202 | pktLen += 1 + int(calcPacketAdaptationFieldLength(d.AdaptationField)) 203 | writeAf = false 204 | } 205 | 206 | bytesAvailable := m.packetSize - pktLen 207 | if payloadStart { 208 | pesHeaderLengthCurrent := pesHeaderLength + int(calcPESOptionalHeaderLength(d.PES.Header.OptionalHeader)) 209 | // d.AdaptationField with pes header are too big, we don't have space to write pes header 210 | if bytesAvailable < pesHeaderLengthCurrent { 211 | pkt.Header.HasAdaptationField = true 212 | if pkt.AdaptationField == nil { 213 | pkt.AdaptationField = newStuffingAdaptationField(bytesAvailable) 214 | } else { 215 | pkt.AdaptationField.StuffingLength = bytesAvailable 216 | } 217 | } else { 218 | pkt.Header.HasPayload = true 219 | pkt.Header.PayloadUnitStartIndicator = true 220 | } 221 | } else { 222 | pkt.Header.HasPayload = true 223 | } 224 | 225 | if pkt.Header.HasPayload { 226 | m.buf.Reset() 227 | if d.PES.Header.StreamID == 0 { 228 | d.PES.Header.StreamID = ctx.es.StreamType.ToPESStreamID() 229 | } 230 | 231 | ntot, npayload, err := writePESData( 232 | m.bufWriter, 233 | d.PES.Header, 234 | d.PES.Data[payloadBytesWritten:], 235 | payloadStart, 236 | bytesAvailable, 237 | ) 238 | if err != nil { 239 | return bytesWritten, err 240 | } 241 | 242 | payloadBytesWritten += npayload 243 | 244 | pkt.Payload = m.buf.Bytes() 245 | 246 | bytesAvailable -= ntot 247 | // if we still have some space in packet, we should stuff it with adaptation field stuffing 248 | // we can't stuff packets with 0xff at the end of a packet since it's not uncommon for PES payloads to have length unspecified 249 | if bytesAvailable > 0 { 250 | pkt.Header.HasAdaptationField = true 251 | if pkt.AdaptationField == nil { 252 | pkt.AdaptationField = newStuffingAdaptationField(bytesAvailable) 253 | } else { 254 | pkt.AdaptationField.StuffingLength = bytesAvailable 255 | } 256 | } 257 | 258 | n, err = writePacket(m.bitsWriter, &pkt, m.packetSize) 259 | if err != nil { 260 | return bytesWritten, err 261 | } 262 | 263 | bytesWritten += n 264 | 265 | payloadStart = false 266 | } 267 | } 268 | 269 | if d.AdaptationField != nil { 270 | d.AdaptationField.StuffingLength = 0 271 | } 272 | 273 | return bytesWritten, nil 274 | } 275 | 276 | // Writes given packet to MPEG-TS stream 277 | // Stuffs with 0xffs if packet turns out to be shorter than target packet length 278 | func (m *Muxer) WritePacket(p *Packet) (int, error) { 279 | return writePacket(m.bitsWriter, p, m.packetSize) 280 | } 281 | 282 | func (m *Muxer) retransmitTables(force bool) (int, error) { 283 | m.tablesRetransmitCounter++ 284 | if !force && m.tablesRetransmitCounter < m.tablesRetransmitPeriod { 285 | return 0, nil 286 | } 287 | 288 | n, err := m.WriteTables() 289 | if err != nil { 290 | return n, err 291 | } 292 | 293 | m.tablesRetransmitCounter = 0 294 | return n, nil 295 | } 296 | 297 | func (m *Muxer) WriteTables() (int, error) { 298 | bytesWritten := 0 299 | 300 | if err := m.generatePAT(); err != nil { 301 | return bytesWritten, err 302 | } 303 | 304 | if err := m.generatePMT(); err != nil { 305 | return bytesWritten, err 306 | } 307 | 308 | n, err := m.w.Write(m.patBytes.Bytes()) 309 | if err != nil { 310 | return bytesWritten, err 311 | } 312 | bytesWritten += n 313 | 314 | n, err = m.w.Write(m.pmtBytes.Bytes()) 315 | if err != nil { 316 | return bytesWritten, err 317 | } 318 | bytesWritten += n 319 | 320 | return bytesWritten, nil 321 | } 322 | 323 | func (m *Muxer) generatePAT() error { 324 | d := m.pm.toPATDataUnlocked() 325 | 326 | versionNumber := m.patVersion.get() 327 | if m.pmUpdated { 328 | versionNumber = m.patVersion.inc() 329 | } 330 | 331 | syntax := &PSISectionSyntax{ 332 | Data: &PSISectionSyntaxData{PAT: d}, 333 | Header: &PSISectionSyntaxHeader{ 334 | CurrentNextIndicator: true, 335 | // TODO support for PAT tables longer than 1 TS packet 336 | //LastSectionNumber: 0, 337 | //SectionNumber: 0, 338 | TableIDExtension: d.TransportStreamID, 339 | VersionNumber: uint8(versionNumber), 340 | }, 341 | } 342 | section := PSISection{ 343 | Header: &PSISectionHeader{ 344 | SectionLength: calcPATSectionLength(d), 345 | SectionSyntaxIndicator: true, 346 | TableID: PSITableID(d.TransportStreamID), 347 | }, 348 | Syntax: syntax, 349 | } 350 | psiData := PSIData{ 351 | Sections: []*PSISection{§ion}, 352 | } 353 | 354 | m.buf.Reset() 355 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: &m.buf}) 356 | if _, err := writePSIData(w, &psiData); err != nil { 357 | return err 358 | } 359 | 360 | m.patBytes.Reset() 361 | wPacket := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: &m.patBytes}) 362 | 363 | pkt := Packet{ 364 | Header: PacketHeader{ 365 | HasPayload: true, 366 | PayloadUnitStartIndicator: true, 367 | PID: PIDPAT, 368 | ContinuityCounter: uint8(m.patCC.inc()), 369 | }, 370 | Payload: m.buf.Bytes(), 371 | } 372 | if _, err := writePacket(wPacket, &pkt, m.packetSize); err != nil { 373 | // FIXME save old PAT and rollback to it here maybe? 374 | return err 375 | } 376 | 377 | m.pmUpdated = false 378 | 379 | return nil 380 | } 381 | 382 | func (m *Muxer) generatePMT() error { 383 | hasPCRPID := false 384 | for _, es := range m.pmt.ElementaryStreams { 385 | if es.ElementaryPID == m.pmt.PCRPID { 386 | hasPCRPID = true 387 | break 388 | } 389 | } 390 | if !hasPCRPID { 391 | return ErrPCRPIDInvalid 392 | } 393 | 394 | versionNumber := m.pmtVersion.get() 395 | if m.pmtUpdated { 396 | versionNumber = m.pmtVersion.inc() 397 | } 398 | 399 | syntax := &PSISectionSyntax{ 400 | Data: &PSISectionSyntaxData{PMT: &m.pmt}, 401 | Header: &PSISectionSyntaxHeader{ 402 | CurrentNextIndicator: true, 403 | // TODO support for PMT tables longer than 1 TS packet 404 | //LastSectionNumber: 0, 405 | //SectionNumber: 0, 406 | TableIDExtension: m.pmt.ProgramNumber, 407 | VersionNumber: uint8(versionNumber), 408 | }, 409 | } 410 | section := PSISection{ 411 | Header: &PSISectionHeader{ 412 | SectionLength: calcPMTSectionLength(&m.pmt), 413 | SectionSyntaxIndicator: true, 414 | TableID: PSITableIDPMT, 415 | }, 416 | Syntax: syntax, 417 | } 418 | psiData := PSIData{ 419 | Sections: []*PSISection{§ion}, 420 | } 421 | 422 | m.buf.Reset() 423 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: &m.buf}) 424 | if _, err := writePSIData(w, &psiData); err != nil { 425 | return err 426 | } 427 | 428 | m.pmtBytes.Reset() 429 | wPacket := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: &m.pmtBytes}) 430 | 431 | pkt := Packet{ 432 | Header: PacketHeader{ 433 | HasPayload: true, 434 | PayloadUnitStartIndicator: true, 435 | PID: pmtStartPID, // FIXME multiple programs support 436 | ContinuityCounter: uint8(m.pmtCC.inc()), 437 | }, 438 | Payload: m.buf.Bytes(), 439 | } 440 | if _, err := writePacket(wPacket, &pkt, m.packetSize); err != nil { 441 | // FIXME save old PMT and rollback to it here maybe? 442 | return err 443 | } 444 | 445 | m.pmtUpdated = false 446 | 447 | return nil 448 | } 449 | -------------------------------------------------------------------------------- /data_psi_test.go: -------------------------------------------------------------------------------- 1 | package astits 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/asticode/go-astikit" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | var psi = &PSIData{ 12 | PointerField: 4, 13 | Sections: []*PSISection{ 14 | { 15 | CRC32: uint32(0x7ffc6102), 16 | Header: &PSISectionHeader{ 17 | PrivateBit: true, 18 | SectionLength: 30, 19 | SectionSyntaxIndicator: true, 20 | TableID: 78, 21 | TableType: PSITableTypeEIT, 22 | }, 23 | Syntax: &PSISectionSyntax{ 24 | Data: &PSISectionSyntaxData{EIT: eit}, 25 | Header: psiSectionSyntaxHeader, 26 | }, 27 | }, 28 | { 29 | CRC32: uint32(0xfebaa941), 30 | Header: &PSISectionHeader{ 31 | PrivateBit: true, 32 | SectionLength: 25, 33 | SectionSyntaxIndicator: true, 34 | TableID: 64, 35 | TableType: PSITableTypeNIT, 36 | }, 37 | Syntax: &PSISectionSyntax{ 38 | Data: &PSISectionSyntaxData{NIT: nit}, 39 | Header: psiSectionSyntaxHeader, 40 | }, 41 | }, 42 | { 43 | CRC32: uint32(0x60739f61), 44 | Header: &PSISectionHeader{ 45 | PrivateBit: true, 46 | SectionLength: 17, 47 | SectionSyntaxIndicator: true, 48 | TableID: 0, 49 | TableType: PSITableTypePAT, 50 | }, 51 | Syntax: &PSISectionSyntax{ 52 | Data: &PSISectionSyntaxData{PAT: pat}, 53 | Header: psiSectionSyntaxHeader, 54 | }, 55 | }, 56 | { 57 | CRC32: uint32(0xc68442e8), 58 | Header: &PSISectionHeader{ 59 | PrivateBit: true, 60 | SectionLength: 24, 61 | SectionSyntaxIndicator: true, 62 | TableID: 2, 63 | TableType: PSITableTypePMT, 64 | }, 65 | Syntax: &PSISectionSyntax{ 66 | Data: &PSISectionSyntaxData{PMT: pmt}, 67 | Header: psiSectionSyntaxHeader, 68 | }, 69 | }, 70 | { 71 | CRC32: uint32(0xef3751d6), 72 | Header: &PSISectionHeader{ 73 | PrivateBit: true, 74 | SectionLength: 20, 75 | SectionSyntaxIndicator: true, 76 | TableID: 66, 77 | TableType: PSITableTypeSDT, 78 | }, 79 | Syntax: &PSISectionSyntax{ 80 | Data: &PSISectionSyntaxData{SDT: sdt}, 81 | Header: psiSectionSyntaxHeader, 82 | }, 83 | }, 84 | { 85 | CRC32: uint32(0x6969b13), 86 | Header: &PSISectionHeader{ 87 | PrivateBit: true, 88 | SectionLength: 14, 89 | SectionSyntaxIndicator: true, 90 | TableID: 115, 91 | TableType: PSITableTypeTOT, 92 | }, 93 | Syntax: &PSISectionSyntax{ 94 | Data: &PSISectionSyntaxData{TOT: tot}, 95 | }, 96 | }, 97 | {Header: &PSISectionHeader{TableID: 254, TableType: PSITableTypeUnknown}}, 98 | }, 99 | } 100 | 101 | func psiBytes() []byte { 102 | buf := &bytes.Buffer{} 103 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: buf}) 104 | w.Write(uint8(4)) // Pointer field 105 | w.Write([]byte("test")) // Pointer field bytes 106 | w.Write(uint8(78)) // EIT table ID 107 | w.Write("1") // EIT syntax section indicator 108 | w.Write("1") // EIT private bit 109 | w.Write("11") // EIT reserved 110 | w.Write("000000011110") // EIT section length 111 | w.Write(psiSectionSyntaxHeaderBytes()) // EIT syntax section header 112 | w.Write(eitBytes()) // EIT data 113 | w.Write(uint32(0x7ffc6102)) // EIT CRC32 114 | w.Write(uint8(64)) // NIT table ID 115 | w.Write("1") // NIT syntax section indicator 116 | w.Write("1") // NIT private bit 117 | w.Write("11") // NIT reserved 118 | w.Write("000000011001") // NIT section length 119 | w.Write(psiSectionSyntaxHeaderBytes()) // NIT syntax section header 120 | w.Write(nitBytes()) // NIT data 121 | w.Write(uint32(0xfebaa941)) // NIT CRC32 122 | w.Write(uint8(0)) // PAT table ID 123 | w.Write("1") // PAT syntax section indicator 124 | w.Write("1") // PAT private bit 125 | w.Write("11") // PAT reserved 126 | w.Write("000000010001") // PAT section length 127 | w.Write(psiSectionSyntaxHeaderBytes()) // PAT syntax section header 128 | w.Write(patBytes()) // PAT data 129 | w.Write(uint32(0x60739f61)) // PAT CRC32 130 | w.Write(uint8(2)) // PMT table ID 131 | w.Write("1") // PMT syntax section indicator 132 | w.Write("1") // PMT private bit 133 | w.Write("11") // PMT reserved 134 | w.Write("000000011000") // PMT section length 135 | w.Write(psiSectionSyntaxHeaderBytes()) // PMT syntax section header 136 | w.Write(pmtBytes()) // PMT data 137 | w.Write(uint32(0xc68442e8)) // PMT CRC32 138 | w.Write(uint8(66)) // SDT table ID 139 | w.Write("1") // SDT syntax section indicator 140 | w.Write("1") // SDT private bit 141 | w.Write("11") // SDT reserved 142 | w.Write("000000010100") // SDT section length 143 | w.Write(psiSectionSyntaxHeaderBytes()) // SDT syntax section header 144 | w.Write(sdtBytes()) // SDT data 145 | w.Write(uint32(0xef3751d6)) // SDT CRC32 146 | w.Write(uint8(115)) // TOT table ID 147 | w.Write("1") // TOT syntax section indicator 148 | w.Write("1") // TOT private bit 149 | w.Write("11") // TOT reserved 150 | w.Write("000000001110") // TOT section length 151 | w.Write(totBytes()) // TOT data 152 | w.Write(uint32(0x6969b13)) // TOT CRC32 153 | w.Write(uint8(254)) // Unknown table ID 154 | w.Write(uint8(0)) // PAT table ID 155 | return buf.Bytes() 156 | } 157 | 158 | func TestParsePSIData(t *testing.T) { 159 | // Invalid CRC32 160 | buf := &bytes.Buffer{} 161 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: buf}) 162 | w.Write(uint8(0)) // Pointer field 163 | w.Write(uint8(115)) // TOT table ID 164 | w.Write("1") // TOT syntax section indicator 165 | w.Write("1") // TOT private bit 166 | w.Write("11") // TOT reserved 167 | w.Write("000000001110") // TOT section length 168 | w.Write(totBytes()) // TOT data 169 | w.Write(uint32(32)) // TOT CRC32 170 | _, err := parsePSIData(astikit.NewBytesIterator(buf.Bytes())) 171 | assert.EqualError(t, err, "astits: parsing PSI table failed: astits: Table CRC32 20 != computed CRC32 6969b13") 172 | 173 | // Valid 174 | d, err := parsePSIData(astikit.NewBytesIterator(psiBytes())) 175 | assert.NoError(t, err) 176 | assert.Equal(t, d, psi) 177 | } 178 | 179 | var psiSectionHeader = &PSISectionHeader{ 180 | PrivateBit: true, 181 | SectionLength: 2730, 182 | SectionSyntaxIndicator: true, 183 | TableID: 0, 184 | TableType: PSITableTypePAT, 185 | } 186 | 187 | func psiSectionHeaderBytes() []byte { 188 | buf := &bytes.Buffer{} 189 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: buf}) 190 | w.Write(uint8(0)) // Table ID 191 | w.Write("1") // Syntax section indicator 192 | w.Write("1") // Private bit 193 | w.Write("11") // Reserved 194 | w.Write("101010101010") // Section length 195 | return buf.Bytes() 196 | } 197 | 198 | func TestParsePSISectionHeader(t *testing.T) { 199 | // Unknown table type 200 | buf := &bytes.Buffer{} 201 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: buf}) 202 | w.Write(uint8(254)) // Table ID 203 | w.Write("1") // Syntax section indicator 204 | w.Write("0000000") // Finish the byte 205 | d, _, _, _, _, err := parsePSISectionHeader(astikit.NewBytesIterator(buf.Bytes())) 206 | assert.Equal(t, d, &PSISectionHeader{ 207 | TableID: 254, 208 | TableType: PSITableTypeUnknown, 209 | }) 210 | assert.NoError(t, err) 211 | 212 | // Valid table type 213 | d, offsetStart, offsetSectionsStart, offsetSectionsEnd, offsetEnd, err := parsePSISectionHeader(astikit.NewBytesIterator(psiSectionHeaderBytes())) 214 | assert.Equal(t, d, psiSectionHeader) 215 | assert.Equal(t, 0, offsetStart) 216 | assert.Equal(t, 3, offsetSectionsStart) 217 | assert.Equal(t, 2729, offsetSectionsEnd) 218 | assert.Equal(t, 2733, offsetEnd) 219 | assert.NoError(t, err) 220 | } 221 | 222 | func TestPSITableType(t *testing.T) { 223 | for i := PSITableIDEITStart; i <= PSITableIDEITEnd; i++ { 224 | assert.Equal(t, PSITableTypeEIT, i.Type()) 225 | } 226 | assert.Equal(t, PSITableTypeDIT, PSITableIDDIT.Type()) 227 | assert.Equal(t, PSITableTypeNIT, PSITableIDNITVariant1.Type()) 228 | assert.Equal(t, PSITableTypeNIT, PSITableIDNITVariant2.Type()) 229 | assert.Equal(t, PSITableTypeSDT, PSITableIDSDTVariant1.Type()) 230 | assert.Equal(t, PSITableTypeSDT, PSITableIDSDTVariant2.Type()) 231 | 232 | assert.Equal(t, PSITableTypeBAT, PSITableIDBAT.Type()) 233 | assert.Equal(t, PSITableTypeNull, PSITableIDNull.Type()) 234 | assert.Equal(t, PSITableTypePAT, PSITableIDPAT.Type()) 235 | assert.Equal(t, PSITableTypePMT, PSITableIDPMT.Type()) 236 | assert.Equal(t, PSITableTypeRST, PSITableIDRST.Type()) 237 | assert.Equal(t, PSITableTypeSIT, PSITableIDSIT.Type()) 238 | assert.Equal(t, PSITableTypeST, PSITableIDST.Type()) 239 | assert.Equal(t, PSITableTypeTDT, PSITableIDTDT.Type()) 240 | assert.Equal(t, PSITableTypeTOT, PSITableIDTOT.Type()) 241 | assert.Equal(t, PSITableTypeUnknown, PSITableID(1).Type()) 242 | } 243 | 244 | var psiSectionSyntaxHeader = &PSISectionSyntaxHeader{ 245 | CurrentNextIndicator: true, 246 | LastSectionNumber: 3, 247 | SectionNumber: 2, 248 | TableIDExtension: 1, 249 | VersionNumber: 21, 250 | } 251 | 252 | func psiSectionSyntaxHeaderBytes() []byte { 253 | buf := &bytes.Buffer{} 254 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: buf}) 255 | w.Write(uint16(1)) // Table ID extension 256 | w.Write("11") // Reserved bits 257 | w.Write("10101") // Version number 258 | w.Write("1") // Current/next indicator 259 | w.Write(uint8(2)) // Section number 260 | w.Write(uint8(3)) // Last section number 261 | return buf.Bytes() 262 | } 263 | 264 | func TestParsePSISectionSyntaxHeader(t *testing.T) { 265 | h, err := parsePSISectionSyntaxHeader(astikit.NewBytesIterator(psiSectionSyntaxHeaderBytes())) 266 | assert.Equal(t, psiSectionSyntaxHeader, h) 267 | assert.NoError(t, err) 268 | } 269 | 270 | func TestPSIToData(t *testing.T) { 271 | p := &Packet{} 272 | assert.Equal(t, []*DemuxerData{ 273 | {EIT: eit, FirstPacket: p, PID: 2}, 274 | {FirstPacket: p, NIT: nit, PID: 2}, 275 | {FirstPacket: p, PAT: pat, PID: 2}, 276 | {FirstPacket: p, PMT: pmt, PID: 2}, 277 | {FirstPacket: p, SDT: sdt, PID: 2}, 278 | {FirstPacket: p, TOT: tot, PID: 2}, 279 | }, psi.toData(p, uint16(2))) 280 | } 281 | 282 | type psiDataTestCase struct { 283 | name string 284 | bytesFunc func(*astikit.BitsWriter) 285 | data *PSIData 286 | } 287 | 288 | var psiDataTestCases = []psiDataTestCase{ 289 | { 290 | "PAT", 291 | func(w *astikit.BitsWriter) { 292 | w.Write(uint8(4)) // Pointer field 293 | w.Write([]byte{0, 0, 0, 0}) // Pointer field bytes 294 | w.Write(uint8(0)) // PAT table ID 295 | w.Write("1") // PAT syntax section indicator 296 | w.Write("1") // PAT private bit 297 | w.Write("11") // PAT reserved 298 | w.Write("000000010001") // PAT section length 299 | w.Write(psiSectionSyntaxHeaderBytes()) // PAT syntax section header 300 | w.Write(patBytes()) // PAT data 301 | w.Write(uint32(0x60739f61)) // PAT CRC32 302 | }, 303 | &PSIData{ 304 | PointerField: 4, 305 | Sections: []*PSISection{ 306 | { 307 | CRC32: uint32(0x60739f61), 308 | Header: &PSISectionHeader{ 309 | PrivateBit: true, 310 | SectionLength: 17, 311 | SectionSyntaxIndicator: true, 312 | TableID: 0, 313 | TableType: PSITableTypePAT, 314 | }, 315 | Syntax: &PSISectionSyntax{ 316 | Data: &PSISectionSyntaxData{PAT: pat}, 317 | Header: psiSectionSyntaxHeader, 318 | }, 319 | }, 320 | }, 321 | }, 322 | }, 323 | { 324 | "PMT", 325 | func(w *astikit.BitsWriter) { 326 | w.Write(uint8(4)) // Pointer field 327 | w.Write([]byte{0, 0, 0, 0}) // Pointer field bytes 328 | w.Write(uint8(2)) // PMT table ID 329 | w.Write("1") // PMT syntax section indicator 330 | w.Write("1") // PMT private bit 331 | w.Write("11") // PMT reserved 332 | w.Write("000000011000") // PMT section length 333 | w.Write(psiSectionSyntaxHeaderBytes()) // PMT syntax section header 334 | w.Write(pmtBytes()) // PMT data 335 | w.Write(uint32(0xc68442e8)) // PMT CRC32 336 | }, 337 | &PSIData{ 338 | PointerField: 4, 339 | Sections: []*PSISection{ 340 | { 341 | CRC32: uint32(0xc68442e8), 342 | Header: &PSISectionHeader{ 343 | PrivateBit: true, 344 | SectionLength: 24, 345 | SectionSyntaxIndicator: true, 346 | TableID: 2, 347 | TableType: PSITableTypePMT, 348 | }, 349 | Syntax: &PSISectionSyntax{ 350 | Data: &PSISectionSyntaxData{PMT: pmt}, 351 | Header: psiSectionSyntaxHeader, 352 | }, 353 | }, 354 | }, 355 | }, 356 | }, 357 | } 358 | 359 | func TestWritePSIData(t *testing.T) { 360 | for _, tc := range psiDataTestCases { 361 | t.Run(tc.name, func(t *testing.T) { 362 | bufExpected := bytes.Buffer{} 363 | wExpected := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: &bufExpected}) 364 | bufActual := bytes.Buffer{} 365 | wActual := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: &bufActual}) 366 | 367 | tc.bytesFunc(wExpected) 368 | 369 | n, err := writePSIData(wActual, tc.data) 370 | assert.NoError(t, err) 371 | assert.Equal(t, bufExpected.Len(), n) 372 | assert.Equal(t, n, bufActual.Len()) 373 | assert.Equal(t, bufExpected.Bytes(), bufActual.Bytes()) 374 | }) 375 | } 376 | } 377 | 378 | func BenchmarkParsePSIData(b *testing.B) { 379 | pb := psiBytes() 380 | b.ReportAllocs() 381 | for i := 0; i < b.N; i++ { 382 | parsePSIData(astikit.NewBytesIterator(pb)) 383 | } 384 | } 385 | -------------------------------------------------------------------------------- /cmd/astits-probe/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "errors" 7 | "flag" 8 | "fmt" 9 | "io" 10 | "log" 11 | "net" 12 | "net/url" 13 | "os" 14 | "os/signal" 15 | "strings" 16 | "syscall" 17 | 18 | "github.com/asticode/go-astikit" 19 | "github.com/asticode/go-astits" 20 | "github.com/pkg/profile" 21 | ) 22 | 23 | // Flags 24 | var ( 25 | ctx, cancel = context.WithCancel(context.Background()) 26 | cpuProfiling = flag.Bool("cp", false, "if yes, cpu profiling is enabled") 27 | dataTypes = astikit.NewFlagStrings() 28 | format = flag.String("f", "", "the format") 29 | inputPath = flag.String("i", "", "the input path") 30 | memoryProfiling = flag.Bool("mp", false, "if yes, memory profiling is enabled") 31 | ) 32 | 33 | func main() { 34 | // Init 35 | flag.Usage = func() { 36 | fmt.Fprintf(flag.CommandLine.Output(), "Usage of %s :\n", os.Args[0]) 37 | flag.PrintDefaults() 38 | } 39 | flag.Var(dataTypes, "d", "the datatypes whitelist (all, pat, pmt, pes, eit, nit, sdt, tot)") 40 | cmd := astikit.FlagCmd() 41 | flag.Parse() 42 | 43 | // Handle signals 44 | handleSignals() 45 | 46 | // Start profiling 47 | if *cpuProfiling { 48 | defer profile.Start(profile.CPUProfile, profile.ProfilePath(".")).Stop() 49 | } else if *memoryProfiling { 50 | defer profile.Start(profile.MemProfile, profile.ProfilePath(".")).Stop() 51 | } 52 | 53 | // Build the reader 54 | var r io.Reader 55 | var err error 56 | if r, err = buildReader(ctx); err != nil { 57 | log.Fatal(fmt.Errorf("astits: parsing input failed: %w", err)) 58 | } 59 | 60 | // Make sure the reader is closed properly 61 | if c, ok := r.(io.Closer); ok { 62 | defer c.Close() 63 | } 64 | 65 | // Create the demuxer 66 | var dmx = astits.NewDemuxer(ctx, r, astits.DemuxerOptLogger(log.Default())) 67 | 68 | // Switch on command 69 | switch cmd { 70 | case "data": 71 | // Fetch data 72 | if err = data(dmx); err != nil { 73 | if !errors.Is(err, astits.ErrNoMorePackets) { 74 | log.Fatal(fmt.Errorf("astits: fetching data failed: %w", err)) 75 | } 76 | } 77 | case "packets": 78 | // Fetch packets 79 | if err = packets(dmx); err != nil { 80 | if !errors.Is(err, astits.ErrNoMorePackets) { 81 | log.Fatal(fmt.Errorf("astits: fetching packets failed: %w", err)) 82 | } 83 | } 84 | default: 85 | // Fetch the programs 86 | var pgms []*Program 87 | if pgms, err = programs(dmx); err != nil { 88 | if !errors.Is(err, astits.ErrNoMorePackets) { 89 | log.Fatal(fmt.Errorf("astits: fetching programs failed: %w", err)) 90 | } 91 | } 92 | 93 | // Print 94 | switch *format { 95 | case "json": 96 | var e = json.NewEncoder(os.Stdout) 97 | e.SetIndent("", " ") 98 | if err = e.Encode(pgms); err != nil { 99 | log.Fatal(fmt.Errorf("astits: json encoding to stdout failed: %w", err)) 100 | } 101 | default: 102 | fmt.Println("Programs are:") 103 | for _, pgm := range pgms { 104 | log.Printf("* %s\n", pgm) 105 | } 106 | } 107 | } 108 | } 109 | 110 | func handleSignals() { 111 | ch := make(chan os.Signal, 1) 112 | signal.Notify(ch) 113 | go func() { 114 | for s := range ch { 115 | if s != syscall.SIGURG { 116 | log.Printf("Received signal %s\n", s) 117 | } 118 | switch s { 119 | case syscall.SIGABRT, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM: 120 | cancel() 121 | return 122 | } 123 | } 124 | }() 125 | } 126 | 127 | func buildReader(ctx context.Context) (r io.Reader, err error) { 128 | // Validate input 129 | if len(*inputPath) <= 0 { 130 | err = errors.New("use -i to indicate an input path") 131 | return 132 | } 133 | 134 | // Parse input 135 | var u *url.URL 136 | if u, err = url.Parse(*inputPath); err != nil { 137 | err = fmt.Errorf("astits: parsing input path failed: %w", err) 138 | return 139 | } 140 | 141 | // Switch on scheme 142 | switch u.Scheme { 143 | case "udp": 144 | // Resolve addr 145 | var addr *net.UDPAddr 146 | if addr, err = net.ResolveUDPAddr("udp", u.Host); err != nil { 147 | err = fmt.Errorf("astits: resolving udp addr %s failed: %w", u.Host, err) 148 | return 149 | } 150 | 151 | // Listen to multicast UDP 152 | var c *net.UDPConn 153 | if c, err = net.ListenMulticastUDP("udp", nil, addr); err != nil { 154 | err = fmt.Errorf("astits: listening on multicast udp addr %s failed: %w", u.Host, err) 155 | return 156 | } 157 | c.SetReadBuffer(4096) 158 | r = c 159 | default: 160 | // Open file 161 | var f *os.File 162 | if f, err = os.Open(*inputPath); err != nil { 163 | err = fmt.Errorf("astits: opening %s failed: %w", *inputPath, err) 164 | return 165 | } 166 | r = f 167 | } 168 | return 169 | } 170 | 171 | func packets(dmx *astits.Demuxer) (err error) { 172 | // Loop through packets 173 | var p *astits.Packet 174 | log.Println("Fetching packets...") 175 | for { 176 | // Get next packet 177 | if p, err = dmx.NextPacket(); err != nil { 178 | if err == astits.ErrNoMorePackets { 179 | break 180 | } 181 | err = fmt.Errorf("astits: getting next packet failed: %w", err) 182 | return 183 | } 184 | 185 | // Log packet 186 | log.Printf("PKT: %d\n", p.Header.PID) 187 | log.Printf(" Continuity Counter: %v\n", p.Header.ContinuityCounter) 188 | log.Printf(" Payload Unit Start Indicator: %v\n", p.Header.PayloadUnitStartIndicator) 189 | log.Printf(" Has Payload: %v\n", p.Header.HasPayload) 190 | log.Printf(" Has Adaptation Field: %v\n", p.Header.HasAdaptationField) 191 | log.Printf(" Transport Error Indicator: %v\n", p.Header.TransportErrorIndicator) 192 | log.Printf(" Transport Priority: %v\n", p.Header.TransportPriority) 193 | log.Printf(" Transport Scrambling Control: %v\n", p.Header.TransportScramblingControl) 194 | if p.Header.HasAdaptationField { 195 | log.Printf(" Adaptation Field: %+v\n", p.AdaptationField) 196 | } 197 | } 198 | return nil 199 | } 200 | 201 | func data(dmx *astits.Demuxer) (err error) { 202 | // Determine which data to log 203 | var logAll, logEIT, logNIT, logPAT, logPES, logPMT, logSDT, logTOT bool 204 | if _, ok := dataTypes.Map["all"]; ok { 205 | logAll = true 206 | } 207 | if _, ok := dataTypes.Map["eit"]; ok { 208 | logEIT = true 209 | } 210 | if _, ok := dataTypes.Map["nit"]; ok { 211 | logNIT = true 212 | } 213 | if _, ok := dataTypes.Map["pat"]; ok { 214 | logPAT = true 215 | } 216 | if _, ok := dataTypes.Map["pes"]; ok { 217 | logPES = true 218 | } 219 | if _, ok := dataTypes.Map["pmt"]; ok { 220 | logPMT = true 221 | } 222 | if _, ok := dataTypes.Map["sdt"]; ok { 223 | logSDT = true 224 | } 225 | if _, ok := dataTypes.Map["tot"]; ok { 226 | logTOT = true 227 | } 228 | 229 | // Loop through data 230 | var d *astits.DemuxerData 231 | log.Println("Fetching data...") 232 | for { 233 | // Get next data 234 | if d, err = dmx.NextData(); err != nil { 235 | if err == astits.ErrNoMorePackets { 236 | break 237 | } 238 | err = fmt.Errorf("astits: getting next data failed: %w", err) 239 | return 240 | } 241 | 242 | // Log data 243 | if d.EIT != nil && (logAll || logEIT) { 244 | log.Printf("EIT: %d\n", d.PID) 245 | log.Println(eventsToString(d.EIT.Events)) 246 | } else if d.NIT != nil && (logAll || logNIT) { 247 | log.Printf("NIT: %d\n", d.PID) 248 | } else if d.PAT != nil && (logAll || logPAT) { 249 | log.Printf("PAT: %d\n", d.PID) 250 | log.Printf(" Transport Stream ID: %v\n", d.PAT.TransportStreamID) 251 | log.Println(" Programs:") 252 | for _, p := range d.PAT.Programs { 253 | log.Printf(" %+v\n", p) 254 | } 255 | } else if d.PES != nil && (logAll || logPES) { 256 | log.Printf("PES: %d\n", d.PID) 257 | log.Printf(" Stream ID: %v\n", d.PES.Header.StreamID) 258 | log.Printf(" Packet Length: %v\n", d.PES.Header.PacketLength) 259 | log.Printf(" Optional Header: %+v\n", d.PES.Header.OptionalHeader) 260 | } else if d.PMT != nil && (logAll || logPMT) { 261 | log.Printf("PMT: %d\n", d.PID) 262 | log.Printf(" ProgramNumber: %v\n", d.PMT.ProgramNumber) 263 | log.Printf(" PCR PID: %v\n", d.PMT.PCRPID) 264 | log.Println(" Elementary Streams:") 265 | for _, s := range d.PMT.ElementaryStreams { 266 | log.Printf(" %+v\n", s) 267 | } 268 | log.Println(" Program Descriptors:") 269 | for _, d := range d.PMT.ProgramDescriptors { 270 | log.Printf(" %+v\n", d) 271 | } 272 | } else if d.SDT != nil && (logAll || logSDT) { 273 | log.Printf("SDT: %d\n", d.PID) 274 | } else if d.TOT != nil && (logAll || logTOT) { 275 | log.Printf("TOT: %d\n", d.PID) 276 | } 277 | } 278 | return 279 | } 280 | 281 | func programs(dmx *astits.Demuxer) (o []*Program, err error) { 282 | // Loop through data 283 | var d *astits.DemuxerData 284 | var pgmsToProcess = make(map[uint16]bool) 285 | var pgms = make(map[uint16]*Program) 286 | log.Println("Fetching data...") 287 | for { 288 | // Get next data 289 | if d, err = dmx.NextData(); err != nil { 290 | if err == astits.ErrNoMorePackets { 291 | err = nil 292 | break 293 | } 294 | err = fmt.Errorf("astits: getting next data failed: %w", err) 295 | return 296 | } 297 | 298 | // Check data 299 | if d.PAT != nil { 300 | // Build programs list 301 | for _, p := range d.PAT.Programs { 302 | // Program number 0 is reserved to NIT 303 | if p.ProgramNumber > 0 { 304 | // Program has not already been added 305 | if _, ok := pgms[p.ProgramNumber]; !ok { 306 | pgmsToProcess[p.ProgramNumber] = true 307 | pgms[p.ProgramNumber] = newProgram(p.ProgramNumber, p.ProgramMapID) 308 | } 309 | } 310 | } 311 | } else if d.PMT != nil { 312 | // Program has already been processed 313 | if _, ok := pgmsToProcess[d.PMT.ProgramNumber]; !ok { 314 | continue 315 | } 316 | 317 | // Update program 318 | for _, dsc := range d.PMT.ProgramDescriptors { 319 | pgms[d.PMT.ProgramNumber].Descriptors = append(pgms[d.PMT.ProgramNumber].Descriptors, descriptorToString(dsc)) 320 | } 321 | 322 | // Add elementary streams 323 | for _, es := range d.PMT.ElementaryStreams { 324 | var s = newStream(es.ElementaryPID, es.StreamType) 325 | for _, d := range es.ElementaryStreamDescriptors { 326 | s.Descriptors = append(s.Descriptors, descriptorToString(d)) 327 | } 328 | pgms[d.PMT.ProgramNumber].Streams = append(pgms[d.PMT.ProgramNumber].Streams, s) 329 | } 330 | 331 | // Update list of programs to process 332 | delete(pgmsToProcess, d.PMT.ProgramNumber) 333 | 334 | // All PMTs have been processed 335 | if len(pgmsToProcess) == 0 { 336 | break 337 | } 338 | } 339 | } 340 | 341 | // Build final data 342 | for _, p := range pgms { 343 | o = append(o, p) 344 | } 345 | return 346 | } 347 | 348 | // Program represents a program 349 | type Program struct { 350 | Descriptors []string `json:"descriptors,omitempty"` 351 | ID uint16 `json:"id,omitempty"` 352 | MapID uint16 `json:"map_id,omitempty"` 353 | Streams []*Stream `json:"streams,omitempty"` 354 | } 355 | 356 | // Stream represents a stream 357 | type Stream struct { 358 | Descriptors []string `json:"descriptors,omitempty"` 359 | ID uint16 `json:"id,omitempty"` 360 | Type astits.StreamType `json:"type,omitempty"` 361 | } 362 | 363 | func newProgram(id, mapID uint16) *Program { 364 | return &Program{ 365 | ID: id, 366 | MapID: mapID, 367 | } 368 | } 369 | 370 | func newStream(id uint16, _type astits.StreamType) *Stream { 371 | return &Stream{ 372 | ID: id, 373 | Type: _type, 374 | } 375 | } 376 | 377 | // String implements the Stringer interface 378 | func (p Program) String() (o string) { 379 | o = fmt.Sprintf("[%d] - Map ID: %d", p.ID, p.MapID) 380 | for _, d := range p.Descriptors { 381 | o += fmt.Sprintf(" - %s", d) 382 | } 383 | for _, s := range p.Streams { 384 | o += fmt.Sprintf("\n * %s", s.String()) 385 | } 386 | return 387 | } 388 | 389 | // String implements the Stringer interface 390 | func (s Stream) String() (o string) { 391 | // Get type 392 | var t = fmt.Sprintf("unlisted stream type %d", s.Type) 393 | switch s.Type { 394 | case astits.StreamTypeMPEG1Audio: 395 | t = "MPEG-1 audio" 396 | case astits.StreamTypeMPEG2HalvedSampleRateAudio: 397 | t = "MPEG-2 halved sample rate audio" 398 | case astits.StreamTypeMPEG2PacketizedData: 399 | t = "DVB subtitles/VBI or AC-3" 400 | case astits.StreamTypeADTS: 401 | t = "ADTS" 402 | case astits.StreamTypeH264Video: 403 | t = "H264 video" 404 | case astits.StreamTypeH265Video: 405 | t = "H265 video" 406 | } 407 | 408 | // Output 409 | o = fmt.Sprintf("[%d] - Type: %s", s.ID, t) 410 | for _, d := range s.Descriptors { 411 | o += fmt.Sprintf(" - %s", d) 412 | } 413 | return 414 | } 415 | 416 | func eventsToString(es []*astits.EITDataEvent) string { 417 | var os []string 418 | for idx, e := range es { 419 | os = append(os, eventToString(idx, e)) 420 | } 421 | return strings.Join(os, "\n") 422 | } 423 | 424 | func eventToString(idx int, e *astits.EITDataEvent) (s string) { 425 | s += fmt.Sprintf("- #%d | id: %d | start: %s | duration: %s | status: %s\n", idx+1, e.EventID, e.StartTime.Format("15:04:05"), e.Duration, runningStatusToString(e.RunningStatus)) 426 | var os []string 427 | for _, d := range e.Descriptors { 428 | os = append(os, " - "+descriptorToString(d)) 429 | } 430 | return s + strings.Join(os, "\n") 431 | } 432 | 433 | func runningStatusToString(s uint8) string { 434 | switch s { 435 | case astits.RunningStatusNotRunning: 436 | return "not running" 437 | case astits.RunningStatusPausing: 438 | return "pausing" 439 | case astits.RunningStatusRunning: 440 | return "running" 441 | } 442 | return "unknown" 443 | } 444 | 445 | func descriptorToString(d *astits.Descriptor) string { 446 | switch d.Tag { 447 | case astits.DescriptorTagAC3: 448 | return fmt.Sprintf("[AC3] ac3 asvc: %d | bsid: %d | component type: %d | mainid: %d | info: %s", d.AC3.ASVC, d.AC3.BSID, d.AC3.ComponentType, d.AC3.MainID, d.AC3.AdditionalInfo) 449 | case astits.DescriptorTagComponent: 450 | return fmt.Sprintf("[Component] language: %s | text: %s | component tag: %d | component type: %d | stream content: %d | stream content ext: %d", d.Component.ISO639LanguageCode, d.Component.Text, d.Component.ComponentTag, d.Component.ComponentType, d.Component.StreamContent, d.Component.StreamContentExt) 451 | case astits.DescriptorTagContent: 452 | var os []string 453 | for _, i := range d.Content.Items { 454 | os = append(os, fmt.Sprintf("content nibble 1: %d | content nibble 2: %d | user byte: %d", i.ContentNibbleLevel1, i.ContentNibbleLevel2, i.UserByte)) 455 | } 456 | return "[Content] " + strings.Join(os, " - ") 457 | case astits.DescriptorTagExtendedEvent: 458 | s := fmt.Sprintf("[Extended event] language: %s | text: %s", d.ExtendedEvent.ISO639LanguageCode, d.ExtendedEvent.Text) 459 | for _, i := range d.ExtendedEvent.Items { 460 | s += fmt.Sprintf(" | %s: %s", i.Description, i.Content) 461 | } 462 | return s 463 | case astits.DescriptorTagISO639LanguageAndAudioType: 464 | return fmt.Sprintf("[ISO639 language and audio type] language: %s | audio type: %d", d.ISO639LanguageAndAudioType.Language, d.ISO639LanguageAndAudioType.Type) 465 | case astits.DescriptorTagMaximumBitrate: 466 | return fmt.Sprintf("[Maximum bitrate] maximum bitrate: %d", d.MaximumBitrate.Bitrate) 467 | case astits.DescriptorTagNetworkName: 468 | return fmt.Sprintf("[Network name] network name: %s", d.NetworkName.Name) 469 | case astits.DescriptorTagParentalRating: 470 | var os []string 471 | for _, i := range d.ParentalRating.Items { 472 | os = append(os, fmt.Sprintf("country: %s | rating: %d | minimum age: %d", i.CountryCode, i.Rating, i.MinimumAge())) 473 | } 474 | return "[Parental rating] " + strings.Join(os, " - ") 475 | case astits.DescriptorTagPrivateDataSpecifier: 476 | return fmt.Sprintf("[Private data specifier] specifier: %d", d.PrivateDataSpecifier.Specifier) 477 | case astits.DescriptorTagService: 478 | return fmt.Sprintf("[Service] service %s | provider: %s", d.Service.Name, d.Service.Provider) 479 | case astits.DescriptorTagShortEvent: 480 | return fmt.Sprintf("[Short event] language: %s | name: %s | text: %s", d.ShortEvent.Language, d.ShortEvent.EventName, d.ShortEvent.Text) 481 | case astits.DescriptorTagStreamIdentifier: 482 | return fmt.Sprintf("[Stream identifier] stream identifier component tag: %d", d.StreamIdentifier.ComponentTag) 483 | case astits.DescriptorTagSubtitling: 484 | var os []string 485 | for _, i := range d.Subtitling.Items { 486 | os = append(os, fmt.Sprintf("subtitling composition page: %d | ancillary page %d: %s", i.CompositionPageID, i.AncillaryPageID, i.Language)) 487 | } 488 | return "[Subtitling] " + strings.Join(os, " - ") 489 | case astits.DescriptorTagTeletext: 490 | var os []string 491 | for _, t := range d.Teletext.Items { 492 | os = append(os, fmt.Sprintf("Teletext page %01d%02d: %s", t.Magazine, t.Page, t.Language)) 493 | } 494 | return "[Teletext] " + strings.Join(os, " - ") 495 | } 496 | return fmt.Sprintf("unlisted descriptor tag 0x%x", d.Tag) 497 | } 498 | -------------------------------------------------------------------------------- /data_pes_test.go: -------------------------------------------------------------------------------- 1 | package astits 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/asticode/go-astikit" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestHasPESOptionalHeader(t *testing.T) { 12 | var a []int 13 | for i := 0; i <= 255; i++ { 14 | if !hasPESOptionalHeader(uint8(i)) { 15 | a = append(a, i) 16 | } 17 | } 18 | assert.Equal(t, []int{StreamIDPaddingStream, StreamIDPrivateStream2}, a) 19 | } 20 | 21 | var dsmTrickModeSlow = &DSMTrickMode{ 22 | RepeatControl: 21, 23 | TrickModeControl: TrickModeControlSlowMotion, 24 | } 25 | 26 | func dsmTrickModeSlowBytes() []byte { 27 | buf := &bytes.Buffer{} 28 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: buf}) 29 | w.Write("001") // Control 30 | w.Write("10101") // Repeat control 31 | return buf.Bytes() 32 | } 33 | 34 | type dsmTrickModeTestCase struct { 35 | name string 36 | bytesFunc func(w *astikit.BitsWriter) 37 | trickMode *DSMTrickMode 38 | } 39 | 40 | var dsmTrickModeTestCases = []dsmTrickModeTestCase{ 41 | { 42 | "fast_forward", 43 | func(w *astikit.BitsWriter) { 44 | w.Write("000") // Control 45 | w.Write("10") // Field ID 46 | w.Write("1") // Intra slice refresh 47 | w.Write("11") // Frequency truncation 48 | }, 49 | &DSMTrickMode{ 50 | FieldID: 2, 51 | FrequencyTruncation: 3, 52 | IntraSliceRefresh: 1, 53 | TrickModeControl: TrickModeControlFastForward, 54 | }, 55 | }, 56 | { 57 | "slow_motion", 58 | func(w *astikit.BitsWriter) { 59 | w.Write("001") 60 | w.Write("10101") 61 | }, 62 | &DSMTrickMode{ 63 | RepeatControl: 0b10101, 64 | TrickModeControl: TrickModeControlSlowMotion, 65 | }, 66 | }, 67 | { 68 | "freeze_frame", 69 | func(w *astikit.BitsWriter) { 70 | w.Write("010") // Control 71 | w.Write("10") // Field ID 72 | w.Write("111") // Reserved 73 | }, 74 | &DSMTrickMode{ 75 | FieldID: 2, 76 | TrickModeControl: TrickModeControlFreezeFrame, 77 | }, 78 | }, 79 | { 80 | "fast_reverse", 81 | func(w *astikit.BitsWriter) { 82 | w.Write("011") // Control 83 | w.Write("10") // Field ID 84 | w.Write("1") // Intra slice refresh 85 | w.Write("11") // Frequency truncation 86 | }, 87 | &DSMTrickMode{ 88 | FieldID: 2, 89 | FrequencyTruncation: 3, 90 | IntraSliceRefresh: 1, 91 | TrickModeControl: TrickModeControlFastReverse, 92 | }, 93 | }, 94 | { 95 | "slow_reverse", 96 | func(w *astikit.BitsWriter) { 97 | w.Write("100") 98 | w.Write("01010") 99 | }, 100 | &DSMTrickMode{ 101 | RepeatControl: 0b01010, 102 | TrickModeControl: TrickModeControlSlowReverse, 103 | }, 104 | }, 105 | { 106 | "reserved", 107 | func(w *astikit.BitsWriter) { 108 | w.Write("101") 109 | w.Write("11111") 110 | }, 111 | &DSMTrickMode{ 112 | TrickModeControl: 5, // reserved 113 | }, 114 | }, 115 | } 116 | 117 | func TestParseDSMTrickMode(t *testing.T) { 118 | for _, tc := range dsmTrickModeTestCases { 119 | t.Run(tc.name, func(t *testing.T) { 120 | buf := &bytes.Buffer{} 121 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: buf}) 122 | tc.bytesFunc(w) 123 | assert.Equal(t, parseDSMTrickMode(buf.Bytes()[0]), tc.trickMode) 124 | }) 125 | } 126 | } 127 | 128 | func TestWriteDSMTrickMode(t *testing.T) { 129 | for _, tc := range dsmTrickModeTestCases { 130 | t.Run(tc.name, func(t *testing.T) { 131 | bufExpected := &bytes.Buffer{} 132 | wExpected := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: bufExpected}) 133 | tc.bytesFunc(wExpected) 134 | 135 | bufActual := &bytes.Buffer{} 136 | wActual := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: bufActual}) 137 | 138 | n, err := writeDSMTrickMode(wActual, tc.trickMode) 139 | assert.NoError(t, err) 140 | assert.Equal(t, 1, n) 141 | assert.Equal(t, n, bufActual.Len()) 142 | assert.Equal(t, bufExpected.Bytes(), bufActual.Bytes()) 143 | }) 144 | } 145 | } 146 | 147 | var ptsClockReference = &ClockReference{Base: 5726623061} 148 | 149 | func ptsBytes(flag string) []byte { 150 | buf := &bytes.Buffer{} 151 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: buf}) 152 | w.Write(flag) // Flag 153 | w.Write("101") // 32...30 154 | w.Write("1") // Dummy 155 | w.Write("010101010101010") // 29...15 156 | w.Write("1") // Dummy 157 | w.Write("101010101010101") // 14...0 158 | w.Write("1") // Dummy 159 | return buf.Bytes() 160 | } 161 | 162 | var dtsClockReference = &ClockReference{Base: 5726623060} 163 | 164 | func dtsBytes(flag string) []byte { 165 | buf := &bytes.Buffer{} 166 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: buf}) 167 | w.Write(flag) // Flag 168 | w.Write("101") // 32...30 169 | w.Write("1") // Dummy 170 | w.Write("010101010101010") // 29...15 171 | w.Write("1") // Dummy 172 | w.Write("101010101010100") // 14...0 173 | w.Write("1") // Dummy 174 | return buf.Bytes() 175 | } 176 | 177 | func TestParsePTSOrDTS(t *testing.T) { 178 | v, err := parsePTSOrDTS(astikit.NewBytesIterator(ptsBytes("0010"))) 179 | assert.Equal(t, v, ptsClockReference) 180 | assert.NoError(t, err) 181 | } 182 | 183 | func TestWritePTSOrDTS(t *testing.T) { 184 | buf := &bytes.Buffer{} 185 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: buf}) 186 | n, err := writePTSOrDTS(w, uint8(0b0010), dtsClockReference) 187 | assert.NoError(t, err) 188 | assert.Equal(t, n, 5) 189 | assert.Equal(t, n, buf.Len()) 190 | assert.Equal(t, dtsBytes("0010"), buf.Bytes()) 191 | } 192 | 193 | func escrBytes() []byte { 194 | buf := &bytes.Buffer{} 195 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: buf}) 196 | w.Write("11") // Dummy 197 | w.Write("011") // 32...30 198 | w.Write("1") // Dummy 199 | w.Write("000010111110000") // 29...15 200 | w.Write("1") // Dummy 201 | w.Write("000010111001111") // 14...0 202 | w.Write("1") // Dummy 203 | w.Write("000111010") // Ext 204 | w.Write("1") // Dummy 205 | return buf.Bytes() 206 | } 207 | 208 | func TestParseESCR(t *testing.T) { 209 | v, err := parseESCR(astikit.NewBytesIterator(escrBytes())) 210 | assert.Equal(t, v, clockReference) 211 | assert.NoError(t, err) 212 | } 213 | 214 | func TestWriteESCR(t *testing.T) { 215 | buf := &bytes.Buffer{} 216 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: buf}) 217 | n, err := writeESCR(w, clockReference) 218 | assert.NoError(t, err) 219 | assert.Equal(t, n, 6) 220 | assert.Equal(t, n, buf.Len()) 221 | assert.Equal(t, escrBytes(), buf.Bytes()) 222 | } 223 | 224 | type pesTestCase struct { 225 | name string 226 | headerBytesFunc func(w *astikit.BitsWriter, withStuffing bool, withCRC bool) 227 | optionalHeaderBytesFunc func(w *astikit.BitsWriter, withStuffing bool, withCRC bool) 228 | bytesFunc func(w *astikit.BitsWriter, withStuffing bool, withCRC bool) 229 | pesData *PESData 230 | } 231 | 232 | var pesTestCases = []pesTestCase{ 233 | { 234 | "without_header", 235 | func(w *astikit.BitsWriter, withStuffing bool, withCRC bool) { 236 | w.Write("000000000000000000000001") // Prefix 237 | w.Write(uint8(StreamIDPaddingStream)) // Stream ID 238 | w.Write(uint16(4)) // Packet length 239 | }, 240 | func(w *astikit.BitsWriter, withStuffing bool, withCRC bool) { 241 | // do nothing here 242 | }, 243 | func(w *astikit.BitsWriter, withStuffing bool, withCRC bool) { 244 | w.Write([]byte("data")) // Data 245 | }, 246 | &PESData{ 247 | Data: []byte("data"), 248 | Header: &PESHeader{ 249 | PacketLength: 4, 250 | StreamID: StreamIDPaddingStream, 251 | }, 252 | }, 253 | }, 254 | { 255 | "with_header", 256 | func(w *astikit.BitsWriter, withStuffing bool, withCRC bool) { 257 | packetLength := 67 258 | stuffing := []byte("stuff") 259 | 260 | if !withStuffing { 261 | packetLength -= len(stuffing) 262 | } 263 | 264 | if !withCRC { 265 | packetLength -= 2 266 | } 267 | 268 | w.Write("000000000000000000000001") // Prefix 269 | w.Write(uint8(1)) // Stream ID 270 | w.Write(uint16(packetLength)) // Packet length 271 | 272 | }, 273 | func(w *astikit.BitsWriter, withStuffing bool, withCRC bool) { 274 | optionalHeaderLength := 60 275 | stuffing := []byte("stuff") 276 | 277 | if !withStuffing { 278 | optionalHeaderLength -= len(stuffing) 279 | } 280 | 281 | if !withCRC { 282 | optionalHeaderLength -= 2 283 | } 284 | 285 | w.Write("10") // Marker bits 286 | w.Write("01") // Scrambling control 287 | w.Write("1") // Priority 288 | w.Write("1") // Data alignment indicator 289 | w.Write("1") // Copyright 290 | w.Write("1") // Original or copy 291 | w.Write("11") // PTS/DTS indicator 292 | w.Write("1") // ESCR flag 293 | w.Write("1") // ES rate flag 294 | w.Write("1") // DSM trick mode flag 295 | w.Write("1") // Additional copy flag 296 | w.Write(withCRC) // CRC flag 297 | w.Write("1") // Extension flag 298 | w.Write(uint8(optionalHeaderLength)) // Header length 299 | w.Write(ptsBytes("0011")) // PTS 300 | w.Write(dtsBytes("0001")) // DTS 301 | w.Write(escrBytes()) // ESCR 302 | w.Write("101010101010101010101011") // ES rate 303 | w.Write(dsmTrickModeSlowBytes()) // DSM trick mode 304 | w.Write("11111111") // Additional copy info 305 | if withCRC { 306 | w.Write(uint16(4)) // CRC 307 | } 308 | // Extension starts here 309 | w.Write("1") // Private data flag 310 | w.Write("0") // Pack header field flag 311 | w.Write("1") // Program packet sequence counter flag 312 | w.Write("1") // PSTD buffer flag 313 | w.Write("111") // Dummy 314 | w.Write("1") // Extension 2 flag 315 | w.Write([]byte("1234567890123456")) // Private data 316 | //w.Write(uint8(5)) // Pack field 317 | w.Write("1101010111010101") // Packet sequence counter 318 | w.Write("0111010101010101") // PSTD buffer 319 | w.Write("10001010") // Extension 2 header 320 | w.Write([]byte("extension2")) // Extension 2 data 321 | if withStuffing { 322 | w.Write(stuffing) // Optional header stuffing bytes 323 | } 324 | }, 325 | func(w *astikit.BitsWriter, withStuffing bool, withCRC bool) { 326 | stuffing := []byte("stuff") 327 | w.Write([]byte("data")) // Data 328 | if withStuffing { 329 | w.Write(stuffing) // Stuffing 330 | } 331 | }, 332 | &PESData{ 333 | Data: []byte("data"), 334 | Header: &PESHeader{ 335 | OptionalHeader: &PESOptionalHeader{ 336 | AdditionalCopyInfo: 127, 337 | CRC: 4, 338 | DataAlignmentIndicator: true, 339 | DSMTrickMode: dsmTrickModeSlow, 340 | DTS: dtsClockReference, 341 | ESCR: clockReference, 342 | ESRate: 1398101, 343 | Extension2Data: []byte("extension2"), 344 | Extension2Length: 10, 345 | HasAdditionalCopyInfo: true, 346 | HasCRC: true, 347 | HasDSMTrickMode: true, 348 | HasESCR: true, 349 | HasESRate: true, 350 | HasExtension: true, 351 | HasExtension2: true, 352 | HasPackHeaderField: false, 353 | HasPrivateData: true, 354 | HasProgramPacketSequenceCounter: true, 355 | HasPSTDBuffer: true, 356 | HeaderLength: 60, 357 | IsCopyrighted: true, 358 | IsOriginal: true, 359 | MarkerBits: 2, 360 | MPEG1OrMPEG2ID: 1, 361 | OriginalStuffingLength: 21, 362 | PacketSequenceCounter: 85, 363 | //PackField: 5, 364 | Priority: true, 365 | PrivateData: []byte("1234567890123456"), 366 | PSTDBufferScale: 1, 367 | PSTDBufferSize: 5461, 368 | PTSDTSIndicator: 3, 369 | PTS: ptsClockReference, 370 | ScramblingControl: 1, 371 | }, 372 | PacketLength: 67, 373 | StreamID: 1, 374 | }, 375 | }, 376 | }, 377 | } 378 | 379 | // used by TestParseData 380 | func pesWithHeaderBytes() []byte { 381 | buf := bytes.Buffer{} 382 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: &buf}) 383 | pesTestCases[1].headerBytesFunc(w, true, true) 384 | pesTestCases[1].optionalHeaderBytesFunc(w, true, true) 385 | pesTestCases[1].bytesFunc(w, true, true) 386 | return buf.Bytes() 387 | } 388 | 389 | // used by TestParseData 390 | func pesWithHeader() *PESData { 391 | return pesTestCases[1].pesData 392 | } 393 | 394 | func TestParsePESData(t *testing.T) { 395 | for _, tc := range pesTestCases { 396 | t.Run(tc.name, func(t *testing.T) { 397 | buf := bytes.Buffer{} 398 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: &buf}) 399 | tc.headerBytesFunc(w, true, true) 400 | tc.optionalHeaderBytesFunc(w, true, true) 401 | tc.bytesFunc(w, true, true) 402 | d, err := parsePESData(astikit.NewBytesIterator(buf.Bytes())) 403 | assert.NoError(t, err) 404 | assert.Equal(t, tc.pesData, d) 405 | }) 406 | } 407 | } 408 | 409 | func TestWritePESData(t *testing.T) { 410 | for _, tc := range pesTestCases { 411 | t.Run(tc.name, func(t *testing.T) { 412 | bufExpected := bytes.Buffer{} 413 | wExpected := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: &bufExpected}) 414 | tc.headerBytesFunc(wExpected, false, false) 415 | tc.optionalHeaderBytesFunc(wExpected, false, false) 416 | tc.bytesFunc(wExpected, false, false) 417 | 418 | bufActual := bytes.Buffer{} 419 | wActual := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: &bufActual}) 420 | 421 | start := true 422 | totalBytes := 0 423 | payloadPos := 0 424 | 425 | for payloadPos+1 < len(tc.pesData.Data) { 426 | n, payloadN, err := writePESData( 427 | wActual, 428 | tc.pesData.Header, 429 | tc.pesData.Data[payloadPos:], 430 | start, 431 | MpegTsPacketSize-mpegTsPacketHeaderSize, 432 | ) 433 | assert.NoError(t, err) 434 | start = false 435 | 436 | totalBytes += n 437 | payloadPos += payloadN 438 | } 439 | 440 | assert.Equal(t, totalBytes, bufActual.Len()) 441 | assert.Equal(t, bufExpected.Len(), bufActual.Len()) 442 | assert.Equal(t, bufExpected.Bytes(), bufActual.Bytes()) 443 | }) 444 | } 445 | } 446 | 447 | func TestWritePESHeader(t *testing.T) { 448 | for _, tc := range pesTestCases { 449 | t.Run(tc.name, func(t *testing.T) { 450 | bufExpected := bytes.Buffer{} 451 | wExpected := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: &bufExpected}) 452 | tc.headerBytesFunc(wExpected, false, false) 453 | tc.optionalHeaderBytesFunc(wExpected, false, false) 454 | 455 | bufActual := bytes.Buffer{} 456 | wActual := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: &bufActual}) 457 | 458 | n, err := writePESHeader(wActual, tc.pesData.Header, len(tc.pesData.Data)) 459 | assert.NoError(t, err) 460 | assert.Equal(t, n, bufActual.Len()) 461 | assert.Equal(t, bufExpected.Len(), bufActual.Len()) 462 | assert.Equal(t, bufExpected.Bytes(), bufActual.Bytes()) 463 | }) 464 | } 465 | } 466 | 467 | func TestWritePESOptionalHeader(t *testing.T) { 468 | for _, tc := range pesTestCases { 469 | t.Run(tc.name, func(t *testing.T) { 470 | bufExpected := bytes.Buffer{} 471 | wExpected := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: &bufExpected}) 472 | tc.optionalHeaderBytesFunc(wExpected, false, false) 473 | 474 | bufActual := bytes.Buffer{} 475 | wActual := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: &bufActual}) 476 | 477 | n, err := writePESOptionalHeader(wActual, tc.pesData.Header.OptionalHeader) 478 | assert.NoError(t, err) 479 | assert.Equal(t, n, bufActual.Len()) 480 | assert.Equal(t, bufExpected.Len(), bufActual.Len()) 481 | assert.Equal(t, bufExpected.Bytes(), bufActual.Bytes()) 482 | }) 483 | } 484 | } 485 | 486 | func BenchmarkParsePESData(b *testing.B) { 487 | bss := make([][]byte, len(pesTestCases)) 488 | 489 | for ti, tc := range pesTestCases { 490 | buf := bytes.Buffer{} 491 | w := astikit.NewBitsWriter(astikit.BitsWriterOptions{Writer: &buf}) 492 | tc.headerBytesFunc(w, true, true) 493 | tc.optionalHeaderBytesFunc(w, true, true) 494 | tc.bytesFunc(w, true, true) 495 | bss[ti] = buf.Bytes() 496 | } 497 | 498 | for ti, tc := range pesTestCases { 499 | b.Run(tc.name, func(b *testing.B) { 500 | b.ReportAllocs() 501 | for i := 0; i < b.N; i++ { 502 | parsePESData(astikit.NewBytesIterator(bss[ti])) 503 | } 504 | }) 505 | } 506 | } 507 | --------------------------------------------------------------------------------