Ready-to-use SRT / WebRTC / RTSP / RTMP / LL-HLS media server and media proxy that allows to read, publish, proxy, record and playback video and audio streams.
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.
 
 
 
 
 
 

182 lines
4.2 KiB

package hls
import (
"io"
"time"
"github.com/aler9/gortsplib"
"github.com/aler9/rtsp-simple-server/internal/h264"
)
const (
// an offset between PCR and PTS/DTS is needed to avoid PCR > PTS
pcrOffset = 500 * time.Millisecond
segmentMinAUCount = 100
)
// Muxer is a HLS muxer.
type Muxer struct {
hlsSegmentCount int
hlsSegmentDuration time.Duration
videoTrack *gortsplib.Track
audioTrack *gortsplib.Track
h264Conf *gortsplib.TrackConfigH264
aacConf *gortsplib.TrackConfigAAC
videoDTSEst *h264.DTSEstimator
audioAUCount int
currentSegment *segment
startPCR time.Time
startPTS time.Duration
primaryPlaylist *primaryPlaylist
streamPlaylist *streamPlaylist
}
// NewMuxer allocates a Muxer.
func NewMuxer(
hlsSegmentCount int,
hlsSegmentDuration time.Duration,
videoTrack *gortsplib.Track,
audioTrack *gortsplib.Track) (*Muxer, error) {
var h264Conf *gortsplib.TrackConfigH264
if videoTrack != nil {
var err error
h264Conf, err = videoTrack.ExtractConfigH264()
if err != nil {
return nil, err
}
}
var aacConf *gortsplib.TrackConfigAAC
if audioTrack != nil {
var err error
aacConf, err = audioTrack.ExtractConfigAAC()
if err != nil {
return nil, err
}
}
m := &Muxer{
hlsSegmentCount: hlsSegmentCount,
hlsSegmentDuration: hlsSegmentDuration,
videoTrack: videoTrack,
audioTrack: audioTrack,
h264Conf: h264Conf,
aacConf: aacConf,
currentSegment: newSegment(videoTrack, audioTrack, h264Conf, aacConf),
primaryPlaylist: newPrimaryPlaylist(videoTrack, audioTrack, h264Conf),
streamPlaylist: newStreamPlaylist(hlsSegmentCount),
}
return m, nil
}
// Close closes a Muxer.
func (m *Muxer) Close() {
m.streamPlaylist.close()
}
// WriteH264 writes H264 NALUs, grouped by PTS, into the muxer.
func (m *Muxer) WriteH264(pts time.Duration, nalus [][]byte) error {
idrPresent := func() bool {
for _, nalu := range nalus {
typ := h264.NALUType(nalu[0] & 0x1F)
if typ == h264.NALUTypeIDR {
return true
}
}
return false
}()
// skip group silently until we find one with a IDR
if !m.currentSegment.firstPacketWritten && !idrPresent {
return nil
}
if m.currentSegment.firstPacketWritten {
if idrPresent &&
m.currentSegment.duration() >= m.hlsSegmentDuration {
m.streamPlaylist.pushSegment(m.currentSegment)
m.currentSegment = newSegment(m.videoTrack, m.audioTrack, m.h264Conf, m.aacConf)
m.currentSegment.setStartPCR(m.startPCR)
}
} else {
m.startPCR = time.Now()
m.startPTS = pts
m.currentSegment.setStartPCR(m.startPCR)
m.videoDTSEst = h264.NewDTSEstimator()
}
pts -= m.startPTS
err := m.currentSegment.writeH264(
m.videoDTSEst.Feed(pts)+pcrOffset,
pts+pcrOffset,
idrPresent,
nalus)
if err != nil {
return err
}
return nil
}
// WriteAAC writes AAC AUs, grouped by PTS, into the muxer.
func (m *Muxer) WriteAAC(pts time.Duration, aus [][]byte) error {
if m.videoTrack == nil {
if m.currentSegment.firstPacketWritten {
if m.audioAUCount >= segmentMinAUCount &&
m.currentSegment.duration() >= m.hlsSegmentDuration {
m.audioAUCount = 0
m.streamPlaylist.pushSegment(m.currentSegment)
m.currentSegment = newSegment(m.videoTrack, m.audioTrack, m.h264Conf, m.aacConf)
m.currentSegment.setStartPCR(m.startPCR)
}
} else {
m.startPCR = time.Now()
m.startPTS = pts
m.currentSegment.setStartPCR(m.startPCR)
}
} else {
if !m.currentSegment.firstPacketWritten {
return nil
}
}
pts = pts - m.startPTS + pcrOffset
for _, au := range aus {
err := m.currentSegment.writeAAC(pts, au)
if err != nil {
return err
}
if m.videoTrack == nil {
m.audioAUCount++
}
pts += 1000 * time.Second / time.Duration(m.aacConf.SampleRate)
}
return nil
}
// PrimaryPlaylist returns a reader to read the primary playlist
func (m *Muxer) PrimaryPlaylist() io.Reader {
return m.primaryPlaylist.reader()
}
// StreamPlaylist returns a reader to read the stream playlist.
func (m *Muxer) StreamPlaylist() io.Reader {
return m.streamPlaylist.reader()
}
// Segment returns a reader to read a segment.
func (m *Muxer) Segment(fname string) io.Reader {
return m.streamPlaylist.segment(fname)
}