2021-07-24 16:31:54 +00:00
|
|
|
package hls
|
|
|
|
|
|
|
|
import (
|
|
|
|
"io"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/aler9/gortsplib"
|
|
|
|
|
|
|
|
"github.com/aler9/rtsp-simple-server/internal/h264"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2021-09-07 07:50:18 +00:00
|
|
|
// an offset between PCR and PTS/DTS is needed to avoid PCR > PTS
|
|
|
|
pcrOffset = 500 * time.Millisecond
|
2021-07-24 16:31:54 +00:00
|
|
|
|
|
|
|
segmentMinAUCount = 100
|
|
|
|
)
|
|
|
|
|
|
|
|
// Muxer is a HLS muxer.
|
|
|
|
type Muxer struct {
|
|
|
|
hlsSegmentCount int
|
|
|
|
hlsSegmentDuration time.Duration
|
|
|
|
videoTrack *gortsplib.Track
|
|
|
|
audioTrack *gortsplib.Track
|
|
|
|
|
2021-08-25 17:51:59 +00:00
|
|
|
h264Conf *gortsplib.TrackConfigH264
|
|
|
|
aacConf *gortsplib.TrackConfigAAC
|
2021-08-23 10:26:06 +00:00
|
|
|
videoDTSEst *h264.DTSEstimator
|
|
|
|
audioAUCount int
|
|
|
|
currentSegment *segment
|
|
|
|
startPCR time.Time
|
|
|
|
startPTS time.Duration
|
|
|
|
primaryPlaylist *primaryPlaylist
|
|
|
|
streamPlaylist *streamPlaylist
|
2021-07-24 16:31:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewMuxer allocates a Muxer.
|
|
|
|
func NewMuxer(
|
|
|
|
hlsSegmentCount int,
|
|
|
|
hlsSegmentDuration time.Duration,
|
|
|
|
videoTrack *gortsplib.Track,
|
|
|
|
audioTrack *gortsplib.Track) (*Muxer, error) {
|
2021-08-25 17:51:59 +00:00
|
|
|
var h264Conf *gortsplib.TrackConfigH264
|
2021-08-14 13:39:29 +00:00
|
|
|
if videoTrack != nil {
|
|
|
|
var err error
|
2021-08-25 17:51:59 +00:00
|
|
|
h264Conf, err = videoTrack.ExtractConfigH264()
|
2021-08-14 13:39:29 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-25 17:51:59 +00:00
|
|
|
var aacConf *gortsplib.TrackConfigAAC
|
2021-07-24 16:31:54 +00:00
|
|
|
if audioTrack != nil {
|
2021-08-25 17:51:59 +00:00
|
|
|
var err error
|
|
|
|
aacConf, err = audioTrack.ExtractConfigAAC()
|
2021-07-24 16:31:54 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
m := &Muxer{
|
|
|
|
hlsSegmentCount: hlsSegmentCount,
|
|
|
|
hlsSegmentDuration: hlsSegmentDuration,
|
|
|
|
videoTrack: videoTrack,
|
|
|
|
audioTrack: audioTrack,
|
2021-08-25 17:51:59 +00:00
|
|
|
h264Conf: h264Conf,
|
|
|
|
aacConf: aacConf,
|
|
|
|
currentSegment: newSegment(videoTrack, audioTrack, h264Conf, aacConf),
|
|
|
|
primaryPlaylist: newPrimaryPlaylist(videoTrack, audioTrack, h264Conf),
|
2021-08-23 10:26:06 +00:00
|
|
|
streamPlaylist: newStreamPlaylist(hlsSegmentCount),
|
2021-07-24 16:31:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return m, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close closes a Muxer.
|
|
|
|
func (m *Muxer) Close() {
|
2021-08-23 10:26:06 +00:00
|
|
|
m.streamPlaylist.close()
|
2021-07-24 16:31:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
2021-08-23 10:26:06 +00:00
|
|
|
if !m.currentSegment.firstPacketWritten && !idrPresent {
|
2021-07-24 16:31:54 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-08-23 10:26:06 +00:00
|
|
|
if m.currentSegment.firstPacketWritten {
|
|
|
|
if idrPresent &&
|
|
|
|
m.currentSegment.duration() >= m.hlsSegmentDuration {
|
|
|
|
m.streamPlaylist.pushSegment(m.currentSegment)
|
2021-07-24 16:31:54 +00:00
|
|
|
|
2021-08-25 17:51:59 +00:00
|
|
|
m.currentSegment = newSegment(m.videoTrack, m.audioTrack, m.h264Conf, m.aacConf)
|
2021-08-23 10:26:06 +00:00
|
|
|
m.currentSegment.setStartPCR(m.startPCR)
|
2021-07-24 16:31:54 +00:00
|
|
|
}
|
2021-08-23 10:26:06 +00:00
|
|
|
} else {
|
2021-08-14 15:25:40 +00:00
|
|
|
m.startPCR = time.Now()
|
2021-08-14 15:08:35 +00:00
|
|
|
m.startPTS = pts
|
2021-08-23 10:26:06 +00:00
|
|
|
m.currentSegment.setStartPCR(m.startPCR)
|
2021-09-07 07:50:18 +00:00
|
|
|
m.videoDTSEst = h264.NewDTSEstimator()
|
2021-07-24 16:31:54 +00:00
|
|
|
}
|
|
|
|
|
2021-09-07 07:50:18 +00:00
|
|
|
pts -= m.startPTS
|
2021-08-23 10:26:06 +00:00
|
|
|
|
|
|
|
err := m.currentSegment.writeH264(
|
2021-09-07 07:50:18 +00:00
|
|
|
m.videoDTSEst.Feed(pts)+pcrOffset,
|
|
|
|
pts+pcrOffset,
|
2021-07-24 16:31:54 +00:00
|
|
|
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 {
|
2021-07-29 14:27:15 +00:00
|
|
|
if m.videoTrack == nil {
|
2021-08-23 10:26:06 +00:00
|
|
|
if m.currentSegment.firstPacketWritten {
|
|
|
|
if m.audioAUCount >= segmentMinAUCount &&
|
|
|
|
m.currentSegment.duration() >= m.hlsSegmentDuration {
|
|
|
|
m.audioAUCount = 0
|
2021-07-24 16:31:54 +00:00
|
|
|
|
2021-08-23 10:26:06 +00:00
|
|
|
m.streamPlaylist.pushSegment(m.currentSegment)
|
2021-08-14 15:25:40 +00:00
|
|
|
|
2021-08-25 17:51:59 +00:00
|
|
|
m.currentSegment = newSegment(m.videoTrack, m.audioTrack, m.h264Conf, m.aacConf)
|
2021-08-23 10:26:06 +00:00
|
|
|
m.currentSegment.setStartPCR(m.startPCR)
|
2021-07-24 16:31:54 +00:00
|
|
|
}
|
2021-08-23 10:26:06 +00:00
|
|
|
} else {
|
2021-08-14 15:25:40 +00:00
|
|
|
m.startPCR = time.Now()
|
2021-08-14 15:08:35 +00:00
|
|
|
m.startPTS = pts
|
2021-08-23 10:26:06 +00:00
|
|
|
m.currentSegment.setStartPCR(m.startPCR)
|
2021-07-24 16:31:54 +00:00
|
|
|
}
|
|
|
|
} else {
|
2021-08-23 10:26:06 +00:00
|
|
|
if !m.currentSegment.firstPacketWritten {
|
2021-07-24 16:31:54 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-07 07:50:18 +00:00
|
|
|
pts = pts - m.startPTS + pcrOffset
|
2021-08-14 15:08:35 +00:00
|
|
|
|
2021-07-24 16:31:54 +00:00
|
|
|
for i, au := range aus {
|
2021-08-25 17:51:59 +00:00
|
|
|
auPTS := pts + time.Duration(i)*1000*time.Second/time.Duration(m.aacConf.SampleRate)
|
2021-07-24 16:31:54 +00:00
|
|
|
|
2021-08-25 17:51:59 +00:00
|
|
|
err := m.currentSegment.writeAAC(auPTS, au)
|
2021-07-24 16:31:54 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-08-14 15:25:40 +00:00
|
|
|
|
2021-09-07 08:23:32 +00:00
|
|
|
if m.videoTrack == nil {
|
|
|
|
m.audioAUCount++
|
|
|
|
}
|
2021-07-24 16:31:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-08-16 16:07:10 +00:00
|
|
|
// PrimaryPlaylist returns a reader to read the primary playlist
|
|
|
|
func (m *Muxer) PrimaryPlaylist() io.Reader {
|
2021-08-23 10:26:06 +00:00
|
|
|
return m.primaryPlaylist.reader()
|
2021-08-16 16:07:10 +00:00
|
|
|
}
|
2021-07-24 16:31:54 +00:00
|
|
|
|
2021-08-16 16:07:10 +00:00
|
|
|
// StreamPlaylist returns a reader to read the stream playlist.
|
|
|
|
func (m *Muxer) StreamPlaylist() io.Reader {
|
2021-08-23 10:26:06 +00:00
|
|
|
return m.streamPlaylist.reader()
|
2021-07-24 16:31:54 +00:00
|
|
|
}
|
|
|
|
|
2021-08-23 10:26:06 +00:00
|
|
|
// Segment returns a reader to read a segment.
|
|
|
|
func (m *Muxer) Segment(fname string) io.Reader {
|
|
|
|
return m.streamPlaylist.segment(fname)
|
2021-07-24 16:31:54 +00:00
|
|
|
}
|