@ -1,10 +1,11 @@
@@ -1,10 +1,11 @@
package hls
import (
"bytes "
"fmt "
"time"
"github.com/aler9/gortsplib/v2/pkg/codecs/h264"
"github.com/aler9/gortsplib/v2/pkg/codecs/h265"
"github.com/aler9/gortsplib/v2/pkg/format"
"github.com/aler9/rtsp-simple-server/internal/hls/fmp4"
@ -45,6 +46,21 @@ func findCompatiblePartDuration(
@@ -45,6 +46,21 @@ func findCompatiblePartDuration(
return i
}
type dtsExtractor interface {
Extract ( [ ] [ ] byte , time . Duration ) ( time . Duration , error )
}
func allocateDTSExtractor ( track format . Format ) dtsExtractor {
switch track . ( type ) {
case * format . H264 :
return h264 . NewDTSExtractor ( )
case * format . H265 :
return h265 . NewDTSExtractor ( )
}
return nil
}
type augmentedVideoSample struct {
fmp4 . PartSample
dts time . Duration
@ -62,23 +78,23 @@ type muxerVariantFMP4Segmenter struct {
@@ -62,23 +78,23 @@ type muxerVariantFMP4Segmenter struct {
segmentDuration time . Duration
partDuration time . Duration
segmentMaxSize uint64
videoTrack * format . H264
videoTrack format . Format
audioTrack * format . MPEG4Audio
onSegmentFinalized func ( * muxerVariantFMP4Segment )
onPartFinalized func ( * muxerVariantFMP4Part )
startDTS time . Duration
videoFirstID RReceived bool
videoDTSExtractor * h264 . DTS Extractor
videoSPS [ ] byte
currentSegment * muxerVariantFMP4Segment
nextSegmentID uint64
nextPartID uint64
nextVideoSample * augmentedVideoSample
nextAudioSample * augmentedAudioSample
firstSegmentFinalized bool
sampleDurations map [ time . Duration ] struct { }
adjustedPartDuration time . Duration
startDTS time . Duration
videoFirstRandomAccess Received bool
videoDTSExtractor dts Extractor
lastVideoParams [ ] [ ] byte
currentSegment * muxerVariantFMP4Segment
nextSegmentID uint64
nextPartID uint64
nextVideoSample * augmentedVideoSample
nextAudioSample * augmentedAudioSample
firstSegmentFinalized bool
sampleDurations map [ time . Duration ] struct { }
adjustedPartDuration time . Duration
}
func newMuxerVariantFMP4Segmenter (
@ -87,7 +103,7 @@ func newMuxerVariantFMP4Segmenter(
@@ -87,7 +103,7 @@ func newMuxerVariantFMP4Segmenter(
segmentDuration time . Duration ,
partDuration time . Duration ,
segmentMaxSize uint64 ,
videoTrack * format . H264 ,
videoTrack format . Format ,
audioTrack * format . MPEG4Audio ,
onSegmentFinalized func ( * muxerVariantFMP4Segment ) ,
onPartFinalized func ( * muxerVariantFMP4Part ) ,
@ -140,50 +156,65 @@ func (m *muxerVariantFMP4Segmenter) adjustPartDuration(du time.Duration) {
@@ -140,50 +156,65 @@ func (m *muxerVariantFMP4Segmenter) adjustPartDuration(du time.Duration) {
}
}
func ( m * muxerVariantFMP4Segmenter ) writeH264 ( ntp time . Time , pts time . Duration , nalus [ ] [ ] byte ) error {
idrPresent := false
nonIDRPresent := false
func ( m * muxerVariantFMP4Segmenter ) writeH26x ( ntp time . Time , pts time . Duration , au [ ] [ ] byte ) error {
randomAccessPresent := false
switch m . videoTrack . ( type ) {
case * format . H264 :
nonIDRPresent := false
for _ , nalu := range au {
typ := h264 . NALUType ( nalu [ 0 ] & 0x1F )
for _ , nalu := range nalus {
typ := h264 . NALUType ( nalu [ 0 ] & 0x1F )
switch typ {
case h264 . NALUTypeIDR :
idrPresent = true
switch typ {
case h264 . NALUTypeIDR :
randomAccessPresent = true
case h264 . NALUTypeNonIDR :
nonIDRPresent = true
case h264 . NALUTypeNonIDR :
nonIDRPresent = true
}
}
}
if ! idrPresent && ! nonIDRPresent {
return nil
if ! randomAccessPresent && ! nonIDRPresent {
return nil
}
case * format . H265 :
for _ , nalu := range au {
typ := h265 . NALUType ( ( nalu [ 0 ] >> 1 ) & 0b111111 )
switch typ {
case h265 . NALUType_IDR_W_RADL , h265 . NALUType_IDR_N_LP , h265 . NALUType_CRA_NUT :
randomAccessPresent = true
}
}
}
return m . writeH264Entry ( ntp , pts , nalus , idrPresent )
return m . writeH26x Entry ( ntp , pts , au , randomAccess Present)
}
func ( m * muxerVariantFMP4Segmenter ) writeH264Entry (
func ( m * muxerVariantFMP4Segmenter ) writeH26x Entry (
ntp time . Time ,
pts time . Duration ,
nalus [ ] [ ] byte ,
idrPresent bool ,
au [ ] [ ] byte ,
randomAccess Present bool ,
) error {
var dts time . Duration
if ! m . videoFirstID RReceived {
if ! m . videoFirstRandomAccess Received {
// skip sample silently until we find one with an IDR
if ! idr Present {
if ! randomAccess Present {
return nil
}
m . videoFirstID RReceived = true
m . videoDTSExtractor = h264 . NewDTSExtractor ( )
m . videoSPS = m . videoTrack . SafeSPS ( )
m . videoFirstRandomAccess Received = true
m . videoDTSExtractor = allocateDTSExtractor ( m . videoTrack )
m . lastVideoParams = extractVideoParams ( m . videoTrack )
var err error
dts , err = m . videoDTSExtractor . Extract ( n al us , pts )
dts , err = m . videoDTSExtractor . Extract ( au , pts )
if err != nil {
return err
return fmt . Errorf ( "unable to extract DTS: %v" , err )
}
m . startDTS = dts
@ -191,16 +222,16 @@ func (m *muxerVariantFMP4Segmenter) writeH264Entry(
@@ -191,16 +222,16 @@ func (m *muxerVariantFMP4Segmenter) writeH264Entry(
pts -= m . startDTS
} else {
var err error
dts , err = m . videoDTSExtractor . Extract ( n al us , pts )
dts , err = m . videoDTSExtractor . Extract ( au , pts )
if err != nil {
return err
return fmt . Errorf ( "unable to extract DTS: %v" , err )
}
dts -= m . startDTS
pts -= m . startDTS
}
avcc , err := h264 . AVCCMarshal ( n al us )
avcc , err := h264 . AVCCMarshal ( au )
if err != nil {
return err
}
@ -208,7 +239,7 @@ func (m *muxerVariantFMP4Segmenter) writeH264Entry(
@@ -208,7 +239,7 @@ func (m *muxerVariantFMP4Segmenter) writeH264Entry(
sample := & augmentedVideoSample {
PartSample : fmp4 . PartSample {
PTSOffset : int32 ( durationGoToMp4 ( pts - dts , 90000 ) ) ,
IsNonSyncSample : ! idr Present,
IsNonSyncSample : ! randomAccess Present,
Payload : avcc ,
} ,
dts : dts ,
@ -247,12 +278,12 @@ func (m *muxerVariantFMP4Segmenter) writeH264Entry(
@@ -247,12 +278,12 @@ func (m *muxerVariantFMP4Segmenter) writeH264Entry(
}
// switch segment
if idr Present {
sps := m . videoTrack . SafeSPS ( )
s psChanged := ! bytes . Equal ( m . videoSPS , sp s)
if randomAccess Present {
videoParams := extractVideoParams ( m . videoTrack )
param sChanged := ! videoParams Equal( m . lastVideoParams , videoParam s)
if ( m . nextVideoSample . dts - m . currentSegment . startDTS ) >= m . segmentDuration ||
s psChanged {
param sChanged {
err := m . currentSegment . finalize ( m . nextVideoSample . dts )
if err != nil {
return err
@ -273,10 +304,11 @@ func (m *muxerVariantFMP4Segmenter) writeH264Entry(
@@ -273,10 +304,11 @@ func (m *muxerVariantFMP4Segmenter) writeH264Entry(
m . onPartFinalized ,
)
// if SPS changed, reset adjusted part duration
if spsChanged {
m . videoSPS = sps
if paramsChanged {
m . lastVideoParams = videoParams
m . firstSegmentFinalized = false
// reset adjusted part duration
m . sampleDurations = make ( map [ time . Duration ] struct { } )
}
}
@ -288,7 +320,7 @@ func (m *muxerVariantFMP4Segmenter) writeH264Entry(
@@ -288,7 +320,7 @@ func (m *muxerVariantFMP4Segmenter) writeH264Entry(
func ( m * muxerVariantFMP4Segmenter ) writeAAC ( ntp time . Time , dts time . Duration , au [ ] byte ) error {
if m . videoTrack != nil {
// wait for the video track
if ! m . videoFirstID RReceived {
if ! m . videoFirstRandomAccess Received {
return nil
}