Browse Source

rtmp, hls: remove initial difference of 2secs between PTS and DTS of H264

pull/591/head
aler9 5 years ago
parent
commit
02afa8ff99
  1. 49
      internal/core/rtmp_conn.go
  2. 23
      internal/h264/dtsestimator.go
  3. 23
      internal/h264/dtsestimator_test.go
  4. 16
      internal/hls/muxer.go
  5. 3
      internal/hls/muxer_test.go

49
internal/core/rtmp_conn.go

@ -27,11 +27,6 @@ import (
const ( const (
rtmpConnPauseAfterAuthError = 2 * time.Second rtmpConnPauseAfterAuthError = 2 * time.Second
// an offset is needed to
// - avoid negative PTS values
// - avoid PTS < DTS during startup
rtmpConnPTSOffset = 2 * time.Second
) )
func pathNameAndQuery(inURL *url.URL) (string, url.Values) { func pathNameAndQuery(inURL *url.URL) (string, url.Values) {
@ -285,7 +280,9 @@ func (c *rtmpConn) runRead(ctx context.Context) error {
c.conn.NetConn().SetReadDeadline(time.Time{}) c.conn.NetConn().SetReadDeadline(time.Time{})
var videoBuf [][]byte var videoBuf [][]byte
videoDTSEst := h264.NewDTSEstimator() var videoStartPTS time.Duration
var videoDTSEst *h264.DTSEstimator
videoFirstIDRFound := false
for { for {
data, ok := c.ringBuffer.Pull() data, ok := c.ringBuffer.Pull()
@ -324,11 +321,6 @@ func (c *rtmpConn) runRead(ctx context.Context) error {
// RTP marker means that all the NALUs with the same PTS have been received. // RTP marker means that all the NALUs with the same PTS have been received.
// send them together. // send them together.
if pkt.Marker { if pkt.Marker {
data, err := h264.EncodeAVCC(videoBuf)
if err != nil {
return err
}
idrPresent := func() bool { idrPresent := func() bool {
for _, nalu := range nalus { for _, nalu := range nalus {
typ := h264.NALUType(nalu[0] & 0x1F) typ := h264.NALUType(nalu[0] & 0x1F)
@ -339,9 +331,25 @@ func (c *rtmpConn) runRead(ctx context.Context) error {
return false return false
}() }()
pts += rtmpConnPTSOffset // wait until we receive an IDR
if !videoFirstIDRFound {
if !idrPresent {
videoBuf = nil
continue
}
videoFirstIDRFound = true
videoStartPTS = pts
videoDTSEst = h264.NewDTSEstimator()
}
data, err := h264.EncodeAVCC(videoBuf)
if err != nil {
return err
}
dts := videoDTSEst.Feed(idrPresent, pts) pts -= videoStartPTS
dts := videoDTSEst.Feed(pts)
c.conn.NetConn().SetWriteDeadline(time.Now().Add(c.writeTimeout)) c.conn.NetConn().SetWriteDeadline(time.Now().Add(c.writeTimeout))
err = c.conn.WritePacket(av.Packet{ err = c.conn.WritePacket(av.Packet{
@ -373,18 +381,27 @@ func (c *rtmpConn) runRead(ctx context.Context) error {
continue continue
} }
for i, au := range aus { if videoTrack != nil && !videoFirstIDRFound {
auPTS := pts + rtmpConnPTSOffset + time.Duration(i)*1000*time.Second/time.Duration(audioClockRate) continue
}
pts -= videoStartPTS
if pts < 0 {
continue
}
for _, au := range aus {
c.conn.NetConn().SetWriteDeadline(time.Now().Add(c.writeTimeout)) c.conn.NetConn().SetWriteDeadline(time.Now().Add(c.writeTimeout))
err := c.conn.WritePacket(av.Packet{ err := c.conn.WritePacket(av.Packet{
Type: av.AAC, Type: av.AAC,
Data: au, Data: au,
Time: auPTS, Time: pts,
}) })
if err != nil { if err != nil {
return err return err
} }
pts += 1000 * time.Second / time.Duration(audioClockRate)
} }
} }
} }

23
internal/h264/dtsestimator.go

@ -20,23 +20,20 @@ func NewDTSEstimator() *DTSEstimator {
} }
// Feed provides PTS to the estimator, and returns the estimated DTS. // Feed provides PTS to the estimator, and returns the estimated DTS.
func (d *DTSEstimator) Feed(idrPresent bool, pts time.Duration) time.Duration { func (d *DTSEstimator) Feed(pts time.Duration) time.Duration {
if d.initializing > 0 { switch d.initializing {
case 2:
d.initializing--
return 0
case 1:
d.initializing-- d.initializing--
dts := d.prevDTS + time.Millisecond
d.prevPrevPTS = d.prevPTS
d.prevPTS = pts d.prevPTS = pts
d.prevDTS = dts d.prevDTS = time.Millisecond
return dts return time.Millisecond
} }
dts := func() time.Duration { dts := func() time.Duration {
// IDR
if idrPresent {
// DTS is always PTS
return pts
}
// P or I frame // P or I frame
if pts > d.prevPTS { if pts > d.prevPTS {
// previous frame was B // previous frame was B
@ -52,7 +49,7 @@ func (d *DTSEstimator) Feed(idrPresent bool, pts time.Duration) time.Duration {
} }
// B Frame // B Frame
// do not increase // increase by a small quantity
return d.prevDTS + time.Millisecond return d.prevDTS + time.Millisecond
}() }()

23
internal/h264/dtsestimator_test.go

@ -10,18 +10,23 @@ import (
func TestDTSEstimator(t *testing.T) { func TestDTSEstimator(t *testing.T) {
est := NewDTSEstimator() est := NewDTSEstimator()
dts := est.Feed(false, 2*time.Second) // initial state
dts := est.Feed(0)
require.Equal(t, time.Duration(0), dts)
// b-frame
dts = est.Feed(1*time.Second - 200*time.Millisecond)
require.Equal(t, time.Millisecond, dts) require.Equal(t, time.Millisecond, dts)
dts = est.Feed(false, 2*time.Second-200*time.Millisecond) // b-frame
dts = est.Feed(1*time.Second - 400*time.Millisecond)
require.Equal(t, 2*time.Millisecond, dts) require.Equal(t, 2*time.Millisecond, dts)
dts = est.Feed(false, 2*time.Second-400*time.Millisecond) // p-frame
require.Equal(t, 3*time.Millisecond, dts) dts = est.Feed(1 * time.Second)
require.Equal(t, 1*time.Second-400*time.Millisecond, dts)
dts = est.Feed(false, 2*time.Second+200*time.Millisecond)
require.Equal(t, 2*time.Second-400*time.Millisecond, dts)
dts = est.Feed(true, 2*time.Second+300*time.Millisecond) // p-frame
require.Equal(t, 2*time.Second+300*time.Millisecond, dts) dts = est.Feed(1*time.Second + 200*time.Millisecond)
require.Equal(t, 1*time.Second-399*time.Millisecond, dts)
} }

16
internal/hls/muxer.go

@ -10,10 +10,8 @@ import (
) )
const ( const (
// an offset is needed to // an offset between PCR and PTS/DTS is needed to avoid PCR > PTS
// - avoid negative PTS values pcrOffset = 500 * time.Millisecond
// - avoid PTS < DTS during startup
ptsOffset = 2 * time.Second
segmentMinAUCount = 100 segmentMinAUCount = 100
) )
@ -67,7 +65,6 @@ func NewMuxer(
audioTrack: audioTrack, audioTrack: audioTrack,
h264Conf: h264Conf, h264Conf: h264Conf,
aacConf: aacConf, aacConf: aacConf,
videoDTSEst: h264.NewDTSEstimator(),
currentSegment: newSegment(videoTrack, audioTrack, h264Conf, aacConf), currentSegment: newSegment(videoTrack, audioTrack, h264Conf, aacConf),
primaryPlaylist: newPrimaryPlaylist(videoTrack, audioTrack, h264Conf), primaryPlaylist: newPrimaryPlaylist(videoTrack, audioTrack, h264Conf),
streamPlaylist: newStreamPlaylist(hlsSegmentCount), streamPlaylist: newStreamPlaylist(hlsSegmentCount),
@ -110,13 +107,14 @@ func (m *Muxer) WriteH264(pts time.Duration, nalus [][]byte) error {
m.startPCR = time.Now() m.startPCR = time.Now()
m.startPTS = pts m.startPTS = pts
m.currentSegment.setStartPCR(m.startPCR) m.currentSegment.setStartPCR(m.startPCR)
m.videoDTSEst = h264.NewDTSEstimator()
} }
pts = pts + ptsOffset - m.startPTS pts -= m.startPTS
err := m.currentSegment.writeH264( err := m.currentSegment.writeH264(
m.videoDTSEst.Feed(idrPresent, pts), m.videoDTSEst.Feed(pts)+pcrOffset,
pts, pts+pcrOffset,
idrPresent, idrPresent,
nalus) nalus)
if err != nil { if err != nil {
@ -150,7 +148,7 @@ func (m *Muxer) WriteAAC(pts time.Duration, aus [][]byte) error {
} }
} }
pts = pts + ptsOffset - m.startPTS pts = pts - m.startPTS + pcrOffset
for i, au := range aus { for i, au := range aus {
auPTS := pts + time.Duration(i)*1000*time.Second/time.Duration(m.aacConf.SampleRate) auPTS := pts + time.Duration(i)*1000*time.Second/time.Duration(m.aacConf.SampleRate)

3
internal/hls/muxer_test.go

@ -93,8 +93,7 @@ func TestMuxer(t *testing.T) {
byts = byts[188:] byts = byts[188:]
checkTSPacket(t, byts, 256, 3) checkTSPacket(t, byts, 256, 3)
alen := int(byts[4]) byts = byts[4+145+15:]
byts = byts[4+alen+20:]
require.Equal(t, require.Equal(t,
[]byte{ []byte{
0, 0, 0, 1, 9, 240, // AUD 0, 0, 0, 1, 9, 240, // AUD

Loading…
Cancel
Save