Browse Source

hls: move fMP4 generator into dedicated folder

pull/1055/head
aler9 3 years ago
parent
commit
e146df9ed7
  1. 17
      internal/hls/fmp4/audiosample.go
  2. 19
      internal/hls/fmp4/init.go
  3. 22
      internal/hls/fmp4/mp4writer.go
  4. 291
      internal/hls/fmp4/part.go
  5. 24
      internal/hls/fmp4/videosample.go
  6. 29
      internal/hls/muxer_variant_fmp4.go
  7. 299
      internal/hls/muxer_variant_fmp4_part.go
  8. 16
      internal/hls/muxer_variant_fmp4_segment.go
  9. 72
      internal/hls/muxer_variant_fmp4_segmenter.go

17
internal/hls/fmp4/audiosample.go

@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
package fmp4
import (
"time"
)
// AudioSample is an audio sample.
type AudioSample struct {
AU []byte
PTS time.Duration
Next *AudioSample
}
// Duration returns the sample duration.
func (s AudioSample) Duration() time.Duration {
return s.Next.PTS - s.PTS
}

19
internal/hls/muxer_variant_fmp4_init.go → internal/hls/fmp4/init.go

@ -1,11 +1,9 @@ @@ -1,11 +1,9 @@
package hls
package fmp4
import (
gomp4 "github.com/abema/go-mp4"
"github.com/aler9/gortsplib"
"github.com/aler9/gortsplib/pkg/h264"
"github.com/aler9/rtsp-simple-server/internal/mp4"
)
type myEsds struct {
@ -21,7 +19,7 @@ func init() { //nolint:gochecknoinits @@ -21,7 +19,7 @@ func init() { //nolint:gochecknoinits
gomp4.AddBoxDef(&myEsds{}, 0)
}
func mp4InitGenerateVideoTrack(w *mp4.Writer, trackID int, videoTrack *gortsplib.TrackH264) error {
func generateInitVideoTrack(w *mp4Writer, trackID int, videoTrack *gortsplib.TrackH264) error {
/*
trak
- tkhd
@ -81,7 +79,7 @@ func mp4InitGenerateVideoTrack(w *mp4.Writer, trackID int, videoTrack *gortsplib @@ -81,7 +79,7 @@ func mp4InitGenerateVideoTrack(w *mp4.Writer, trackID int, videoTrack *gortsplib
}
_, err = w.WriteBox(&gomp4.Mdhd{ // <mdhd/>
Timescale: fmp4VideoTimescale, // the number of time units that pass per second
Timescale: videoTimescale, // the number of time units that pass per second
Language: [3]byte{'u', 'n', 'd'},
})
if err != nil {
@ -265,7 +263,7 @@ func mp4InitGenerateVideoTrack(w *mp4.Writer, trackID int, videoTrack *gortsplib @@ -265,7 +263,7 @@ func mp4InitGenerateVideoTrack(w *mp4.Writer, trackID int, videoTrack *gortsplib
return nil
}
func mp4InitGenerateAudioTrack(w *mp4.Writer, trackID int, audioTrack *gortsplib.TrackMPEG4Audio) error {
func generateInitAudioTrack(w *mp4Writer, trackID int, audioTrack *gortsplib.TrackMPEG4Audio) error {
/*
trak
- tkhd
@ -511,7 +509,8 @@ func mp4InitGenerateAudioTrack(w *mp4.Writer, trackID int, audioTrack *gortsplib @@ -511,7 +509,8 @@ func mp4InitGenerateAudioTrack(w *mp4.Writer, trackID int, audioTrack *gortsplib
return nil
}
func mp4InitGenerate(videoTrack *gortsplib.TrackH264, audioTrack *gortsplib.TrackMPEG4Audio) ([]byte, error) {
// GenerateInit generates a FMP4 initialization file.
func GenerateInit(videoTrack *gortsplib.TrackH264, audioTrack *gortsplib.TrackMPEG4Audio) ([]byte, error) {
/*
- ftyp
- moov
@ -523,7 +522,7 @@ func mp4InitGenerate(videoTrack *gortsplib.TrackH264, audioTrack *gortsplib.Trac @@ -523,7 +522,7 @@ func mp4InitGenerate(videoTrack *gortsplib.TrackH264, audioTrack *gortsplib.Trac
- trex (audio)
*/
w := mp4.NewWriter()
w := newMP4Writer()
_, err := w.WriteBox(&gomp4.Ftyp{ // <ftyp/>
MajorBrand: [4]byte{'m', 'p', '4', '2'},
@ -558,7 +557,7 @@ func mp4InitGenerate(videoTrack *gortsplib.TrackH264, audioTrack *gortsplib.Trac @@ -558,7 +557,7 @@ func mp4InitGenerate(videoTrack *gortsplib.TrackH264, audioTrack *gortsplib.Trac
trackID := 1
if videoTrack != nil {
err := mp4InitGenerateVideoTrack(w, trackID, videoTrack)
err := generateInitVideoTrack(w, trackID, videoTrack)
if err != nil {
return nil, err
}
@ -567,7 +566,7 @@ func mp4InitGenerate(videoTrack *gortsplib.TrackH264, audioTrack *gortsplib.Trac @@ -567,7 +566,7 @@ func mp4InitGenerate(videoTrack *gortsplib.TrackH264, audioTrack *gortsplib.Trac
}
if audioTrack != nil {
err := mp4InitGenerateAudioTrack(w, trackID, audioTrack)
err := generateInitAudioTrack(w, trackID, audioTrack)
if err != nil {
return nil, err
}

22
internal/mp4/writer.go → internal/hls/fmp4/mp4writer.go

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
package mp4
package fmp4
import (
"io"
@ -7,15 +7,15 @@ import ( @@ -7,15 +7,15 @@ import (
"github.com/orcaman/writerseeker"
)
// Writer is a MP4 writer.
type Writer struct {
// mp4Writer is a MP4 writer.
type mp4Writer struct {
buf *writerseeker.WriterSeeker
w *gomp4.Writer
}
// NewWriter allocates a Writer.
func NewWriter() *Writer {
w := &Writer{
// newMP4Writer allocates a mp4Writer.
func newMP4Writer() *mp4Writer {
w := &mp4Writer{
buf: &writerseeker.WriterSeeker{},
}
@ -25,7 +25,7 @@ func NewWriter() *Writer { @@ -25,7 +25,7 @@ func NewWriter() *Writer {
}
// WriteBoxStart writes a box start.
func (w *Writer) WriteBoxStart(box gomp4.IImmutableBox) (int, error) {
func (w *mp4Writer) WriteBoxStart(box gomp4.IImmutableBox) (int, error) {
bi := &gomp4.BoxInfo{
Type: box.GetType(),
}
@ -44,13 +44,13 @@ func (w *Writer) WriteBoxStart(box gomp4.IImmutableBox) (int, error) { @@ -44,13 +44,13 @@ func (w *Writer) WriteBoxStart(box gomp4.IImmutableBox) (int, error) {
}
// WriteBoxEnd writes a box end.
func (w *Writer) WriteBoxEnd() error {
func (w *mp4Writer) WriteBoxEnd() error {
_, err := w.w.EndBox()
return err
}
// WriteBox writes a self-closing box.
func (w *Writer) WriteBox(box gomp4.IImmutableBox) (int, error) {
func (w *mp4Writer) WriteBox(box gomp4.IImmutableBox) (int, error) {
off, err := w.WriteBoxStart(box)
if err != nil {
return 0, err
@ -65,7 +65,7 @@ func (w *Writer) WriteBox(box gomp4.IImmutableBox) (int, error) { @@ -65,7 +65,7 @@ func (w *Writer) WriteBox(box gomp4.IImmutableBox) (int, error) {
}
// RewriteBox rewrites a box.
func (w *Writer) RewriteBox(off int, box gomp4.IImmutableBox) error {
func (w *mp4Writer) RewriteBox(off int, box gomp4.IImmutableBox) error {
prevOff, err := w.w.Seek(0, io.SeekCurrent)
if err != nil {
return err
@ -95,6 +95,6 @@ func (w *Writer) RewriteBox(off int, box gomp4.IImmutableBox) error { @@ -95,6 +95,6 @@ func (w *Writer) RewriteBox(off int, box gomp4.IImmutableBox) error {
}
// Bytes returns the MP4 content.
func (w *Writer) Bytes() []byte {
func (w *mp4Writer) Bytes() []byte {
return w.buf.Bytes()
}

291
internal/hls/fmp4/part.go

@ -0,0 +1,291 @@ @@ -0,0 +1,291 @@
package fmp4
import (
"math"
"time"
gomp4 "github.com/abema/go-mp4"
"github.com/aler9/gortsplib"
)
func durationGoToMp4(v time.Duration, timescale time.Duration) int64 {
return int64(math.Round(float64(v*timescale) / float64(time.Second)))
}
func generatePartVideoTraf(
w *mp4Writer,
trackID int,
videoSamples []*VideoSample,
) (*gomp4.Trun, int, error) {
/*
traf
- tfhd
- tfdt
- trun
*/
_, err := w.WriteBoxStart(&gomp4.Traf{}) // <traf>
if err != nil {
return nil, 0, err
}
flags := 0
_, err = w.WriteBox(&gomp4.Tfhd{ // <tfhd/>
FullBox: gomp4.FullBox{
Flags: [3]byte{2, byte(flags >> 8), byte(flags)},
},
TrackID: uint32(trackID),
})
if err != nil {
return nil, 0, err
}
_, err = w.WriteBox(&gomp4.Tfdt{ // <tfdt/>
FullBox: gomp4.FullBox{
Version: 1,
},
// sum of decode durations of all earlier samples
BaseMediaDecodeTimeV1: uint64(durationGoToMp4(videoSamples[0].DTS, videoTimescale)),
})
if err != nil {
return nil, 0, err
}
flags = 0
flags |= 0x01 // data offset present
flags |= 0x100 // sample duration present
flags |= 0x200 // sample size present
flags |= 0x400 // sample flags present
flags |= 0x800 // sample composition time offset present or v1
trun := &gomp4.Trun{ // <trun/>
FullBox: gomp4.FullBox{
Version: 1,
Flags: [3]byte{0, byte(flags >> 8), byte(flags)},
},
SampleCount: uint32(len(videoSamples)),
}
for _, e := range videoSamples {
off := e.PTS - e.DTS
flags := uint32(0)
if !e.IDRPresent {
flags |= 1 << 16 // sample_is_non_sync_sample
}
trun.Entries = append(trun.Entries, gomp4.TrunEntry{
SampleDuration: uint32(durationGoToMp4(e.Duration(), videoTimescale)),
SampleSize: uint32(len(e.AVCC)),
SampleFlags: flags,
SampleCompositionTimeOffsetV1: int32(durationGoToMp4(off, videoTimescale)),
})
}
trunOffset, err := w.WriteBox(trun)
if err != nil {
return nil, 0, err
}
err = w.WriteBoxEnd() // </traf>
if err != nil {
return nil, 0, err
}
return trun, trunOffset, nil
}
func generatePartAudioTraf(
w *mp4Writer,
trackID int,
audioTrack *gortsplib.TrackMPEG4Audio,
audioSamples []*AudioSample,
) (*gomp4.Trun, int, error) {
/*
traf
- tfhd
- tfdt
- trun
*/
if len(audioSamples) == 0 {
return nil, 0, nil
}
_, err := w.WriteBoxStart(&gomp4.Traf{}) // <traf>
if err != nil {
return nil, 0, err
}
flags := 0
_, err = w.WriteBox(&gomp4.Tfhd{ // <tfhd/>
FullBox: gomp4.FullBox{
Flags: [3]byte{2, byte(flags >> 8), byte(flags)},
},
TrackID: uint32(trackID),
})
if err != nil {
return nil, 0, err
}
_, err = w.WriteBox(&gomp4.Tfdt{ // <tfdt/>
FullBox: gomp4.FullBox{
Version: 1,
},
// sum of decode durations of all earlier samples
BaseMediaDecodeTimeV1: uint64(durationGoToMp4(audioSamples[0].PTS, time.Duration(audioTrack.ClockRate()))),
})
if err != nil {
return nil, 0, err
}
flags = 0
flags |= 0x01 // data offset present
flags |= 0x100 // sample duration present
flags |= 0x200 // sample size present
trun := &gomp4.Trun{ // <trun/>
FullBox: gomp4.FullBox{
Version: 0,
Flags: [3]byte{0, byte(flags >> 8), byte(flags)},
},
SampleCount: uint32(len(audioSamples)),
}
for _, e := range audioSamples {
trun.Entries = append(trun.Entries, gomp4.TrunEntry{
SampleDuration: uint32(durationGoToMp4(e.Duration(), time.Duration(audioTrack.ClockRate()))),
SampleSize: uint32(len(e.AU)),
})
}
trunOffset, err := w.WriteBox(trun)
if err != nil {
return nil, 0, err
}
err = w.WriteBoxEnd() // </traf>
if err != nil {
return nil, 0, err
}
return trun, trunOffset, nil
}
// GeneratePart generates a FMP4 part file.
func GeneratePart(
videoTrack *gortsplib.TrackH264,
audioTrack *gortsplib.TrackMPEG4Audio,
videoSamples []*VideoSample,
audioSamples []*AudioSample,
) ([]byte, error) {
/*
moof
- mfhd
- traf (video)
- traf (audio)
mdat
*/
w := newMP4Writer()
moofOffset, err := w.WriteBoxStart(&gomp4.Moof{}) // <moof>
if err != nil {
return nil, err
}
_, err = w.WriteBox(&gomp4.Mfhd{ // <mfhd/>
SequenceNumber: 0,
})
if err != nil {
return nil, err
}
trackID := 1
var videoTrun *gomp4.Trun
var videoTrunOffset int
if videoTrack != nil {
var err error
videoTrun, videoTrunOffset, err = generatePartVideoTraf(
w, trackID, videoSamples)
if err != nil {
return nil, err
}
trackID++
}
var audioTrun *gomp4.Trun
var audioTrunOffset int
if audioTrack != nil {
var err error
audioTrun, audioTrunOffset, err = generatePartAudioTraf(w, trackID, audioTrack, audioSamples)
if err != nil {
return nil, err
}
}
err = w.WriteBoxEnd() // </moof>
if err != nil {
return nil, err
}
mdat := &gomp4.Mdat{} // <mdat/>
dataSize := 0
videoDataSize := 0
if videoTrack != nil {
for _, e := range videoSamples {
dataSize += len(e.AVCC)
}
videoDataSize = dataSize
}
if audioTrack != nil {
for _, e := range audioSamples {
dataSize += len(e.AU)
}
}
mdat.Data = make([]byte, dataSize)
pos := 0
if videoTrack != nil {
for _, e := range videoSamples {
pos += copy(mdat.Data[pos:], e.AVCC)
}
}
if audioTrack != nil {
for _, e := range audioSamples {
pos += copy(mdat.Data[pos:], e.AU)
}
}
mdatOffset, err := w.WriteBox(mdat)
if err != nil {
return nil, err
}
if videoTrack != nil {
videoTrun.DataOffset = int32(mdatOffset - moofOffset + 8)
err = w.RewriteBox(videoTrunOffset, videoTrun)
if err != nil {
return nil, err
}
}
if audioTrack != nil && audioTrun != nil {
audioTrun.DataOffset = int32(videoDataSize + mdatOffset - moofOffset + 8)
err = w.RewriteBox(audioTrunOffset, audioTrun)
if err != nil {
return nil, err
}
}
return w.Bytes(), nil
}

24
internal/hls/fmp4/videosample.go

@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
package fmp4
import (
"time"
)
const (
videoTimescale = 90000
)
// VideoSample is a video sample.
type VideoSample struct {
NALUs [][]byte
PTS time.Duration
DTS time.Duration
AVCC []byte
IDRPresent bool
Next *VideoSample
}
// Duration returns the sample duration.
func (s VideoSample) Duration() time.Duration {
return s.Next.DTS - s.DTS
}

29
internal/hls/muxer_variant_fmp4.go

@ -7,35 +7,10 @@ import ( @@ -7,35 +7,10 @@ import (
"time"
"github.com/aler9/gortsplib"
)
const (
fmp4VideoTimescale = 90000
"github.com/aler9/rtsp-simple-server/internal/hls/fmp4"
)
type fmp4VideoSample struct {
nalus [][]byte
pts time.Duration
dts time.Duration
avcc []byte
idrPresent bool
next *fmp4VideoSample
}
func (s fmp4VideoSample) duration() time.Duration {
return s.next.dts - s.dts
}
type fmp4AudioSample struct {
au []byte
pts time.Duration
next *fmp4AudioSample
}
func (s fmp4AudioSample) duration() time.Duration {
return s.next.pts - s.pts
}
type muxerVariantFMP4 struct {
playlist *muxerVariantFMP4Playlist
segmenter *muxerVariantFMP4Segmenter
@ -110,7 +85,7 @@ func (v *muxerVariantFMP4) file(name string, msn string, part string, skip strin @@ -110,7 +85,7 @@ func (v *muxerVariantFMP4) file(name string, msn string, part string, skip strin
if v.initContent == nil ||
(v.videoTrack != nil && (!bytes.Equal(v.videoLastSPS, sps) || !bytes.Equal(v.videoLastPPS, pps))) {
initContent, err := mp4InitGenerate(v.videoTrack, v.audioTrack)
initContent, err := fmp4.GenerateInit(v.videoTrack, v.audioTrack)
if err != nil {
return &MuxerFileResponse{Status: http.StatusInternalServerError}
}

299
internal/hls/muxer_variant_fmp4_part.go

@ -3,298 +3,15 @@ package hls @@ -3,298 +3,15 @@ package hls
import (
"bytes"
"io"
"math"
"strconv"
"time"
gomp4 "github.com/abema/go-mp4"
"github.com/aler9/gortsplib"
"github.com/aler9/gortsplib/pkg/mpeg4audio"
"github.com/aler9/rtsp-simple-server/internal/mp4"
"github.com/aler9/rtsp-simple-server/internal/hls/fmp4"
)
func durationGoToMp4(v time.Duration, timescale time.Duration) int64 {
return int64(math.Round(float64(v*timescale) / float64(time.Second)))
}
func mp4PartGenerateVideoTraf(
w *mp4.Writer,
trackID int,
videoSamples []*fmp4VideoSample,
) (*gomp4.Trun, int, error) {
/*
traf
- tfhd
- tfdt
- trun
*/
_, err := w.WriteBoxStart(&gomp4.Traf{}) // <traf>
if err != nil {
return nil, 0, err
}
flags := 0
_, err = w.WriteBox(&gomp4.Tfhd{ // <tfhd/>
FullBox: gomp4.FullBox{
Flags: [3]byte{2, byte(flags >> 8), byte(flags)},
},
TrackID: uint32(trackID),
})
if err != nil {
return nil, 0, err
}
_, err = w.WriteBox(&gomp4.Tfdt{ // <tfdt/>
FullBox: gomp4.FullBox{
Version: 1,
},
// sum of decode durations of all earlier samples
BaseMediaDecodeTimeV1: uint64(durationGoToMp4(videoSamples[0].dts, fmp4VideoTimescale)),
})
if err != nil {
return nil, 0, err
}
flags = 0
flags |= 0x01 // data offset present
flags |= 0x100 // sample duration present
flags |= 0x200 // sample size present
flags |= 0x400 // sample flags present
flags |= 0x800 // sample composition time offset present or v1
trun := &gomp4.Trun{ // <trun/>
FullBox: gomp4.FullBox{
Version: 1,
Flags: [3]byte{0, byte(flags >> 8), byte(flags)},
},
SampleCount: uint32(len(videoSamples)),
}
for _, e := range videoSamples {
off := e.pts - e.dts
flags := uint32(0)
if !e.idrPresent {
flags |= 1 << 16 // sample_is_non_sync_sample
}
trun.Entries = append(trun.Entries, gomp4.TrunEntry{
SampleDuration: uint32(durationGoToMp4(e.duration(), fmp4VideoTimescale)),
SampleSize: uint32(len(e.avcc)),
SampleFlags: flags,
SampleCompositionTimeOffsetV1: int32(durationGoToMp4(off, fmp4VideoTimescale)),
})
}
trunOffset, err := w.WriteBox(trun)
if err != nil {
return nil, 0, err
}
err = w.WriteBoxEnd() // </traf>
if err != nil {
return nil, 0, err
}
return trun, trunOffset, nil
}
func mp4PartGenerateAudioTraf(
w *mp4.Writer,
trackID int,
audioTrack *gortsplib.TrackMPEG4Audio,
audioSamples []*fmp4AudioSample,
) (*gomp4.Trun, int, error) {
/*
traf
- tfhd
- tfdt
- trun
*/
if len(audioSamples) == 0 {
return nil, 0, nil
}
_, err := w.WriteBoxStart(&gomp4.Traf{}) // <traf>
if err != nil {
return nil, 0, err
}
flags := 0
_, err = w.WriteBox(&gomp4.Tfhd{ // <tfhd/>
FullBox: gomp4.FullBox{
Flags: [3]byte{2, byte(flags >> 8), byte(flags)},
},
TrackID: uint32(trackID),
})
if err != nil {
return nil, 0, err
}
_, err = w.WriteBox(&gomp4.Tfdt{ // <tfdt/>
FullBox: gomp4.FullBox{
Version: 1,
},
// sum of decode durations of all earlier samples
BaseMediaDecodeTimeV1: uint64(durationGoToMp4(audioSamples[0].pts, time.Duration(audioTrack.ClockRate()))),
})
if err != nil {
return nil, 0, err
}
flags = 0
flags |= 0x01 // data offset present
flags |= 0x100 // sample duration present
flags |= 0x200 // sample size present
trun := &gomp4.Trun{ // <trun/>
FullBox: gomp4.FullBox{
Version: 0,
Flags: [3]byte{0, byte(flags >> 8), byte(flags)},
},
SampleCount: uint32(len(audioSamples)),
}
for _, e := range audioSamples {
trun.Entries = append(trun.Entries, gomp4.TrunEntry{
SampleDuration: uint32(durationGoToMp4(e.duration(), time.Duration(audioTrack.ClockRate()))),
SampleSize: uint32(len(e.au)),
})
}
trunOffset, err := w.WriteBox(trun)
if err != nil {
return nil, 0, err
}
err = w.WriteBoxEnd() // </traf>
if err != nil {
return nil, 0, err
}
return trun, trunOffset, nil
}
func mp4PartGenerate(
videoTrack *gortsplib.TrackH264,
audioTrack *gortsplib.TrackMPEG4Audio,
videoSamples []*fmp4VideoSample,
audioSamples []*fmp4AudioSample,
) ([]byte, error) {
/*
moof
- mfhd
- traf (video)
- traf (audio)
mdat
*/
w := mp4.NewWriter()
moofOffset, err := w.WriteBoxStart(&gomp4.Moof{}) // <moof>
if err != nil {
return nil, err
}
_, err = w.WriteBox(&gomp4.Mfhd{ // <mfhd/>
SequenceNumber: 0,
})
if err != nil {
return nil, err
}
trackID := 1
var videoTrun *gomp4.Trun
var videoTrunOffset int
if videoTrack != nil {
var err error
videoTrun, videoTrunOffset, err = mp4PartGenerateVideoTraf(
w, trackID, videoSamples)
if err != nil {
return nil, err
}
trackID++
}
var audioTrun *gomp4.Trun
var audioTrunOffset int
if audioTrack != nil {
var err error
audioTrun, audioTrunOffset, err = mp4PartGenerateAudioTraf(w, trackID, audioTrack, audioSamples)
if err != nil {
return nil, err
}
}
err = w.WriteBoxEnd() // </moof>
if err != nil {
return nil, err
}
mdat := &gomp4.Mdat{} // <mdat/>
dataSize := 0
videoDataSize := 0
if videoTrack != nil {
for _, e := range videoSamples {
dataSize += len(e.avcc)
}
videoDataSize = dataSize
}
if audioTrack != nil {
for _, e := range audioSamples {
dataSize += len(e.au)
}
}
mdat.Data = make([]byte, dataSize)
pos := 0
if videoTrack != nil {
for _, e := range videoSamples {
pos += copy(mdat.Data[pos:], e.avcc)
}
}
if audioTrack != nil {
for _, e := range audioSamples {
pos += copy(mdat.Data[pos:], e.au)
}
}
mdatOffset, err := w.WriteBox(mdat)
if err != nil {
return nil, err
}
if videoTrack != nil {
videoTrun.DataOffset = int32(mdatOffset - moofOffset + 8)
err = w.RewriteBox(videoTrunOffset, videoTrun)
if err != nil {
return nil, err
}
}
if audioTrack != nil && audioTrun != nil {
audioTrun.DataOffset = int32(videoDataSize + mdatOffset - moofOffset + 8)
err = w.RewriteBox(audioTrunOffset, audioTrun)
if err != nil {
return nil, err
}
}
return w.Bytes(), nil
}
func fmp4PartName(id uint64) string {
return "part" + strconv.FormatUint(id, 10)
}
@ -305,8 +22,8 @@ type muxerVariantFMP4Part struct { @@ -305,8 +22,8 @@ type muxerVariantFMP4Part struct {
id uint64
isIndependent bool
videoSamples []*fmp4VideoSample
audioSamples []*fmp4AudioSample
videoSamples []*fmp4.VideoSample
audioSamples []*fmp4.AudioSample
renderedContent []byte
renderedDuration time.Duration
}
@ -341,7 +58,7 @@ func (p *muxerVariantFMP4Part) duration() time.Duration { @@ -341,7 +58,7 @@ func (p *muxerVariantFMP4Part) duration() time.Duration {
if p.videoTrack != nil {
ret := time.Duration(0)
for _, e := range p.videoSamples {
ret += e.duration()
ret += e.Duration()
}
return ret
}
@ -356,7 +73,7 @@ func (p *muxerVariantFMP4Part) duration() time.Duration { @@ -356,7 +73,7 @@ func (p *muxerVariantFMP4Part) duration() time.Duration {
func (p *muxerVariantFMP4Part) finalize() error {
if len(p.videoSamples) > 0 || len(p.audioSamples) > 0 {
var err error
p.renderedContent, err = mp4PartGenerate(
p.renderedContent, err = fmp4.GeneratePart(
p.videoTrack,
p.audioTrack,
p.videoSamples,
@ -374,13 +91,13 @@ func (p *muxerVariantFMP4Part) finalize() error { @@ -374,13 +91,13 @@ func (p *muxerVariantFMP4Part) finalize() error {
return nil
}
func (p *muxerVariantFMP4Part) writeH264(sample *fmp4VideoSample) {
if sample.idrPresent {
func (p *muxerVariantFMP4Part) writeH264(sample *fmp4.VideoSample) {
if sample.IDRPresent {
p.isIndependent = true
}
p.videoSamples = append(p.videoSamples, sample)
}
func (p *muxerVariantFMP4Part) writeAAC(sample *fmp4AudioSample) {
func (p *muxerVariantFMP4Part) writeAAC(sample *fmp4.AudioSample) {
p.audioSamples = append(p.audioSamples, sample)
}

16
internal/hls/muxer_variant_fmp4_segment.go

@ -7,6 +7,8 @@ import ( @@ -7,6 +7,8 @@ import (
"time"
"github.com/aler9/gortsplib"
"github.com/aler9/rtsp-simple-server/internal/hls/fmp4"
)
type partsReader struct {
@ -101,8 +103,8 @@ func (s *muxerVariantFMP4Segment) getRenderedDuration() time.Duration { @@ -101,8 +103,8 @@ func (s *muxerVariantFMP4Segment) getRenderedDuration() time.Duration {
}
func (s *muxerVariantFMP4Segment) finalize(
nextVideoSample *fmp4VideoSample,
nextAudioSample *fmp4AudioSample,
nextVideoSample *fmp4.VideoSample,
nextAudioSample *fmp4.AudioSample,
) error {
err := s.currentPart.finalize()
if err != nil {
@ -117,7 +119,7 @@ func (s *muxerVariantFMP4Segment) finalize( @@ -117,7 +119,7 @@ func (s *muxerVariantFMP4Segment) finalize(
s.currentPart = nil
if s.videoTrack != nil {
s.renderedDuration = nextVideoSample.dts - s.startDTS
s.renderedDuration = nextVideoSample.DTS - s.startDTS
} else {
s.renderedDuration = 0
for _, pa := range s.parts {
@ -128,8 +130,8 @@ func (s *muxerVariantFMP4Segment) finalize( @@ -128,8 +130,8 @@ func (s *muxerVariantFMP4Segment) finalize(
return nil
}
func (s *muxerVariantFMP4Segment) writeH264(sample *fmp4VideoSample, adjustedPartDuration time.Duration) error {
size := uint64(len(sample.avcc))
func (s *muxerVariantFMP4Segment) writeH264(sample *fmp4.VideoSample, adjustedPartDuration time.Duration) error {
size := uint64(len(sample.AVCC))
if (s.size + size) > s.segmentMaxSize {
return fmt.Errorf("reached maximum segment size")
@ -160,8 +162,8 @@ func (s *muxerVariantFMP4Segment) writeH264(sample *fmp4VideoSample, adjustedPar @@ -160,8 +162,8 @@ func (s *muxerVariantFMP4Segment) writeH264(sample *fmp4VideoSample, adjustedPar
return nil
}
func (s *muxerVariantFMP4Segment) writeAAC(sample *fmp4AudioSample, adjustedPartDuration time.Duration) error {
size := uint64(len(sample.au))
func (s *muxerVariantFMP4Segment) writeAAC(sample *fmp4.AudioSample, adjustedPartDuration time.Duration) error {
size := uint64(len(sample.AU))
if (s.size + size) > s.segmentMaxSize {
return fmt.Errorf("reached maximum segment size")

72
internal/hls/muxer_variant_fmp4_segmenter.go

@ -6,6 +6,8 @@ import ( @@ -6,6 +6,8 @@ import (
"github.com/aler9/gortsplib"
"github.com/aler9/gortsplib/pkg/h264"
"github.com/aler9/rtsp-simple-server/internal/hls/fmp4"
)
func partDurationIsCompatible(partDuration time.Duration, sampleDuration time.Duration) bool {
@ -60,8 +62,8 @@ type muxerVariantFMP4Segmenter struct { @@ -60,8 +62,8 @@ type muxerVariantFMP4Segmenter struct {
currentSegment *muxerVariantFMP4Segment
nextSegmentID uint64
nextPartID uint64
nextVideoSample *fmp4VideoSample
nextAudioSample *fmp4AudioSample
nextVideoSample *fmp4.VideoSample
nextAudioSample *fmp4.AudioSample
firstSegmentFinalized bool
sampleDurations map[time.Duration]struct{}
adjustedPartDuration time.Duration
@ -144,18 +146,18 @@ func (m *muxerVariantFMP4Segmenter) writeH264(pts time.Duration, nalus [][]byte) @@ -144,18 +146,18 @@ func (m *muxerVariantFMP4Segmenter) writeH264(pts time.Duration, nalus [][]byte)
return err
}
return m.writeH264Entry(&fmp4VideoSample{
pts: pts,
nalus: nalus,
avcc: avcc,
idrPresent: idrPresent,
return m.writeH264Entry(&fmp4.VideoSample{
PTS: pts,
NALUs: nalus,
AVCC: avcc,
IDRPresent: idrPresent,
})
}
func (m *muxerVariantFMP4Segmenter) writeH264Entry(sample *fmp4VideoSample) error {
func (m *muxerVariantFMP4Segmenter) writeH264Entry(sample *fmp4.VideoSample) error {
if !m.videoFirstIDRReceived {
// skip sample silently until we find one with an IDR
if !sample.idrPresent {
if !sample.IDRPresent {
return nil
}
@ -164,25 +166,25 @@ func (m *muxerVariantFMP4Segmenter) writeH264Entry(sample *fmp4VideoSample) erro @@ -164,25 +166,25 @@ func (m *muxerVariantFMP4Segmenter) writeH264Entry(sample *fmp4VideoSample) erro
m.videoSPS = append([]byte(nil), m.videoTrack.SafeSPS()...)
var err error
sample.dts, err = m.videoDTSExtractor.Extract(sample.nalus, sample.pts)
sample.DTS, err = m.videoDTSExtractor.Extract(sample.NALUs, sample.PTS)
if err != nil {
return err
}
sample.nalus = nil
sample.NALUs = nil
m.startDTS = sample.dts
sample.dts = 0
sample.pts -= m.startDTS
m.startDTS = sample.DTS
sample.DTS = 0
sample.PTS -= m.startDTS
} else {
var err error
sample.dts, err = m.videoDTSExtractor.Extract(sample.nalus, sample.pts)
sample.DTS, err = m.videoDTSExtractor.Extract(sample.NALUs, sample.PTS)
if err != nil {
return err
}
sample.nalus = nil
sample.NALUs = nil
sample.dts -= m.startDTS
sample.pts -= m.startDTS
sample.DTS -= m.startDTS
sample.PTS -= m.startDTS
}
// put samples into a queue in order to
@ -192,7 +194,7 @@ func (m *muxerVariantFMP4Segmenter) writeH264Entry(sample *fmp4VideoSample) erro @@ -192,7 +194,7 @@ func (m *muxerVariantFMP4Segmenter) writeH264Entry(sample *fmp4VideoSample) erro
if sample == nil {
return nil
}
sample.next = m.nextVideoSample
sample.Next = m.nextVideoSample
now := time.Now()
@ -202,7 +204,7 @@ func (m *muxerVariantFMP4Segmenter) writeH264Entry(sample *fmp4VideoSample) erro @@ -202,7 +204,7 @@ func (m *muxerVariantFMP4Segmenter) writeH264Entry(sample *fmp4VideoSample) erro
m.lowLatency,
m.genSegmentID(),
now,
sample.dts,
sample.DTS,
m.segmentMaxSize,
m.videoTrack,
m.audioTrack,
@ -211,7 +213,7 @@ func (m *muxerVariantFMP4Segmenter) writeH264Entry(sample *fmp4VideoSample) erro @@ -211,7 +213,7 @@ func (m *muxerVariantFMP4Segmenter) writeH264Entry(sample *fmp4VideoSample) erro
)
}
m.adjustPartDuration(sample.duration())
m.adjustPartDuration(sample.Duration())
err := m.currentSegment.writeH264(sample, m.adjustedPartDuration)
if err != nil {
@ -219,13 +221,13 @@ func (m *muxerVariantFMP4Segmenter) writeH264Entry(sample *fmp4VideoSample) erro @@ -219,13 +221,13 @@ func (m *muxerVariantFMP4Segmenter) writeH264Entry(sample *fmp4VideoSample) erro
}
// switch segment
if sample.next.idrPresent {
if sample.Next.IDRPresent {
sps := m.videoTrack.SafeSPS()
spsChanged := !bytes.Equal(m.videoSPS, sps)
if (sample.next.dts-m.currentSegment.startDTS) >= m.segmentDuration ||
if (sample.Next.DTS-m.currentSegment.startDTS) >= m.segmentDuration ||
spsChanged {
err := m.currentSegment.finalize(sample.next, nil)
err := m.currentSegment.finalize(sample.Next, nil)
if err != nil {
return err
}
@ -237,7 +239,7 @@ func (m *muxerVariantFMP4Segmenter) writeH264Entry(sample *fmp4VideoSample) erro @@ -237,7 +239,7 @@ func (m *muxerVariantFMP4Segmenter) writeH264Entry(sample *fmp4VideoSample) erro
m.lowLatency,
m.genSegmentID(),
now,
sample.next.dts,
sample.Next.DTS,
m.segmentMaxSize,
m.videoTrack,
m.audioTrack,
@ -258,20 +260,20 @@ func (m *muxerVariantFMP4Segmenter) writeH264Entry(sample *fmp4VideoSample) erro @@ -258,20 +260,20 @@ func (m *muxerVariantFMP4Segmenter) writeH264Entry(sample *fmp4VideoSample) erro
}
func (m *muxerVariantFMP4Segmenter) writeAAC(pts time.Duration, au []byte) error {
return m.writeAACEntry(&fmp4AudioSample{
pts: pts,
au: au,
return m.writeAACEntry(&fmp4.AudioSample{
PTS: pts,
AU: au,
})
}
func (m *muxerVariantFMP4Segmenter) writeAACEntry(sample *fmp4AudioSample) error {
func (m *muxerVariantFMP4Segmenter) writeAACEntry(sample *fmp4.AudioSample) error {
if m.videoTrack != nil {
// wait for the video track
if !m.videoFirstIDRReceived {
return nil
}
sample.pts -= m.startDTS
sample.PTS -= m.startDTS
}
// put samples into a queue in order to
@ -280,7 +282,7 @@ func (m *muxerVariantFMP4Segmenter) writeAACEntry(sample *fmp4AudioSample) error @@ -280,7 +282,7 @@ func (m *muxerVariantFMP4Segmenter) writeAACEntry(sample *fmp4AudioSample) error
if sample == nil {
return nil
}
sample.next = m.nextAudioSample
sample.Next = m.nextAudioSample
now := time.Now()
@ -291,7 +293,7 @@ func (m *muxerVariantFMP4Segmenter) writeAACEntry(sample *fmp4AudioSample) error @@ -291,7 +293,7 @@ func (m *muxerVariantFMP4Segmenter) writeAACEntry(sample *fmp4AudioSample) error
m.lowLatency,
m.genSegmentID(),
now,
sample.pts,
sample.PTS,
m.segmentMaxSize,
m.videoTrack,
m.audioTrack,
@ -313,8 +315,8 @@ func (m *muxerVariantFMP4Segmenter) writeAACEntry(sample *fmp4AudioSample) error @@ -313,8 +315,8 @@ func (m *muxerVariantFMP4Segmenter) writeAACEntry(sample *fmp4AudioSample) error
// switch segment
if m.videoTrack == nil &&
(sample.next.pts-m.currentSegment.startDTS) >= m.segmentDuration {
err := m.currentSegment.finalize(nil, sample.next)
(sample.Next.PTS-m.currentSegment.startDTS) >= m.segmentDuration {
err := m.currentSegment.finalize(nil, sample.Next)
if err != nil {
return err
}
@ -326,7 +328,7 @@ func (m *muxerVariantFMP4Segmenter) writeAACEntry(sample *fmp4AudioSample) error @@ -326,7 +328,7 @@ func (m *muxerVariantFMP4Segmenter) writeAACEntry(sample *fmp4AudioSample) error
m.lowLatency,
m.genSegmentID(),
now,
sample.next.pts,
sample.Next.PTS,
m.segmentMaxSize,
m.videoTrack,
m.audioTrack,

Loading…
Cancel
Save