golanggohlsrtmpwebrtcmedia-serverobs-studiortcprtmp-proxyrtmp-serverrtprtsprtsp-proxyrtsp-relayrtsp-serversrtstreamingwebrtc-proxy
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
187 lines
4.2 KiB
187 lines
4.2 KiB
package hls |
|
|
|
import ( |
|
"context" |
|
"time" |
|
|
|
"github.com/aler9/gortsplib" |
|
"github.com/aler9/gortsplib/pkg/h264" |
|
"github.com/asticode/go-astits" |
|
) |
|
|
|
const ( |
|
mpegtsSegmentMinAUCount = 100 |
|
) |
|
|
|
type writerFunc func(p []byte) (int, error) |
|
|
|
func (f writerFunc) Write(p []byte) (int, error) { |
|
return f(p) |
|
} |
|
|
|
type muxerVariantMPEGTSSegmenter struct { |
|
segmentDuration time.Duration |
|
segmentMaxSize uint64 |
|
videoTrack *gortsplib.TrackH264 |
|
audioTrack *gortsplib.TrackAAC |
|
onSegmentReady func(*muxerVariantMPEGTSSegment) |
|
|
|
writer *astits.Muxer |
|
currentSegment *muxerVariantMPEGTSSegment |
|
videoSPS *h264.SPS |
|
videoDTSExtractor *h264.DTSExtractor |
|
startPCR time.Time |
|
startPTS time.Duration |
|
} |
|
|
|
func newMuxerVariantMPEGTSSegmenter( |
|
segmentDuration time.Duration, |
|
segmentMaxSize uint64, |
|
videoTrack *gortsplib.TrackH264, |
|
audioTrack *gortsplib.TrackAAC, |
|
onSegmentReady func(*muxerVariantMPEGTSSegment), |
|
) *muxerVariantMPEGTSSegmenter { |
|
m := &muxerVariantMPEGTSSegmenter{ |
|
segmentDuration: segmentDuration, |
|
segmentMaxSize: segmentMaxSize, |
|
videoTrack: videoTrack, |
|
audioTrack: audioTrack, |
|
onSegmentReady: onSegmentReady, |
|
videoDTSExtractor: h264.NewDTSExtractor(), |
|
} |
|
|
|
m.writer = astits.NewMuxer( |
|
context.Background(), |
|
writerFunc(func(p []byte) (int, error) { |
|
return m.currentSegment.write(p) |
|
})) |
|
|
|
if videoTrack != nil { |
|
m.writer.AddElementaryStream(astits.PMTElementaryStream{ |
|
ElementaryPID: 256, |
|
StreamType: astits.StreamTypeH264Video, |
|
}) |
|
} |
|
|
|
if audioTrack != nil { |
|
m.writer.AddElementaryStream(astits.PMTElementaryStream{ |
|
ElementaryPID: 257, |
|
StreamType: astits.StreamTypeAACAudio, |
|
}) |
|
} |
|
|
|
if videoTrack != nil { |
|
m.writer.SetPCRPID(256) |
|
} else { |
|
m.writer.SetPCRPID(257) |
|
} |
|
|
|
return m |
|
} |
|
|
|
func (m *muxerVariantMPEGTSSegmenter) writeH264(pts time.Duration, nalus [][]byte) error { |
|
now := time.Now() |
|
idrPresent := h264.IDRPresent(nalus) |
|
|
|
if m.currentSegment == nil { |
|
// skip groups silently until we find one with a IDR |
|
if !idrPresent { |
|
return nil |
|
} |
|
|
|
// create first segment |
|
m.currentSegment = newMuxerVariantMPEGTSSegment(now, m.segmentMaxSize, |
|
m.videoTrack, m.audioTrack, m.writer.WriteData) |
|
m.startPCR = now |
|
m.startPTS = pts |
|
pts = 0 |
|
} else { |
|
pts -= m.startPTS |
|
|
|
// switch segment |
|
if idrPresent && |
|
m.currentSegment.startPTS != nil && |
|
(pts-*m.currentSegment.startPTS) >= m.segmentDuration { |
|
m.currentSegment.endPTS = pts |
|
m.onSegmentReady(m.currentSegment) |
|
m.currentSegment = newMuxerVariantMPEGTSSegment(now, m.segmentMaxSize, |
|
m.videoTrack, m.audioTrack, m.writer.WriteData) |
|
} |
|
} |
|
|
|
if idrPresent { |
|
sps := m.videoTrack.SPS() |
|
var psps h264.SPS |
|
err := psps.Unmarshal(sps) |
|
if err != nil { |
|
return err |
|
} |
|
m.videoSPS = &psps |
|
} |
|
|
|
dts, err := m.videoDTSExtractor.Extract(nalus, idrPresent, pts, m.videoSPS) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
err = m.currentSegment.writeH264( |
|
now.Sub(m.startPCR), |
|
dts, |
|
pts, |
|
idrPresent, |
|
nalus) |
|
if err != nil { |
|
if m.currentSegment.buf.Len() > 0 { |
|
m.onSegmentReady(m.currentSegment) |
|
} |
|
m.currentSegment = nil |
|
return err |
|
} |
|
|
|
return nil |
|
} |
|
|
|
func (m *muxerVariantMPEGTSSegmenter) writeAAC(pts time.Duration, aus [][]byte) error { |
|
now := time.Now() |
|
|
|
if m.videoTrack == nil { |
|
if m.currentSegment == nil { |
|
// create first segment |
|
m.currentSegment = newMuxerVariantMPEGTSSegment(now, m.segmentMaxSize, |
|
m.videoTrack, m.audioTrack, m.writer.WriteData) |
|
m.startPCR = now |
|
m.startPTS = pts |
|
pts = 0 |
|
} else { |
|
pts -= m.startPTS |
|
|
|
// switch segment |
|
if m.currentSegment.audioAUCount >= mpegtsSegmentMinAUCount && |
|
m.currentSegment.startPTS != nil && |
|
(pts-*m.currentSegment.startPTS) >= m.segmentDuration { |
|
m.currentSegment.endPTS = pts |
|
m.onSegmentReady(m.currentSegment) |
|
m.currentSegment = newMuxerVariantMPEGTSSegment(now, m.segmentMaxSize, |
|
m.videoTrack, m.audioTrack, m.writer.WriteData) |
|
} |
|
} |
|
} else { |
|
// wait for the video track |
|
if m.currentSegment == nil { |
|
return nil |
|
} |
|
|
|
pts -= m.startPTS |
|
} |
|
|
|
err := m.currentSegment.writeAAC(now.Sub(m.startPCR), pts, aus) |
|
if err != nil { |
|
if m.currentSegment.buf.Len() > 0 { |
|
m.onSegmentReady(m.currentSegment) |
|
} |
|
m.currentSegment = nil |
|
return err |
|
} |
|
|
|
return nil |
|
}
|
|
|