clone

a desktop tv and program guide for over-the-air broadcasts: what's on air, what your tuner gets, live tv, recordings

main
airwaves / internal / ts / psi_test.go
psi_test.go 7.2K
1package ts_test23import (4 "reflect"5 "testing"67 "airwaves/internal/ts"8 "airwaves/internal/ts/tstest"9)1011func demux(b []byte) *ts.Demux {12 d := ts.NewDemux()13 for _, p := range packets(b) {14 d.Packet(p)15 }16 return d17}1819type stream struct {20 PID int21 Codec string22 Lang string23 Described bool24}2526func streams(p ts.Program) []stream {27 var out []stream28 for _, s := range p.Streams {29 out = append(out, stream{s.PID, s.Codec(), s.Lang, s.Described})30 }31 return out32}3334func TestDemux(t *testing.T) {35 d := demux(denver.Packets(40))36 if !d.Ready() {37 t.Fatal("not ready after the tables")38 }39 if tsid, ok := d.TSID(); !ok || tsid != 467 {40 t.Errorf("TSID = %d, %v", tsid, ok)41 }42 progs := d.Programs()43 if len(progs) != 3 {44 t.Fatalf("%d programs, want 3", len(progs))45 }46 want := map[int][]stream{47 6: {{0x31, "MPEG2VIDEO", "", false}, {0x34, "AC3", "eng", false}, {0x35, "AC3", "spa", false}, {0x36, "AC3", "eng", true}},48 7: {{0x41, "H264", "", false}, {0x44, "EAC3", "eng", false}, {0x45, "AAC", "spa", true}},49 8: {{0x51, "MPEG2VIDEO", "", false}, {0x54, "AC3", "", false}},50 }51 for _, p := range progs {52 if got := streams(p); !reflect.DeepEqual(got, want[p.Number]) {53 t.Errorf("program %d streams = %+v, want %+v", p.Number, got, want[p.Number])54 }55 }56 p, _ := d.Program(6)57 if p.PMTPID != 0x30 || p.PCRPID != 0x31 || len(p.Audio()) != 3 || p.Audio()[0].Lang != "eng" {58 t.Errorf("program 6 = %+v", p)59 }60 chans := d.Channels()61 wantChans := []ts.VirtualChannel{62 {Major: 2, Minor: 1, Name: "KWGN-DT", Program: 6, TSID: 467, ServiceType: 2},63 {Major: 2, Minor: 2, Name: "Antenna", Program: 7, TSID: 467, ServiceType: 2},64 {Major: 2, Minor: 3, Name: "Court", Program: 8, TSID: 467, ServiceType: 2},65 }66 if !reflect.DeepEqual(chans, wantChans) {67 t.Errorf("channels = %+v", chans)68 }69}7071func TestDemuxNotReady(t *testing.T) {72 d := ts.NewDemux()73 if d.Ready() || d.Channels() != nil {74 t.Error("ready, or with channels, before any packet")75 }76 if _, ok := d.TSID(); ok {77 t.Error("a TSID before the PAT")78 }79 // The PAT alone: the PMTs it lists are still to come.80 d = demux(tstest.Packetize(0, patSection(1, 0, [][2]int{{6, 0x30}})))81 if d.Ready() {82 t.Error("ready without the PMT")83 }84}8586// TestDemuxBigVCT reads a VCT spanning packets.87func TestDemuxBigVCT(t *testing.T) {88 m := tstest.Mux{TSID: 1}89 for i := range 9 {90 m.Programs = append(m.Programs, tstest.Program{Number: i + 1, PMTPID: 0x100 + i, VideoPID: 0x200 + i, Major: 50, Minor: i + 1, Name: "KTFD"})91 }92 chans := demux(m.Packets(60)).Channels()93 if len(chans) != 9 || chans[8].Minor != 9 || chans[8].Program != 9 {94 t.Errorf("channels = %+v", chans)95 }96}9798func patSection(tsid, version int, progs [][2]int) []byte {99 s := []byte{0x00, 0xb0, 0, byte(tsid >> 8), byte(tsid), 0xc1 | byte(version)<<1, 0, 0}100 for _, p := range progs {101 s = append(s, byte(p[0]>>8), byte(p[0]), 0xe0|byte(p[1]>>8), byte(p[1]))102 }103 return tstest.Section(s)104}105106// pmtSection is a PMT whose streams are stream_type, PID and descriptors.107func pmtSection(number, version, pcr int, es ...[]byte) []byte {108 s := []byte{0x02, 0xb0, 0, byte(number >> 8), byte(number), 0xc1 | byte(version)<<1, 0, 0, 0xe0 | byte(pcr>>8), byte(pcr), 0xf0, 0}109 for _, e := range es {110 s = append(s, e...)111 }112 return tstest.Section(s)113}114115func es(typ byte, pid int, desc ...byte) []byte {116 return append([]byte{typ, 0xe0 | byte(pid>>8), byte(pid), 0xf0, byte(len(desc))}, desc...)117}118119func TestDemuxDescriptors(t *testing.T) {120 pmt := pmtSection(3, 0, 0x31,121 es(0x02, 0x31),122 // AC-3 descriptor, 1+1 mode (a second langcod), visually impaired,123 // with 3 bytes of text and then the language.124 es(0x81, 0x34, 0x81, 14, 0x08, 0x38, 2<<5|0<<1|1, 0xff, 0xff, 0x00, 3<<1|1, 'A', 'B', 'C', 0xbf, 'S', 'P', 'A'),125 // AC-3 descriptor with no language, and an ISO 639 one.126 es(0x81, 0x35, 0x81, 3, 0x08, 0x38, 0x05, 0x0a, 4, 'f', 'r', 'a', 0),127 // Private data: DVB's AC-4, E-AC-3 and AC-3 descriptors, and128 // registration.129 es(0x06, 0x36, 0x7f, 2, 0x15, 0x00),130 es(0x06, 0x37, 0x7a, 1, 0x00),131 es(0x06, 0x38, 0x05, 4, 'A', 'C', '-', '3'),132 es(0x06, 0x39), // data133 es(0x24, 0x3a),134 )135 b := append(tstest.Packetize(0, patSection(1, 0, [][2]int{{3, 0x30}})), tstest.Packetize(0x30, pmt)...)136 p, ok := demux(b).Program(3)137 if !ok {138 t.Fatal("no PMT")139 }140 want := []stream{141 {0x31, "MPEG2VIDEO", "", false},142 {0x34, "AC3", "spa", true},143 {0x35, "AC3", "fra", false},144 {0x36, "AC4", "", false},145 {0x37, "EAC3", "", false},146 {0x38, "AC3", "", false},147 {0x39, "", "", false},148 {0x3a, "HEVC", "", false},149 }150 if got := streams(p); !reflect.DeepEqual(got, want) {151 t.Errorf("streams = %+v\nwant %+v", got, want)152 }153 if !p.Streams[0].Video() || p.Streams[0].Audio() || !p.Streams[3].Audio() || p.Streams[6].Audio() || p.Streams[6].Video() {154 t.Error("Video and Audio")155 }156 if len(p.Audio()) != 5 {157 t.Errorf("%d audio streams, want 5", len(p.Audio()))158 }159}160161// TestDemuxSections reads sections that share packets and cross from one162// packet to the next, found by the pointer field.163func TestDemuxSections(t *testing.T) {164 big := func(n, pid int) []byte {165 var e [][]byte166 for i := range 20 {167 e = append(e, es(0x81, pid+i, 0x0a, 4, 'e', 'n', 'g', 0))168 }169 return pmtSection(n, 0, pid, e...)170 }171 // Two programs' PMTs on one PID, back to back: the first ends, and the172 // second starts, partway into the second packet.173 b := append(tstest.Packetize(0, patSection(1, 0, [][2]int{{1, 0x30}, {2, 0x30}})),174 tstest.Packetize(0x30, big(1, 0x100), big(2, 0x200))...)175 d := demux(b)176 p1, ok1 := d.Program(1)177 p2, ok2 := d.Program(2)178 if !ok1 || !ok2 || len(p1.Streams) != 20 || len(p2.Streams) != 20 || p2.Streams[19].PID != 0x213 || !d.Ready() {179 t.Errorf("programs %v %v: %d and %d streams", ok1, ok2, len(p1.Streams), len(p2.Streams))180 }181}182183func TestDemuxVersions(t *testing.T) {184 d := ts.NewDemux()185 next := map[int]byte{} // continuity counters, carried on between sections186 feed := func(pid int, s []byte) {187 for _, p := range packets(tstest.Packetize(pid, s)) {188 p[3] = p[3]&0xf0 | next[pid]189 next[pid] = (next[pid] + 1) & 0x0f190 d.Packet(p)191 }192 }193 feed(0, patSection(1, 0, [][2]int{{1, 0x30}, {2, 0x40}}))194 feed(0x30, pmtSection(1, 0, 0x31, es(0x02, 0x31)))195 feed(0x40, pmtSection(2, 0, 0x41, es(0x02, 0x41)))196 if len(d.Programs()) != 2 {197 t.Fatalf("%d programs", len(d.Programs()))198 }199 // A new PMT version adds a stream.200 feed(0x30, pmtSection(1, 1, 0x31, es(0x02, 0x31), es(0x81, 0x34)))201 if p, _ := d.Program(1); len(p.Streams) != 2 {202 t.Errorf("program 1 after its new PMT: %+v", p)203 }204 // A new PAT drops program 2.205 feed(0, patSection(1, 1, [][2]int{{1, 0x30}}))206 if progs := d.Programs(); len(progs) != 1 || progs[0].Number != 1 {207 t.Errorf("programs after the new PAT: %+v", progs)208 }209 if !d.Ready() {210 t.Error("not ready")211 }212}213214func TestDemuxBadCRC(t *testing.T) {215 pmt := pmtSection(1, 0, 0x31, es(0x02, 0x31))216 pmt[len(pmt)-1] ^= 1217 b := append(tstest.Packetize(0, patSection(1, 0, [][2]int{{1, 0x30}})), tstest.Packetize(0x30, pmt)...)218 if _, ok := demux(b).Program(1); ok {219 t.Error("read a PMT with a bad CRC")220 }221 // A damaged packet is skipped.222 b = append(tstest.Packetize(0, patSection(1, 0, [][2]int{{1, 0x30}})), tstest.Packetize(0x30, pmtSection(1, 0, 0x31))...)223 tstest.SetTEI(b, 1)224 if _, ok := demux(b).Program(1); ok {225 t.Error("read a PMT from a flagged packet")226 }227}