Files
monibuca/pkg/format/ts/pmt.go
langhuihui 8a9fffb987 refactor: frame converter and mp4 track improvements
- Refactor frame converter implementation
- Update mp4 track to use ICodex
- General refactoring and code improvements

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-28 19:55:37 +08:00

368 lines
10 KiB
Go

package mpegts
import (
"bytes"
"io"
"m7s.live/v5/pkg/codec"
"m7s.live/v5/pkg/util"
"net"
)
// ios13818-1-CN.pdf 46(60)-153(167)/page
//
// PMT
var (
TSHeader = []byte{0x47, 0x40 | (PID_PMT >> 8), PID_PMT & 0xff, 0x10, 0x00} //PID:0x100
PSI = []byte{0x02, 0xb0, 0x17, 0x00, 0x01, 0xc1, 0x00, 0x00}
PMT = []byte{0xe0 | (PID_VIDEO >> 8), PID_VIDEO & 0xff, 0xf0, 0x00} //PcrPID:0x101
h264 = []byte{STREAM_TYPE_H264, 0xe0 | (PID_VIDEO >> 8), PID_VIDEO & 0xff, 0xf0, 0x00}
h265 = []byte{STREAM_TYPE_H265, 0xe0 | (PID_VIDEO >> 8), PID_VIDEO & 0xff, 0xf0, 0x00}
aac = []byte{STREAM_TYPE_AAC, 0xe0 | (PID_AUDIO >> 8), PID_AUDIO & 0xff, 0xf0, 0x00}
pcma = []byte{STREAM_TYPE_G711A, 0xe0 | (PID_AUDIO >> 8), PID_AUDIO & 0xff, 0xf0, 0x00}
pcmu = []byte{STREAM_TYPE_G711U, 0xe0 | (PID_AUDIO >> 8), PID_AUDIO & 0xff, 0xf0, 0x00}
Stuffing []byte
)
func init() {
Stuffing = util.GetFillBytes(0xff, TS_PACKET_SIZE)
}
// TS Header :
// SyncByte = 0x47
// TransportErrorIndicator = 0(B:0), PayloadUnitStartIndicator = 1(B:0), TransportPriority = 0(B:0),
// Pid = 4097(0x1001),
// TransportScramblingControl = 0(B:00), AdaptionFieldControl = 1(B:01), ContinuityCounter = 0(B:0000),
// PSI :
// TableID = 0x02,
// SectionSyntaxIndicator = 1(B:1), Zero = 0(B:0), Reserved1 = 3(B:11),
// SectionLength = 23(0x17)
// ProgramNumber = 0x0001
// Reserved2 = 3(B:11), VersionNumber = (B:00000), CurrentNextIndicator = 1(B:0),
// SectionNumber = 0x00
// LastSectionNumber = 0x00
// PMT:
// Reserved3 = 15(B:1110), PcrPID = 256(0x100)
// Reserved4 = 16(B:1111), ProgramInfoLength = 0(0x000)
// H264:
// StreamType = 0x1b,
// Reserved5 = 15(B:1110), ElementaryPID = 256(0x100)
// Reserved6 = 16(B:1111), EsInfoLength = 0(0x000)
// AAC:
// StreamType = 0x0f,
// Reserved5 = 15(B:1110), ElementaryPID = 257(0x101)
// Reserved6 = 16(B:1111), EsInfoLength = 0(0x000)
type MpegTsPmtStream struct {
StreamType byte // 8 bits 指示具有 PID值的包内承载的节目元类型,其 PID值由 elementary_PID所指定
Reserved5 byte // 3 bits 保留位
ElementaryPID uint16 // 13 bits 指定承载相关节目元的传输流包的 PID
Reserved6 byte // 4 bits 保留位
EsInfoLength uint16 // 12 bits 该字段的头两比特必为'00',剩余 10比特指示紧随 ES_info_length字段的相关节目元描述符的字节数
// N Loop Descriptors
Descriptor []MpegTsDescriptor // 不确定字节数,可变
}
// Program Map Table (节目映射表)
type MpegTsPMT struct {
// PSI
TableID byte // 8 bits 0x00->PAT,0x02->PMT
SectionSyntaxIndicator byte // 1 bit 段语法标志位,固定为1
Zero byte // 1 bit 0
Reserved1 byte // 2 bits 保留位
SectionLength uint16 // 12 bits 该字段的头两比特必为'00',剩余 10 比特指定该分段的字节数,紧随 section_length 字段开始,并包括 CRC.此字段中的值应不超过 1021(0x3FD)
ProgramNumber uint16 // 16 bits 指定 program_map_PID 所适用的节目
Reserved2 byte // 2 bits 保留位
VersionNumber byte // 5 bits 范围0-31,表示PAT的版本号
CurrentNextIndicator byte // 1 bit 发送的PAT是当前有效还是下一个PAT有效
SectionNumber byte // 8 bits 分段的号码.PAT可能分为多段传输.第一段为00,以后每个分段加1,最多可能有256个分段
LastSectionNumber byte // 8 bits 最后一个分段的号码
Reserved3 byte // 3 bits 保留位 0x07
PcrPID uint16 // 13 bits 指明TS包的PID值.该TS包含有PCR域,该PCR值对应于由节目号指定的对应节目.如果对于私有数据流的节目定义与PCR无关.这个域的值将为0x1FFF
Reserved4 byte // 4 bits 预留位 0x0F
ProgramInfoLength uint16 // 12 bits 前两位bit为00.该域指出跟随其后对节目信息的描述的byte数
ProgramInfoDescriptor []MpegTsDescriptor // N Loop Descriptors 可变 节目信息描述
// N Loop
Stream []MpegTsPmtStream // PMT表里面的所有音视频索引信息
Crc32 uint32 // 32 bits 包含处理全部传输流节目映射分段之后,在附件 B 规定的解码器中给出寄存器零输出的 CRC 值
}
func ReadPMT(r io.Reader) (pmt MpegTsPMT, err error) {
lr, psi, err := ReadPSI(r, PSI_TYPE_PMT)
if err != nil {
return
}
pmt = psi.Pmt
// reserved3(3) + pcrPID(13)
pcrPID, err := util.ReadByteToUint16(lr, true)
if err != nil {
return
}
pmt.PcrPID = pcrPID & 0x1fff
// reserved4(4) + programInfoLength(12)
// programInfoLength(12) == 0x00(固定为0) + programInfoLength(10)
programInfoLength, err := util.ReadByteToUint16(lr, true)
if err != nil {
return
}
pmt.ProgramInfoLength = programInfoLength & 0x3ff
// 如果length>0那么,紧跟programInfoLength后面就有length个字节
if pmt.ProgramInfoLength > 0 {
lr := &io.LimitedReader{R: lr, N: int64(pmt.ProgramInfoLength)}
pmt.ProgramInfoDescriptor, err = ReadPMTDescriptor(lr)
if err != nil {
return
}
}
// N Loop
// 开始N循环,读取所有的流的信息
for lr.N > 0 {
var streams MpegTsPmtStream
// streamType(8)
streams.StreamType, err = util.ReadByteToUint8(lr)
if err != nil {
return
}
// reserved5(3) + elementaryPID(13)
streams.ElementaryPID, err = util.ReadByteToUint16(lr, true)
if err != nil {
return
}
streams.ElementaryPID = streams.ElementaryPID & 0x1fff
// reserved6(4) + esInfoLength(12)
// esInfoLength(12) == 0x00(固定为0) + esInfoLength(10)
streams.EsInfoLength, err = util.ReadByteToUint16(lr, true)
if err != nil {
return
}
streams.EsInfoLength = streams.EsInfoLength & 0x3ff
// 如果length>0那么,紧跟esInfoLength后面就有length个字节
if streams.EsInfoLength > 0 {
lr := &io.LimitedReader{R: lr, N: int64(streams.EsInfoLength)}
streams.Descriptor, err = ReadPMTDescriptor(lr)
if err != nil {
return
}
}
// 每读取一个流的信息(音频流或者视频流或者其他),都保存起来
pmt.Stream = append(pmt.Stream, streams)
}
if cr, ok := r.(*util.Crc32Reader); ok {
err = cr.ReadCrc32UIntAndCheck()
if err != nil {
return
}
}
return
}
func ReadPMTDescriptor(lr *io.LimitedReader) (Desc []MpegTsDescriptor, err error) {
var desc MpegTsDescriptor
for lr.N > 0 {
// tag (8)
desc.Tag, err = util.ReadByteToUint8(lr)
if err != nil {
return
}
// length (8)
desc.Length, err = util.ReadByteToUint8(lr)
if err != nil {
return
}
desc.Data = make([]byte, desc.Length)
_, err = lr.Read(desc.Data)
if err != nil {
return
}
Desc = append(Desc, desc)
}
return
}
func WritePMTDescriptor(w io.Writer, descs []MpegTsDescriptor) (err error) {
for _, desc := range descs {
// tag(8)
if err = util.WriteUint8ToByte(w, desc.Tag); err != nil {
return
}
// length (8)
if err = util.WriteUint8ToByte(w, uint8(len(desc.Data))); err != nil {
return
}
// data
if _, err = w.Write(desc.Data); err != nil {
return
}
}
return
}
func WritePMTBody(w io.Writer, pmt MpegTsPMT) (err error) {
// reserved3(3) + pcrPID(13)
if err = util.WriteUint16ToByte(w, pmt.PcrPID|7<<13, true); err != nil {
return
}
// programInfoDescriptor 节目信息描述,字节数不能确定
bw := &bytes.Buffer{}
if err = WritePMTDescriptor(bw, pmt.ProgramInfoDescriptor); err != nil {
return
}
pmt.ProgramInfoLength = uint16(bw.Len())
// reserved4(4) + programInfoLength(12)
// programInfoLength(12) == 0x00(固定为0) + programInfoLength(10)
if err = util.WriteUint16ToByte(w, pmt.ProgramInfoLength|0xf000, true); err != nil {
return
}
// programInfoDescriptor
if _, err = w.Write(bw.Bytes()); err != nil {
return
}
// 循环读取所有的流的信息(音频或者视频)
for _, esinfo := range pmt.Stream {
// streamType(8)
if err = util.WriteUint8ToByte(w, esinfo.StreamType); err != nil {
return
}
// reserved5(3) + elementaryPID(13)
if err = util.WriteUint16ToByte(w, esinfo.ElementaryPID|7<<13, true); err != nil {
return
}
// descriptor ES流信息描述,字节数不能确定
bw := &bytes.Buffer{}
if err = WritePMTDescriptor(bw, esinfo.Descriptor); err != nil {
return
}
esinfo.EsInfoLength = uint16(bw.Len())
// reserved6(4) + esInfoLength(12)
// esInfoLength(12) == 0x00(固定为0) + esInfoLength(10)
if err = util.WriteUint16ToByte(w, esinfo.EsInfoLength|0xf000, true); err != nil {
return
}
// descriptor
if _, err = w.Write(bw.Bytes()); err != nil {
return
}
}
return
}
func WritePMT(w io.Writer, pmt MpegTsPMT) (err error) {
bw := &bytes.Buffer{}
if err = WritePMTBody(bw, pmt); err != nil {
return
}
if pmt.SectionLength == 0 {
pmt.SectionLength = 2 + 3 + 4 + uint16(len(bw.Bytes()))
}
psi := MpegTsPSI{}
psi.Pmt = pmt
if err = WritePSI(w, PSI_TYPE_PMT, psi, bw.Bytes()); err != nil {
return
}
return
}
// func WritePMTPacket(w io.Writer, tsHeader []byte, pmt MpegTsPMT) (err error) {
// if pmt.TableID != TABLE_TSPMS {
// err = errors.New("PMT table ID error")
// return
// }
// // 将所有要写的数据(PMT),全部放入到buffer中去.
// // buffer 里面已经写好了整个PMT表(PointerField+PSI+PMT+CRC)
// bw := &bytes.Buffer{}
// if err = WritePMT(bw, pmt); err != nil {
// return
// }
// // TODO:如果Pmt.Stream里面包含的信息很大,大于188?
// stuffingBytes := util.GetFillBytes(0xff, TS_PACKET_SIZE-4-bw.Len())
// var PMTPacket []byte
// PMTPacket = append(PMTPacket, tsHeader...)
// PMTPacket = append(PMTPacket, bw.Bytes()...)
// PMTPacket = append(PMTPacket, stuffingBytes...)
// fmt.Println("-------------------------")
// fmt.Println("Write PMT :", PMTPacket)
// fmt.Println("-------------------------")
// // 写PMT负载
// if _, err = w.Write(PMTPacket); err != nil {
// return
// }
// return
// }
func WritePMTPacket(w io.Writer, videoCodec codec.FourCC, audioCodec codec.FourCC) {
w.Write(TSHeader)
crc := make([]byte, 4)
paddingSize := TS_PACKET_SIZE - len(crc) - len(PSI) - len(PMT) - len(TSHeader) - 10
pmt := net.Buffers{PSI, PMT}
switch videoCodec {
case codec.FourCC_H264:
pmt = append(pmt, h264)
case codec.FourCC_H265:
pmt = append(pmt, h265)
default:
paddingSize += 5
}
switch audioCodec {
case codec.FourCC_MP4A:
pmt = append(pmt, aac)
case codec.FourCC_ALAW:
pmt = append(pmt, pcma)
case codec.FourCC_ULAW:
pmt = append(pmt, pcmu)
default:
paddingSize += 5
}
util.PutBE(crc, GetCRC32_2(pmt))
pmt = append(pmt, crc, Stuffing[:paddingSize])
pmt.WriteTo(w)
}