diff --git a/codec/mpegts/mpegts.go b/codec/mpegts/mpegts.go new file mode 100644 index 0000000..67af318 --- /dev/null +++ b/codec/mpegts/mpegts.go @@ -0,0 +1,577 @@ +package mpegts + +import ( + "bytes" + "errors" + "io" + "io/ioutil" + + "github.com/Monibuca/engine/v4/util" + //"sync" +) + +// NALU AUD 00 00 00 01 09 F0 + +const ( + TS_PACKET_SIZE = 188 + TS_DVHS_PACKET_SIZE = 192 + TS_FEC_PACKET_SIZE = 204 + + TS_MAX_PACKET_SIZE = 204 + + PID_PAT = 0x0000 + PID_CAT = 0x0001 + PID_TSDT = 0x0002 + PID_RESERVED1 = 0x0003 + PID_RESERVED2 = 0x000F + PID_NIT_ST = 0x0010 + PID_SDT_BAT_ST = 0x0011 + PID_EIT_ST = 0x0012 + PID_RST_ST = 0x0013 + PID_TDT_TOT_ST = 0x0014 + PID_NET_SYNC = 0x0015 + PID_RESERVED3 = 0x0016 + PID_RESERVED4 = 0x001B + PID_SIGNALLING = 0x001C + PID_MEASURE = 0x001D + PID_DIT = 0x001E + PID_SIT = 0x001F + // 0x0003 - 0x000F Reserved + // 0x0010 - 0x1FFE May be assigned as network_PID, Program_map_PID, elementary_PID, or for other purposes + // 0x1FFF Null Packet + + // program_association_section + // conditional_access_section + // TS_program_map_section + // TS_description_section + // ISO_IEC_14496_scene_description_section + // ISO_IEC_14496_object_descriptor_section + // Metadata_section + // IPMP_Control_Information_section (defined in ISO/IEC 13818-11) + TABLE_PAS = 0x00 + TABLE_CAS = 0x01 + TABLE_TSPMS = 0x02 + TABLE_TSDS = 0x03 + TABLE_ISO_IEC_14496_SDC = 0x04 + TABLE_ISO_IEC_14496_ODC = 0x05 + TABLE_MS = 0x06 + TABLE_IPMP_CIS = 0x07 + // 0x06 - 0x37 ITU-T Rec. H.222.0 | ISO/IEC 13818-1 reserved + // 0x38 - 0x3F Defined in ISO/IEC 13818-6 + // 0x40 - 0xFE User private + // 0xFF Forbidden + + STREAM_TYPE_H264 = 0x1B + STREAM_TYPE_AAC = 0x0F + + // 1110 xxxx + // 110x xxxx + STREAM_ID_VIDEO = 0xE0 // ITU-T Rec. H.262 | ISO/IEC 13818-2 or ISO/IEC 11172-2 or ISO/IEC14496-2 video stream number xxxx + STREAM_ID_AUDIO = 0xC0 // ISO/IEC 13818-3 or ISO/IEC 11172-3 or ISO/IEC 13818-7 or ISO/IEC14496-3 audio stream number x xxxx + + PAT_PKT_TYPE = 0 + PMT_PKT_TYPE = 1 + PES_PKT_TYPE = 2 +) + +// +// MPEGTS -> PAT + PMT + PES +// ES -> PES -> TS +// + +type MpegTsStream struct { + firstTsPkt *MpegTsPacket // 每一帧的第一个TS包 + patPkt *MpegTsPacket // 装载PAT的TS包 + pmtPkt *MpegTsPacket // 装载PMT的TS包 + pat *MpegTsPAT // PAT表信息 + pmt *MpegTsPMT // PMT表信息 + closed bool //是否已经关闭 + TsPesPktChan chan *MpegTsPesStream // TS + PES Packet Channel,将封装的每一帧ES数据,通过channel来传输 +} + +func NewMpegTsStream(bufferLength int) (ts *MpegTsStream) { + ts = new(MpegTsStream) + ts.firstTsPkt = new(MpegTsPacket) + ts.patPkt = new(MpegTsPacket) + ts.pmtPkt = new(MpegTsPacket) + ts.pat = new(MpegTsPAT) + ts.pmt = new(MpegTsPMT) + ts.TsPesPktChan = make(chan *MpegTsPesStream, bufferLength) + return +} + +// ios13818-1-CN.pdf 33/165 +// +// TS +// + +// Packet == Header + Payload == 188 bytes +type MpegTsPacket struct { + Header MpegTsHeader + Payload []byte +} + +// 前面32bit的数据即TS分组首部,它指出了这个分组的属性 +type MpegTsHeader struct { + SyncByte byte // 8 bits 同步字节,固定为0x47,表示后面是一个TS分组 + TransportErrorIndicator byte // 1 bit 传输错误标志位 + PayloadUnitStartIndicator byte // 1 bit 负载单元开始标志(packet不满188字节时需填充).为1时,表示在4个字节后,有一个调整字节 + TransportPriority byte // 1 bit 传输优先级 + Pid uint16 // 13 bits Packet ID号码,唯一的号码对应不同的包.为0表示携带的是PAT表 + TransportScramblingControl byte // 2 bits 加密标志位(00:未加密;其他表示已加密) + AdaptionFieldControl byte // 2 bits 附加区域控制.表示TS分组首部后面是否跟随有调整字段和有效负载.01仅含有效负载(没有adaptation_field),10仅含调整字段(没有Payload),11含有调整字段和有效负载(有adaptation_field,adaptation_field之后是Payload).为00的话解码器不进行处理.空分组没有调整字段 + ContinuityCounter byte // 4 bits 包递增计数器.范围0-15,具有相同的PID的TS分组传输时每次加1,到15后清0.不过,有些情况下是不计数的. + + MpegTsHeaderAdaptationField +} + +// 调整字段,只可能出现在每一帧的开头(当含有pcr的时候),或者结尾(当不满足188个字节的时候) +// adaptionFieldControl 00 -> 高字节代表调整字段, 低字节代表负载字段 0x20 0x10 +// PCR字段编码在MPEG-2 TS包的自适应字段(Adaptation field)的6个Byte中,其中6 bits为预留位,42 bits为有效位() +// MpegTsHeaderAdaptationField + stuffing bytes +type MpegTsHeaderAdaptationField struct { + AdaptationFieldLength byte // 8bits 本区域除了本字节剩下的长度(不包含本字节!!!切记), if adaptationFieldLength > 0, 那么就有下面8个字段. adaptation_field_length 值必须在 0 到 182 的区间内.当 adaptation_field_control 值为'10'时,adaptation_field_length 值必须为 183 + DiscontinuityIndicator byte // 1bit 置于"1"时,指示当前传输流包的不连续性状态为真.当 discontinuity_indicator 设置为"0"或不存在时,不连续性状态为假.不连续性指示符用于指示两种类型的不连续性,系统时间基不连续性和 continuity_counter 不连续性. + RandomAccessIndicator byte // 1bit 指示当前的传输流包以及可能的具有相同 PID 的后续传输流包,在此点包含有助于随机接入的某些信息.特别的,该比特置于"1"时,在具有当前 PID 的传输流包的有效载荷中起始的下一个 PES 包必须包含一个 discontinuity_indicator 字段中规定的基本流接入点.此外,在视频情况中,显示时间标记必须在跟随基本流接入点的第一图像中存在 + ElementaryStreamPriorityIndicator byte // 1bit 在具有相同 PID 的包之间,它指示此传输流包有效载荷内承载的基本流数据的优先级.1->指示该有效载荷具有比其他传输流包有效载荷更高的优先级 + PCRFlag byte // 1bit 1->指示 adaptation_field 包含以两部分编码的 PCR 字段.0->指示自适应字段不包含任何 PCR 字段 + OPCRFlag byte // 1bit 1->指示 adaptation_field 包含以两部分编码的 OPCR字段.0->指示自适应字段不包含任何 OPCR 字段 + SplicingPointFlag byte // 1bit 1->指示 splice_countdown 字段必须在相关自适应字段中存在,指定拼接点的出现.0->指示自适应字段中 splice_countdown 字段不存在 + TrasportPrivateDataFlag byte // 1bit 1->指示自适应字段包含一个或多个 private_data 字节.0->指示自适应字段不包含任何 private_data 字节 + AdaptationFieldExtensionFlag byte // 1bit 1->指示自适应字段扩展的存在.0->指示自适应字段中自适应字段扩展不存在 + + // Optional Fields + ProgramClockReferenceBase uint64 // 33 bits pcr + Reserved1 byte // 6 bits + ProgramClockReferenceExtension uint16 // 9 bits + OriginalProgramClockReferenceBase uint64 // 33 bits opcr + Reserved2 byte // 6 bits + OriginalProgramClockReferenceExtension uint16 // 9 bits + SpliceCountdown byte // 8 bits + TransportPrivateDataLength byte // 8 bits 指定紧随传输private_data_length 字段的 private_data 字节数. private_data 字节数不能使专用数据扩展超出自适应字段的范围 + PrivateDataByte byte // 8 bits 不通过 ITU-T|ISO/IEC 指定 + AdaptationFieldExtensionLength byte // 8 bits 指定紧随此字段的扩展的自适应字段数据的字节数,包括要保留的字节(如果存在) + LtwFlag byte // 1 bit 1->指示 ltw_offset 字段存在 + PiecewiseRateFlag byte // 1 bit 1->指示 piecewise_rate 字段存在 + SeamlessSpliceFlag byte // 1 bit 1->指示 splice_type 以及 DTS_next_AU 字段存在. 0->指示无论是 splice_type 字段还是 DTS_next_AU 字段均不存在 + + // Optional Fields + LtwValidFlag byte // 1 bit 1->指示 ltw_offset 的值必将生效.0->指示 ltw_offset 字段中该值未定义 + LtwOffset uint16 // 15 bits 其值仅当 ltw_valid 标志字段具有'1'值时才定义.定义时,法定时间窗补偿以(300/fs)秒为度量单位,其中 fs 为此 PID 所归属的节目的系统时钟频率 + Reserved3 byte // 2 bits 保留 + PiecewiseRate uint32 // 22 bits 只要当 ltw_flag 和 ltw_valid_flag 均置于‘1’时,此 22 比特字段的含义才确定 + SpliceType byte // 4 bits + DtsNextAU uint64 // 33 bits (解码时间标记下一个存取单元) + + // stuffing bytes + // 此为固定的 8 比特值等于'1111 1111',能够通过编码器插入.它亦能被解码器丢弃 +} + +// ios13818-1-CN.pdf 77 +// +// Descriptor +// + +type MpegTsDescriptor struct { + Tag byte // 8 bits 标识每一个描述符 + Length byte // 8 bits 指定紧随 descriptor_length 字段的描述符的字节数 + Data []byte +} + +func ReadTsPacket(r io.Reader) (packet MpegTsPacket, err error) { + lr := &io.LimitedReader{R: r, N: TS_PACKET_SIZE} + + // header + packet.Header, err = ReadTsHeader(lr) + if err != nil { + return + } + + // payload + packet.Payload = make([]byte, lr.N) + _, err = lr.Read(packet.Payload) + if err != nil { + return + } + + return +} + +func ReadTsHeader(r io.Reader) (header MpegTsHeader, err error) { + var h uint32 + + // MPEGTS Header 4个字节 + h, err = util.ReadByteToUint32(r, true) + if err != nil { + return + } + + // payloadUnitStartIndicator + // 为1时,表示在4个字节后,有一个调整字节.包头后需要除去一个字节才是有效数据(payload_unit_start_indicator="1") + // header.payloadUnitStartIndicator = uint8(h & 0x400000) + + // | 1111 1111 | 0000 0000 | 0000 0000 | 0000 0000 | + if (h&0xff000000)>>24 != 0x47 { + err = errors.New("mpegts header sync error!") + return + } + + // | 1111 1111 | 0000 0000 | 0000 0000 | 0000 0000 | + header.SyncByte = byte((h & 0xff000000) >> 24) + + // | 0000 0000 | 1000 0000 | 0000 0000 | 0000 0000 | + header.TransportErrorIndicator = byte((h & 0x800000) >> 23) + + // | 0000 0000 | 0100 0000 | 0000 0000 | 0000 0000 | + header.PayloadUnitStartIndicator = byte((h & 0x400000) >> 22) + + // | 0000 0000 | 0010 0000 | 0000 0000 | 0000 0000 | + header.TransportPriority = byte((h & 0x200000) >> 21) + + // | 0000 0000 | 0001 1111 | 1111 1111 | 0000 0000 | + header.Pid = uint16((h & 0x1fff00) >> 8) + + // | 0000 0000 | 0000 0000 | 0000 0000 | 1100 0000 | + header.TransportScramblingControl = byte((h & 0xc0) >> 6) + + // | 0000 0000 | 0000 0000 | 0000 0000 | 0011 0000 | + // 0x30 , 0x20 -> adaptation_field, 0x10 -> Payload + header.AdaptionFieldControl = byte((h & 0x30) >> 4) + + // | 0000 0000 | 0000 0000 | 0000 0000 | 0000 1111 | + header.ContinuityCounter = byte(h & 0xf) + + // | 0010 0000 | + // adaptionFieldControl + // 表示TS分组首部后面是否跟随有调整字段和有效负载. + // 01仅含有效负载(没有adaptation_field) + // 10仅含调整字段(没有Payload) + // 11含有调整字段和有效负载(有adaptation_field,adaptation_field之后是Payload). + // 为00的话解码器不进行处理.空分组没有调整字段 + // 当值为'11时,adaptation_field_length 值必须在0 到182 的区间内. + // 当值为'10'时,adaptation_field_length 值必须为183. + // 对于承载PES 包的传输流包,只要存在欠充足的PES 包数据就需要通过填充来完全填满传输流包的有效载荷字节. + // 填充通过规定自适应字段长度比自适应字段中数据元的长度总和还要长来实现,以致于自适应字段在完全容纳有效的PES 包数据后,有效载荷字节仍有剩余.自适应字段中额外空间采用填充字节填满. + if header.AdaptionFieldControl >= 2 { + // adaptationFieldLength + header.AdaptationFieldLength, err = util.ReadByteToUint8(r) + if err != nil { + return + } + + if header.AdaptationFieldLength > 0 { + lr := &io.LimitedReader{R: r, N: int64(header.AdaptationFieldLength)} + + // discontinuityIndicator(1) + // randomAccessIndicator(1) + // elementaryStreamPriorityIndicator + // PCRFlag + // OPCRFlag + // splicingPointFlag + // trasportPrivateDataFlag + // adaptationFieldExtensionFlag + var flags uint8 + flags, err = util.ReadByteToUint8(lr) + if err != nil { + return + } + + header.DiscontinuityIndicator = flags & 0x80 + header.RandomAccessIndicator = flags & 0x40 + header.ElementaryStreamPriorityIndicator = flags & 0x20 + header.PCRFlag = flags & 0x10 + header.OPCRFlag = flags & 0x08 + header.SplicingPointFlag = flags & 0x04 + header.TrasportPrivateDataFlag = flags & 0x02 + header.AdaptationFieldExtensionFlag = flags & 0x01 + + // randomAccessIndicator + // 在此点包含有助于随机接入的某些信息. + // 特别的,该比特置于"1"时,在具有当前 PID 的传输流包的有效载荷中起始的下一个 PES 包必须包含一个 discontinuity_indicator 字段中规定的基本流接入点. + // 此外,在视频情况中,显示时间标记必须在跟随基本流接入点的第一图像中存在 + if header.RandomAccessIndicator != 0 { + } + + // PCRFlag + // 1->指示 adaptation_field 包含以两部分编码的 PCR 字段. + // 0->指示自适应字段不包含任何 PCR 字段 + if header.PCRFlag != 0 { + var pcr uint64 + pcr, err = util.ReadByteToUint48(lr, true) + if err != nil { + return + } + + // PCR(i) = PCR_base(i)*300 + PCR_ext(i) + // afd.programClockReferenceBase * 300 + afd.programClockReferenceExtension + header.ProgramClockReferenceBase = pcr >> 15 // 9 bits + 6 bits + header.ProgramClockReferenceExtension = uint16(pcr & 0x1ff) // 9 bits -> | 0000 0001 | 1111 1111 | + } + + // OPCRFlag + if header.OPCRFlag != 0 { + var opcr uint64 + opcr, err = util.ReadByteToUint48(lr, true) + if err != nil { + return + } + + // OPCR(i) = OPCR_base(i)*300 + OPCR_ext(i) + // afd.originalProgramClockReferenceBase * 300 + afd.originalProgramClockReferenceExtension + header.OriginalProgramClockReferenceBase = opcr >> 15 // 9 bits + 6 bits + header.OriginalProgramClockReferenceExtension = uint16(opcr & 0x1ff) // 9 bits -> | 0000 0001 | 1111 1111 | + } + + // splicingPointFlag + // 1->指示 splice_countdown 字段必须在相关自适应字段中存在,指定拼接点的出现. + // 0->指示自适应字段中 splice_countdown 字段不存在 + if header.SplicingPointFlag != 0 { + header.SpliceCountdown, err = util.ReadByteToUint8(lr) + if err != nil { + return + } + } + + // trasportPrivateDataFlag + // 1->指示自适应字段包含一个或多个 private_data 字节. + // 0->指示自适应字段不包含任何 private_data 字节 + if header.TrasportPrivateDataFlag != 0 { + header.TransportPrivateDataLength, err = util.ReadByteToUint8(lr) + if err != nil { + return + } + + // privateDataByte + b := make([]byte, header.TransportPrivateDataLength) + if _, err = lr.Read(b); err != nil { + return + } + } + + // adaptationFieldExtensionFlag + if header.AdaptationFieldExtensionFlag != 0 { + } + + // 消耗掉剩下的数据,我们不关心 + if lr.N > 0 { + // Discard 是一个 io.Writer,对它进行的任何 Write 调用都将无条件成功 + // 但是ioutil.Discard不记录copy得到的数值 + // 用于发送需要读取但不想存储的数据,目的是耗尽读取端的数据 + if _, err = io.CopyN(ioutil.Discard, lr, int64(lr.N)); err != nil { + return + } + } + + } + } + + return +} + +func WriteTsHeader(w io.Writer, header MpegTsHeader) (written int, err error) { + if header.SyncByte != 0x47 { + err = errors.New("mpegts header sync error!") + return + } + + h := uint32(header.SyncByte)<<24 + uint32(header.TransportErrorIndicator)<<23 + uint32(header.PayloadUnitStartIndicator)<<22 + uint32(header.TransportPriority)<<21 + uint32(header.Pid)<<8 + uint32(header.TransportScramblingControl)<<6 + uint32(header.AdaptionFieldControl)<<4 + uint32(header.ContinuityCounter) + if err = util.WriteUint32ToByte(w, h, true); err != nil { + return + } + + written += 4 + + if header.AdaptionFieldControl >= 2 { + // adaptationFieldLength(8) + if err = util.WriteUint8ToByte(w, header.AdaptationFieldLength); err != nil { + return + } + + written += 1 + + if header.AdaptationFieldLength > 0 { + + // discontinuityIndicator(1) + // randomAccessIndicator(1) + // elementaryStreamPriorityIndicator(1) + // PCRFlag(1) + // OPCRFlag(1) + // splicingPointFlag(1) + // trasportPrivateDataFlag(1) + // adaptationFieldExtensionFlag(1) + threeIndicatorFiveFlags := uint8(header.DiscontinuityIndicator<<7) + uint8(header.RandomAccessIndicator<<6) + uint8(header.ElementaryStreamPriorityIndicator<<5) + uint8(header.PCRFlag<<4) + uint8(header.OPCRFlag<<3) + uint8(header.SplicingPointFlag<<2) + uint8(header.TrasportPrivateDataFlag<<1) + uint8(header.AdaptationFieldExtensionFlag) + if err = util.WriteUint8ToByte(w, threeIndicatorFiveFlags); err != nil { + return + } + + written += 1 + + // PCR(i) = PCR_base(i)*300 + PCR_ext(i) + if header.PCRFlag != 0 { + pcr := header.ProgramClockReferenceBase<<15 | 0x3f<<9 | uint64(header.ProgramClockReferenceExtension) + if err = util.WriteUint48ToByte(w, pcr, true); err != nil { + return + } + + written += 6 + } + + // OPCRFlag + if header.OPCRFlag != 0 { + opcr := header.OriginalProgramClockReferenceBase<<15 | 0x3f<<9 | uint64(header.OriginalProgramClockReferenceExtension) + if err = util.WriteUint48ToByte(w, opcr, true); err != nil { + return + } + + written += 6 + } + } + + } + + return +} + +// +//func (s *MpegTsStream) TestWrite(fileName string) error { +// +// if fileName != "" { +// file, err := os.Create(fileName) +// if err != nil { +// panic(err) +// } +// defer file.Close() +// +// patTsHeader := []byte{0x47, 0x40, 0x00, 0x10} +// +// if err := WritePATPacket(file, patTsHeader, *s.pat); err != nil { +// panic(err) +// } +// +// // TODO:这里的pid应该是由PAT给的 +// pmtTsHeader := []byte{0x47, 0x41, 0x00, 0x10} +// +// if err := WritePMTPacket(file, pmtTsHeader, *s.pmt); err != nil { +// panic(err) +// } +// } +// +// var videoFrame int +// var audioFrame int +// for { +// tsPesPkt, ok := <-s.TsPesPktChan +// if !ok { +// fmt.Println("frame index, video , audio :", videoFrame, audioFrame) +// break +// } +// +// if tsPesPkt.PesPkt.Header.StreamID == STREAM_ID_AUDIO { +// audioFrame++ +// } +// +// if tsPesPkt.PesPkt.Header.StreamID == STREAM_ID_VIDEO { +// println(tsPesPkt.PesPkt.Header.Pts) +// videoFrame++ +// } +// +// fmt.Sprintf("%s", tsPesPkt) +// +// // if err := WritePESPacket(file, tsPesPkt.TsPkt.Header, tsPesPkt.PesPkt); err != nil { +// // return err +// // } +// +// } +// +// return nil +//} + +func (s *MpegTsStream) readPAT(packet *MpegTsPacket, pr io.Reader) (err error) { + // 首先找到PID==0x00的TS包(PAT) + if PID_PAT == packet.Header.Pid { + if len(packet.Payload) == 188 { + pr = &util.Crc32Reader{R: pr, Crc32: 0xffffffff} + } + // Header + PSI + Paylod + pat, err := ReadPAT(pr) + if err != nil { + return err + } + s.pat = &pat + s.patPkt = packet + } + return +} +func (s *MpegTsStream) readPMT(packet *MpegTsPacket, pr io.Reader) (err error) { + // 在读取PAT中已经将所有频道节目信息(PMT_PID)保存了起来 + // 接着读取所有TS包里面的PID,找出PID==PMT_PID的TS包,就是PMT表 + for _, v := range s.pat.Program { + if v.ProgramMapPID == packet.Header.Pid { + if len(packet.Payload) == 188 { + pr = &util.Crc32Reader{R: pr, Crc32: 0xffffffff} + } + // Header + PSI + Paylod + pmt, err := ReadPMT(pr) + if err != nil { + return err + } + // send pmt + s.pmt = &pmt + s.pmtPkt = packet + } + } + return +} +func (s *MpegTsStream) Feed(ts io.Reader) error { + var frame int64 + var tsPktArr []MpegTsPacket + for { + packet, err := ReadTsPacket(ts) + if err == io.EOF { + // 文件结尾 把最后面的数据发出去 + pesPkt, err := TsToPES(tsPktArr) + if err != nil { + return err + } + s.TsPesPktChan <- &MpegTsPesStream{ + TsPkt: *s.firstTsPkt, + PesPkt: pesPkt, + } + return nil + } + if err != nil { + return err + } + pr := bytes.NewReader(packet.Payload) + err = s.readPAT(&packet, pr) + if err != nil { + return err + } + err = s.readPMT(&packet, pr) + if err != nil { + return err + } + // 在读取PMT中已经将所有的音视频PES的索引信息全部保存了起来 + // 接着读取所有TS包里面的PID,找出PID==elementaryPID的TS包,就是音视频数据 + for _, v := range s.pmt.Stream { + if v.ElementaryPID == packet.Header.Pid { + if packet.Header.PayloadUnitStartIndicator == 1 { + if frame != 0 { + pesPkt, err := TsToPES(tsPktArr) + if err != nil { + return err + } + s.TsPesPktChan <- &MpegTsPesStream{ + TsPkt: *s.firstTsPkt, + PesPkt: pesPkt, + } + + tsPktArr = nil + } + s.firstTsPkt = &packet + frame++ + } + tsPktArr = append(tsPktArr, packet) + } + } + } +} diff --git a/codec/mpegts/mpegts.md b/codec/mpegts/mpegts.md new file mode 100644 index 0000000..6497206 --- /dev/null +++ b/codec/mpegts/mpegts.md @@ -0,0 +1,520 @@ +#MPEGTS + +---------- + +Name:苏荣 +Data:2016/5/27 09:03:30 + + +---------- + +## PSI(Program Specific Information) 节目特定信息 +PSI 可以认为属于 6 个表: +1) 节目相关表(PAT) +2) TS 节目映射表(PMT) +3) 网络信息表(NIT) +4) 有条件访问表(CAT) +5) 传输流描述表 +6) IPMP 控制信息表 + +##ES流(Elementary Stream):基本码流,不分段的音频、视频或其他信息的连续码流. + +##PES流:把基本流ES分割成段,并加上相应头文件打包成形的打包基本码流 + +##PS流(Program Stream):节目流,将具有共同时间基准的一个或多个PES组合(复合)而成的单一数据流(用于播放或编辑系统,如m2p). + +##TS流(Transport Stream):传输流,将具有共同时间基准或独立时间基准的一个或多个PES组合(复合)而成的单一数据流(用于数据传输). + +##PES ES TS +视频压缩成H264码流,可以称之为ES流,将其每帧打包为PES流,然后分拆为多个188字节,称为TS流. + +H264(ES) = PES1(一帧ES打包) + PES2(一帧ES打包) + PES3(一帧ES打包) + ... + +PES1 = PES1 Header + PES1 Payload = PES1 Packet Start Code Prefix + Stream ID + PES1 Packet Length + Send PES1 Header(不确定大小) + PES1 Payload + +PES1 Payload = TS1 Payload + TS2 Payload + TS3 Payload + ... + +PES1 = TS1 + TS2 + TS3 + .... + +PES1 = TS1(TS1 Header + PES1 Header + TS1 Payload) + TS2(有三种可能) + TS3(有三种可能) + ...... + +TS1(TS流第一个包) = TS1 Header + PES1 Header + TS1 Payload + +TS2(TS流第二个包,第一种情况) = TS2 Header + 自适应字段 + TS2 Payload (出现概率 1%) + +TS2(TS流第二个包,第二种情况) = TS2 Header + 自适应字段 (出现概率 0.1%) + +TS2(TS流第二个包,第三种情况) = TS2 Header + TS2 Payload (出现概率 98.9%) + +一段ES流 = N个PES(N帧) + +同一个PES的TS的PID是相同的 + +##寻找第一个TS包 +Header PID = 0x000 说明数据包是PAT表信息 +第一个TS包 一般叫做 PAT (Program Association Table,节目相关表) + +TS流 : PID=005 + PID=002 + PID=000 + +一般来说第一个TS包一般在第一个位置,本例举出一个特殊情况(第一个TS包在第三) + +在寻找第一个TS包时,不断读取TS包,直到找到pid=000的位置,并将读取过的TS包置入缓冲区 + +##寻找下一个TS包 +第二个TS包 一般叫做PMT(Program Map Table,节目映射表) + +##解析TS包 +payload_unit_start_indicator : 该字段用来表示TS包的有效净荷有PES包或者PSI数据的情况. + +当TS包带有PES包数据时(出现概率99.9%).不带PES包(出现概率0.1%). + +1. 当TS包带有PES包数据时,payload_unit_start_indicator具有以下的特点: +a. 置为1,标识TS包的有效净荷以PES包的第一个字节开始. +b. 置为0,表示TS包的开始不是PES包. + +2. 当TS包带有PSI数据时,payload_unit_start_indicator具有以下特点: +a. 置为1,表示TS包带有PSI部分的第一个字节,即第一个字节带有指针pointer_field. +b. 置为0,表示TS包不带有一个PSI部分的第一个字节,即在有效净荷中没有指针point_field. +c. 对于空包的包,payload_unit_start_indicator应该置为0 + +adaptionFieldControl: +01 -> 仅含有效负载(TS包第三种情况) +10 -> 仅含调整字段(TS包第二种情况) +11 -> 含有调整字段和有效负载(TS包第一种情况) + +TS流,通过一个个的TS包来传送. TS包可以是传送PSI SI等各表的数据包,也可以是传送节目音视频数据(携带的PES包:音视频基本流包)的包;TS携带 PSI SI等表的数据时,各个表以各表对应的Section语法格式做为传输单元存放到TS包中 以便传输; +TS包,有一个TS包的PID,系统就是根据这个PID来找对应的TS包;对于包含音视频数据(PES包)的TS包,系统通过TS的PID找到对应TS数据包,提取其中的数据组合成节目的音视频;对于携带PSI SI等数据的TS包,系统通过TS的PID找到对应TS数据包,提取各个PSI SI数据表格,用来指导系统;因此其中部分PID用来固定传输某些数据内容. + +有了TS的PID后, 如果TS包携带的是PSI SI等表格的Section数据时,有时还不能确定该PID的TS包中携带的数据是什么,SDT BAT ST 等表传送时,都用的是PID为0X0011的TS数据包,对于这种携带PSI SI Section单元的TS包,对应的数据(表的Section语法中)还有一个 TABLE_ID字段,用来可以确定是具体的什么表 + +因此PID+TableID就可以确定负载带了什么,是PES还是PSI. + + +---------- + + +1. 第一个包: + +包头 : 47 60 00 10 +0x47 : syncByte +0x6 : 0110(这里的最后一个字节,要给到下面),payload_unit_start_indicator = 1. +0x000 : 0 0000 0000 0000, pid = 0,说明是第一个TS包(PAT表) +0x10 : 0001 0000, adaptionFieldControl = 01,说明仅含有效负载(TS包第三种情况) + +负载 : 00 00 B0 0D 00 00 C1 00 00 00 01 E0 +81 0C 8C BE 32 FF FF......FF + +指针 : 00 +table id : 00 +固定值 : B (1011) +section_length : 0 0D(值:13) +transport_stream_id : 00 00 +version number & current_next_indicator : C1 +section_number : 00 +last_section_number : 00 +program_number : 00 01 +program_map_PID : E0 81(因为program_number > 0) +CRC_32 : 0C 8C BE 32 + + if (program_number == 0) + { + network_PID + }else + { + program_map_PID + } + +E0 81 = reserved3 + program_map_PID = | 1110 0000 | 1000 0001 | +program_map_PID = 0x81(说明PMT的pid为081) + + +---------- + + +2. 第二个包 + +包头 : 47 60 81 10 +0x47 : syncByte +0x6 : 0110(这里的最后一个字节,要给到下面),payload_unit_start_indicator = 1. +0x081 : 0 0000 1000 0001, pid = 0x081(说明是PMT表,因为前面的PAT表给出了) +0x10 : 0001 0000, adaptionFieldControl = 01,说明仅含有效负载(TS包第三种情况) + +负载 : 00 02 B0 17 00 01 C1 00 00 E8 10 F0 00 1B E8 10 +F0 00 03 E8 14 F0 00 66 74 A4 2D FF FF FF FF FF......FF + +指针 : 00 +table id : 02 +固定值 : B +section_length : 0 17(值:23,表示到后面FF FF FF FF FF FF之前总共有23个字节) +program_number : 00 01 +reserved2 & version_number & current_next_indicator : C1 +section_number : 00 +last_section_number : 00 +PCR_PID : E8 10 +program_info_length : F0 00 前4位为保留位 后12位为描述信息长度 此处为0 + +第一流分析 : 1B E8 10 F0 00 +stream_type : 1B 视频流(H264)(ITU-T H.264建议书| SO/IEC 14496-10 视频中定义的 AVC 视频流) +elementary_PID : E8 10 前3位为保留位取后13位 则PID=810 表示此PID的都是视频流 +ES_info_length : F0 00 前4位为保留位 后12位为描述信息长度 此处为0 + +第二流分析 : 03 E8 14 F0 00 +stream_type : 03 音频流(MP3) +elementary_PID : E8 14 前3位为保留位取后13位 则PID=814 表示此PID的都是音频流 +ES_info_length : F0 00 前4位为保留位 后12位为描述信息长度 此处为0 + + + +CRC : 66 74 A4 2D + + +reserved4 + program_info_length = | 1111 0000 | 0000 0000 | +program_info_length = 0 + +stream_type : 03 表示流是音频流 MP3 格式 814 表示 pid=814 的TS包存储的是MP3格式的音频流. +stream_type : 01 表示流是视频流 h264格式 810 表示 pid=810 的TS包存储的是h264格式的视频流 + + +---------- + + +3. 第三个包 +包头 : 47 48 14 10 +0x47 : syncByte +0x4 : 0100(这里的最后一个字节,要给到下面),payload_unit_start_indicator = 1. +0x814 : 0 1000 0001 0100, pid = 0x814(音频MP3) +0x10 : 0001 0000, adaptionFieldControl = 01 + +这里: +payload_unit_start_indicator = 1, 说明有效载荷起始符为1,含有PES包头 +adaptionFieldControl = 01, 说明仅含有效负载(TS包第三种情况) + +负载 : 00 00 01 C0 01 88 80 80 05 21 00 01 96 07 FF FD 85 00 33 22 22 11 22 11 11 11 11 11 11 24 82 41 00 90 40 00 00 00 00 00 40 00 ....... 70 34 5B CE 64 B7 D2 F5 4E 07 50 8E 11 1E 60 61 21 32 11 59 + +packetStartCodePrefix : 00 00 01 +streamID : C0 +pes_PacketLength : 01 88(值为392,占用392个字节,一帧数据长度,也可以置为0) +Sned PES HEADER : 占用不确定位 本例为:80 80 05 21 00 01 96 07 + + +Sned PES HEADER 包括以下几个字段: 80 80 05 21 00 01 96 07(解析为二进制显示) +| 8 0 | 8 0 | 0 5 | 2 1 | 0 0 | 0 1 | 9 6 | 0 7 | +| 1000 0000| 1000 0000 | 0000 0101 | 0010 0001 | 0000 0000 | 0000 0001 | 1001 0110 | 0000 1110 | + +(注意,下面的数值是用二进制表示,不特别声明,都是用16进制表示) +(0x80) +constTen : 10 固定 +PES_scrambling_control : 00 PES加扰控制 +PES_priority : 0 PES 包中该有效载荷的优先级 +data_alignment_indicator : 0 数据定位指示符 +copyright : 0 PES 包有效载荷的素材依靠版权所保护 +original_or_copy : 0 PES 包有效载荷的内容是原始的 + +(0x80) +PTS_DTS_flags : 10 PES 包头中 PTS 字段存在 +ESCR_flag : 0 +ES_rate_flag : 0 +DSM_trick_mode_flag : 0 +additional_copy_info_flag : 0 +PES_CRC_flag : 0 +PES_extension_flag : 0 + +(0x05) +PES_header_data_length : 0000 0101(值为5)PES头数据长度,表示后面还有5个字节,之后就是一帧的数据 + +(0x4200032C)(十进制:1107297068) +PTS(presentation time stamp): 0010 0001 0000 0000 0000 0001 1001 0110 0 + +下面字段在本例中都没有: +ESCR(42) = ESCR_base(33) + ESCR_extension(9) +ES_rate(22) +DSM特技方式(8) +additional_copy_info(7) +previous_PES_packet_CRC(16) +PES_Extension(不确定) + + +因为 PTS_DTS_flags == 10,所以本例中只有PTS没有DTS. + + +注意 : 本TS包 包含PES头信息 说明开始下一帧 + +---------- + + +4. 第四个包 +包头 : 47 08 14 11 +0x47 : syncByte +0x0 : 0000(这里的最后一个字节,要给到下面),payload_unit_start_indicator = 0. +0x814 : 0 1000 0001 0100, pid = 0x814(音频MP3) +0x11 : 0001 0001, adaptionFieldControl = 01 + +这里: +payload_unit_start_indicator = 0, 说明有效载荷起始符为0,不含有PES包头 +adaptionFieldControl = 01, 说明仅含有效负载(TS包第三种情况) + +---------- + + +5. 第五个包 +包头 : 47 08 14 32 +0x47 : syncByte +0x0 : 0000(这里的最后一个字节,要给到下面),payload_unit_start_indicator = 0. +0x814 : 0 1000 0001 0100, pid = 0x814(音频MP3) +0x32 : 0011 0010, adaptionFieldControl = 11 + +这里: +payload_unit_start_indicator = 0, 说明有效载荷起始符为0,不含有PES包头 +adaptionFieldControl = 11, 说明先有自适应字段,再有有效载荷(TS包第一种情况) + +负载 : 99 00 FF FF FF ... FF 52 DE E6 B5 D0 76 CD CB B2 24 B3 92 AD 4E CD 19 D2 CC 82 D4 78 10 80 6C 0E 99 49 A4 59 C0 + +adaptation_field_length : 99(值为153,表示占用153个字节) + +discontinuity_indicator & random_access_indicator & +elementary_stream_priority_indicator & PCR_flag & +OPCR_flag & splicing_point_flag & +transport_private_data_flag & adaptation_field_extension_flag : 00 剩下的所有字段都为0 + +(00 FF FF FF ... FF)这里都是调整字段,从52 DE E6 B5 D0(从00(FF之前,99之后) 开始算是第1个字节,跳到第153个字节)开始,就是真正的帧数据了 + + +---------- + + +6. 第六个包 +包头 : 47 48 14 13 +0x47 : syncByte +0x4 : 0100(这里的最后一个字节,要给到下面),payload_unit_start_indicator = 1. +0x814 : 0 1000 0001 0100, pid = 0x814(音频MP3) +0x13 : 0001 0011, adaptionFieldControl = 01,说明仅含有效负载(TS包第三种情况) + +这里: +payload_unit_start_indicator = 1, 说明有效载荷起始符为1,含有PES包头 +adaptionFieldControl = 01, 说明仅含有效负载(TS包第三种情况) + +负载 : 00 00 01 C0 01 88 80 80 05 21 00 01 A6 E7 FF FD + +packetStartCodePrefix : 00 00 01 +streamID : C0 +pes_PacketLength : 01 88(值为392,占用392个字节) +Sned PES HEADER : 占用不确定位 + +所以本包数据流ID 和 第二个包的流ID是一样的 + +注意 : 本TS包 又包含PES头信息 说明开始下一帧 + + +---------- + +7. 第七个包 +包头 : 47 48 10 30 +0x47 : syncByte +0x4 : 0100(这里的最后一个字节,要给到下面),payload_unit_start_indicator = 1. +0x810 : 0 1000 0001 0000, pid = 0x810(视频H264) +0x30 : 0011 0000, adaptionFieldControl = 11,说明含有调整字段和有效负载(TS包第一种情况) + +这里: +payload_unit_start_indicator = 1, 说明有效载荷起始符为1,含有PES包头 +adaptionFieldControl = 11, 说明含有调整字段和有效负载(TS包第一种情况) + +负载 : 07 10 00 00 01 0F 7E 88 00 00 01 E0 00 00 80 C0 0A 31 00 01 96 07 11 00 01 7E 91 00 00 00 01 67 4D 40 1E 96 ...... D2 99 71 F3 + +adaptation_field_length : 07(值为7,表示占用153个字节) + +discontinuity_indicator & random_access_indicator & +elementary_stream_priority_indicator & PCR_flag & +OPCR_flag & splicing_point_flag & +transport_private_data_flag & adaptation_field_extension_flag : 10 + +(10 00 00 01 0F 7E 88)调整字段 + +packetStartCodePrefix : 00 00 01 +streamID : EO +pes_PacketLength : 00 00(值为0,占用0个字节,一帧数据长度,也可以置为0,此时需要自己去计算) +Sned PES HEADER : 占用不确定位 + + +---------- + + +8. 第八个包 +包头 : 47 08 10 11 +0x47 : syncByte +0x0 : 0000(这里的最后一个字节,要给到下面),payload_unit_start_indicator = 0. +0x810 : 0 1000 0001 0000, pid = 0x810(视频H264) +0x11 : 0001 0001, adaptionFieldControl = 01, 说明仅含有效负载(TS包第三种情况) + +这里: +payload_unit_start_indicator = 0, 说明有效载荷起始符为0,不含有PES包头 +adaptionFieldControl = 01, 说明仅含有效负载(TS包第三种情况) + + +---------- + +总结这个八个包: + +第一个TS包(PID:0X00) : 包含了PAT. +第二个TS包(PID:0X81) : 包含了PMT. +第三个TS包(PID:0x814) : 音频PES包头所有的TS包. +第四个TS包(PID:0x814) : 音频TS包. +第五个TS包(PID:0x814) : 音频TS包. +第六个TS包(PID:0x814) : 音频PES包头所有的TS包. +第七个TS包(PID:0x810) : 视频PES包头所有的TS包. +第八个TS包(PID:0x810) : 视频TS包. + + +---------- + + +// Packet Header: +// PID是TS流中唯一识别标志,Packet Data是什么内容就是由PID决定的.如果一个TS流中的一个Packet的Packet Header中的PID是0x0000, +// 那么这个Packet的Packet Data就是DVB的PAT表而非其他类型数据(如Video,Audio或其他业务信息). + +// 分析一个Header: +// 二进制: 0100 0111 0000 0111 1110 0101 0001 0010 +// 十六进制: 4 7 0 7 e 5 1 2 + +// syncByte = 0x47 就是0x47,这是DVB TS规定的同步字节,固定是0x47 +// transportErrorIndicator = 0 表示当前包没有发生传输错误 +// payloadUnitStartIndicator = 0 具体含义参考ISO13818-1标准文档 +// transportPriority = 0 表示当前包是低优先级 +// pid = 0x07e5(0 0111 1110 0101) Video PID +// transportScramblingControl = 00 表示节目没有加密 +// adaptionFieldControl = 01 具体含义参考ISO13818-1标准文档 +// continuityCounter = 0010 表示当前传送的相同类型的包是第3个 + + +---------- + + +// 分析一段TS流:(PAT) +// Packet Header : 0x47 0x40 0x00 0x10 +// Packet Data : 00 00 b0 11 00 01 c1 00 00 00 00 e0 1f 00 01 e1 00 24 ac48 84 ff ff ... ff ff + +// Header PID = 0x0000 说明数据包是PAT表信息,包头后需要除去一个字节才是有效数据(payload_unit_start_indicator="1") +// 所以,Packet Data就应该是 : 00 b0 11 00 01 c1 00 00 00 00 e0 1f 00 01 e1 00 24 ac48 84 ff ff ... ff ff + +// +// 00 | b0 11 | 00 01 | c1 | 00 | 00 | 00 00 | e0 1f | 00 01 e1 00 | +// + +// table_id = 0000 0000 + +// section_syntax_indicator = 1 +// zero = 0 +// reserved1 = 11 +// sectionLength = 0000 0001 0001 + +// transportStreamID = 0000 0000 0000 0001 + +// reserved2 = 11 +// versionNumber = 0000 0 +// currentNextIndicator 1 + +// sectionNumber = 0000 0000 + +// lastSectionNumber = 0000 0000 + +// programNumber = 0000 0000 0000 0000 + +// reserved3 = 111 +// networkPID = 0 0000 0001 1111 + +// crc32 + + +---------- + + +// 分析一段TS流:(PMT) +// Packet Header : 0x47 0x43 0xe8 0x12 +// Packet Data : 00 02 b0 12 00 01 c1 00 00 e3 e9 f0 00 1b e3 e9 f0 00 f0 af b4 4f ff ff ... ff ff + +// Header PID = 0x03e8 说明数据包是PMT表信息,包头后需要除去一个字节才是有效数据(payload_unit_start_indicator="1") +// 所以,Packet Data就应该是 : 02 b0 12 00 01 c1 00 00 e3 e9 f0 00 1b e3 e9 f0 00 f0 af b4 4f ff ff ... ff ff + +// 1 2 3 4 5 6 7 8 9 10 11 12 +// 02 | b0 12 | 00 01 | c1 | 00 | 00 | e3 e9 | f0 00 | 1b | e3 e9 | f0 00 | f0 af b4 4f | +// + +// 1: +// table_id = 0000 0010 + +// 2: +// section_syntax_indicator = 1 +// zero = 0 +// reserved1 = 11 +// section_length = 0000 0001 0010 + +// 3: +// program_number = 0000 0000 0000 0001 + +// 4: +// reserved2 = 11 +// version_number = 00 000 +// current_next_indicator = 1 + +// 5: +// section_number = 0000 0000 + +// 6: +// last_section_number = 0000 0000 + +// 7: +// reserved3 = 111 +// PCR_PID = 0 0011 1110 1001 + +// 8: +// reserved4 = 1111 +// program_info_length = 0000 0000 0000 + +// 9: +// stream_type = 0001 1011 + +// 10: +// reserved5 = 111 +// elementary_PID = 0 0011 1110 1001 + +// 11: +// reserved6 = 1111 +// ES_info_length = 0000 0000 0000 + +// 12: +// crc + + +---------- + + +##TS流解码过程 +1. 获取TS中的PAT +2. 获取TS中的PMT +3. 根据PMT可以知道当前网络中传输的视频(音频)类型(H264),相应的PID,PCR的PID等信息. +4. 设置demux 模块的视频Filter 为相应视频的PID和stream type等. +5. 从视频Demux Filter 后得到的TS数据包中的payload 数据就是 one piece of PES,在TS header中有一些关于此 payload属于哪个 PES的 第多少个数据包. 因此软件中应该将此payload中的数据copy到PES的buffer中,用于拼接一个PES包. +6. 拼接好的PES包的包头会有 PTS,DTS信息,去掉PES的header就是 ES. +7. 直接将 被被拔掉 PES包头的ES包送给decoder就可以进行解码.解码出来的数据就是一帧一帧的视频数据,这些数据至少应当与PES中的PTS关联一下,以便进行视音频同步. +8. I,B,B,P 信息是在ES中的. + + +---------- + + +1. 首先找到PID为0x00的TS包,找到里面的节目映射表(PMT)PID,因为可能有几个节目信息.所以可能有几个PMT_PID,以一个为例 +2. 接着查找该PMT_PID的TS包,通常就紧接着.在该PMT包中找音频和视频的PID.以视频为例. +3. 开始提取一帧ES数据 + 3.1 查找视频PID的TS包 + 3.2 找PES包头,方法:TS包头第2个字节的高6位(有效载荷单元起始指示符)为1的TS包,跳过自适应字段,找到PES包头,提取时间戳,再跳至ES数据,这就是一帧ES数据的开始部分. + 3.3 查找有效载荷单元起始指示符为0的TS包.跳过TS包头,跳过自适应字段,提取后面的ES数据 + 3.4 同3.3接着查找 + 3.5 当碰到有效载荷单元起始指示符又变为1的视频TS包,就知道这是下一帧的开始了,将前面的所有ES数据组合成一帧数据.开始下一轮组帧. + + +---------- + + +##参考文档: + +1. [TS流](http://blog.csdn.net/cabbage2008/article/category/5885203) +1. [TS各个表 与 SECTION 的解析 CAS原理 ](http://blog.sina.com.cn/s/blog_6b94d5680101r5l6.html) \ No newline at end of file diff --git a/codec/mpegts/mpegts_crc32.go b/codec/mpegts/mpegts_crc32.go new file mode 100644 index 0000000..9204316 --- /dev/null +++ b/codec/mpegts/mpegts_crc32.go @@ -0,0 +1,60 @@ +package mpegts + +// http://www.stmc.edu.hk/~vincent/ffmpeg_0.4.9-pre1/libavformat/mpegtsenc.c + +var Crc32_Table = []uint32{ + 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, + 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, + 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 0x4c11db70, 0x48d0c6c7, + 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, + 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, + 0x709f7b7a, 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, + 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, 0xbaea46ef, + 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, + 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, + 0xceb42022, 0xca753d95, 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, + 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0, + 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, + 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, + 0x0808d07d, 0x0cc9cdca, 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, + 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, + 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc, + 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, + 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, 0xe0b41de7, 0xe4750050, + 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, + 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, + 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, + 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, 0x4f040d56, 0x4bc510e1, + 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, + 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, + 0x3f9b762c, 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, + 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, 0xf5ee4bb9, + 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, + 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, + 0xcda1f604, 0xc960ebb3, 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, + 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71, + 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, + 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, + 0x470cdd2b, 0x43cdc09c, 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, + 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, + 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a, + 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, + 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, 0xe3a1cbc1, 0xe760d676, + 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, + 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, + 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, + 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4, +} + +func GetCRC32(data []byte) (crc uint32) { + crc = 0xffffffff + + for _, v := range data { + crc = (crc << 8) ^ Crc32_Table[((crc>>24)^uint32(v))&0xff] + + } + + return +} diff --git a/codec/mpegts/mpegts_pat.go b/codec/mpegts/mpegts_pat.go new file mode 100644 index 0000000..cb464a9 --- /dev/null +++ b/codec/mpegts/mpegts_pat.go @@ -0,0 +1,229 @@ +package mpegts + +import ( + "bytes" + "errors" + "fmt" + "github.com/Monibuca/engine/v4/util" + "io" +) + +// ios13818-1-CN.pdf 43(57)/166 +// +// PAT +// + +var DefaultPATPacket = []byte{ + // TS Header + 0x47, 0x40, 0x00, 0x10, + + // Pointer Field + 0x00, + + // PSI + 0x00, 0xb0, 0x0d, 0x00, 0x01, 0xc1, 0x00, 0x00, + + // PAT + 0x00, 0x01, 0xe1, 0x00, + + // CRC + 0xe8, 0xf9, 0x5e, 0x7d, + + // Stuffing 167 bytes + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +} + +// TS Header : +// SyncByte = 0x47 +// TransportErrorIndicator = 0(B:0), PayloadUnitStartIndicator = 1(B:0), TransportPriority = 0(B:0), +// Pid = 0, +// TransportScramblingControl = 0(B:00), AdaptionFieldControl = 1(B:01), ContinuityCounter = 0(B:0000), + +// PSI : +// TableID = 0x00, +// SectionSyntaxIndicator = 1(B:1), Zero = 0(B:0), Reserved1 = 3(B:11), +// SectionLength = 13(0x00d) +// TransportStreamID = 0x0001 +// Reserved2 = 3(B:11), VersionNumber = (B:00000), CurrentNextIndicator = 1(B:0), +// SectionNumber = 0x00 +// LastSectionNumber = 0x00 + +// PAT : +// ProgramNumber = 0x0001 +// Reserved3 = 15(B:1110), ProgramMapPID = 4097(0x1001) + +// PAT表主要包含频道号码和每一个频道对应的PMT的PID号码,这些信息我们在处理PAT表格的时候会保存起来,以后会使用到这些数据 +type MpegTsPATProgram struct { + ProgramNumber uint16 // 16 bit 节目号 + Reserved3 byte // 3 bits 保留位 + NetworkPID uint16 // 13 bits 网络信息表(NIT)的PID,节目号为0时对应的PID为network_PID + ProgramMapPID uint16 // 13 bit 节目映射表的PID,节目号大于0时对应的PID.每个节目对应一个 +} + +// Program Association Table (节目关联表) +// 节目号为0x0000时,表示这是NIT,PID=0x001f,即3. +// 节目号为0x0001时,表示这是PMT,PID=0x100,即256 +type MpegTsPAT 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) + TransportStreamID uint16 // 16 bits 该字段充当标签,标识网络内此传输流有别于任何其他多路复用流.其值由用户规定 + Reserved2 byte // 2 bits 保留位 + VersionNumber byte // 5 bits 范围0-31,表示PAT的版本号 + CurrentNextIndicator byte // 1 bit 发送的PAT是当前有效还是下一个PAT有效,0则要等待下一个表 + SectionNumber byte // 8 bits 分段的号码.PAT可能分为多段传输.第一段为00,以后每个分段加1,最多可能有256个分段 + LastSectionNumber byte // 8 bits 最后一个分段的号码 + + // N Loop + Program []MpegTsPATProgram // PAT表里面的所有频道索引信息 + + Crc32 uint32 // 32 bits 包含处理全部传输流节目映射分段之后,在附件 B 规定的解码器中给出寄存器零输出的 CRC 值 +} + +func ReadPAT(r io.Reader) (pat MpegTsPAT, err error) { + lr, psi, err := ReadPSI(r, PSI_TYPE_PAT) + if err != nil { + return + } + + pat = psi.Pat + + // N Loop + // 一直循环去读4个字节,用lr的原因是确保不会读过头了. + for lr.N > 0 { + + // 获取每一个频道的节目信息,保存起来 + programs := MpegTsPATProgram{} + + programs.ProgramNumber, err = util.ReadByteToUint16(lr, true) + if err != nil { + return + } + + // 如果programNumber为0,则是NetworkPID,否则是ProgramMapPID(13) + if programs.ProgramNumber == 0 { + programs.NetworkPID, err = util.ReadByteToUint16(lr, true) + if err != nil { + return + } + + programs.NetworkPID = programs.NetworkPID & 0x1fff + } else { + programs.ProgramMapPID, err = util.ReadByteToUint16(lr, true) + if err != nil { + return + } + + programs.ProgramMapPID = programs.ProgramMapPID & 0x1fff + } + + pat.Program = append(pat.Program, programs) + } + if cr, ok := r.(*util.Crc32Reader); ok { + err = cr.ReadCrc32UIntAndCheck() + if err != nil { + return + } + } + + return +} + +func WritePAT(w io.Writer, pat MpegTsPAT) (err error) { + bw := &bytes.Buffer{} + + // 将pat(所有的节目索引信息)写入到缓冲区中 + for _, pats := range pat.Program { + if err = util.WriteUint16ToByte(bw, pats.ProgramNumber, true); err != nil { + return + } + + if pats.ProgramNumber == 0 { + if err = util.WriteUint16ToByte(bw, pats.NetworkPID&0x1fff|7<<13, true); err != nil { + return + } + } else { + // | 0001 1111 | 1111 1111 | + // 7 << 13 -> 1110 0000 0000 0000 + if err = util.WriteUint16ToByte(bw, pats.ProgramMapPID&0x1fff|7<<13, true); err != nil { + return + } + } + } + + if pat.SectionLength == 0 { + pat.SectionLength = 2 + 3 + 4 + uint16(len(bw.Bytes())) + } + + psi := MpegTsPSI{} + + psi.Pat = pat + + if err = WritePSI(w, PSI_TYPE_PAT, psi, bw.Bytes()); err != nil { + return + } + + return +} + +func WritePATPacket(w io.Writer, tsHeader []byte, pat MpegTsPAT) (err error) { + if pat.TableID != TABLE_PAS { + err = errors.New("PAT table ID error") + return + } + + // 将所有要写的数据(PAT),全部放入到buffer中去. + // buffer 里面已经写好了整个pat表(PointerField+PSI+PAT+CRC) + bw := &bytes.Buffer{} + if err = WritePAT(bw, pat); err != nil { + return + } + + // TODO:如果Pat.Program里面包含的信息很大,大于188? + stuffingBytes := util.GetFillBytes(0xff, TS_PACKET_SIZE-4-bw.Len()) + + // PATPacket = TsHeader + PAT + Stuffing Bytes + var PATPacket []byte + PATPacket = append(PATPacket, tsHeader...) + PATPacket = append(PATPacket, bw.Bytes()...) + PATPacket = append(PATPacket, stuffingBytes...) + + fmt.Println("-------------------------") + fmt.Println("Write PAT :", PATPacket) + fmt.Println("-------------------------") + + // 写PAT负载 + if _, err = w.Write(PATPacket); err != nil { + return + } + + return +} + +func WriteDefaultPATPacket(w io.Writer) (err error) { + _, err = w.Write(DefaultPATPacket) + if err != nil { + return + } + + return +} diff --git a/codec/mpegts/mpegts_pes.go b/codec/mpegts/mpegts_pes.go new file mode 100644 index 0000000..12b8bf3 --- /dev/null +++ b/codec/mpegts/mpegts_pes.go @@ -0,0 +1,753 @@ +package mpegts + +import ( + "bytes" + "errors" + "fmt" + "github.com/Monibuca/engine/v4/util" + "github.com/Monibuca/engine/v4/codec" + "io" + "io/ioutil" +) + +// ios13818-1-CN.pdf 45/166 +// +// PES +// + +// 每个传输流和节目流在逻辑上都是由 PES 包构造的 +type MpegTsPesStream struct { + TsPkt MpegTsPacket + PesPkt MpegTsPESPacket +} + +// PES--Packetized Elementary Streams (分组的ES),ES形成的分组称为PES分组,是用来传递ES的一种数据结构 +// 1110 xxxx 为视频流(0xE0) +// 110x xxxx 为音频流(0xC0) +type MpegTsPESPacket struct { + Header MpegTsPESHeader + Payload []byte +} + +type MpegTsPESHeader struct { + PacketStartCodePrefix uint32 // 24 bits 同跟随它的 stream_id 一起组成标识包起始端的包起始码.packet_start_code_prefix 为比特串"0000 0000 0000 0000 0000 0001"(0x000001) + StreamID byte // 8 bits stream_id 指示基本流的类型和编号,如 stream_id 表 2-22 所定义的.传输流中,stream_id 可以设置为准确描述基本流类型的任何有效值,如表 2-22 所规定的.传输流中,基本流类型在 2.4.4 中所指示的节目特定信息中指定 + PesPacketLength uint16 // 16 bits 指示 PES 包中跟随该字段最后字节的字节数.0->指示 PES 包长度既未指示也未限定并且仅在这样的 PES 包中才被允许,该 PES 包的有效载荷由来自传输流包中所包含的视频基本流的字节组成 + + MpegTsOptionalPESHeader + + PayloadLength uint64 // 这个不是标准文档里面的字段,是自己添加的,方便计算 +} + +// 可选的PES Header = MpegTsOptionalPESHeader + stuffing bytes(0xFF) m * 8 +type MpegTsOptionalPESHeader struct { + ConstTen byte // 2 bits 常量10 + PesScramblingControl byte // 2 bit 指示 PES 包有效载荷的加扰方式.当加扰在 PES 等级上实施时, PES 包头,其中包括任选字段只要存在,应不加扰(见表 2-23) + PesPriority byte // 1 bit 指示在此 PES 包中该有效载荷的优先级.1->指示该 PES 包有效载荷比具有此字段置于"0"的其他 PES 包有效载荷有更高的有效载荷优先级.多路复用器能够使用该PES_priority 比特最佳化基本流内的数据 + DataAlignmentIndicator byte // 1 bit 1->指示 PES 包头之后紧随 2.6.10 中data_stream_alignment_descriptor 字段中指示的视频句法单元或音频同步字,只要该描述符字段存在.若置于值"1"并且该描述符不存在,则要求表 2-53,表 2-54 或表 2-55 的 alignment_type"01"中所指示的那种校准.0->不能确定任何此类校准是否发生 + Copyright byte // 1 bit 1->指示相关 PES 包有效载荷的素材依靠版权所保护.0->不能确定该素材是否依靠版权所保护 + OriginalOrCopy byte // 1 bit 1->指示相关 PES 包有效载荷的内容是原始的.0->指示相关 PES 包有效载荷的内容是复制的 + PtsDtsFlags byte // 2 bits 10->PES 包头中 PTS 字段存在. 11->PES 包头中 PTS 字段和 DTS 字段均存在. 00->PES 包头中既无任何 PTS 字段也无任何 DTS 字段存在. 01->禁用 + EscrFlag byte // 1 bit 1->指示 PES 包头中 ESCR 基准字段和 ESCR 扩展字段均存在.0->指示无任何 ESCR 字段存在 + EsRateFlag byte // 1 bit 1->指示 PES 包头中 ES_rate 字段存在.0->指示无任何 ES_rate 字段存在 + DsmTrickModeFlag byte // 1 bit 1->指示 8 比特特技方式字段存在.0->指示此字段不存在 + AdditionalCopyInfoFlag byte // 1 bit 1->指示 additional_copy_info 存在.0->时指示此字段不存在 + PesCRCFlag byte // 1 bit 1->指示 PES 包中 CRC 字段存在.0->指示此字段不存在 + PesExtensionFlag byte // 1 bit 1->时指示 PES 包头中扩展字段存在.0->指示此字段不存在 + PesHeaderDataLength byte // 8 bits 指示在此 PES包头中包含的由任选字段和任意填充字节所占据的字节总数.任选字段的存在由前导 PES_header_data_length 字段的字节来指定 + + // Optional Field + Pts uint64 // 33 bits 指示时间与解码时间的关系如下: PTS 为三个独立字段编码的 33 比特数.它指示基本流 n 的显示单元 k 在系统目标解码器中的显示时间 tpn(k).PTS 值以系统时钟频率除以 300(产生 90 kHz)的周期为单位指定.显示时间依照以下公式 2-11 从 PTS 中推出.有关编码显示时间标记频率上的限制参阅 2.7.4 + Dts uint64 // 33 bits 指示基本流 n 的存取单元 j 在系统目标解码器中的解码时间 tdn(j). DTS 的值以系统时钟频率除以 300(生成90 kHz)的周期为单位指定.依照以下公式 2-12 从 DTS 中推出解码时间 + EscrBase uint64 // 33 bits 其值由 ESCR_base(i) 给出,如公式 2-14 中给出的 + EscrExtension uint16 // 9 bits 其值由 ESCR_ext(i) 给出,如公式 2-15 中给出的. ESCR 字段指示包含 ESCR_base 最后比特的字节到达 PES流的 PES-STD 输入端的预期时间(参阅 2.5.2.4) + EsRate uint32 // 22 bits 在PES 流情况中,指定系统目标解码器接收 PES 包字节的速率.ES_rate 在包括它的 PES 包以及相同 PES 流的后续 PES 包中持续有效直至遇到新的 ES_rate 字段时为止.ES 速率值以 50 字节/秒为度量单位.0 值禁用 + TrickModeControl byte // 3 bits 指示适用于相关视频流的特技方式.在其他类型基本流的情况中,此字段以及后随 5 比特所规定的那些含义未确定.对于 trick_mode 状态的定义,参阅 2.4.2.3 的特技方式段落 + TrickModeValue byte // 5 bits + AdditionalCopyInfo byte // 7 bits 包含与版权信息有关的专用数据 + PreviousPESPacketCRC uint16 // 16 bits 包含产生解码器中 16 寄存器零输出的 CRC 值, 类似于附件 A 中定义的解码器. 但在处理先前的 PES 包数据字节之后, PES 包头除外,采用多项式 + + // PES Extension + PesPrivateDataFlag byte // 1 bit 1->指示该 PES 包头包含专用数据. 0->指示 PES 包头中不存在专用数据 + PackHeaderFieldFlag byte // 1 bit 1->指示 ISO/IEC 11172-1 包头或节目流包头在此 PES包头中存储.若此字段处于节目流中包含的 PES 包中,则此字段应设置为"0.传输流中, 0->指示该 PES 头中无任何包头存在 + ProgramPacketSequenceCounterFlag byte // 1 bit 1->指示 program_packet_sequence_counter, MPEG1_MPEG2_identifier 以及 original_stuff_length 字段在 PES 包中存在.0->它指示这些字段在 PES 头中不存在 + PSTDBufferFlag byte // 1 bit 1->指示 P-STD_buffer_scale 和 P-STD_buffer_size 在 PES包头中存在.0->指示这些字段在 PES 头中不存在 + Reserved byte // 3 bits + PesExtensionFlag2 byte // 1 bits 1->指示 PES_extension_field_length 字段及相关的字段存在.0->指示 PES_extension_field_length 字段以及任何相关的字段均不存在. + + // Optional Field + PesPrivateData [16]byte // 128 bits 此数据,同前后字段数据结合,应不能仿真packet_start_code_prefix (0x000001) + PackHeaderField byte // 8 bits 指示 pack_header_field() 的长度,以字节为单位 + ProgramPacketSequenceCounter byte // 7 bits + Mpeg1Mpeg2Identifier byte // 1 bit 1->指示此 PES 包承载来自 ISO/IEC 11172-1 流的信息.0->指示此 PES 包承载来自节目流的信息 + OriginalStuffLength byte // 6 bits 在原始 ITU-T H.222.0 建议书| ISO/IEC 13818-1 PES 包头或在原始 ISO/IEC 11172-1 包头中所使用的填充字节数 + PSTDBufferScale byte // 1bit 它的含义仅当节目流中包含此 PES 包时才规定.它指示所使用的标度因子用于解释后续的 P-STD_buffer_size 字段.若前导 stream_id 指示音频流,则P-STD 缓冲器标度字段必为"0"值.若前导 stream_id 指示视频流,则 P-STD_buffer_scale 字段必为"1"值.对于所有其他流类型,该值可为"1"或为"0" + PSTDBufferSize uint16 // 13 bits 其含义仅当节目流中包含此 PES包时才规定.它规定在 P-STD 中,输入缓冲器 BSn 的尺寸.若 STD_buffer_scale 为 "0"值,则 P-STD_buffer_size以 128 字节为单位度量该缓冲器尺寸.若 P-STD_buffer_scale 为"1",则 P-STD_buffer_size 以 1024 字节为单位度量该缓冲器尺寸 + PesExtensionFieldLength byte // 7 bits 指示 PES 扩展字段中跟随此长度字段的直至并包括任何保留字节为止的数据长度,以字节为度量单位 + StreamIDExtensionFlag byte // 1 bits + //pesExtensionField []byte // PES_extension_field_length bits + //packField []byte // pack_field_length bits +} + +// pts_dts_Flags == "10" -> PTS +// 0010 4 +// PTS[32...30] 3 +// marker_bit 1 +// PTS[29...15] 15 +// marker_bit 1 +// PTS[14...0] 15 +// marker_bit 1 + +// pts_dts_Flags == "11" -> PTS + DTS + +type MpegtsPESFrame struct { + Pid uint16 + IsKeyFrame bool + ContinuityCounter byte + ProgramClockReferenceBase uint64 +} + +func ReadPESHeader(r io.Reader) (header MpegTsPESHeader, err error) { + var flags uint8 + var length uint + + // packetStartCodePrefix(24) (0x000001) + header.PacketStartCodePrefix, err = util.ReadByteToUint24(r, true) + if err != nil { + return + } + + if header.PacketStartCodePrefix != 0x0000001 { + err = errors.New("read PacketStartCodePrefix is not 0x0000001") + return + } + + // streamID(8) + header.StreamID, err = util.ReadByteToUint8(r) + if err != nil { + return + } + + // pes_PacketLength(16) + header.PesPacketLength, err = util.ReadByteToUint16(r, true) + if err != nil { + return + } + + length = uint(header.PesPacketLength) + + // PES包长度可能为0,这个时候,需要自己去算 + // 0 <= len <= 65535 + // 如果当length为0,那么先设置为最大值,然后用LimitedReade去读,如果读到最后面剩下的字节数小于65536,才是正确的包大小. + // 一个包一般情况下不可能会读1<<31个字节. + if length == 0 { + length = 1 << 31 + } + + // lrPacket 和 lrHeader 位置指针是在同一位置的 + lrPacket := &io.LimitedReader{R: r, N: int64(length)} + lrHeader := lrPacket + + // constTen(2) + // pes_ScramblingControl(2) + // pes_Priority(1) + // dataAlignmentIndicator(1) + // copyright(1) + // originalOrCopy(1) + flags, err = util.ReadByteToUint8(lrHeader) + if err != nil { + return + } + + header.ConstTen = flags & 0xc0 + header.PesScramblingControl = flags & 0x30 + header.PesPriority = flags & 0x08 + header.DataAlignmentIndicator = flags & 0x04 + header.Copyright = flags & 0x02 + header.OriginalOrCopy = flags & 0x01 + + // pts_dts_Flags(2) + // escr_Flag(1) + // es_RateFlag(1) + // dsm_TrickModeFlag(1) + // additionalCopyInfoFlag(1) + // pes_CRCFlag(1) + // pes_ExtensionFlag(1) + flags, err = util.ReadByteToUint8(lrHeader) + if err != nil { + return + } + + header.PtsDtsFlags = flags & 0xc0 + header.EscrFlag = flags & 0x20 + header.EsRateFlag = flags & 0x10 + header.DsmTrickModeFlag = flags & 0x08 + header.AdditionalCopyInfoFlag = flags & 0x04 + header.PesCRCFlag = flags & 0x02 + header.PesExtensionFlag = flags & 0x01 + + // pes_HeaderDataLength(8) + header.PesHeaderDataLength, err = util.ReadByteToUint8(lrHeader) + if err != nil { + return + } + + length = uint(header.PesHeaderDataLength) + + lrHeader = &io.LimitedReader{R: lrHeader, N: int64(length)} + + // 00 -> PES 包头中既无任何PTS 字段也无任何DTS 字段存在 + // 10 -> PES 包头中PTS 字段存在 + // 11 -> PES 包头中PTS 字段和DTS 字段均存在 + // 01 -> 禁用 + + // PTS(33) + if flags&0x80 != 0 { + var pts uint64 + pts, err = util.ReadByteToUint40(lrHeader, true) + if err != nil { + return + } + + header.Pts = util.GetPtsDts(pts) + } + + // DTS(33) + if flags&0x80 != 0 && flags&0x40 != 0 { + var dts uint64 + dts, err = util.ReadByteToUint40(lrHeader, true) + if err != nil { + return + } + + header.Dts = util.GetPtsDts(dts) + } + + // reserved(2) + escr_Base1(3) + marker_bit(1) + + // escr_Base2(15) + marker_bit(1) + escr_Base23(15) + + // marker_bit(1) + escr_Extension(9) + marker_bit(1) + if header.EscrFlag != 0 { + _, err = util.ReadByteToUint48(lrHeader, true) + if err != nil { + return + } + + //s.pes.escr_Base = escrBaseEx & 0x3fffffffe00 + //s.pes.escr_Extension = uint16(escrBaseEx & 0x1ff) + } + + // es_Rate(22) + if header.EsRateFlag != 0 { + header.EsRate, err = util.ReadByteToUint24(lrHeader, true) + if err != nil { + return + } + } + + // 不知道为什么这里不用 + /* + // trickModeControl(3) + trickModeValue(5) + if s.pes.dsm_TrickModeFlag != 0 { + trickMcMv, err := util.ReadByteToUint8(lrHeader) + if err != nil { + return err + } + + s.pes.trickModeControl = trickMcMv & 0xe0 + s.pes.trickModeValue = trickMcMv & 0x1f + } + */ + + // marker_bit(1) + additionalCopyInfo(7) + if header.AdditionalCopyInfoFlag != 0 { + header.AdditionalCopyInfo, err = util.ReadByteToUint8(lrHeader) + if err != nil { + return + } + + header.AdditionalCopyInfo = header.AdditionalCopyInfo & 0x7f + } + + // previous_PES_Packet_CRC(16) + if header.PesCRCFlag != 0 { + header.PreviousPESPacketCRC, err = util.ReadByteToUint16(lrHeader, true) + if err != nil { + return + } + } + + // pes_PrivateDataFlag(1) + packHeaderFieldFlag(1) + programPacketSequenceCounterFlag(1) + + // p_STD_BufferFlag(1) + reserved(3) + pes_ExtensionFlag2(1) + if header.PesExtensionFlag != 0 { + var flags uint8 + flags, err = util.ReadByteToUint8(lrHeader) + if err != nil { + return + } + + header.PesPrivateDataFlag = flags & 0x80 + header.PackHeaderFieldFlag = flags & 0x40 + header.ProgramPacketSequenceCounterFlag = flags & 0x20 + header.PSTDBufferFlag = flags & 0x10 + header.PesExtensionFlag2 = flags & 0x01 + + // TODO:下面所有的标志位,可能获取到的数据,都简单的读取后,丢弃,如果日后需要,在这里处理 + + // pes_PrivateData(128) + if header.PesPrivateDataFlag != 0 { + if _, err = io.CopyN(ioutil.Discard, lrHeader, int64(16)); err != nil { + return + } + } + + // packFieldLength(8) + if header.PackHeaderFieldFlag != 0 { + if _, err = io.CopyN(ioutil.Discard, lrHeader, int64(1)); err != nil { + return + } + } + + // marker_bit(1) + programPacketSequenceCounter(7) + marker_bit(1) + + // mpeg1_mpeg2_Identifier(1) + originalStuffLength(6) + if header.ProgramPacketSequenceCounterFlag != 0 { + if _, err = io.CopyN(ioutil.Discard, lrHeader, int64(2)); err != nil { + return + } + } + + // 01 + p_STD_bufferScale(1) + p_STD_bufferSize(13) + if header.PSTDBufferFlag != 0 { + if _, err = io.CopyN(ioutil.Discard, lrHeader, int64(2)); err != nil { + return + } + } + + // marker_bit(1) + pes_Extension_Field_Length(7) + + // streamIDExtensionFlag(1) + if header.PesExtensionFlag != 0 { + if _, err = io.CopyN(ioutil.Discard, lrHeader, int64(2)); err != nil { + return + } + } + } + + // 把剩下的头的数据消耗掉 + if lrHeader.N > 0 { + if _, err = io.CopyN(ioutil.Discard, lrHeader, int64(lrHeader.N)); err != nil { + return + } + } + + // 2的16次方,16个字节 + if lrPacket.N < 65536 { + // 这里得到的其实是负载长度,因为已经偏移过了Header部分. + //header.pes_PacketLength = uint16(lrPacket.N) + header.PayloadLength = uint64(lrPacket.N) + } + + return +} + +func WritePESHeader(w io.Writer, header MpegTsPESHeader) (written int, err error) { + if header.PacketStartCodePrefix != 0x0000001 { + err = errors.New("write PacketStartCodePrefix is not 0x0000001") + return + } + + // packetStartCodePrefix(24) (0x000001) + if err = util.WriteUint24ToByte(w, header.PacketStartCodePrefix, true); err != nil { + return + } + + written += 3 + + // streamID(8) + if err = util.WriteUint8ToByte(w, header.StreamID); err != nil { + return + } + + written += 1 + + // pes_PacketLength(16) + // PES包长度可能为0,这个时候,需要自己去算 + // 0 <= len <= 65535 + if err = util.WriteUint16ToByte(w, header.PesPacketLength, true); err != nil { + return + } + + //fmt.Println("Length :", payloadLength) + //fmt.Println("PES Packet Length :", header.pes_PacketLength) + + written += 2 + + // constTen(2) + // pes_ScramblingControl(2) + // pes_Priority(1) + // dataAlignmentIndicator(1) + // copyright(1) + // originalOrCopy(1) + // 1000 0001 + if header.ConstTen != 0x80 { + err = errors.New("pes header ConstTen != 0x80") + return + } + + flags := header.ConstTen | header.PesScramblingControl | header.PesPriority | header.DataAlignmentIndicator | header.Copyright | header.OriginalOrCopy + if err = util.WriteUint8ToByte(w, flags); err != nil { + return + } + + written += 1 + + // pts_dts_Flags(2) + // escr_Flag(1) + // es_RateFlag(1) + // dsm_TrickModeFlag(1) + // additionalCopyInfoFlag(1) + // pes_CRCFlag(1) + // pes_ExtensionFlag(1) + sevenFlags := header.PtsDtsFlags | header.EscrFlag | header.EsRateFlag | header.DsmTrickModeFlag | header.AdditionalCopyInfoFlag | header.PesCRCFlag | header.PesExtensionFlag + if err = util.WriteUint8ToByte(w, sevenFlags); err != nil { + return + } + + written += 1 + + // pes_HeaderDataLength(8) + if err = util.WriteUint8ToByte(w, header.PesHeaderDataLength); err != nil { + return + } + + written += 1 + + // PtsDtsFlags == 192(11), 128(10), 64(01)禁用, 0(00) + if header.PtsDtsFlags&0x80 != 0 { + // PTS和DTS都存在(11),否则只有PTS(10) + if header.PtsDtsFlags&0x80 != 0 && header.PtsDtsFlags&0x40 != 0 { + // 11:PTS和DTS + // PTS(33) + 4 + 3 + pts := util.PutPtsDts(header.Pts) | 3<<36 + if err = util.WriteUint40ToByte(w, pts, true); err != nil { + return + } + + written += 5 + + // DTS(33) + 4 + 3 + dts := util.PutPtsDts(header.Dts) | 1<<36 + if err = util.WriteUint40ToByte(w, dts, true); err != nil { + return + } + + written += 5 + } else { + // 10:只有PTS + // PTS(33) + 4 + 3 + pts := util.PutPtsDts(header.Pts) | 2<<36 + if err = util.WriteUint40ToByte(w, pts, true); err != nil { + return + } + + written += 5 + } + } + + return +} + +func WritePESPacket(w io.Writer, frame *MpegtsPESFrame, packet MpegTsPESPacket) (err error) { + var tsPkts []byte + if tsPkts, err = PESToTs(frame, packet); err != nil { + return + } + + // bw.Bytes == PES Packet + if _, err = w.Write(tsPkts); err != nil { + return + } + + return +} + +func IowWritePESPacket(w io.Writer, tsHeader MpegTsHeader, packet MpegTsPESPacket) (err error) { + if packet.Header.PacketStartCodePrefix != 0x000001 { + return errors.New("packetStartCodePrefix != 0x000001") + } + + bw := &bytes.Buffer{} + + // TODO:如果头长度大于65536,字段会为0,是否要改? + _, err = WritePESHeader(bw, packet.Header) + if err != nil { + return + } + + PESPacket := &util.IOVec{} + PESPacket.Append(bw.Bytes()) // header + PESPacket.Append(packet.Payload) // packet + + // 用IOVecWriter来写PES包,IOVecWriter实现了Write方法. + // 因为通常在将一帧PES封装成TS包(188字节)的时候,一般情况下一帧PES字节数会大于188,并且分多次封装. + // 例如这一帧PES字节数为189,那么在封装第二个TS包的时候就只会封装1字节,会导致多次写操作,降低性能. + // 因此将所有的字节数,都写进缓冲中去,然后用系统调用syscall来写入. + iow := util.NewIOVecWriter(w) + + var isKeyFrame bool + var headerLength int + + isKeyFrame = CheckPESPacketIsKeyFrame(packet) + + // 写一帧PES + // 如果是I帧,会有pcr,所以会有调整字段AF. + // 如果当前包字节不满188字节,会需要填充0xff,所以会有调整字段AF. + for i := 0; PESPacket.Length > 0; i++ { + + header := MpegTsHeader{ + SyncByte: 0x47, + Pid: tsHeader.Pid, + AdaptionFieldControl: 1, + ContinuityCounter: byte(i % 15), + } + + // 每一帧开头 + if i == 0 { + header.PayloadUnitStartIndicator = 1 + } + + // I帧 + if isKeyFrame { + header.AdaptionFieldControl = 0x03 + header.AdaptationFieldLength = 7 + header.PCRFlag = 1 + header.RandomAccessIndicator = tsHeader.RandomAccessIndicator + header.ProgramClockReferenceBase = tsHeader.ProgramClockReferenceBase + header.ProgramClockReferenceExtension = tsHeader.ProgramClockReferenceExtension + + isKeyFrame = false + } + + // 这个包大小,会在每一次PESPacket.WriteTo中慢慢减少. + packetLength := PESPacket.Length + + // 包不满188字节 + if packetLength < TS_PACKET_SIZE-4 { + + if header.AdaptionFieldControl >= 2 { + header.AdaptationFieldLength = uint8(TS_PACKET_SIZE - 4 - 1 - packetLength - 7) + } else { + header.AdaptionFieldControl = 0x03 + header.AdaptationFieldLength = uint8(TS_PACKET_SIZE - 4 - 1 - packetLength) + } + + headerLength, err = WriteTsHeader(iow, header) + if err != nil { + return + } + + stuffingLength := int(header.AdaptationFieldLength - 1) + if _, err = iow.Write(util.GetFillBytes(0xff, stuffingLength)); err != nil { + return + } + + headerLength += stuffingLength + + } else { + headerLength, err = WriteTsHeader(iow, header) + if err != nil { + return + } + } + + /* + if headerLength, err = writeTsHeader(iow, header, packetLength); err != nil { + return + } + */ + + payloadLength := 188 - headerLength + + // 写PES负载 + if _, err = PESPacket.WriteTo(iow, payloadLength); err != nil { + return + } + } + + iow.Flush() + + return +} + +func CheckPESPacketIsKeyFrame(packet MpegTsPESPacket) bool { + + nalus := bytes.SplitN(packet.Payload, codec.NALU_Delimiter1, -1) + + for _, v := range nalus { + if v[0]&0x1f == codec.NALU_IDR_Picture { + return true + } + } + + return false +} + +func TsToPES(tsPkts []MpegTsPacket) (pesPkt MpegTsPESPacket, err error) { + var index int + + for i := 0; i < len(tsPkts); i++ { + if tsPkts[i].Header.SyncByte != 0x47 { + err = errors.New("mpegts header sync error!") + return + } + + if tsPkts[i].Header.PayloadUnitStartIndicator == 1 { + index++ + + // 一个PES包里面只可能包含一个PayloadUnitStartIndicator=1的TS包. + if index >= 2 { + err = errors.New("TsToPES error PayloadUnitStartIndicator >= 2") + return + } + + r := bytes.NewReader(tsPkts[i].Payload) + lr := &io.LimitedReader{R: r, N: int64(len(tsPkts[i].Payload))} + + // TS Packet PES Header Start Index + hBegin := lr.N + + // PES Header + pesPkt.Header, err = ReadPESHeader(lr) + if err != nil { + return + } + + // TS Packet PES Header End Index + hEnd := lr.N + + pesHeaderLength := hBegin - hEnd + + if pesHeaderLength > 0 && pesHeaderLength <= hBegin { + pesPkt.Payload = append(pesPkt.Payload, tsPkts[i].Payload[pesHeaderLength:]...) + } + } + + if tsPkts[i].Header.PayloadUnitStartIndicator == 0 { + // MpegTsPacket Header 已经包含了自适应字段在里面,所以MpegTsPacket Payload直接就是PES Pyaload + pesPkt.Payload = append(pesPkt.Payload, tsPkts[i].Payload...) + } + + } + + return +} + +func PESToTs(frame *MpegtsPESFrame, packet MpegTsPESPacket) (tsPkts []byte, err error) { + if packet.Header.PacketStartCodePrefix != 0x000001 { + err = errors.New("packetStartCodePrefix != 0x000001") + return + } + + bwPESPkt := &bytes.Buffer{} + _, err = WritePESHeader(bwPESPkt, packet.Header) + if err != nil { + return + } + + if _, err = bwPESPkt.Write(packet.Payload); err != nil { + return + } + + var tsHeaderLength int + for i := 0; bwPESPkt.Len() > 0; i++ { + bwTsHeader := &bytes.Buffer{} + + tsHeader := MpegTsHeader{ + SyncByte: 0x47, + TransportErrorIndicator: 0, + PayloadUnitStartIndicator: 0, + TransportPriority: 0, + Pid: frame.Pid, + TransportScramblingControl: 0, + AdaptionFieldControl: 1, + ContinuityCounter: frame.ContinuityCounter, + } + + frame.ContinuityCounter++ + frame.ContinuityCounter = frame.ContinuityCounter % 16 + + // 每一帧的开头,当含有pcr的时候,包含调整字段 + if i == 0 { + tsHeader.PayloadUnitStartIndicator = 1 + + // 当PCRFlag为1的时候,包含调整字段 + if frame.IsKeyFrame { + tsHeader.AdaptionFieldControl = 0x03 + tsHeader.AdaptationFieldLength = 7 + tsHeader.PCRFlag = 1 + tsHeader.RandomAccessIndicator = 1 + tsHeader.ProgramClockReferenceBase = frame.ProgramClockReferenceBase + } + } + + pesPktLength := bwPESPkt.Len() + + // 每一帧的结尾,当不满足188个字节的时候,包含调整字段 + if pesPktLength < TS_PACKET_SIZE-4 { + var tsStuffingLength uint8 + + tsHeader.AdaptionFieldControl = 0x03 + tsHeader.AdaptationFieldLength = uint8(TS_PACKET_SIZE - 4 - 1 - pesPktLength) + + // TODO:如果第一个TS包也是最后一个TS包,是不是需要考虑这个情况? + // MpegTsHeader最少占6个字节.(前4个走字节 + AdaptationFieldLength(1 byte) + 3个指示符5个标志位(1 byte)) + if tsHeader.AdaptationFieldLength >= 1 { + tsStuffingLength = tsHeader.AdaptationFieldLength - 1 + } else { + tsStuffingLength = 0 + } + + // error + tsHeaderLength, err = WriteTsHeader(bwTsHeader, tsHeader) + if err != nil { + return + } + + if tsStuffingLength > 0 { + if _, err = bwTsHeader.Write(util.GetFillBytes(0xff, int(tsStuffingLength))); err != nil { + return + } + } + + tsHeaderLength += int(tsStuffingLength) + } else { + tsHeaderLength, err = WriteTsHeader(bwTsHeader, tsHeader) + if err != nil { + return + } + } + + tsPayloadLength := TS_PACKET_SIZE - tsHeaderLength + + //fmt.Println("tsPayloadLength :", tsPayloadLength) + + // 这里不断的减少PES包 + tsHeaderByte := bwTsHeader.Bytes() + tsPayloadByte := bwPESPkt.Next(tsPayloadLength) + + // tmp := tsHeaderByte[3] << 2 + // tmp = tmp >> 6 + // if tmp == 2 { + // fmt.Println("fuck you mother.") + // } + + tsPktByte := append(tsHeaderByte, tsPayloadByte...) + + if len(tsPktByte) != TS_PACKET_SIZE { + err = errors.New(fmt.Sprintf("%s, packet size=%d", "TS_PACKET_SIZE != 188,", len(tsPktByte))) + return + } + + tsPkts = append(tsPkts, tsPktByte...) + } + + return +} diff --git a/codec/mpegts/mpegts_pmt.go b/codec/mpegts/mpegts_pmt.go new file mode 100644 index 0000000..6207f68 --- /dev/null +++ b/codec/mpegts/mpegts_pmt.go @@ -0,0 +1,384 @@ +package mpegts + +import ( + "bytes" + "errors" + "fmt" + "io" + + "github.com/Monibuca/engine/v4/util" +) + +// ios13818-1-CN.pdf 46(60)-153(167)/page +// +// PMT +// + +var DefaultPMTPacket = []byte{ + // TS Header + 0x47, 0x41, 0x00, 0x10, + + // Pointer Field + 0x00, + + // PSI + 0x02, 0xb0, 0x17, 0x00, 0x01, 0xc1, 0x00, 0x00, + + // PMT + 0xe1, 0x01, + 0xf0, 0x00, + + // H264 + 0x1b, 0xe1, 0x01, 0xf0, 0x00, + + // AAC + 0x0f, 0xe1, 0x02, 0xf0, 0x00, + + //0x00, 0x00, 0x00, 0x00, 0x00, + + // CRC for not audio + //0x00, 0x00, 0x00, 0x00, + + // CRC for AAC + 0x9e, 0x28, 0xc6, 0xdd, + + // CRC for MP3 + // 0x4e, 0x59, 0x3d, 0x1e, + + // Stuffing 157 bytes + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +} + +// 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 WriteDefaultPMTPacket(w io.Writer) (err error) { + _, err = w.Write(DefaultPMTPacket) + if err != nil { + return + } + + return +} diff --git a/codec/mpegts/mpegts_psi.go b/codec/mpegts/mpegts_psi.go new file mode 100644 index 0000000..c93fcba --- /dev/null +++ b/codec/mpegts/mpegts_psi.go @@ -0,0 +1,231 @@ +package mpegts + +import ( + "errors" + "fmt" + "github.com/Monibuca/engine/v4/util" + "io" + "io/ioutil" +) + +// +// PSI +// + +const ( + PSI_TYPE_PAT = 1 + PSI_TYPE_PMT = 2 + PSI_TYPE_NIT = 3 + PSI_TYPE_CAT = 4 + PSI_TYPE_TST = 5 + PSI_TYPE_IPMP_CIT = 6 +) + +type MpegTsPSI struct { + // PAT + // PMT + // CAT + // NIT + Pat MpegTsPAT + Pmt MpegTsPMT +} + +// 当传输流包有效载荷包含 PSI 数据时,payload_unit_start_indicator 具有以下意义: +// 若传输流包承载 PSI分段的首字节,则 payload_unit_start_indicator 值必为 1,指示此传输流包的有效载荷的首字节承载pointer_field. +// 若传输流包不承载 PSI 分段的首字节,则 payload_unit_start_indicator 值必为 0,指示在此有效载荷中不存在 pointer_field +// 只要是PSI就一定会有pointer_field +func ReadPSI(r io.Reader, pt uint32) (lr *io.LimitedReader, psi MpegTsPSI, err error) { + // pointer field(8) + cr, ok := r.(*util.Crc32Reader) + if ok { + r = cr.R + } + pointer_field, err := util.ReadByteToUint8(r) + if err != nil { + return + } + + if pointer_field != 0 { + // 无论如何都应该确保能将pointer_field读取到,并且io.Reader指针向下移动 + // ioutil.Discard常用在,http中,如果Get请求,获取到了很大的Body,要丢弃Body,就用这个方法. + // 因为http默认重链接的时候,必须等body读取完成. + // 用于发送需要读取但不想存储的数据,目的是耗尽读取端的数据 + if _, err = io.CopyN(ioutil.Discard, r, int64(pointer_field)); err != nil { + return + } + } + if ok { + r = cr + } + + // table id(8) + tableId, err := util.ReadByteToUint8(r) + if err != nil { + return + } + + // sectionSyntaxIndicator(1) + zero(1) + reserved1(2) + sectionLength(12) + // sectionLength 前两个字节固定为00 + sectionSyntaxIndicatorAndSectionLength, err := util.ReadByteToUint16(r, true) + if err != nil { + return + } + + // 指定该分段的字节数,紧随 section_length 字段开始,并包括 CRC + // 因此剩下最多只能在读sectionLength长度的字节 + lr = &io.LimitedReader{R: r, N: int64(sectionSyntaxIndicatorAndSectionLength & 0x3FF)} + + // PAT TransportStreamID(16) or PMT ProgramNumber(16) + transportStreamIdOrProgramNumber, err := util.ReadByteToUint16(lr, true) + if err != nil { + return + } + + // reserved2(2) + versionNumber(5) + currentNextIndicator(1) + versionNumberAndCurrentNextIndicator, err := util.ReadByteToUint8(lr) + if err != nil { + return + } + + // sectionNumber(8) + sectionNumber, err := util.ReadByteToUint8(lr) + if err != nil { + return + } + + // lastSectionNumber(8) + lastSectionNumber, err := util.ReadByteToUint8(lr) + if err != nil { + return + } + + // 因为lr.N是从sectionLength开始计算,所以要减去 pointer_field(8) + table id(8) + sectionSyntaxIndicator(1) + zero(1) + reserved1(2) + sectionLength(12) + lr.N -= 4 + + switch pt { + case PSI_TYPE_PAT: + { + if tableId != TABLE_PAS { + err = errors.New(fmt.Sprintf("%s, id=%d", "read pmt table id != 2", tableId)) + return + } + + psi.Pat.TableID = tableId + psi.Pat.SectionSyntaxIndicator = uint8((sectionSyntaxIndicatorAndSectionLength & 0x8000) >> 15) + psi.Pat.SectionLength = sectionSyntaxIndicatorAndSectionLength & 0x3FF + psi.Pat.TransportStreamID = transportStreamIdOrProgramNumber + psi.Pat.VersionNumber = versionNumberAndCurrentNextIndicator & 0x3e + psi.Pat.CurrentNextIndicator = versionNumberAndCurrentNextIndicator & 0x01 + psi.Pat.SectionNumber = sectionNumber + psi.Pat.LastSectionNumber = lastSectionNumber + } + case PSI_TYPE_PMT: + { + if tableId != TABLE_TSPMS { + err = errors.New(fmt.Sprintf("%s, id=%d", "read pmt table id != 2", tableId)) + return + } + + psi.Pmt.TableID = tableId + psi.Pmt.SectionSyntaxIndicator = uint8((sectionSyntaxIndicatorAndSectionLength & 0x8000) >> 15) + psi.Pmt.SectionLength = sectionSyntaxIndicatorAndSectionLength & 0x3FF + psi.Pmt.ProgramNumber = transportStreamIdOrProgramNumber + psi.Pmt.VersionNumber = versionNumberAndCurrentNextIndicator & 0x3e + psi.Pmt.CurrentNextIndicator = versionNumberAndCurrentNextIndicator & 0x01 + psi.Pmt.SectionNumber = sectionNumber + psi.Pmt.LastSectionNumber = lastSectionNumber + } + } + + return +} + +func WritePSI(w io.Writer, pt uint32, psi MpegTsPSI, data []byte) (err error) { + var tableId, versionNumberAndCurrentNextIndicator, sectionNumber, lastSectionNumber uint8 + var sectionSyntaxIndicatorAndSectionLength, transportStreamIdOrProgramNumber uint16 + + switch pt { + case PSI_TYPE_PAT: + { + if psi.Pat.TableID != TABLE_PAS { + err = errors.New(fmt.Sprintf("%s, id=%d", "write pmt table id != 0", tableId)) + return + } + + tableId = psi.Pat.TableID + sectionSyntaxIndicatorAndSectionLength = uint16(psi.Pat.SectionSyntaxIndicator)<<15 | 3<<12 | psi.Pat.SectionLength + transportStreamIdOrProgramNumber = psi.Pat.TransportStreamID + versionNumberAndCurrentNextIndicator = psi.Pat.VersionNumber<<1 | psi.Pat.CurrentNextIndicator + sectionNumber = psi.Pat.SectionNumber + lastSectionNumber = psi.Pat.LastSectionNumber + } + case PSI_TYPE_PMT: + { + if psi.Pmt.TableID != TABLE_TSPMS { + err = errors.New(fmt.Sprintf("%s, id=%d", "write pmt table id != 2", tableId)) + return + } + + tableId = psi.Pmt.TableID + sectionSyntaxIndicatorAndSectionLength = uint16(psi.Pmt.SectionSyntaxIndicator)<<15 | 3<<12 | psi.Pmt.SectionLength + transportStreamIdOrProgramNumber = psi.Pmt.ProgramNumber + versionNumberAndCurrentNextIndicator = psi.Pmt.VersionNumber<<1 | psi.Pmt.CurrentNextIndicator + sectionNumber = psi.Pmt.SectionNumber + lastSectionNumber = psi.Pmt.LastSectionNumber + } + } + + // pointer field(8) + if err = util.WriteUint8ToByte(w, 0); err != nil { + return + } + + cw := &util.Crc32Writer{W: w, Crc32: 0xffffffff} + + // table id(8) + if err = util.WriteUint8ToByte(cw, tableId); err != nil { + return + } + + // sectionSyntaxIndicator(1) + zero(1) + reserved1(2) + sectionLength(12) + // sectionLength 前两个字节固定为00 + // 1 0 11 sectionLength + if err = util.WriteUint16ToByte(cw, sectionSyntaxIndicatorAndSectionLength, true); err != nil { + return + } + + // PAT TransportStreamID(16) or PMT ProgramNumber(16) + if err = util.WriteUint16ToByte(cw, transportStreamIdOrProgramNumber, true); err != nil { + return + } + + // reserved2(2) + versionNumber(5) + currentNextIndicator(1) + // 0x3 << 6 -> 1100 0000 + // 0x3 << 6 | 1 -> 1100 0001 + if err = util.WriteUint8ToByte(cw, versionNumberAndCurrentNextIndicator); err != nil { + return + } + + // sectionNumber(8) + if err = util.WriteUint8ToByte(cw, sectionNumber); err != nil { + return + } + + // lastSectionNumber(8) + if err = util.WriteUint8ToByte(cw, lastSectionNumber); err != nil { + return + } + + // data + if _, err = cw.Write(data); err != nil { + return + } + + // crc32 + crc32 := util.BigLittleSwap(uint(cw.Crc32)) + if err = util.WriteUint32ToByte(cw, uint32(crc32), true); err != nil { + return + } + + return +} diff --git a/common/stream.go b/common/stream.go index 4236cac..d297c25 100644 --- a/common/stream.go +++ b/common/stream.go @@ -1,9 +1,15 @@ package common -import "context" +import ( + "context" + + log "github.com/sirupsen/logrus" +) type IStream interface { context.Context Update() uint32 AddTrack(Track) + IsClosed() bool + log.Ext1FieldLogger } diff --git a/config/config.go b/config/config.go index 61a41f6..b942644 100644 --- a/config/config.go +++ b/config/config.go @@ -44,7 +44,12 @@ func (config Config) Unmarshal(s any) { el = el.Elem() } t := el.Type() - + if t.Kind() == reflect.Map { + for k, v := range config { + el.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v).Convert(t.Elem())) + } + return + } //字段映射,小写对应的大写 nameMap := make(map[string]string) for i, j := 0, t.NumField(); i < j; i++ { @@ -52,12 +57,11 @@ func (config Config) Unmarshal(s any) { nameMap[strings.ToLower(name)] = name } for k, v := range config { - value := reflect.ValueOf(v) // 需要被写入的字段 fv := el.FieldByName(nameMap[k]) - if t.Kind() == reflect.Slice { + if value := reflect.ValueOf(v); value.Kind() == reflect.Slice { l := value.Len() - s := reflect.MakeSlice(t.Elem(), l, value.Cap()) + s := reflect.MakeSlice(fv.Type(), l, value.Cap()) for i := 0; i < l; i++ { fv := value.Field(i) if fv.Type() == reflect.TypeOf(config) { @@ -68,6 +72,11 @@ func (config Config) Unmarshal(s any) { } fv.Set(s) } else if child, ok := v.(Config); ok { + if fv.Kind() == reflect.Map { + if fv.IsNil() { + fv.Set(reflect.MakeMap(fv.Type())) + } + } child.Unmarshal(fv) } else { fv.Set(value) @@ -116,10 +125,14 @@ func (config Config) HasChild(key string) (ok bool) { } func (config Config) GetChild(key string) Config { - return config[strings.ToLower(key)].(Config) + if v, ok := config[strings.ToLower(key)]; ok { + return v.(Config) + } + return nil } func Struct2Config(s any) (config Config) { + config = make(Config) var t reflect.Type var v reflect.Value if vv, ok := s.(reflect.Value); ok { @@ -141,9 +154,6 @@ func Struct2Config(s any) (config Config) { case reflect.Slice: fallthrough default: - if config == nil { - config = make(Config) - } reflect.ValueOf(config).SetMapIndex(reflect.ValueOf(strings.ToLower(ft.Name)), v.Field(i)) } } diff --git a/config/types.go b/config/types.go index d482b79..5f2067d 100644 --- a/config/types.go +++ b/config/types.go @@ -3,7 +3,7 @@ package config type Publish struct { PubAudio bool PubVideo bool - KillExit bool // 是否踢掉已经存在的发布者 + KickExsit bool // 是否踢掉已经存在的发布者 PublishTimeout Second // 发布无数据超时 WaitCloseTimeout Second // 延迟自动关闭(无订阅时) } @@ -36,10 +36,6 @@ type Engine struct { EnableFLV bool //开启FLV格式,hdl协议使用 } -func (g *Engine) Update(override Config) { - override.Unmarshal(g) -} - var Global = &Engine{ Publish{true, true, false, 10, 10}, Subscribe{true, true, false, 10}, diff --git a/go.mod b/go.mod index 202aaad..f7c91ea 100644 --- a/go.mod +++ b/go.mod @@ -9,13 +9,13 @@ require ( github.com/mattn/go-colorable v0.1.8 github.com/pion/rtp v1.7.4 github.com/q191201771/naza v0.19.1 + github.com/sirupsen/logrus v1.8.1 golang.org/x/sync v0.0.0-20201207232520-09787c993a3a gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c ) require ( github.com/asaskevich/EventBus v0.0.0-20200907212545-49d423059eef - github.com/davecgh/go-spew v1.1.1 // indirect github.com/mattn/go-isatty v0.0.12 // indirect github.com/pion/randutil v0.1.0 // indirect github.com/stretchr/testify v1.7.0 // indirect diff --git a/go.sum b/go.sum index 5951d82..20e2efc 100644 --- a/go.sum +++ b/go.sum @@ -41,8 +41,11 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/q191201771/naza v0.19.1 h1:4KLcxT2CHztO+7miPRtBG3FFgadSQYQw1gPPPKN7rnY= github.com/q191201771/naza v0.19.1/go.mod h1:5LeGupZZFtYP1g/S203n9vXoUNVdlRnPIfM6rExjqt0= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sqs/goreturns v0.0.0-20181028201513-538ac6014518/go.mod h1:CKI4AZ4XmGV240rTHfO0hfE83S6/a3/Q1siZJ/vXf7A= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= @@ -68,7 +71,5 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/http.go b/http.go index 297ec70..832f570 100644 --- a/http.go +++ b/http.go @@ -3,9 +3,8 @@ package engine import ( "encoding/json" "net/http" - + log "github.com/sirupsen/logrus" "github.com/Monibuca/engine/v4/config" - "github.com/Monibuca/engine/v4/util" . "github.com/logrusorgru/aurora" ) @@ -15,9 +14,8 @@ type GlobalConfig struct { } func (cfg *GlobalConfig) Update(override config.Config) { - cfg.Engine.Update(override) Engine.RawConfig = config.Struct2Config(cfg.Engine) - util.Print(Green("api server start at "), BrightBlue(cfg.ListenAddr), BrightBlue(cfg.ListenAddrTLS)) + log.Infoln(Green("api server start at"), BrightBlue(cfg.ListenAddr), BrightBlue(cfg.ListenAddrTLS)) cfg.Listen(Engine, cfg) } diff --git a/log.go b/log.go new file mode 100644 index 0000000..b55e1d0 --- /dev/null +++ b/log.go @@ -0,0 +1,33 @@ +package engine + +import ( + "io" + "github.com/mattn/go-colorable" + "github.com/Monibuca/engine/v4/util" + log "github.com/sirupsen/logrus" +) + +// MultiLogWriter 可动态增减输出的多端写日志类 +type MultiLogWriter struct { + writers util.Slice[io.Writer] + io.Writer +} +var colorableStdout = colorable.NewColorableStdout() +var LogWriter = MultiLogWriter{ + writers: util.Slice[io.Writer]{colorableStdout}, + Writer: colorableStdout, +} + +func init() { + log.SetOutput(LogWriter) +} + +func (ml *MultiLogWriter) Add(w io.Writer) { + ml.writers.Add(w) + ml.Writer = io.MultiWriter(ml.writers...) +} + +func (ml *MultiLogWriter) Delete(w io.Writer) { + ml.writers.Delete(w) + ml.Writer = io.MultiWriter(ml.writers...) +} diff --git a/main.go b/main.go index 5ec4153..24c099c 100644 --- a/main.go +++ b/main.go @@ -4,20 +4,18 @@ import ( "context" "fmt" "io/ioutil" - "log" "net/http" "os" "path/filepath" "reflect" "runtime" - "strings" - "time" // colorable + "time" "github.com/Monibuca/engine/v4/config" "github.com/Monibuca/engine/v4/util" "github.com/google/uuid" - . "github.com/logrusorgru/aurora" + log "github.com/sirupsen/logrus" "gopkg.in/yaml.v3" ) @@ -26,72 +24,57 @@ var ( ConfigRaw []byte StartTime time.Time //启动时间 Plugins = make(map[string]*Plugin) // Plugins 所有的插件配置 - settingDir string EngineConfig = &GlobalConfig{ Engine: config.Global, ServeMux: http.NewServeMux(), } - Engine = InstallPlugin(EngineConfig) + settingDir string //配置缓存目录,该目录按照插件名称作为文件名存储修改过的配置 + Engine = InstallPlugin(EngineConfig) //复用安装插件逻辑,将全局配置信息注入,并启动server + toolManForGetHandlerFuncType http.HandlerFunc //专门用来获取HandlerFunc类型的工具人 + handlerFuncType = reflect.TypeOf(toolManForGetHandlerFuncType) //供反射使用的Handler类型的类型 + MergeConfigs = []string{"Publish", "Subscribe"} //需要合并配置的属性项,插件若没有配置则使用全局配置 + PullOnSubscribeList = make(map[string]PullOnSubscribe) //按需拉流的配置信息 ) -func InstallPlugin[T config.Plugin](config T) *Plugin { - t := reflect.TypeOf(config).Elem() - name := strings.TrimSuffix(t.Name(), "Config") - plugin := &Plugin{ - Name: name, - Config: config, - } - _, pluginFilePath, _, _ := runtime.Caller(1) - configDir := filepath.Dir(pluginFilePath) - if parts := strings.Split(configDir, "@"); len(parts) > 1 { - plugin.Version = parts[len(parts)-1] - } - if _, ok := Plugins[name]; ok { - return nil - } - Plugins[name] = plugin - log.Print(Green("install plugin"), BrightCyan(name), BrightBlue(plugin.Version)) - return plugin +type PullOnSubscribe struct { + Plugin PullPlugin + Puller } -// Plugin 插件信息 -type Plugin struct { - context.Context `json:"-"` - context.CancelFunc `json:"-"` - Name string //插件名称 - Config config.Plugin //插件配置 - Version string //插件版本 - RawConfig config.Config //配置的map形式方便查询 - Modified config.Config //修改过的配置项 +func (p PullOnSubscribe) Pull(streamPath string) { + p.Plugin.PullStream(streamPath, p.Puller) } -// Run 启动Monibuca引擎 +// Run 启动Monibuca引擎,传入总的Context,可用于关闭所有 func Run(ctx context.Context, configFile string) (err error) { Engine.Context = ctx if err := util.CreateShutdownScript(); err != nil { - log.Print(Red("create shutdown script error:"), err) + log.Errorln("create shutdown script error:", err) } StartTime = time.Now() if ConfigRaw, err = ioutil.ReadFile(configFile); err != nil { - log.Print(Red("read config file error:"), err) + log.Errorln("read config file error:", err) } settingDir = filepath.Join(filepath.Dir(configFile), ".m7s") if err = os.MkdirAll(settingDir, 0755); err != nil { - log.Print(Red("create dir .m7s error:"), err) + log.Errorln("create dir .m7s error:", err) return } - util.Print(BgGreen(White("Ⓜ starting m7s "))) + log.Infoln(BgGreen(White("Ⓜ starting m7s v4"))) var cg config.Config if ConfigRaw != nil { if err = yaml.Unmarshal(ConfigRaw, &cg); err == nil { Engine.RawConfig = cg.GetChild("global") + Engine.RawConfig.Unmarshal(config.Global) } + } else { + log.Warnln("no config file found , use default config values") } Engine.registerHandler() go EngineConfig.Update(Engine.RawConfig) - for name, config := range Plugins { - config.RawConfig = cg.GetChild(name) - config.assign() + for name, plugin := range Plugins { + plugin.RawConfig = cg.GetChild(name) + plugin.assign() } UUID := uuid.NewString() reportTimer := time.NewTimer(time.Minute) @@ -110,97 +93,3 @@ func Run(ctx context.Context, configFile string) (err error) { } } } - -func (opt *Plugin) HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) { - if opt == nil { - return - } - var cors bool - if v, ok := opt.RawConfig["cors"]; ok { - cors = v.(bool) - } else if EngineConfig.CORS { - cors = true - } - if opt != Engine { - pattern = "/" + strings.ToLower(opt.Name) + pattern - } - Engine.HandleFunc(pattern, func(rw http.ResponseWriter, r *http.Request) { - if cors { - util.CORS(rw, r) - } - handler(rw, r) - }) -} - -func (opt *Plugin) HandleApi(pattern string, handler func(http.ResponseWriter, *http.Request)) { - if opt == nil { - return - } - pattern = "/api" + pattern - util.Println("http handle added:", pattern) - opt.HandleFunc(pattern, handler) -} - -// 读取独立配置合并入总配置中 -func (opt *Plugin) assign() { - f, err := os.Open(opt.settingPath()) - if err == nil { - if err = yaml.NewDecoder(f).Decode(&opt.Modified); err == nil { - if opt.RawConfig == nil { - opt.RawConfig = opt.Modified - } else { - opt.RawConfig.Assign(opt.Modified) - } - } - } - t := reflect.TypeOf(opt.Config).Elem() - // 用全局配置覆盖没有设置的配置 - for i, j := 0, t.NumField(); i < j; i++ { - fname := t.Field(i).Name - if Engine.RawConfig.Has(fname) { - if !opt.RawConfig.Has(fname) { - opt.RawConfig.Set(fname, Engine.RawConfig[fname]) - } else if opt.RawConfig.HasChild(fname) { - opt.RawConfig.GetChild(fname).Merge(Engine.RawConfig.GetChild(fname)) - } - } - } - opt.registerHandler() - opt.Update() -} - -func (opt *Plugin) Update() { - if opt.CancelFunc != nil { - opt.CancelFunc() - } - opt.Context, opt.CancelFunc = context.WithCancel(Engine) - go opt.Config.Update(opt.RawConfig) -} - -func (opt *Plugin) registerHandler() { - t := reflect.TypeOf(opt.Config).Elem() - v := reflect.ValueOf(opt.Config).Elem() - // 注册http响应 - for i, j := 0, t.NumMethod(); i < j; i++ { - mt := t.Method(i) - if strings.HasPrefix(mt.Name, "API") { - parts := strings.Split(mt.Name, "_") - parts[0] = "" - patten := reflect.ValueOf(strings.Join(parts, "/")) - reflect.ValueOf(opt.HandleApi).Call([]reflect.Value{patten, v.Method(i)}) - } - } -} - -func (opt *Plugin) settingPath() string { - return filepath.Join(settingDir, strings.ToLower(opt.Name)+".yaml") -} - -func (opt *Plugin) Save() error { - file, err := os.OpenFile(opt.settingPath(), os.O_CREATE|os.O_WRONLY, 0644) - if err == nil { - defer file.Close() - err = yaml.NewEncoder(file).Encode(opt.Modified) - } - return err -} diff --git a/plugin.go b/plugin.go new file mode 100644 index 0000000..fd71c46 --- /dev/null +++ b/plugin.go @@ -0,0 +1,173 @@ +package engine + +import ( + "context" + "net/http" + "os" + "path/filepath" + "reflect" + "runtime" + "strings" + + "github.com/Monibuca/engine/v4/config" + "github.com/Monibuca/engine/v4/util" + . "github.com/logrusorgru/aurora" + log "github.com/sirupsen/logrus" + "gopkg.in/yaml.v3" +) + +// InstallPlugin 安装插件,传入插件配置生成插件信息对象 +func InstallPlugin(config config.Plugin) *Plugin { + t := reflect.TypeOf(config).Elem() + name := strings.TrimSuffix(t.Name(), "Config") + plugin := &Plugin{ + Name: name, + Config: config, + } + _, pluginFilePath, _, _ := runtime.Caller(1) + configDir := filepath.Dir(pluginFilePath) + if parts := strings.Split(configDir, "@"); len(parts) > 1 { + plugin.Version = parts[len(parts)-1] + } + if _, ok := Plugins[name]; ok { + return nil + } + if config != EngineConfig { + plugin.Entry = log.WithField("plugin", name) + Plugins[name] = plugin + plugin.Infoln(Green("install"), BrightBlue(plugin.Version)) + } + return plugin +} + +// Plugin 插件信息 +type Plugin struct { + context.Context `json:"-"` + context.CancelFunc `json:"-"` + Name string //插件名称 + Config config.Plugin //插件配置 + Version string //插件版本 + RawConfig config.Config //配置的map形式方便查询 + Modified config.Config //修改过的配置项 + *log.Entry +} + +type PullPlugin interface { + PullStream(string, Puller) bool +} + +func (opt *Plugin) HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) { + if opt == nil { + return + } + var cors bool + if v, ok := opt.RawConfig["cors"]; ok { + cors = v.(bool) + } else if EngineConfig.CORS { + cors = true + } + if !strings.HasPrefix(pattern, "/") { + pattern = "/" + pattern + } + if opt != Engine { + pattern = "/" + strings.ToLower(opt.Name) + pattern + } + opt.Infoln("http handle added:", pattern) + EngineConfig.HandleFunc(pattern, func(rw http.ResponseWriter, r *http.Request) { + if cors { + util.CORS(rw, r) + } + opt.Debugln(r.RemoteAddr, " -> ", pattern) + handler(rw, r) + }) +} + +// 读取独立配置合并入总配置中 +func (opt *Plugin) assign() { + f, err := os.Open(opt.settingPath()) + if err == nil { + if err = yaml.NewDecoder(f).Decode(&opt.Modified); err == nil { + if opt.RawConfig == nil { + opt.RawConfig = opt.Modified + } else { + opt.RawConfig.Assign(opt.Modified) + } + } + } + t := reflect.TypeOf(opt.Config).Elem() + // 用全局配置覆盖没有设置的配置 + for _, fname := range MergeConfigs { + if _, ok := t.FieldByName(fname); ok { + if Engine.RawConfig.Has(fname) { + if !opt.RawConfig.Has(fname) { + opt.RawConfig.Set(fname, Engine.RawConfig[fname]) + } else if opt.RawConfig.HasChild(fname) { + opt.RawConfig.GetChild(fname).Merge(Engine.RawConfig.GetChild(fname)) + } + } + } + } + opt.registerHandler() + opt.Update() +} + +func (opt *Plugin) Update() { + if opt.CancelFunc != nil { + opt.CancelFunc() + } + opt.Context, opt.CancelFunc = context.WithCancel(Engine) + opt.RawConfig.Unmarshal(opt.Config) + opt.autoPull() + go opt.Config.Update(opt.RawConfig) +} + +func (opt *Plugin) autoPull() { + t := reflect.TypeOf(opt.Config).Elem() + v := reflect.ValueOf(opt.Config).Elem() + for i, j := 0, t.NumField(); i < j; i++ { + if t.Field(i).Name == "Pull" { + var pullConfig config.Pull + reflect.ValueOf(&pullConfig).Elem().Set(v.Field(i)) + for streamPath, url := range pullConfig.AutoPullList { + puller := Puller{RemoteURL: url, Config: &pullConfig} + if pullConfig.PullOnStart { + opt.Config.(PullPlugin).PullStream(streamPath, puller) + } else if pullConfig.PullOnSubscribe { + PullOnSubscribeList[streamPath] = PullOnSubscribe{opt.Config.(PullPlugin), puller} + } + } + } + } +} +func (opt *Plugin) registerHandler() { + t := reflect.TypeOf(opt.Config) + v := reflect.ValueOf(opt.Config) + // 注册http响应 + for i, j := 0, t.NumMethod(); i < j; i++ { + mt := t.Method(i) + mv := v.Method(i) + if mv.CanConvert(handlerFuncType) { + patten := "/" + if mt.Name != "ServeHTTP" { + patten = strings.ToLower(strings.ReplaceAll(mt.Name, "_", "/")) + } + reflect.ValueOf(opt.HandleFunc).Call([]reflect.Value{reflect.ValueOf(patten), mv}) + } + } +} + +func (opt *Plugin) settingPath() string { + return filepath.Join(settingDir, strings.ToLower(opt.Name)+".yaml") +} + +func (opt *Plugin) Save() error { + file, err := os.OpenFile(opt.settingPath(), os.O_CREATE|os.O_WRONLY, 0644) + if err == nil { + defer file.Close() + err = yaml.NewEncoder(file).Encode(opt.Modified) + } + if err == nil { + opt.Info("config saved") + } + return err +} diff --git a/publisher.go b/publisher.go index 9bbf798..0d2d161 100644 --- a/publisher.go +++ b/publisher.go @@ -2,23 +2,31 @@ package engine import ( "io" - "net/url" + "reflect" "time" "github.com/Monibuca/engine/v4/config" + log "github.com/sirupsen/logrus" ) type IPublisher interface { Close() // 流关闭时或者被踢时触发 OnStateChange(oldState StreamState, newState StreamState) bool + OnStateChanged(oldState StreamState, newState StreamState) +} +type IPuller interface { + IPublisher + Pull(int) } - type Publisher struct { - Type string - *Stream `json:"-"` + Type string + Config *config.Publish + *Stream `json:"-"` + specific IPublisher + *log.Entry } -func (pub *Publisher) Publish(streamPath string, realPub IPublisher, config config.Publish) bool { +func (pub *Publisher) Publish(streamPath string, specific IPublisher, config config.Publish) bool { Streams.Lock() defer Streams.Unlock() s, created := findOrCreateStream(streamPath, time.Second) @@ -26,18 +34,25 @@ func (pub *Publisher) Publish(streamPath string, realPub IPublisher, config conf return false } if s.Publisher != nil { - if config.KillExit { + if config.KickExsit { + s.Warnln("kick", s.Publisher) s.Publisher.Close() } else { + s.Warnln("publisher exsit", s.Publisher) return false } } pub.Stream = s - s.Publisher = realPub + pub.specific = specific + pub.Config = &config + s.Publisher = specific + if pub.Type == "" { + pub.Type = reflect.TypeOf(specific).Elem().Name() + } + pub.Entry = s.WithField("puber", pub.Type) if created { s.PublishTimeout = config.PublishTimeout.Duration() s.WaitCloseTimeout = config.WaitCloseTimeout.Duration() - go s.run() } s.actionChan <- PublishAction{} return true @@ -46,14 +61,37 @@ func (pub *Publisher) Publish(streamPath string, realPub IPublisher, config conf func (pub *Publisher) OnStateChange(oldState StreamState, newState StreamState) bool { return true } +func (pub *Publisher) OnStateChanged(oldState StreamState, newState StreamState) { +} // 用于远程拉流的发布者 type Puller struct { Publisher - RemoteURL *url.URL - io.ReadCloser + Config *config.Pull + RemoteURL string + io.Reader + io.Closer + pullCount int } -func (puller *Puller) Close() { - puller.ReadCloser.Close() +func (pub *Puller) pull() { + pub.specific.(IPuller).Pull(pub.pullCount) + pub.pullCount++ +} + +func (pub *Puller) OnStateChanged(oldState StreamState, newState StreamState) { + switch newState { + case STATE_WAITTRACK: + go pub.pull() + case STATE_WAITPUBLISH: + if pub.Config.AutoReconnect && pub.Publish(pub.Path, pub.specific, *pub.Publisher.Config) { + go pub.pull() + } + } +} + +func (p *Puller) Close() { + if p.Closer != nil { + p.Closer.Close() + } } diff --git a/stream.go b/stream.go index dff679b..e796399 100644 --- a/stream.go +++ b/stream.go @@ -10,6 +10,7 @@ import ( "github.com/Monibuca/engine/v4/track" "github.com/Monibuca/engine/v4/util" . "github.com/logrusorgru/aurora" + log "github.com/sirupsen/logrus" ) type StreamState byte @@ -101,6 +102,7 @@ type Stream struct { FrameCount uint32 //帧总数 AppName string StreamName string + *log.Entry `json:"-"` } func (s *Stream) UnPublish() { @@ -117,20 +119,21 @@ func findOrCreateStream(streamPath string, waitTimeout time.Duration) (s *Stream } p := strings.Split(u.Path, "/") if len(p) < 2 { - util.Println(Red("Stream Path Format Error:"), streamPath) + log.Warnln(Red("Stream Path Format Error:"), streamPath) return nil, false } if s, ok := Streams.Map[u.Path]; ok { - util.Println(Green("Stream Found:"), u.Path) + s.Debugln(Green("Stream Found")) return s, false } else { - util.Println(Green("Stream Created:"), u.Path) p := strings.Split(u.Path, "/") s = &Stream{ URL: u, AppName: p[0], StreamName: p[len(p)-1], + Entry: log.WithField("stream", u.Path), } + s.Infoln("created:", streamPath) s.WaitTimeout = waitTimeout Streams.Map[u.Path] = s s.actionChan = make(chan any, 1) @@ -138,6 +141,7 @@ func findOrCreateStream(streamPath string, waitTimeout time.Duration) (s *Stream s.timeout = time.NewTimer(waitTimeout) s.Context, s.cancel = context.WithCancel(Engine) s.Init(s) + go s.run() return s, true } } @@ -145,13 +149,17 @@ func findOrCreateStream(streamPath string, waitTimeout time.Duration) (s *Stream func (r *Stream) action(action StreamAction) bool { if next, ok := StreamFSM[r.State][action]; ok { if r.Publisher == nil || r.Publisher.OnStateChange(r.State, next) { - util.Print(Yellow("Stream "), BrightCyan(r.Path), action, " :", r.State, "->", next) + defer r.Publisher.OnStateChanged(r.State, next) + r.Debugln(action, " :", r.State, "->", next) r.State = next switch next { case STATE_WAITPUBLISH: r.Publisher = nil Bus.Publish(Event_REQUEST_PUBLISH, r) r.timeout.Reset(r.WaitTimeout) + if _, ok = PullOnSubscribeList[r.Path]; ok { + PullOnSubscribeList[r.Path].Pull(r.Path) + } case STATE_WAITTRACK: r.timeout.Reset(time.Second * 5) case STATE_PUBLISHING: @@ -194,22 +202,26 @@ func (r *Stream) Close() { } func (r *Stream) UnSubscribe(sub *Subscriber) { + r.Debugln("unsubscribe", sub.ID) if !r.IsClosed() { r.actionChan <- UnSubscibeAction(sub) } } func (r *Stream) Subscribe(sub *Subscriber) { + r.Debugln("subscribe", sub.ID) if !r.IsClosed() { sub.Stream = r sub.Context, sub.cancel = context.WithCancel(r) r.actionChan <- sub } } + +// 流状态处理中枢,包括接收订阅发布指令等 func (r *Stream) run() { for { select { case <-r.timeout.C: - util.Print(Yellow("Stream "), BrightCyan(r.Path), " timeout:", r.State) + r.Debugln(r.State, "timeout") r.action(ACTION_TIMEOUT) case <-r.Done(): r.action(ACTION_CLOSE) @@ -226,14 +238,14 @@ func (r *Stream) run() { case *Subscriber: r.Subscribers.Add(v) Bus.Publish(Event_SUBSCRIBE, v) - util.Print(Sprintf(Yellow("%s subscriber %s added remains:%d"), BrightCyan(r.Path), Cyan(v.ID), Blue(len(r.Subscribers)))) + v.Infoln(Sprintf(Yellow("added remains:%d"), Cyan(v.ID), Blue(len(r.Subscribers)))) if r.Subscribers.Len() == 1 { r.action(ACTION_FIRSTENTER) } case UnSubscibeAction: if r.Subscribers.Delete(v) { Bus.Publish(Event_UNSUBSCRIBE, v) - util.Print(Sprintf(Yellow("%s subscriber %s removed remains:%d"), BrightCyan(r.Path), Cyan(v.ID), Blue(len(r.Subscribers)))) + (*Subscriber)(v).Infoln(Sprintf(Yellow("removed remains:%d"), Cyan(v.ID), Blue(len(r.Subscribers)))) if r.Subscribers.Len() == 0 && r.WaitCloseTimeout > 0 { r.action(ACTION_LASTLEAVE) } @@ -249,6 +261,7 @@ func (r *Stream) run() { // Update 更新数据重置超时定时器 func (r *Stream) Update() uint32 { if r.State == STATE_PUBLISHING { + r.Traceln("update") r.timeout.Reset(r.PublishTimeout) } return atomic.AddUint32(&r.FrameCount, 1) @@ -256,26 +269,31 @@ func (r *Stream) Update() uint32 { // 如果暂时不知道编码格式可以用这个 func (r *Stream) NewVideoTrack() (vt *track.UnknowVideo) { + r.Debugln("create unknow video track") vt = &track.UnknowVideo{ Stream: r, } return } func (r *Stream) NewAudioTrack() (at *track.UnknowAudio) { + r.Debugln("create unknow audio track") at = &track.UnknowAudio{ Stream: r, } return } func (r *Stream) NewH264Track() *track.H264 { + r.Debugln("create h264 track") return track.NewH264(r) } func (r *Stream) NewH265Track() *track.H265 { + r.Debugln("create h265 track") return track.NewH265(r) } func (r *Stream) NewAACTrack() *track.AAC { + r.Debugln("create aac track") return track.NewAAC(r) } diff --git a/subscriber.go b/subscriber.go index 5c17d43..2b75984 100644 --- a/subscriber.go +++ b/subscriber.go @@ -8,6 +8,7 @@ import ( . "github.com/Monibuca/engine/v4/common" "github.com/Monibuca/engine/v4/config" "github.com/Monibuca/engine/v4/track" + log "github.com/sirupsen/logrus" ) type AudioFrame AVFrame[AudioSlice] @@ -29,6 +30,7 @@ type Subscriber struct { SubscribeArgs url.Values OnAudio func(*AudioFrame) error `json:"-"` OnVideo func(*VideoFrame) error `json:"-"` + *log.Entry } // Close 关闭订阅者 @@ -43,13 +45,15 @@ func (s *Subscriber) Close() { func (sub *Subscriber) Subscribe(streamPath string, config config.Subscribe) bool { Streams.Lock() defer Streams.Unlock() + log.Info(sub.ID, "try to subscribe", streamPath) s, created := findOrCreateStream(streamPath, config.WaitTimeout.Duration()) if s.IsClosed() { + log.Warnln("stream is closed") return false } + sub.Entry = s.Entry.WithField("suber", sub.ID) if created { Bus.Publish(Event_REQUEST_PUBLISH, s) - go s.run() } if s.Subscribe(sub); sub.Stream != nil { sub.Config = config diff --git a/track/base.go b/track/base.go index b1fd60e..503871e 100644 --- a/track/base.go +++ b/track/base.go @@ -30,8 +30,8 @@ type Media[T RawSlice] struct { SampleRate uint32 SampleSize byte DecoderConfiguration DecoderConfiguration[T] `json:"-"` //H264(SPS、PPS) H265(VPS、SPS、PPS) AAC(config) - util.BytesPool //无锁内存池,用于发布者(在同一个协程中)复用小块的内存,通常是解包时需要临时使用 - lastAvccTS uint32 //上一个avcc帧的时间戳 + util.BytesPool //无锁内存池,用于发布者(在同一个协程中)复用小块的内存,通常是解包时需要临时使用 + lastAvccTS uint32 //上一个avcc帧的时间戳 } func (av *Media[T]) WriteRTP(raw []byte) { @@ -56,10 +56,12 @@ func (av *Media[T]) WriteAVCC(ts uint32, frame AVCCFrame) { } else { av.Value.DeltaTime = ts - av.lastAvccTS } + cts := frame.CTS() av.Value.BytesIn = len(frame) av.Value.AppendAVCC(frame) av.Value.DTS = ts * 90 - av.Value.PTS = (ts + frame.CTS()) * 90 + av.Value.PTS = (ts + cts) * 90 + av.Stream.Tracef("WriteAVCC:ts %d,cts %d,len %d", ts, cts, len(frame)) } func (av *Media[T]) Flush() { diff --git a/track/video.go b/track/video.go index 99c4978..1f7f087 100644 --- a/track/video.go +++ b/track/video.go @@ -7,6 +7,7 @@ import ( "github.com/Monibuca/engine/v4/codec" . "github.com/Monibuca/engine/v4/common" + "github.com/Monibuca/engine/v4/config" "github.com/Monibuca/engine/v4/util" ) @@ -32,6 +33,7 @@ func (t *Video) ComputeGOP() { t.GOP = int(t.Value.SeqInTrack - t.IDRing.Value.SeqInTrack) if l := t.Size - t.GOP - 5; l > 5 { t.Size -= l + t.Stream.Debugf("resize %s ringbuffer %d-%d=%d", t.Name, t.Size+l, l, t.Size) //缩小缓冲环节省内存 t.Unlink(l).Do(func(v AVFrame[NALUSlice]) { if v.IFrame { @@ -59,6 +61,7 @@ func (vt *Video) writeAnnexBSlice(annexb AnnexBFrame) { } func (vt *Video) WriteAnnexB(pts uint32, dts uint32, frame AnnexBFrame) { + vt.Stream.Tracef("WriteAnnexB:pts %d,dts %d,len %d", pts, dts, len(frame)) for len(frame) > 0 { before, after, found := bytes.Cut(frame, codec.NALU_Delimiter2) if !found { @@ -83,7 +86,7 @@ func (vt *Video) WriteAVCC(ts uint32, frame AVCCFrame) { vt.Value.AppendRaw(NALUSlice{nalus[vt.nalulenSize:end]}) nalus = nalus[end:] } else { - util.Printf("WriteAVCC error,len %d,nalulenSize:%d,end:%d", len(nalus), vt.nalulenSize, end) + vt.Stream.Errorln("WriteAVCC error,len %d,nalulenSize:%d,end:%d", len(nalus), vt.nalulenSize, end) break } } @@ -91,7 +94,7 @@ func (vt *Video) WriteAVCC(ts uint32, frame AVCCFrame) { func (vt *Video) Flush() { // AVCC格式补完 - if vt.Value.AVCC == nil { + if vt.Value.AVCC == nil && (config.Global.EnableAVCC || config.Global.EnableFLV) { b := []byte{vt.CodecID, 1, 0, 0, 0} if vt.Value.IFrame { b[0] |= 0x10 @@ -107,7 +110,7 @@ func (vt *Video) Flush() { } } // FLV tag 补完 - if vt.Value.FLV == nil { + if vt.Value.FLV == nil && config.Global.EnableFLV { vt.Value.FillFLV(codec.FLV_TAG_TYPE_VIDEO, vt.Value.DTS/90) } // 下一帧为I帧,即将覆盖 diff --git a/tracks.go b/tracks.go index adfdba2..66f10c9 100644 --- a/tracks.go +++ b/tracks.go @@ -6,7 +6,6 @@ import ( "sync" . "github.com/Monibuca/engine/v4/common" - "github.com/Monibuca/engine/v4/util" ) type Tracks struct { @@ -28,14 +27,14 @@ func (ts *Tracks) Init(ctx context.Context) { ts.Context = ctx } -func (ts *Tracks) AddTrack(t Track) { - ts.Lock() - defer ts.Unlock() +func (s *Stream) AddTrack(t Track) { + s.Tracks.Lock() + defer s.Tracks.Unlock() name := t.GetName() - if _, ok := ts.m[name]; !ok { - util.Println("Track", name, "added") - if ts.m[name] = t; ts.Err() == nil { - for _, ch := range ts.waiters[name] { + if _, ok := s.Tracks.m[name]; !ok { + s.Infoln("Track", name, "added") + if s.Tracks.m[name] = t; s.Tracks.Err() == nil { + for _, ch := range s.Tracks.waiters[name] { if *ch != nil { *ch <- t close(*ch) @@ -46,11 +45,11 @@ func (ts *Tracks) AddTrack(t Track) { } } -func (ts *Tracks) GetTrack(name string) Track { - ts.RLock() - defer ts.RUnlock() - return ts.m[name] -} +// func (ts *Tracks) GetTrack(name string) Track { +// ts.RLock() +// defer ts.RUnlock() +// return ts.m[name] +// } // WaitDone 当等待结束时需要调用该函数,防止订阅者无限等待Track func (ts *Tracks) WaitDone() { diff --git a/util/big_little_endian.go b/util/big_little_endian.go new file mode 100644 index 0000000..2bb9f57 --- /dev/null +++ b/util/big_little_endian.go @@ -0,0 +1,269 @@ +package util + +// +// 注意:RTMP模式下都是大端模式 +// + +var LittleEndian littleEndian + +// BigEndian is the big-endian implementation of ByteOrder. +var BigEndian bigEndian + +// 低位字节排放在内存的低地址端,高位字节排放在内存的高地址端. +type littleEndian struct{} + +// b == 0x1234, b[0] == 0x12, b[1] == 0x34 +// b[0]低字节 b[1]高字节 +// 内存地址 低 -> 高 +// 0x34 0x12 + +// byte(v)低字节 b[0]内存低地址 +// byte(v>>8)高字节 b[1]内存高地址 + +// b == 2222 2222 1111 1111 +// b >> 8 -> 0000 0000 2222 2222 +// b << 8 -> 1111 1111 0000 0000 + +func (littleEndian) Uint16(b []byte) uint16 { return uint16(b[0]) | uint16(b[1])<<8 } +func (littleEndian) Uint24(b []byte) uint32 { return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 } +func (littleEndian) Uint32(b []byte) uint32 { + return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24 +} +func (littleEndian) Uint40(b []byte) uint64 { + return uint64(b[0]) | uint64(b[1])<<8 | + uint64(b[2])<<16 | uint64(b[3])<<24 | uint64(b[4])<<32 +} +func (littleEndian) Uint48(b []byte) uint64 { + return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | + uint64(b[3])<<24 | uint64(b[4])<<32 | uint64(b[5])<<40 +} +func (littleEndian) Uint64(b []byte) uint64 { + return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 | + uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56 +} + +// +// Put +// + +func (littleEndian) PutUint16(b []byte, v uint16) { + b[0] = byte(v) + b[1] = byte(v >> 8) +} +func (littleEndian) PutUint24(b []byte, v uint32) { + b[0] = byte(v) + b[1] = byte(v >> 8) + b[2] = byte(v >> 16) +} +func (littleEndian) PutUint32(b []byte, v uint32) { + b[0] = byte(v) + b[1] = byte(v >> 8) + b[2] = byte(v >> 16) + b[3] = byte(v >> 24) +} +func (littleEndian) PutUint64(b []byte, v uint64) { + b[0] = byte(v) + b[1] = byte(v >> 8) + b[2] = byte(v >> 16) + b[3] = byte(v >> 24) + b[4] = byte(v >> 32) + b[5] = byte(v >> 40) + b[6] = byte(v >> 48) + b[7] = byte(v >> 56) +} + +// +// To +// + +func (littleEndian) ToUint16(v uint16) []byte { + b := make([]byte, 2) + b[0] = byte(v) + b[1] = byte(v >> 8) + return b +} +func (littleEndian) ToUint24(v uint32) []byte { + b := make([]byte, 3) + b[0] = byte(v) + b[1] = byte(v >> 8) + b[2] = byte(v >> 16) + return b +} +func (littleEndian) ToUint32(v uint32) []byte { + b := make([]byte, 4) + b[0] = byte(v) + b[1] = byte(v >> 8) + b[2] = byte(v >> 16) + b[3] = byte(v >> 24) + return b +} +func (littleEndian) ToUint40(v uint64) []byte { + b := make([]byte, 5) + b[0] = byte(v) + b[1] = byte(v >> 8) + b[2] = byte(v >> 16) + b[3] = byte(v >> 24) + b[4] = byte(v >> 32) + return b +} +func (littleEndian) ToUint48(v uint64) []byte { + b := make([]byte, 6) + b[0] = byte(v) + b[1] = byte(v >> 8) + b[2] = byte(v >> 16) + b[3] = byte(v >> 24) + b[4] = byte(v >> 32) + b[5] = byte(v >> 40) + return b +} +func (littleEndian) ToUint64(v uint64) []byte { + b := make([]byte, 8) + b[0] = byte(v) + b[1] = byte(v >> 8) + b[2] = byte(v >> 16) + b[3] = byte(v >> 24) + b[4] = byte(v >> 32) + b[5] = byte(v >> 40) + b[6] = byte(v >> 48) + b[7] = byte(v >> 56) + return b +} + +// 高位字节排放在内存的低地址端,低位字节排放在内存的高地址端 +type bigEndian struct{} + +// b == 0x1234, b[0] == 0x12, b[1] == 0x34 +// 内存地址 低 -> 高 +// 0x12 0x34 +func (bigEndian) Uint16(b []byte) uint16 { return uint16(b[1]) | uint16(b[0])<<8 } +func (bigEndian) Uint24(b []byte) uint32 { return uint32(b[2]) | uint32(b[1])<<8 | uint32(b[0])<<16 } +func (bigEndian) Uint32(b []byte) uint32 { + return uint32(b[3]) | uint32(b[2])<<8 | uint32(b[1])<<16 | uint32(b[0])<<24 +} +func (bigEndian) Uint40(b []byte) uint64 { + return uint64(b[4]) | uint64(b[3])<<8 | + uint64(b[2])<<16 | uint64(b[1])<<24 | uint64(b[0])<<32 +} +func (bigEndian) Uint48(b []byte) uint64 { + return uint64(b[5]) | uint64(b[4])<<8 | uint64(b[3])<<16 | + uint64(b[2])<<24 | uint64(b[1])<<32 | uint64(b[0])<<40 +} +func (bigEndian) Uint64(b []byte) uint64 { + return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 | + uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56 +} + +// +// Put +// + +func (bigEndian) PutUint16(b []byte, v uint16) { + b[0] = byte(v >> 8) + b[1] = byte(v) +} +func (bigEndian) PutUint24(b []byte, v uint32) { + b[0] = byte(v >> 16) + b[1] = byte(v >> 8) + b[2] = byte(v) +} +func (bigEndian) PutUint32(b []byte, v uint32) { + b[0] = byte(v >> 24) + b[1] = byte(v >> 16) + b[2] = byte(v >> 8) + b[3] = byte(v) +} +func (bigEndian) PutUint64(b []byte, v uint64) { + b[0] = byte(v >> 56) + b[1] = byte(v >> 48) + b[2] = byte(v >> 40) + b[3] = byte(v >> 32) + b[4] = byte(v >> 24) + b[5] = byte(v >> 16) + b[6] = byte(v >> 8) + b[7] = byte(v) +} + +// +// To +// + +func (bigEndian) ToUint16(v uint16) []byte { + b := make([]byte, 2) + b[0] = byte(v >> 8) + b[1] = byte(v) + return b +} +func (bigEndian) ToUint24(v uint32) []byte { + b := make([]byte, 3) + b[0] = byte(v >> 16) + b[1] = byte(v >> 8) + b[2] = byte(v) + return b +} +func (bigEndian) ToUint32(v uint32) []byte { + b := make([]byte, 4) + b[0] = byte(v >> 24) + b[1] = byte(v >> 16) + b[2] = byte(v >> 8) + b[3] = byte(v) + return b +} +func (bigEndian) ToUint40(v uint64) []byte { + b := make([]byte, 5) + b[0] = byte(v >> 32) + b[1] = byte(v >> 24) + b[2] = byte(v >> 16) + b[3] = byte(v >> 8) + b[4] = byte(v) + return b +} +func (bigEndian) ToUint48(v uint64) []byte { + b := make([]byte, 6) + b[0] = byte(v >> 40) + b[1] = byte(v >> 32) + b[2] = byte(v >> 24) + b[3] = byte(v >> 16) + b[4] = byte(v >> 8) + b[5] = byte(v) + return b +} +func (bigEndian) ToUint64(v uint64) []byte { + b := make([]byte, 8) + b[0] = byte(v >> 56) + b[1] = byte(v >> 48) + b[2] = byte(v >> 40) + b[3] = byte(v >> 32) + b[4] = byte(v >> 24) + b[5] = byte(v >> 16) + b[6] = byte(v >> 8) + b[7] = byte(v) + return b +} + +//哥伦布解码 +func GetUev(buff []byte, start int) (value int, pos int) { + l := len(buff) + var nZeroNum uint = 0 + for start < l*8 { + if (buff[start/8] & (0x80 >> uint(start%8))) > 0 { + break + } + nZeroNum += 1 + start += 1 + } + dwRet := 0 + start += 1 + var i uint + for i = 0; i < nZeroNum; i++ { + dwRet <<= 1 + if (buff[start/8] & (0x80 >> uint(start%8))) > 0 { + dwRet += 1 + } + start += 1 + } + return (1 << nZeroNum) - 1 + dwRet, start +} + +func BigLittleSwap(v uint) uint { + return (v >> 24) | ((v>>16)&0xff)<<8 | ((v>>8)&0xff)<<16 | (v&0xff)<<24 +} diff --git a/util/convert.go b/util/convert.go new file mode 100644 index 0000000..2bf429b --- /dev/null +++ b/util/convert.go @@ -0,0 +1,384 @@ +package util + +import ( + "errors" + "io" +) + +/* +func ReadByteToUintX(r io.Reader, l int) (data uint64, err error) { + if l%8 != 0 || l > 64 { + return 0, errors.New("disable convert") + } + + bb := make([]byte, l) + if _, err := io.ReadFull(r, bb); err != nil { + return 0, err + } + + switch l / 8 { + case 1: + { + return uint8(bb[0]), nil + } + case 2: + { + return BigEndian.Uint16(bb), nil + } + case 3: + { + return BigEndian.Uint24(bb), nil + } + case 4: + { + return BigEndian.Uint32(bb), nil + } + case 5: + { + //return BigEndian.Uint40(bb), nil + return 0, errors.New("disable convert") + } + case 6: + { + return BigEndian.Uint48(bb), nil + } + case 7: + { + //return BigEndian.Uint56(bb), nil + return 0, errors.New("disable convert") + } + case 8: + { + return BigEndian.Uint64(bb), nil + } + } + + return 0, errors.New("convert not exist") +} +*/ + +// // 千万注意大小端,RTMP是大端 +func ByteToUint32N(data []byte) (ret uint32, err error) { + if len(data) > 4 { + return 0, errors.New("ByteToUint32N error!") + } + + for i := 0; i < len(data); i++ { + ret <<= 8 + ret |= uint32(data[i]) + } + + return +} + +// // 千万注意大小端,RTMP是大端 +func ByteToUint64N(data []byte) (ret uint64, err error) { + if len(data) > 8 { + return 0, errors.New("ByteToUint64N error!") + } + + for i := 0; i < len(data); i++ { + ret <<= 8 + ret |= uint64(data[i]) + } + + return +} + +// 千万注意大小端,RTMP是大端 +func ByteToUint32(data []byte, bigEndian bool) (ret uint32, err error) { + if bigEndian { + return BigEndian.Uint32(data), nil + } else { + return LittleEndian.Uint32(data), nil + } +} + +func Uint32ToByte(data uint32, bigEndian bool) (ret []byte, err error) { + if bigEndian { + return BigEndian.ToUint32(data), nil + } else { + return LittleEndian.ToUint32(data), nil + } +} + +func ReadByteToUint8(r io.Reader) (data uint8, err error) { + bb := make([]byte, 1) + if _, err := io.ReadFull(r, bb); err != nil { + return 0, err + } + + return uint8(bb[0]), nil +} + +func ReadByteToUint16(r io.Reader, bigEndian bool) (data uint16, err error) { + bb := make([]byte, 2) + if _, err := io.ReadFull(r, bb); err != nil { + return 0, err + } + + if bigEndian { + return BigEndian.Uint16(bb), nil + } else { + return LittleEndian.Uint16(bb), nil + } +} + +func ReadByteToUint24(r io.Reader, bigEndian bool) (data uint32, err error) { + bb := make([]byte, 3) + if _, err := io.ReadFull(r, bb); err != nil { + return 0, err + } + + if bigEndian { + return BigEndian.Uint24(bb), nil + } else { + return LittleEndian.Uint24(bb), nil + } +} + +func ReadByteToUint32(r io.Reader, bigEndian bool) (data uint32, err error) { + bb := make([]byte, 4) + if _, err := io.ReadFull(r, bb); err != nil { + return 0, err + } + + if bigEndian { + return BigEndian.Uint32(bb), nil + } else { + return LittleEndian.Uint32(bb), nil + } +} + +func ReadByteToUint40(r io.Reader, bigEndian bool) (data uint64, err error) { + bb := make([]byte, 5) + if _, err := io.ReadFull(r, bb); err != nil { + return 0, err + } + + if bigEndian { + return BigEndian.Uint40(bb), nil + } else { + return LittleEndian.Uint40(bb), nil + } +} + +func ReadByteToUint48(r io.Reader, bigEndian bool) (data uint64, err error) { + bb := make([]byte, 6) + if _, err := io.ReadFull(r, bb); err != nil { + return 0, err + } + + if bigEndian { + return BigEndian.Uint48(bb), nil + } else { + return LittleEndian.Uint48(bb), nil + } +} + +/* +func ReadByteToUint56(r io.Reader) (data uint64, err error) { + bb := make([]byte, 7) + if _, err := io.ReadFull(r, bb); err != nil { + return 0, err + } + + return uint8(bb[0]), nil +} +*/ + +func ReadByteToUint64(r io.Reader, bigEndian bool) (data uint64, err error) { + bb := make([]byte, 8) + if _, err := io.ReadFull(r, bb); err != nil { + return 0, err + } + + if bigEndian { + return BigEndian.Uint64(bb), nil + } else { + return LittleEndian.Uint64(bb), nil + } +} + +func WriteUint8ToByte(w io.Writer, data uint8) error { + bb := make([]byte, 8) + bb[0] = byte(data) + _, err := w.Write(bb[:1]) + if err != nil { + return err + } + + return nil +} + +func WriteUint16ToByte(w io.Writer, data uint16, bigEndian bool) error { + var bb []byte + if bigEndian { + bb = BigEndian.ToUint16(data) + } else { + bb = LittleEndian.ToUint16(data) + } + + _, err := w.Write(bb) + if err != nil { + return err + } + + return nil +} + +func WriteUint24ToByte(w io.Writer, data uint32, bigEndian bool) error { + var bb []byte + if bigEndian { + bb = BigEndian.ToUint24(data) + } else { + bb = LittleEndian.ToUint24(data) + } + + _, err := w.Write(bb) + if err != nil { + return err + } + + return nil +} + +func WriteUint32ToByte(w io.Writer, data uint32, bigEndian bool) error { + var bb []byte + if bigEndian { + bb = BigEndian.ToUint32(data) + } else { + bb = LittleEndian.ToUint32(data) + } + + _, err := w.Write(bb) + if err != nil { + return err + } + + return nil +} + +func WriteUint40ToByte(w io.Writer, data uint64, bigEndian bool) error { + var bb []byte + if bigEndian { + bb = BigEndian.ToUint40(data) + } else { + bb = LittleEndian.ToUint40(data) + } + + _, err := w.Write(bb) + if err != nil { + return err + } + + return nil +} + +func WriteUint48ToByte(w io.Writer, data uint64, bigEndian bool) error { + var bb []byte + if bigEndian { + bb = BigEndian.ToUint48(data) + } else { + bb = LittleEndian.ToUint48(data) + } + + _, err := w.Write(bb) + if err != nil { + return err + } + + return nil +} + +func WriteUint64ToByte(w io.Writer, data uint64, bigEndian bool) error { + var bb []byte + if bigEndian { + bb = BigEndian.ToUint64(data) + } else { + bb = LittleEndian.ToUint64(data) + } + + _, err := w.Write(bb) + if err != nil { + return err + } + + return nil +} + +func GetPtsDts(v uint64) uint64 { + // 4 + 3 + 1 + 15 + 1 + 15 + 1 + // 0011 + // 0010 + PTS[30-32] + marker_bit + PTS[29-15] + marker_bit + PTS[14-0] + marker_bit + pts1 := ((v >> 33) & 0x7) << 30 + pts2 := ((v >> 17) & 0x7fff) << 15 + pts3 := ((v >> 1) & 0x7fff) + + return pts1 | pts2 | pts3 +} + +func PutPtsDts(v uint64) uint64 { + // 4 + 3 + 1 + 15 + 1 + 15 + 1 + // 0011 + // 0010 + PTS[30-32] + marker_bit + PTS[29-15] + marker_bit + PTS[14-0] + marker_bit + // 0x100010001 + // 0001 0000 0000 0000 0001 0000 0000 0000 0001 + // 3个 market_it + pts1 := (v >> 30) & 0x7 << 33 + pts2 := (v >> 15) & 0x7fff << 17 + pts3 := (v & 0x7fff) << 1 + + return pts1 | pts2 | pts3 | 0x100010001 +} + +func GetPCR(v uint64) uint64 { + // program_clock_reference_base(33) + Reserved(6) + program_clock_reference_extension(9) + base := v >> 15 + ext := v & 0x1ff + return base*300 + ext +} + +func PutPCR(pcr uint64) uint64 { + base := pcr / 300 + ext := pcr % 300 + return base<<15 | 0x3f<<9 | ext +} + +func GetFillBytes(data byte, n int) []byte { + b := make([]byte, n) + for i := range b { + b[i] = data + } + + return b +} +func ToFloat64(num interface{}) float64 { + switch v := num.(type) { + case uint: + return float64(v) + case int: + return float64(v) + case uint8: + return float64(v) + case uint16: + return float64(v) + case uint32: + return float64(v) + case uint64: + return float64(v) + case int8: + return float64(v) + case int16: + return float64(v) + case int32: + return float64(v) + case int64: + return float64(v) + case float64: + return v + case float32: + return float64(v) + } + return 0 +} diff --git a/util/crc32.go b/util/crc32.go new file mode 100644 index 0000000..0e2a509 --- /dev/null +++ b/util/crc32.go @@ -0,0 +1,126 @@ +package util + +import ( + "fmt" + "io" + "io/ioutil" +) + +var Crc32_Table = []uint32{ + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, + 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, + 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, + 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, + 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, + 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, + 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, + 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, + 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, + 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, + 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, + 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, + 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, + 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, + 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, + 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, + 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, + 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, + 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, + 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, + 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, + 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, + 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, + 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, + 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, + 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, + 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, + 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, + 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, + 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, + 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, + 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, + 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, + 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, + 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, + 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, + 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, + 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, + 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, + 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, + 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, + 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, + 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, + 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, + 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, + 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, + 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, + 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, + 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, + 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, + 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, + 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, + 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, + 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D, +} + +type Crc32Reader struct { + R io.Reader + Crc32 uint32 +} + +type Crc32Writer struct { + W io.Writer + Crc32 uint32 +} + +func (cr *Crc32Reader) Read(b []byte) (n int, err error) { + if n, err = cr.R.Read(b); err != nil { + return + } + + cr.Crc32 = getCrc32(cr.Crc32, b) + + return +} + +func (cr *Crc32Reader) ReadCrc32UIntAndCheck() (err error) { + _, err = io.CopyN(ioutil.Discard, cr, 4) + if err != nil { + return err + } + + if cr.Crc32 != 0 { + err = fmt.Errorf("crc32(%x) != 0", cr.Crc32) + return err + } + + return nil +} + +func (wr *Crc32Writer) Write(b []byte) (n int, err error) { + if n, err = wr.W.Write(b); err != nil { + return + } + + wr.Crc32 = getCrc32(wr.Crc32, b) + + return +} + +func getCrc32(crc uint32, data []byte) uint32 { + for _, v := range data { + crc = Crc32_Table[v^byte(crc)] ^ (crc >> 8) + } + + return crc +} diff --git a/util/index.go b/util/index.go index 1ae918e..1828bf8 100644 --- a/util/index.go +++ b/util/index.go @@ -29,70 +29,3 @@ func Exist(filename string) bool { func ConvertNum[F constraints.Integer, T constraints.Integer](from F, to T) T { return T(from) } - -func ToFloat64(num any) float64 { - switch v := num.(type) { - case uint: - return float64(v) - case int: - return float64(v) - case uint8: - return float64(v) - case uint16: - return float64(v) - case uint32: - return float64(v) - case uint64: - return float64(v) - case int8: - return float64(v) - case int16: - return float64(v) - case int32: - return float64(v) - case int64: - return float64(v) - case float64: - return v - case float32: - return float64(v) - } - return 0 -} -func GetPtsDts(v uint64) uint64 { - // 4 + 3 + 1 + 15 + 1 + 15 + 1 - // 0011 - // 0010 + PTS[30-32] + marker_bit + PTS[29-15] + marker_bit + PTS[14-0] + marker_bit - pts1 := ((v >> 33) & 0x7) << 30 - pts2 := ((v >> 17) & 0x7fff) << 15 - pts3 := ((v >> 1) & 0x7fff) - - return pts1 | pts2 | pts3 -} - -func PutPtsDts(v uint64) uint64 { - // 4 + 3 + 1 + 15 + 1 + 15 + 1 - // 0011 - // 0010 + PTS[30-32] + marker_bit + PTS[29-15] + marker_bit + PTS[14-0] + marker_bit - // 0x100010001 - // 0001 0000 0000 0000 0001 0000 0000 0000 0001 - // 3个 market_it - pts1 := (v >> 30) & 0x7 << 33 - pts2 := (v >> 15) & 0x7fff << 17 - pts3 := (v & 0x7fff) << 1 - - return pts1 | pts2 | pts3 | 0x100010001 -} - -func GetPCR(v uint64) uint64 { - // program_clock_reference_base(33) + Reserved(6) + program_clock_reference_extension(9) - base := v >> 15 - ext := v & 0x1ff - return base*300 + ext -} - -func PutPCR(pcr uint64) uint64 { - base := pcr / 300 - ext := pcr % 300 - return base<<15 | 0x3f<<9 | ext -} \ No newline at end of file diff --git a/util/logger.go b/util/logger.go deleted file mode 100644 index 28c2b78..0000000 --- a/util/logger.go +++ /dev/null @@ -1,87 +0,0 @@ -package util - -import ( - "context" - "fmt" - "io" - "log" - "os" - "time" - - colorable "github.com/mattn/go-colorable" - - "github.com/logrusorgru/aurora" -) - -// MultiLogWriter 多端写日志类 -type MultiLogWriter struct { - writers []io.Writer - io.Writer -} - -var logWriter MultiLogWriter -var multiLogger = log.New(&logWriter, "", log.LstdFlags) -var colorLogger = log.New(colorable.NewColorableStdout(), "", log.LstdFlags) - -func init() { - log.SetOutput(io.MultiWriter(os.Stdout, &logWriter)) - logWriter.Writer = io.MultiWriter() -} - -// AddWriter 添加日志输出端 -func AddWriter(wn io.Writer) { - logWriter.writers = append(logWriter.writers, wn) - logWriter.Writer = io.MultiWriter(logWriter.writers...) -} - -// MayBeError 优雅错误判断加日志辅助函数 -func MayBeError(info error) (hasError bool) { - if hasError = info != nil; hasError { - Print(aurora.Red(info)) - } - return -} -func getNoColor(v ...interface{}) (noColor []interface{}) { - noColor = append(noColor, v...) - for i, value := range v { - if vv, ok := value.(aurora.Value); ok { - noColor[i] = vv.Value() - } - } - return -} - -// Print 带颜色识别 -func Print(v ...interface{}) { - noColor := getNoColor(v...) - colorLogger.Output(2, fmt.Sprint(v...)) - multiLogger.Output(2, fmt.Sprint(noColor...)) -} - -// Printf calls Output to print to the standard logger. -// Arguments are handled in the manner of fmt.Printf. -func Printf(format string, v ...interface{}) { - noColor := getNoColor(v...) - colorLogger.Output(2, fmt.Sprintf(format, v...)) - multiLogger.Output(2, fmt.Sprintf(format, noColor...)) -} - -// Println calls Output to print to the standard logger. -// Arguments are handled in the manner of fmt.Println. -func Println(v ...interface{}) { - noColor := getNoColor(v...) - colorLogger.Output(2, fmt.Sprintln(v...)) - multiLogger.Output(2, fmt.Sprintln(noColor...)) -} - -type Event struct { - Timestamp time.Time - Level int - Label string - Tag string -} -type EventContext struct { - Name string - context.Context - EventChan chan *Event -} diff --git a/util/socket.go b/util/socket.go index 1470a69..396a971 100644 --- a/util/socket.go +++ b/util/socket.go @@ -3,7 +3,7 @@ package util import ( "context" "encoding/json" - "log" + log "github.com/sirupsen/logrus" "net" "net/http" "time" @@ -14,26 +14,41 @@ type TCPListener interface { Process(*net.TCPConn) } -func GetJsonHandler[T any](fetch func() T, tickDur time.Duration) http.HandlerFunc { - return func(rw http.ResponseWriter, r *http.Request) { - if r.URL.Query().Get("json") != "" { - if err := json.NewEncoder(rw).Encode(fetch()); err != nil { - rw.WriteHeader(500) - } - return +func ReturnJson[T any](fetch func() T, tickDur time.Duration, rw http.ResponseWriter, r *http.Request) { + if r.URL.Query().Get("json") != "" { + if err := json.NewEncoder(rw).Encode(fetch()); err != nil { + rw.WriteHeader(500) } - sse := NewSSE(rw, r.Context()) - tick := time.NewTicker(tickDur) - for range tick.C { - if sse.WriteJSON(fetch()) != nil { - tick.Stop() - break - } + return + } + sse := NewSSE(rw, r.Context()) + tick := time.NewTicker(tickDur) + for range tick.C { + if sse.WriteJSON(fetch()) != nil { + tick.Stop() + break } } } - +// func GetJsonHandler[T any](fetch func() T, tickDur time.Duration) http.HandlerFunc { +// return func(rw http.ResponseWriter, r *http.Request) { +// if r.URL.Query().Get("json") != "" { +// if err := json.NewEncoder(rw).Encode(fetch()); err != nil { +// rw.WriteHeader(500) +// } +// return +// } +// sse := NewSSE(rw, r.Context()) +// tick := time.NewTicker(tickDur) +// for range tick.C { +// if sse.WriteJSON(fetch()) != nil { +// tick.Stop() +// break +// } +// } +// } +// } func ListenUDP(address string, networkBuffer int) (*net.UDPConn, error) { addr, err := net.ResolveUDPAddr("udp", address) @@ -45,10 +60,10 @@ func ListenUDP(address string, networkBuffer int) (*net.UDPConn, error) { log.Fatalf("udp server ListenUDP :%s error, %v", address, err) } if err = conn.SetReadBuffer(networkBuffer); err != nil { - Printf("udp server video conn set read buffer error, %v", err) + log.Errorf("udp server video conn set read buffer error, %v", err) } if err = conn.SetWriteBuffer(networkBuffer); err != nil { - Printf("udp server video conn set write buffer error, %v", err) + log.Errorf("udp server video conn set write buffer error, %v", err) } return conn, err } diff --git a/util/vecio.go b/util/vecio.go new file mode 100644 index 0000000..920f308 --- /dev/null +++ b/util/vecio.go @@ -0,0 +1,205 @@ +package util + +import ( + + //"fmt" + "io" + "net" + "os" + "unsafe" +) + +/* +#include +// Structure for scatter/gather I/O. +struct iovec{ + void *iov_base; // Pointer to data. + size_t iov_len; // Length of data. +}; +*/ + +type SysIOVec struct { + Base uintptr + Length uint64 +} + +type IOVec struct { + Data [][]byte + Length int + index int +} + +func (iov *IOVec) Append(b []byte) { + iov.Data = append(iov.Data, b) + iov.Length += len(b) +} + +// Data模型: +// index -> | Data[0][0] | Data[0][1] | Data[0][2] | ... | Data[0][n] | +// | Data[1][0] | Data[1][1] | Data[1][2] | ... | Data[1][n] | +// ...... +// | Data[n][0] | Data[n][1] | Data[n][2] | ... | Data[n][n] | +// +// index是下标 + +func (iov *IOVec) WriteTo(w io.Writer, n int) (written int, err error) { + for n > 0 && iov.Length > 0 { + data := iov.Data[iov.index] + + // 用来存放每次需要写入的数据 + var b []byte + + // 只会读n个字节,超出n个字节,不管 + // 如果iov.Data里面有1000个数据,可是每次只读184个字节,那么剩下的数据(856)重新放回Data + if n > len(data) { + b = data + } else { + b = data[:n] + } + + // n个字节后面的数据 + // 如果这时候n个字节后面已经没有数据了,我们就将下标index往后移一位 + // 否则我们将n个字节后面的数据重新放回Data里. + data = data[len(b):] + if len(data) == 0 { + iov.index++ + } else { + iov.Data[iov.index] = data + } + + n -= len(b) + iov.Length -= len(b) + written += len(b) + + if _, err = w.Write(b); err != nil { + return + } + } + return +} + +type IOVecWriter struct { + fd uintptr + smallBuffer []byte + sysIOV []SysIOVec +} + +func NewIOVecWriter(w io.Writer) (iow *IOVecWriter) { + var err error + var file *os.File + + // TODO:是否要增加其他的类型断言 + switch value := w.(type) { + case *net.TCPConn: + { + file, err = value.File() + if err != nil { + return + } + } + case *os.File: + { + file = value + } + default: + return + } + + iow = &IOVecWriter{ + fd: file.Fd(), + } + + return +} + +// 1 2 3 4 5 6 +// --- -------------- --- --- --- ----------- +// | | | | | | | | | | | | ...... +// --- -------------- --- --- --- ----------- +// +// 1 -> 5个字节, 3 -> 15个字节, 4 -> 10个字节, 5 -> 15个字节 + +// 1,3,4,5内存块太小(小于16个字节),因此我们将它组装起来为samllbuffer +// 并且将Base置于每次组装smallBuffer前总长度的尾部. +// +// samllbuffer: +// 1 3 4 5 ........ +// ------------------------------ +// | | +// ------------------------------ +// <--> 第一个小内存块,假设地址为0xF10000 +// 5 +// <------> 第二个小内存块,假设地址为0xF20000 +// 20 +// <----------> 第三个小内存块,假设地址为0xF30000 +// 30 +// <--------------> 第四个小内存块,假设地址为0xF40000 +// 45 +// +// 开始Base == 每次组装smallBuffer尾部 +// 即: +// Base1 = 0, smallBuffer += 5, +// Base3 = 5, smallBuffer += 15, +// Base4 = 20, smallBuffer += 10, +// Base5 = 30, smallBuffer += 15, +// +// 然后我们将每一块内存块都取出来,比samllBuffer小的内存块,我们就将Base指向内存块的地址 +// 之前小于16个字节的内存块,肯定会比smallBuffer小,因为smallBuffer是所有小内存快的总和. +// 即: +// Base1 = &smallBuffer[0], Base1 = 0xF10000, +// Base3 = &smallBuffer[5], Base3 = 0xF20000, +// Base4 = &smallBuffer[20], Base4 = 0xF30000, +// Base5 = &smallBuffer[30], Base5 = 0xF40000, + +func (iow *IOVecWriter) Write(data []byte) (written int, err error) { + siov := SysIOVec{ + Length: uint64(len(data)), + } + + // unsafe.Pointer == void * + // Base 用整数的形式来记录内存中有几个数据 + // 如果数据小于16,这个时候小块内存的Base还不是数据的内存地址 + if siov.Length < 16 { + // Base 置于上一块samllBuffer的末尾 + // 然后拼接smallBuffer + siov.Base = uintptr(len(iow.smallBuffer)) + iow.smallBuffer = append(iow.smallBuffer, data...) + } else { + siov.Base = uintptr(unsafe.Pointer(&data[0])) + } + + iow.sysIOV = append(iow.sysIOV, siov) + + return written, nil +} + +func (iow *IOVecWriter) Flush() error { + // 取出每一块内存 + for i, _ := range iow.sysIOV { + siov := &iow.sysIOV[i] // 一定要拿地址,如果这里不是取地址,那么无法改变下面Base的值 + if siov.Base < uintptr(len(iow.smallBuffer)) { + // 这个时候小块内存的Base就是数据的内存地址 + siov.Base = uintptr(unsafe.Pointer(&iow.smallBuffer[siov.Base])) + } + } + + N := 1024 + count := len(iow.sysIOV) + // 每次最多取1024个内存块(不管是大内存块,还是小内存块) + for i := 0; i < count; i += N { + n := count - i + if n > N { + n = N + } + + // _, _, errno := syscall.Syscall(syscall.SYS_WRITEV, iow.fd, uintptr(unsafe.Pointer(&iow.sysIOV[i])), uintptr(n)) + // if errno != 0 { + // return errors.New(errno.Error()) + // } + } + + iow.sysIOV = iow.sysIOV[:0] + iow.smallBuffer = iow.smallBuffer[:0] + + return nil +}