1// Package tstest builds synthetic MPEG transport streams for tests: a2// multiplex's tables (PAT, PMTs and ATSC's VCT) and packets of its3// programs' streams, with correct continuity counters.4package tstest56import (7 "cmp"8 "unicode/utf16"910 "airwaves/internal/ts"11)1213// Audio is an audio stream of a program.14type Audio struct {15 PID int16 // Type is the stream_type; zero is 0x81, ATSC's AC-3, described with17 // ATSC's AC-3 audio descriptor. Other types get an ISO 639 language18 // descriptor when they have a language or are described.19 Type byte20 Lang string21 Described bool22}2324// Program is a program of a multiplex.25type Program struct {26 Number, PMTPID, VideoPID int27 // VideoType is the video's stream_type; zero is 0x02, MPEG-2 video.28 VideoType byte29 Audio []Audio30 // Major and Minor are the program's virtual channel in the VCT, Name31 // its short name; it has none when Major is zero.32 Major, Minor int33 Name string34}3536// Mux is a multiplex, as one RF channel carries it.37type Mux struct {38 TSID int39 Programs []Program40 // NoVCT leaves out the VCT, as a broadcast without PSIP would.41 NoVCT bool42}4344// Packets returns n packets of the multiplex: its tables first and again45// every 20 packets or so, and between them packets of each program's46// streams in turn. Video packets carry a PCR every eighth packet.47func (m Mux) Packets(n int) []byte {48 g := &gen{cc: map[int]byte{}}49 var pids []int50 for _, p := range m.Programs {51 if p.VideoPID > 0 {52 pids = append(pids, p.VideoPID)53 }54 for _, a := range p.Audio {55 pids = append(pids, a.PID)56 }57 }58 video := map[int]bool{}59 for _, p := range m.Programs {60 video[p.VideoPID] = true61 }62 for i := 0; len(g.out) < n*ts.PacketSize; i++ {63 if i%20 == 0 || len(pids) == 0 {64 g.section(0, m.pat())65 for _, p := range m.Programs {66 g.section(p.PMTPID, pmt(p))67 }68 if !m.NoVCT {69 g.section(0x1ffb, m.vct())70 }71 continue72 }73 pid := pids[i%len(pids)]74 g.es(pid, video[pid] && i%8 == 1)75 }76 return g.out[:n*ts.PacketSize]77}7879// Packetize carries sections, back to back, in packets of pid: a packet80// where a section starts has the payload unit start flag and a pointer to81// it, and the last packet is padded with stuffing. Continuity counters82// start at 0.83func Packetize(pid int, sections ...[]byte) []byte {84 g := &gen{cc: map[int]byte{}}85 g.section(pid, sections...)86 return g.out87}8889// Section finishes a PSI section: it sets the section length from len(s)90// and appends the CRC. s holds the table ID, two bytes for the flags and91// the length, and the rest of the section without its CRC.92func Section(s []byte) []byte {93 n := len(s) + 4 - 394 s[1] = s[1]&0xf0 | byte(n>>8)&0x0f95 s[2] = byte(n)96 crc := ts.CRC32(s)97 return append(s, byte(crc>>24), byte(crc>>16), byte(crc>>8), byte(crc))98}99100// SetTEI sets the transport error indicator of packet i, as a demodulator101// does for a packet it couldn't correct.102func SetTEI(b []byte, i int) { b[i*ts.PacketSize+1] |= 0x80 }103104// SkipCC advances the continuity counter of packet i, and of every later105// packet of its PID that carries a payload, by one: as if a packet had been106// lost just before packet i, which is one gap.107func SkipCC(b []byte, i int) {108 pid := ts.PID(b[i*ts.PacketSize:])109 for j := i * ts.PacketSize; j+ts.PacketSize <= len(b); j += ts.PacketSize {110 p := b[j : j+ts.PacketSize]111 if ts.PID(p) == pid && p[3]&0x10 != 0 {112 p[3] = p[3]&0xf0 | (p[3]+1)&0x0f113 }114 }115}116117type gen struct {118 out []byte119 cc map[int]byte120 pcr uint64121}122123func (g *gen) packet(pid int, start bool, af, data []byte) {124 p := make([]byte, ts.PacketSize)125 p[0], p[1], p[2] = 0x47, byte(pid>>8)&0x1f, byte(pid)126 if start {127 p[1] |= 0x40128 }129 p[3] = 0x10 | g.cc[pid]130 g.cc[pid] = (g.cc[pid] + 1) & 0x0f131 i := 4132 if af != nil {133 p[3] |= 0x20134 p[4] = byte(len(af))135 i += 1 + copy(p[5:], af)136 }137 i += copy(p[i:], data)138 for ; i < ts.PacketSize; i++ {139 p[i] = 0xff140 }141 g.out = append(g.out, p...)142}143144func (g *gen) section(pid int, sections ...[]byte) {145 var data []byte146 starts := map[int]bool{}147 for _, s := range sections {148 starts[len(data)] = true149 data = append(data, s...)150 }151 for pos := 0; pos < len(data); {152 next := len(data) // the next section start at or after pos153 for k := pos; k < len(data); k++ {154 if starts[k] {155 next = k156 break157 }158 }159 if next < pos+ts.PacketSize-5 {160 end := min(pos+ts.PacketSize-5, len(data))161 g.packet(pid, true, nil, append([]byte{byte(next - pos)}, data[pos:end]...))162 pos = end163 continue164 }165 // No section starts in this packet; one starting right after it166 // begins the next, with stuffing between.167 end := min(pos+ts.PacketSize-4, next)168 g.packet(pid, false, nil, data[pos:end])169 pos = end170 }171}172173// es writes a packet of an elementary stream: filler, with a PCR in its174// adaptation field when pcr is set.175func (g *gen) es(pid int, pcr bool) {176 var af []byte177 if pcr {178 g.pcr += 90 * 40 // 40 ms on the 90 kHz clock179 b := g.pcr180 af = []byte{0x10, byte(b >> 25), byte(b >> 17), byte(b >> 9), byte(b >> 1), byte(b&1)<<7 | 0x7e, 0}181 }182 data := make([]byte, ts.PacketSize)183 for i := range data {184 if data[i] = byte(pid) ^ byte(i); data[i] == 0x47 {185 data[i] = 0 // no sync bytes in the filler, for tests of resyncing186 }187 }188 g.packet(pid, false, af, data)189}190191func (m Mux) pat() []byte {192 s := []byte{0x00, 0xb0, 0, byte(m.TSID >> 8), byte(m.TSID), 0xc1, 0, 0}193 for _, p := range m.Programs {194 s = append(s, byte(p.Number>>8), byte(p.Number), 0xe0|byte(p.PMTPID>>8), byte(p.PMTPID))195 }196 return Section(s)197}198199func pmt(p Program) []byte {200 pcr := p.VideoPID201 if pcr == 0 {202 pcr = 0x1fff203 }204 s := []byte{0x02, 0xb0, 0, byte(p.Number >> 8), byte(p.Number), 0xc1, 0, 0, 0xe0 | byte(pcr>>8), byte(pcr), 0xf0, 0}205 es := func(typ byte, pid int, desc []byte) {206 s = append(s, typ, 0xe0|byte(pid>>8), byte(pid), 0xf0|byte(len(desc)>>8), byte(len(desc)))207 s = append(s, desc...)208 }209 if p.VideoPID > 0 {210 es(cmp.Or(p.VideoType, 0x02), p.VideoPID, nil)211 }212 for _, a := range p.Audio {213 typ := cmp.Or(a.Type, 0x81)214 var desc []byte215 switch {216 case typ == 0x81:217 bsmod := byte(0)218 if a.Described {219 bsmod = 2220 }221 // 48 kHz, 384 kb/s, 2/0, then langcod, mainid, no text.222 d := []byte{0x08, 0x1c << 1, bsmod<<5 | 0x02<<1 | 1, 0xff, 0x00, 0x01}223 if a.Lang != "" {224 d = append(d, 0xbf)225 d = append(d, a.Lang...)226 }227 desc = append([]byte{0x81, byte(len(d))}, d...)228 case a.Lang != "" || a.Described:229 kind := byte(0)230 if a.Described {231 kind = 3232 }233 lang := a.Lang234 if lang == "" {235 lang = "und"236 }237 desc = append([]byte{0x0a, 4}, append([]byte(lang), kind)...)238 }239 es(typ, a.PID, desc)240 }241 return Section(s)242}243244func (m Mux) vct() []byte {245 var chans []Program246 for _, p := range m.Programs {247 if p.Major > 0 {248 chans = append(chans, p)249 }250 }251 s := []byte{0xc8, 0xf0, 0, byte(m.TSID >> 8), byte(m.TSID), 0xc1, 0, 0, 0, byte(len(chans))}252 for _, p := range chans {253 name := make([]byte, 14)254 for i, c := range utf16.Encode([]rune(p.Name)) {255 if i < 7 {256 name[2*i], name[2*i+1] = byte(c>>8), byte(c)257 }258 }259 s = append(s, name...)260 s = append(s,261 0xf0|byte(p.Major>>6), byte(p.Major&0x3f)<<2|byte(p.Minor>>8), byte(p.Minor), 0x04, // 8VSB262 0, 0, 0, 0, // carrier frequency, no longer used263 byte(m.TSID>>8), byte(m.TSID), byte(p.Number>>8), byte(p.Number),264 0x0d, 0xc0|0x02, // not hidden; digital TV265 byte(p.Number>>8), byte(p.Number), // source ID266 0xfc, 0x00, // no descriptors267 )268 }269 s = append(s, 0xfc, 0x00) // no additional descriptors270 return Section(s)271}