diff --git a/pkg/core/track.go b/pkg/core/track.go index ae4b6382..626113a6 100644 --- a/pkg/core/track.go +++ b/pkg/core/track.go @@ -13,7 +13,8 @@ import ( type Packet struct { PayloadType uint8 Sequence uint16 - Timestamp uint32 + Timestamp uint32 // PTS if DTS == 0 else DTS + Composition uint32 // CTS = PTS-DTS (for support B-frames) Payload []byte } diff --git a/pkg/iso/atoms.go b/pkg/iso/atoms.go index bd00980f..945622eb 100644 --- a/pkg/iso/atoms.go +++ b/pkg/iso/atoms.go @@ -276,7 +276,7 @@ const ( TrunSampleCTS = 0x0000800 ) -func (m *Movie) WriteMovieFragment(seq, tid, duration, size, flags uint32, time uint64) { +func (m *Movie) WriteMovieFragment(seq, tid, duration, size, flags uint32, dts uint64, cts uint32) { m.StartAtom(Moof) m.StartAtom(MoofMfhd) @@ -302,17 +302,29 @@ func (m *Movie) WriteMovieFragment(seq, tid, duration, size, flags uint32, time m.EndAtom() m.StartAtom(MoofTrafTfdt) - m.WriteBytes(1) // version - m.Skip(3) // flags - m.WriteUint64(time) // base media decode time + m.WriteBytes(1) // version + m.Skip(3) // flags + m.WriteUint64(dts) // base media decode time m.EndAtom() m.StartAtom(MoofTrafTrun) - m.Skip(1) // version - m.WriteUint24(TrunDataOffset) // flags - m.WriteUint32(1) // sample count - // data offset: current pos + uint32 len + MDAT header len - m.WriteUint32(uint32(len(m.b)) + 4 + 8) + m.Skip(1) // version + + if cts == 0 { + m.WriteUint24(TrunDataOffset) // flags + m.WriteUint32(1) // sample count + + // data offset: current pos + uint32 len + MDAT header len + m.WriteUint32(uint32(len(m.b)) + 4 + 8) + } else { + m.WriteUint24(TrunDataOffset | TrunSampleCTS) + m.WriteUint32(1) + + // data offset: current pos + uint32 len + CTS + MDAT header len + m.WriteUint32(uint32(len(m.b)) + 4 + 4 + 8) + m.WriteUint32(cts) + } + m.EndAtom() // TRUN m.EndAtom() // TRAF diff --git a/pkg/mp4/README.md b/pkg/mp4/README.md index b89cda1b..e20e5d74 100644 --- a/pkg/mp4/README.md +++ b/pkg/mp4/README.md @@ -33,3 +33,4 @@ https://ffmpeg.org/ffmpeg-formats.html#Options-13 - https://jellyfin.org/docs/general/clients/codec-support.html - https://github.com/StaZhu/enable-chromium-hevc-hardware-decoding - https://developer.mozilla.org/ru/docs/Web/Media/Formats/codecs_parameter +- https://gstreamer-devel.narkive.com/rhkUolp2/rtp-dts-pts-result-in-varying-mp4-frame-durations diff --git a/pkg/mp4/muxer.go b/pkg/mp4/muxer.go index f48887a8..77cc7421 100644 --- a/pkg/mp4/muxer.go +++ b/pkg/mp4/muxer.go @@ -152,11 +152,12 @@ func (m *Muxer) GetPayload(trackID byte, packet *rtp.Packet) []byte { mv := iso.NewMovie(1024 + size) mv.WriteMovieFragment( - m.index, uint32(trackID+1), duration, uint32(size), flags, m.dts[trackID], + // ExtensionProfile - wrong place for CTS (supported by mpegts.Demuxer) + m.index, uint32(trackID+1), duration, uint32(size), flags, m.dts[trackID], uint32(packet.ExtensionProfile), ) mv.WriteData(packet.Payload) - //log.Printf("[MP4] track=%d ts=%6d dur=%5d idx=%3d len=%d", trackID+1, m.dts[trackID], duration, m.index, len(packet.Payload)) + //log.Printf("[MP4] idx:%3d trk:%d dts:%6d cts:%4d dur:%5d time:%10d len:%5d", m.index, trackID+1, m.dts[trackID], packet.SSRC, duration, packet.Timestamp, len(packet.Payload)) m.dts[trackID] += uint64(duration) diff --git a/pkg/mpegts/README.md b/pkg/mpegts/README.md index 65fc7a8b..5f19fc78 100644 --- a/pkg/mpegts/README.md +++ b/pkg/mpegts/README.md @@ -1,3 +1,20 @@ +## PTS/DTS/CTS + +``` +if DTS == 0 { + // for I and P frames + packet.Timestamp = PTS (presentation time) +} else { + // for B frames + packet.Timestamp = DTS (decode time) + CTS = PTS-DTS (composition time) +} +``` + +- MPEG-TS container uses PTS and optional DTS. +- MP4 container uses DTS and CTS +- RTP container uses PTS + ## MPEG-TS FFmpeg: diff --git a/pkg/mpegts/demuxer.go b/pkg/mpegts/demuxer.go index b03396b7..83160105 100644 --- a/pkg/mpegts/demuxer.go +++ b/pkg/mpegts/demuxer.go @@ -196,30 +196,33 @@ func (d *Demuxer) readPES(pid uint16, start bool) *rtp.Packet { _ = d.readBit() // Copyright _ = d.readBit() // Original or Copy - pts := d.readBit() // PTS indicator - dts := d.readBit() // DTS indicator - _ = d.readBit() // ESCR flag - _ = d.readBit() // ES rate flag - _ = d.readBit() // DSM trick mode flag - _ = d.readBit() // Additional copy info flag - _ = d.readBit() // CRC flag - _ = d.readBit() // extension flag + ptsi := d.readBit() // PTS indicator + dtsi := d.readBit() // DTS indicator + _ = d.readBit() // ESCR flag + _ = d.readBit() // ES rate flag + _ = d.readBit() // DSM trick mode flag + _ = d.readBit() // Additional copy info flag + _ = d.readBit() // CRC flag + _ = d.readBit() // extension flag headerSize := d.readByte() // PES header length - //log.Printf("[mpegts] pes=%d size=%d header=%d", pes.StreamID, packetSize, headerSize) - if packetSize != 0 { packetSize -= uint16(3 + headerSize) } - if dts != 0 { - d.skip(5) // skip PTSorDTS - pes.PTSorDTS = d.readTime() - headerSize -= 10 - } else if pts != 0 { - pes.PTSorDTS = d.readTime() + if ptsi != 0 { + pes.PTS = d.readTime() headerSize -= 5 + } else { + pes.PTS = 0 + } + + if dtsi != 0 { + pes.DTS = d.readTime() + headerSize -= 5 + } else { + pes.DTS = 0 } d.skip(headerSize) @@ -325,7 +328,8 @@ type PES struct { StreamType byte // from PMT table Sequence uint16 // manual Timestamp uint32 // manual - PTSorDTS uint32 // from extra header, always 90000Hz + PTS uint32 // from extra header, always 90000Hz + DTS uint32 Payload []byte // from PES body Size int // from PES header, can be 0 @@ -348,11 +352,18 @@ func (p *PES) GetPacket() (pkt *rtp.Packet) { pkt = &rtp.Packet{ Header: rtp.Header{ PayloadType: p.StreamType, - Timestamp: p.PTSorDTS, }, Payload: annexb.EncodeToAVCC(p.Payload, false), } + if p.DTS != 0 { + pkt.Timestamp = p.DTS + // wrong place for CTS, but we don't have another one + pkt.ExtensionProfile = uint16(p.PTS - p.DTS) + } else { + pkt.Timestamp = p.PTS + } + case StreamTypeAAC: p.Sequence++ @@ -362,7 +373,7 @@ func (p *PES) GetPacket() (pkt *rtp.Packet) { Marker: true, PayloadType: p.StreamType, SequenceNumber: p.Sequence, - Timestamp: p.PTSorDTS, + Timestamp: p.PTS, //Timestamp: p.Timestamp, }, Payload: aac.ADTStoRTP(p.Payload), @@ -379,7 +390,7 @@ func (p *PES) GetPacket() (pkt *rtp.Packet) { Marker: true, PayloadType: p.StreamType, SequenceNumber: p.Sequence, - Timestamp: p.PTSorDTS, + Timestamp: p.PTS, //Timestamp: p.Timestamp, }, Payload: p.Payload, diff --git a/pkg/mpegts/muxer.go b/pkg/mpegts/muxer.go index 193850ea..5d4129d1 100644 --- a/pkg/mpegts/muxer.go +++ b/pkg/mpegts/muxer.go @@ -51,9 +51,8 @@ func (m *Muxer) GetPayload(pid uint16, timestamp uint32, payload []byte) []byte } if pes.Timestamp != 0 { - pes.PTSorDTS += timestamp - pes.Timestamp + pes.PTS += timestamp - pes.Timestamp } - //log.Print(pid, pes.PTSorDTS, timestamp, pes.Timestamp) pes.Timestamp = timestamp // min header size (3 byte) + adv header size (PES) @@ -74,7 +73,7 @@ func (m *Muxer) GetPayload(pid uint16, timestamp uint32, payload []byte) []byte b[7] = 0x80 // PTS indicator b[8] = 5 // PES header length - WriteTime(b[9:], pes.PTSorDTS) + WriteTime(b[9:], pes.PTS) pes.Payload = append(b, payload...) pes.Size = 1 // set PUSI in first PES