Browse Source
Previously, RTP packets coming from sources other than RTSP (that actually are RTMP and HLS) were generated before the H264 remuxing, and that leaded to invalid streams, expecially when sourceOnDemand is true and the stream has invalid or dynamic SPS/PPS.pull/1057/head
14 changed files with 398 additions and 282 deletions
@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
|
||||
package core |
||||
|
||||
import ( |
||||
"fmt" |
||||
|
||||
"github.com/aler9/gortsplib" |
||||
) |
||||
|
||||
type streamTrack interface { |
||||
writeData(*data) |
||||
} |
||||
|
||||
func newStreamTrack(track gortsplib.Track, generateRTPPackets bool, writeDataInner func(*data)) (streamTrack, error) { |
||||
switch ttrack := track.(type) { |
||||
case *gortsplib.TrackH264: |
||||
return newStreamTrackH264(ttrack, generateRTPPackets, writeDataInner), nil |
||||
|
||||
case *gortsplib.TrackMPEG4Audio: |
||||
return newStreamTrackMPEG4Audio(ttrack, generateRTPPackets, writeDataInner), nil |
||||
|
||||
default: |
||||
if generateRTPPackets { |
||||
return nil, fmt.Errorf("we don't know how to generate RTP packets of track %+v", track) |
||||
} |
||||
return newStreamTrackGeneric(track, writeDataInner), nil |
||||
} |
||||
} |
||||
@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
|
||||
package core |
||||
|
||||
import ( |
||||
"github.com/aler9/gortsplib" |
||||
) |
||||
|
||||
type streamTrackGeneric struct { |
||||
writeDataInner func(*data) |
||||
} |
||||
|
||||
func newStreamTrackGeneric(track gortsplib.Track, writeDataInner func(*data)) *streamTrackGeneric { |
||||
return &streamTrackGeneric{ |
||||
writeDataInner: writeDataInner, |
||||
} |
||||
} |
||||
|
||||
func (t *streamTrackGeneric) writeData(data *data) { |
||||
t.writeDataInner(data) |
||||
} |
||||
@ -0,0 +1,140 @@
@@ -0,0 +1,140 @@
|
||||
package core |
||||
|
||||
import ( |
||||
"bytes" |
||||
|
||||
"github.com/aler9/gortsplib" |
||||
"github.com/aler9/gortsplib/pkg/h264" |
||||
"github.com/aler9/gortsplib/pkg/rtph264" |
||||
) |
||||
|
||||
type streamTrackH264 struct { |
||||
track *gortsplib.TrackH264 |
||||
writeDataInner func(*data) |
||||
|
||||
rtpEncoder *rtph264.Encoder |
||||
} |
||||
|
||||
func newStreamTrackH264( |
||||
track *gortsplib.TrackH264, |
||||
generateRTPPackets bool, |
||||
writeDataInner func(*data), |
||||
) *streamTrackH264 { |
||||
t := &streamTrackH264{ |
||||
track: track, |
||||
writeDataInner: writeDataInner, |
||||
} |
||||
|
||||
if generateRTPPackets { |
||||
t.rtpEncoder = &rtph264.Encoder{PayloadType: 96} |
||||
t.rtpEncoder.Init() |
||||
} |
||||
|
||||
return t |
||||
} |
||||
|
||||
func (t *streamTrackH264) updateTrackParameters(nalus [][]byte) { |
||||
for _, nalu := range nalus { |
||||
typ := h264.NALUType(nalu[0] & 0x1F) |
||||
|
||||
switch typ { |
||||
case h264.NALUTypeSPS: |
||||
if !bytes.Equal(nalu, t.track.SafeSPS()) { |
||||
t.track.SafeSetSPS(append([]byte(nil), nalu...)) |
||||
} |
||||
|
||||
case h264.NALUTypePPS: |
||||
if !bytes.Equal(nalu, t.track.SafePPS()) { |
||||
t.track.SafeSetPPS(append([]byte(nil), nalu...)) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// remux is needed to
|
||||
// - fix corrupted streams
|
||||
// - make streams compatible with all protocols
|
||||
func (t *streamTrackH264) remuxNALUs(dat *data) { |
||||
n := 0 |
||||
for _, nalu := range dat.h264NALUs { |
||||
typ := h264.NALUType(nalu[0] & 0x1F) |
||||
switch typ { |
||||
case h264.NALUTypeSPS, h264.NALUTypePPS: |
||||
continue |
||||
case h264.NALUTypeAccessUnitDelimiter: |
||||
continue |
||||
case h264.NALUTypeIDR: |
||||
n += 2 |
||||
} |
||||
n++ |
||||
} |
||||
|
||||
filteredNALUs := make([][]byte, n) |
||||
i := 0 |
||||
|
||||
for _, nalu := range dat.h264NALUs { |
||||
typ := h264.NALUType(nalu[0] & 0x1F) |
||||
switch typ { |
||||
case h264.NALUTypeSPS, h264.NALUTypePPS: |
||||
// remove since they're automatically added before every IDR
|
||||
continue |
||||
|
||||
case h264.NALUTypeAccessUnitDelimiter: |
||||
// remove since it is not needed
|
||||
continue |
||||
|
||||
case h264.NALUTypeIDR: |
||||
// add SPS and PPS before every IDR
|
||||
filteredNALUs[i] = t.track.SafeSPS() |
||||
i++ |
||||
filteredNALUs[i] = t.track.SafePPS() |
||||
i++ |
||||
} |
||||
|
||||
filteredNALUs[i] = nalu |
||||
i++ |
||||
} |
||||
|
||||
dat.h264NALUs = filteredNALUs |
||||
} |
||||
|
||||
func (t *streamTrackH264) generateRTPPackets(dat *data) { |
||||
// if remuxNALUs() returned nil, do not write any data
|
||||
if dat.h264NALUs == nil { |
||||
return |
||||
} |
||||
|
||||
pkts, err := t.rtpEncoder.Encode(dat.h264NALUs, dat.pts) |
||||
if err != nil { |
||||
return |
||||
} |
||||
|
||||
lastPkt := len(pkts) - 1 |
||||
for i, pkt := range pkts { |
||||
if i != lastPkt { |
||||
t.writeDataInner(&data{ |
||||
trackID: dat.trackID, |
||||
rtpPacket: pkt, |
||||
}) |
||||
} else { |
||||
t.writeDataInner(&data{ |
||||
trackID: dat.trackID, |
||||
rtpPacket: pkt, |
||||
ptsEqualsDTS: dat.ptsEqualsDTS, |
||||
pts: dat.pts, |
||||
h264NALUs: dat.h264NALUs, |
||||
}) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func (t *streamTrackH264) writeData(dat *data) { |
||||
t.updateTrackParameters(dat.h264NALUs) |
||||
t.remuxNALUs(dat) |
||||
|
||||
if dat.rtpPacket != nil { |
||||
t.writeDataInner(dat) |
||||
} else { |
||||
t.generateRTPPackets(dat) |
||||
} |
||||
} |
||||
@ -0,0 +1,58 @@
@@ -0,0 +1,58 @@
|
||||
package core |
||||
|
||||
import ( |
||||
"github.com/aler9/gortsplib" |
||||
"github.com/aler9/gortsplib/pkg/rtpmpeg4audio" |
||||
) |
||||
|
||||
type streamTrackMPEG4Audio struct { |
||||
writeDataInner func(*data) |
||||
|
||||
rtpEncoder *rtpmpeg4audio.Encoder |
||||
} |
||||
|
||||
func newStreamTrackMPEG4Audio( |
||||
track *gortsplib.TrackMPEG4Audio, |
||||
generateRTPPackets bool, |
||||
writeDataInner func(*data), |
||||
) *streamTrackMPEG4Audio { |
||||
t := &streamTrackMPEG4Audio{ |
||||
writeDataInner: writeDataInner, |
||||
} |
||||
|
||||
if generateRTPPackets { |
||||
t.rtpEncoder = &rtpmpeg4audio.Encoder{ |
||||
PayloadType: 96, |
||||
SampleRate: track.ClockRate(), |
||||
SizeLength: 13, |
||||
IndexLength: 3, |
||||
IndexDeltaLength: 3, |
||||
} |
||||
t.rtpEncoder.Init() |
||||
} |
||||
|
||||
return t |
||||
} |
||||
|
||||
func (t *streamTrackMPEG4Audio) generateRTPPackets(dat *data) { |
||||
pkts, err := t.rtpEncoder.Encode([][]byte{dat.mpeg4AudioAU}, dat.pts) |
||||
if err != nil { |
||||
return |
||||
} |
||||
|
||||
for _, pkt := range pkts { |
||||
t.writeDataInner(&data{ |
||||
trackID: dat.trackID, |
||||
rtpPacket: pkt, |
||||
ptsEqualsDTS: true, |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func (t *streamTrackMPEG4Audio) writeData(dat *data) { |
||||
if dat.rtpPacket != nil { |
||||
t.writeDataInner(dat) |
||||
} else { |
||||
t.generateRTPPackets(dat) |
||||
} |
||||
} |
||||
Loading…
Reference in new issue