Skip to content

Commit 7a46f0b

Browse files
authored
Demuxer: emit PAT/PMT when seen (#24)
* packetPool: flush old packets whenever new payload starts * packetPool: split out a packetAccumulator so the pool only has to keep one per-pid map * packetPool: accept demuxer pointer * Demuxer: helper wrapper for parseData * packetAccumulator: flush PAT/PMT as soon as parsed successfully * packetAccumulator: cleanups * test to ensure demuxer returns PAT/PMT when seen * decouple packetPool from Demuxer * strip all whitespace from hex before parsing * move comment * decouple packetPool and packetAccumulator
1 parent c44c340 commit 7a46f0b

File tree

7 files changed

+129
-42
lines changed

7 files changed

+129
-42
lines changed

data.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ type MuxerData struct {
3535
}
3636

3737
// 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) {
38+
func parseData(ps []*Packet, prs PacketsParser, pm *programMap) (ds []*DemuxerData, err error) {
3939
// Use custom parser first
4040
if prs != nil {
4141
var skip bool
@@ -99,7 +99,7 @@ func parseData(ps []*Packet, prs PacketsParser, pm programMap) (ds []*DemuxerDat
9999
}
100100

101101
// isPSIPayload checks whether the payload is a PSI one
102-
func isPSIPayload(pid uint16, pm programMap) bool {
102+
func isPSIPayload(pid uint16, pm *programMap) bool {
103103
return pid == PIDPAT || // PAT
104104
pm.exists(pid) || // PMT
105105
((pid >= 0x10 && pid <= 0x14) || (pid >= 0x1e && pid <= 0x1f)) //DVB

demuxer.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ type Demuxer struct {
2727
optPacketsParser PacketsParser
2828
packetBuffer *packetBuffer
2929
packetPool *packetPool
30-
programMap programMap
30+
programMap *programMap
3131
r io.Reader
3232
}
3333

@@ -40,10 +40,10 @@ func NewDemuxer(ctx context.Context, r io.Reader, opts ...func(*Demuxer)) (d *De
4040
// Init
4141
d = &Demuxer{
4242
ctx: ctx,
43-
packetPool: newPacketPool(),
4443
programMap: newProgramMap(),
4544
r: r,
4645
}
46+
d.packetPool = newPacketPool(d.optPacketsParser, d.programMap)
4747

4848
// Apply options
4949
for _, opt := range opts {
@@ -180,7 +180,7 @@ func (dmx *Demuxer) updateData(ds []*DemuxerData) (d *DemuxerData) {
180180
func (dmx *Demuxer) Rewind() (n int64, err error) {
181181
dmx.dataBuffer = []*DemuxerData{}
182182
dmx.packetBuffer = nil
183-
dmx.packetPool = newPacketPool()
183+
dmx.packetPool = newPacketPool(dmx.optPacketsParser, dmx.programMap)
184184
if n, err = rewind(dmx.r); err != nil {
185185
err = fmt.Errorf("astits: rewinding reader failed: %w", err)
186186
return

demuxer_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,31 @@ package astits
33
import (
44
"bytes"
55
"context"
6+
"encoding/hex"
67
"fmt"
78
"io"
9+
"strings"
810
"testing"
11+
"unicode"
912

1013
"github.com/asticode/go-astikit"
1114
"github.com/stretchr/testify/assert"
1215
)
1316

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+
1431
func TestDemuxerNew(t *testing.T) {
1532
ps := 1
1633
pp := func(ps []*Packet) (ds []*DemuxerData, skip bool, err error) { return }
@@ -84,6 +101,37 @@ func TestDemuxerNextData(t *testing.T) {
84101
assert.EqualError(t, err, ErrNoMorePackets.Error())
85102
}
86103

104+
func TestDemuxerNextDataPATPMT(t *testing.T) {
105+
pat := hexToBytes(`474000100000b00d0001c100000001f0002ab104b2ffffffffffffffff
106+
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
107+
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
108+
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
109+
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
110+
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
111+
ffffffffffffffffff`)
112+
pmt := hexToBytes(`475000100002b0170001c10000e100f0001be100f0000fe101f0002f44
113+
b99bffffffffffffffffffffffffffffffffffffffffffffffffffffffff
114+
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
115+
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
116+
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
117+
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
118+
ffffffffffffffffff`)
119+
r := bytes.NewReader(append(pat, pmt...))
120+
dmx := NewDemuxer(context.Background(), r, DemuxerOptPacketSize(188))
121+
assert.Equal(t, 188*2, r.Len())
122+
123+
d, err := dmx.NextData()
124+
assert.NoError(t, err)
125+
assert.Equal(t, uint16(0), d.FirstPacket.Header.PID)
126+
assert.NotNil(t, d.PAT)
127+
assert.Equal(t, 188, r.Len())
128+
129+
d, err = dmx.NextData()
130+
assert.NoError(t, err)
131+
assert.Equal(t, uint16(0x1000), d.FirstPacket.Header.PID)
132+
assert.NotNil(t, d.PMT)
133+
}
134+
87135
func TestDemuxerRewind(t *testing.T) {
88136
r := bytes.NewReader([]byte("content"))
89137
dmx := NewDemuxer(context.Background(), r)

muxer.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import (
44
"bytes"
55
"context"
66
"errors"
7-
"github.com/asticode/go-astikit"
87
"io"
8+
9+
"github.com/asticode/go-astikit"
910
)
1011

1112
const (
@@ -28,7 +29,7 @@ type Muxer struct {
2829
packetSize int
2930
tablesRetransmitPeriod int // period in PES packets
3031

31-
pm programMap // pid -> programNumber
32+
pm *programMap // pid -> programNumber
3233
pmt PMTData
3334
nextPID uint16
3435
patVersion wrappingCounter

packet_pool.go

Lines changed: 70 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,76 @@ import (
55
"sync"
66
)
77

8-
// packetPool represents a pool of packets
8+
// packetAccumulator keeps track of packets for a single PID and decides when to flush them
9+
type packetAccumulator struct {
10+
parser PacketsParser
11+
pid uint16
12+
programMap *programMap
13+
q []*Packet
14+
}
15+
16+
// newPacketAccumulator creates a new packet queue for a single PID
17+
func newPacketAccumulator(pid uint16, parser PacketsParser, programMap *programMap) *packetAccumulator {
18+
return &packetAccumulator{
19+
parser: parser,
20+
pid: pid,
21+
programMap: programMap,
22+
}
23+
}
24+
25+
// add adds a new packet for this PID to the queue
26+
func (b *packetAccumulator) add(p *Packet) (ps []*Packet) {
27+
mps := b.q
28+
29+
// Empty buffer if we detect a discontinuity
30+
if hasDiscontinuity(mps, p) {
31+
mps = []*Packet{}
32+
}
33+
34+
// Throw away packet if it's the same as the previous one
35+
if isSameAsPrevious(mps, p) {
36+
return
37+
}
38+
39+
// Flush buffer if new payload starts here
40+
if p.Header.PayloadUnitStartIndicator {
41+
ps = mps
42+
mps = []*Packet{p}
43+
} else {
44+
mps = append(mps, p)
45+
}
46+
47+
// Check if PSI payload is complete
48+
if b.programMap != nil &&
49+
(b.pid == PIDPAT || b.programMap.exists(b.pid)) {
50+
// TODO Use partial data parsing instead
51+
if _, err := parseData(mps, b.parser, b.programMap); err == nil {
52+
ps = mps
53+
mps = nil
54+
}
55+
}
56+
57+
b.q = mps
58+
return
59+
}
60+
61+
// packetPool represents a queue of packets for each PID in the stream
962
type packetPool struct {
10-
b map[uint16][]*Packet // Indexed by PID
63+
b map[uint16]*packetAccumulator // Indexed by PID
1164
m *sync.Mutex
65+
66+
parser PacketsParser
67+
programMap *programMap
1268
}
1369

14-
// newPacketPool creates a new packet pool
15-
func newPacketPool() *packetPool {
70+
// newPacketPool creates a new packet pool with an optional parser and programMap
71+
func newPacketPool(parser PacketsParser, programMap *programMap) *packetPool {
1672
return &packetPool{
17-
b: make(map[uint16][]*Packet),
73+
b: make(map[uint16]*packetAccumulator),
1874
m: &sync.Mutex{},
75+
76+
parser: parser,
77+
programMap: programMap,
1978
}
2079
}
2180

@@ -36,34 +95,13 @@ func (b *packetPool) add(p *Packet) (ps []*Packet) {
3695
b.m.Lock()
3796
defer b.m.Unlock()
3897

39-
// Init buffer
40-
var mps []*Packet
41-
var ok bool
42-
if mps, ok = b.b[p.Header.PID]; !ok {
43-
mps = []*Packet{}
44-
}
45-
46-
// Empty buffer if we detect a discontinuity
47-
if hasDiscontinuity(mps, p) {
48-
mps = []*Packet{}
49-
}
50-
51-
// Throw away packet if it's the same as the previous one
52-
if isSameAsPrevious(mps, p) {
53-
return
98+
// Make sure accumulator exists
99+
if _, ok := b.b[p.Header.PID]; !ok {
100+
b.b[p.Header.PID] = newPacketAccumulator(p.Header.PID, b.parser, b.programMap)
54101
}
55102

56-
// Flush buffer if new payload starts here
57-
if p.Header.PayloadUnitStartIndicator {
58-
ps = mps
59-
mps = []*Packet{p}
60-
} else {
61-
mps = append(mps, p)
62-
}
63-
64-
// Assign
65-
b.b[p.Header.PID] = mps
66-
return
103+
// Add to the accumulator
104+
return b.b[p.Header.PID].add(p)
67105
}
68106

69107
// dump dumps the packet pool by looking for the first item with packets inside
@@ -76,7 +114,7 @@ func (b *packetPool) dump() (ps []*Packet) {
76114
}
77115
sort.Ints(keys)
78116
for _, k := range keys {
79-
ps = b.b[uint16(k)]
117+
ps = b.b[uint16(k)].q
80118
delete(b.b, uint16(k))
81119
if len(ps) > 0 {
82120
return

packet_pool_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ func TestIsSameAsPrevious(t *testing.T) {
2121
}
2222

2323
func TestPacketPool(t *testing.T) {
24-
b := newPacketPool()
24+
b := newPacketPool(nil, nil)
2525
ps := b.add(&Packet{Header: &PacketHeader{ContinuityCounter: 0, HasPayload: true, PID: 1}})
2626
assert.Len(t, ps, 0)
2727
ps = b.add(&Packet{Header: &PacketHeader{ContinuityCounter: 1, HasPayload: true, PayloadUnitStartIndicator: true, PID: 1}})

program_map.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ type programMap struct {
99
}
1010

1111
// newProgramMap creates a new program ids map
12-
func newProgramMap() programMap {
13-
return programMap{
12+
func newProgramMap() *programMap {
13+
return &programMap{
1414
m: &sync.Mutex{},
1515
p: make(map[uint16]uint16),
1616
}

0 commit comments

Comments
 (0)