mirror of
https://github.com/bluenviron/mediamtx
synced 2025-02-18 12:36:56 +00:00
move mp4 writer into dedicated folder
This commit is contained in:
parent
cd93b70612
commit
750743d1ed
@ -1,93 +0,0 @@
|
||||
package hls
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/abema/go-mp4"
|
||||
"github.com/orcaman/writerseeker"
|
||||
)
|
||||
|
||||
type mp4Writer struct {
|
||||
buf *writerseeker.WriterSeeker
|
||||
w *mp4.Writer
|
||||
}
|
||||
|
||||
func newMP4Writer() *mp4Writer {
|
||||
w := &mp4Writer{
|
||||
buf: &writerseeker.WriterSeeker{},
|
||||
}
|
||||
|
||||
w.w = mp4.NewWriter(w.buf)
|
||||
|
||||
return w
|
||||
}
|
||||
|
||||
func (w *mp4Writer) writeBoxStart(box mp4.IImmutableBox) (int, error) {
|
||||
bi := &mp4.BoxInfo{
|
||||
Type: box.GetType(),
|
||||
}
|
||||
var err error
|
||||
bi, err = w.w.StartBox(bi)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
_, err = mp4.Marshal(w.w, box, mp4.Context{})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return int(bi.Offset), nil
|
||||
}
|
||||
|
||||
func (w *mp4Writer) writeBoxEnd() error {
|
||||
_, err := w.w.EndBox()
|
||||
return err
|
||||
}
|
||||
|
||||
func (w *mp4Writer) writeBox(box mp4.IImmutableBox) (int, error) {
|
||||
off, err := w.writeBoxStart(box)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
err = w.writeBoxEnd()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return off, nil
|
||||
}
|
||||
|
||||
func (w *mp4Writer) rewriteBox(off int, box mp4.IImmutableBox) error {
|
||||
prevOff, err := w.w.Seek(0, io.SeekCurrent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.w.Seek(int64(off), io.SeekStart)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.writeBoxStart(box)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = w.writeBoxEnd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.w.Seek(prevOff, io.SeekStart)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *mp4Writer) bytes() []byte {
|
||||
return w.buf.Bytes()
|
||||
}
|
@ -1,26 +1,28 @@
|
||||
package hls
|
||||
|
||||
import (
|
||||
"github.com/abema/go-mp4"
|
||||
gomp4 "github.com/abema/go-mp4"
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/pkg/aac"
|
||||
"github.com/aler9/gortsplib/pkg/h264"
|
||||
|
||||
"github.com/aler9/rtsp-simple-server/internal/mp4"
|
||||
)
|
||||
|
||||
type myEsds struct {
|
||||
mp4.FullBox `mp4:"0,extend"`
|
||||
Data []byte `mp4:"1,size=8"`
|
||||
gomp4.FullBox `mp4:"0,extend"`
|
||||
Data []byte `mp4:"1,size=8"`
|
||||
}
|
||||
|
||||
func (*myEsds) GetType() mp4.BoxType {
|
||||
return mp4.StrToBoxType("esds")
|
||||
func (*myEsds) GetType() gomp4.BoxType {
|
||||
return gomp4.StrToBoxType("esds")
|
||||
}
|
||||
|
||||
func init() { //nolint:gochecknoinits
|
||||
mp4.AddBoxDef(&myEsds{}, 0)
|
||||
gomp4.AddBoxDef(&myEsds{}, 0)
|
||||
}
|
||||
|
||||
func mp4InitGenerateVideoTrack(w *mp4Writer, trackID int, videoTrack *gortsplib.TrackH264) error {
|
||||
func mp4InitGenerateVideoTrack(w *mp4.Writer, trackID int, videoTrack *gortsplib.TrackH264) error {
|
||||
/*
|
||||
trak
|
||||
- tkhd
|
||||
@ -44,7 +46,7 @@ func mp4InitGenerateVideoTrack(w *mp4Writer, trackID int, videoTrack *gortsplib.
|
||||
- stco
|
||||
*/
|
||||
|
||||
_, err := w.writeBoxStart(&mp4.Trak{}) // <trak>
|
||||
_, err := w.WriteBoxStart(&gomp4.Trak{}) // <trak>
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -61,8 +63,8 @@ func mp4InitGenerateVideoTrack(w *mp4Writer, trackID int, videoTrack *gortsplib.
|
||||
width := spsp.Width()
|
||||
height := spsp.Height()
|
||||
|
||||
_, err = w.writeBox(&mp4.Tkhd{ // <tkhd/>
|
||||
FullBox: mp4.FullBox{
|
||||
_, err = w.WriteBox(&gomp4.Tkhd{ // <tkhd/>
|
||||
FullBox: gomp4.FullBox{
|
||||
Flags: [3]byte{0, 0, 3},
|
||||
},
|
||||
TrackID: uint32(trackID),
|
||||
@ -74,12 +76,12 @@ func mp4InitGenerateVideoTrack(w *mp4Writer, trackID int, videoTrack *gortsplib.
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.writeBoxStart(&mp4.Mdia{}) // <mdia>
|
||||
_, err = w.WriteBoxStart(&gomp4.Mdia{}) // <mdia>
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.writeBox(&mp4.Mdhd{ // <mdhd/>
|
||||
_, err = w.WriteBox(&gomp4.Mdhd{ // <mdhd/>
|
||||
Timescale: fmp4VideoTimescale, // the number of time units that pass per second
|
||||
Language: [3]byte{'u', 'n', 'd'},
|
||||
})
|
||||
@ -87,7 +89,7 @@ func mp4InitGenerateVideoTrack(w *mp4Writer, trackID int, videoTrack *gortsplib.
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.writeBox(&mp4.Hdlr{ // <hdlr/>
|
||||
_, err = w.WriteBox(&gomp4.Hdlr{ // <hdlr/>
|
||||
HandlerType: [4]byte{'v', 'i', 'd', 'e'},
|
||||
Name: "VideoHandler",
|
||||
})
|
||||
@ -95,13 +97,13 @@ func mp4InitGenerateVideoTrack(w *mp4Writer, trackID int, videoTrack *gortsplib.
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.writeBoxStart(&mp4.Minf{}) // <minf>
|
||||
_, err = w.WriteBoxStart(&gomp4.Minf{}) // <minf>
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.writeBox(&mp4.Vmhd{ // <vmhd/>
|
||||
FullBox: mp4.FullBox{
|
||||
_, err = w.WriteBox(&gomp4.Vmhd{ // <vmhd/>
|
||||
FullBox: gomp4.FullBox{
|
||||
Flags: [3]byte{0, 0, 1},
|
||||
},
|
||||
})
|
||||
@ -109,20 +111,20 @@ func mp4InitGenerateVideoTrack(w *mp4Writer, trackID int, videoTrack *gortsplib.
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.writeBoxStart(&mp4.Dinf{}) // <dinf>
|
||||
_, err = w.WriteBoxStart(&gomp4.Dinf{}) // <dinf>
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.writeBoxStart(&mp4.Dref{ // <dref>
|
||||
_, err = w.WriteBoxStart(&gomp4.Dref{ // <dref>
|
||||
EntryCount: 1,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.writeBox(&mp4.Url{ // <url/>
|
||||
FullBox: mp4.FullBox{
|
||||
_, err = w.WriteBox(&gomp4.Url{ // <url/>
|
||||
FullBox: gomp4.FullBox{
|
||||
Flags: [3]byte{0, 0, 1},
|
||||
},
|
||||
})
|
||||
@ -130,32 +132,32 @@ func mp4InitGenerateVideoTrack(w *mp4Writer, trackID int, videoTrack *gortsplib.
|
||||
return err
|
||||
}
|
||||
|
||||
err = w.writeBoxEnd() // </dref>
|
||||
err = w.WriteBoxEnd() // </dref>
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = w.writeBoxEnd() // </dinf>
|
||||
err = w.WriteBoxEnd() // </dinf>
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.writeBoxStart(&mp4.Stbl{}) // <stbl>
|
||||
_, err = w.WriteBoxStart(&gomp4.Stbl{}) // <stbl>
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.writeBoxStart(&mp4.Stsd{ // <stsd>
|
||||
_, err = w.WriteBoxStart(&gomp4.Stsd{ // <stsd>
|
||||
EntryCount: 1,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.writeBoxStart(&mp4.VisualSampleEntry{ // <avc1>
|
||||
SampleEntry: mp4.SampleEntry{
|
||||
AnyTypeBox: mp4.AnyTypeBox{
|
||||
Type: mp4.BoxTypeAvc1(),
|
||||
_, err = w.WriteBoxStart(&gomp4.VisualSampleEntry{ // <avc1>
|
||||
SampleEntry: gomp4.SampleEntry{
|
||||
AnyTypeBox: gomp4.AnyTypeBox{
|
||||
Type: gomp4.BoxTypeAvc1(),
|
||||
},
|
||||
DataReferenceIndex: 1,
|
||||
},
|
||||
@ -171,9 +173,9 @@ func mp4InitGenerateVideoTrack(w *mp4Writer, trackID int, videoTrack *gortsplib.
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.writeBox(&mp4.AVCDecoderConfiguration{ // <avcc/>
|
||||
AnyTypeBox: mp4.AnyTypeBox{
|
||||
Type: mp4.BoxTypeAvcC(),
|
||||
_, err = w.WriteBox(&gomp4.AVCDecoderConfiguration{ // <avcc/>
|
||||
AnyTypeBox: gomp4.AnyTypeBox{
|
||||
Type: gomp4.BoxTypeAvcC(),
|
||||
},
|
||||
ConfigurationVersion: 1,
|
||||
Profile: spsp.ProfileIdc,
|
||||
@ -181,14 +183,14 @@ func mp4InitGenerateVideoTrack(w *mp4Writer, trackID int, videoTrack *gortsplib.
|
||||
Level: spsp.LevelIdc,
|
||||
LengthSizeMinusOne: 3,
|
||||
NumOfSequenceParameterSets: 1,
|
||||
SequenceParameterSets: []mp4.AVCParameterSet{
|
||||
SequenceParameterSets: []gomp4.AVCParameterSet{
|
||||
{
|
||||
Length: uint16(len(sps)),
|
||||
NALUnit: sps,
|
||||
},
|
||||
},
|
||||
NumOfPictureParameterSets: 1,
|
||||
PictureParameterSets: []mp4.AVCParameterSet{
|
||||
PictureParameterSets: []gomp4.AVCParameterSet{
|
||||
{
|
||||
Length: uint16(len(pps)),
|
||||
NALUnit: pps,
|
||||
@ -199,7 +201,7 @@ func mp4InitGenerateVideoTrack(w *mp4Writer, trackID int, videoTrack *gortsplib.
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.writeBox(&mp4.Btrt{ // <btrt/>
|
||||
_, err = w.WriteBox(&gomp4.Btrt{ // <btrt/>
|
||||
MaxBitrate: 1000000,
|
||||
AvgBitrate: 1000000,
|
||||
})
|
||||
@ -207,56 +209,56 @@ func mp4InitGenerateVideoTrack(w *mp4Writer, trackID int, videoTrack *gortsplib.
|
||||
return err
|
||||
}
|
||||
|
||||
err = w.writeBoxEnd() // </avc1>
|
||||
err = w.WriteBoxEnd() // </avc1>
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = w.writeBoxEnd() // </stsd>
|
||||
err = w.WriteBoxEnd() // </stsd>
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.writeBox(&mp4.Stts{ // <stts>
|
||||
_, err = w.WriteBox(&gomp4.Stts{ // <stts>
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.writeBox(&mp4.Stsc{ // <stsc>
|
||||
_, err = w.WriteBox(&gomp4.Stsc{ // <stsc>
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.writeBox(&mp4.Stsz{ // <stsz>
|
||||
_, err = w.WriteBox(&gomp4.Stsz{ // <stsz>
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.writeBox(&mp4.Stco{ // <stco>
|
||||
_, err = w.WriteBox(&gomp4.Stco{ // <stco>
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = w.writeBoxEnd() // </stbl>
|
||||
err = w.WriteBoxEnd() // </stbl>
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = w.writeBoxEnd() // </minf>
|
||||
err = w.WriteBoxEnd() // </minf>
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = w.writeBoxEnd() // </mdia>
|
||||
err = w.WriteBoxEnd() // </mdia>
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = w.writeBoxEnd() // </trak>
|
||||
err = w.WriteBoxEnd() // </trak>
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -264,7 +266,7 @@ func mp4InitGenerateVideoTrack(w *mp4Writer, trackID int, videoTrack *gortsplib.
|
||||
return nil
|
||||
}
|
||||
|
||||
func mp4InitGenerateAudioTrack(w *mp4Writer, trackID int, audioTrack *gortsplib.TrackAAC) error {
|
||||
func mp4InitGenerateAudioTrack(w *mp4.Writer, trackID int, audioTrack *gortsplib.TrackAAC) error {
|
||||
/*
|
||||
trak
|
||||
- tkhd
|
||||
@ -287,13 +289,13 @@ func mp4InitGenerateAudioTrack(w *mp4Writer, trackID int, audioTrack *gortsplib.
|
||||
- stco
|
||||
*/
|
||||
|
||||
_, err := w.writeBoxStart(&mp4.Trak{}) // <trak>
|
||||
_, err := w.WriteBoxStart(&gomp4.Trak{}) // <trak>
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.writeBox(&mp4.Tkhd{ // <tkhd/>
|
||||
FullBox: mp4.FullBox{
|
||||
_, err = w.WriteBox(&gomp4.Tkhd{ // <tkhd/>
|
||||
FullBox: gomp4.FullBox{
|
||||
Flags: [3]byte{0, 0, 3},
|
||||
},
|
||||
TrackID: uint32(trackID),
|
||||
@ -305,12 +307,12 @@ func mp4InitGenerateAudioTrack(w *mp4Writer, trackID int, audioTrack *gortsplib.
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.writeBoxStart(&mp4.Mdia{}) // <mdia>
|
||||
_, err = w.WriteBoxStart(&gomp4.Mdia{}) // <mdia>
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.writeBox(&mp4.Mdhd{ // <mdhd/>
|
||||
_, err = w.WriteBox(&gomp4.Mdhd{ // <mdhd/>
|
||||
Timescale: uint32(audioTrack.ClockRate()),
|
||||
Language: [3]byte{'u', 'n', 'd'},
|
||||
})
|
||||
@ -318,7 +320,7 @@ func mp4InitGenerateAudioTrack(w *mp4Writer, trackID int, audioTrack *gortsplib.
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.writeBox(&mp4.Hdlr{ // <hdlr/>
|
||||
_, err = w.WriteBox(&gomp4.Hdlr{ // <hdlr/>
|
||||
HandlerType: [4]byte{'s', 'o', 'u', 'n'},
|
||||
Name: "SoundHandler",
|
||||
})
|
||||
@ -326,31 +328,31 @@ func mp4InitGenerateAudioTrack(w *mp4Writer, trackID int, audioTrack *gortsplib.
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.writeBoxStart(&mp4.Minf{}) // <minf>
|
||||
_, err = w.WriteBoxStart(&gomp4.Minf{}) // <minf>
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.writeBox(&mp4.Smhd{ // <smhd/>
|
||||
_, err = w.WriteBox(&gomp4.Smhd{ // <smhd/>
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.writeBoxStart(&mp4.Dinf{}) // <dinf>
|
||||
_, err = w.WriteBoxStart(&gomp4.Dinf{}) // <dinf>
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.writeBoxStart(&mp4.Dref{ // <dref>
|
||||
_, err = w.WriteBoxStart(&gomp4.Dref{ // <dref>
|
||||
EntryCount: 1,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.writeBox(&mp4.Url{ // <url/>
|
||||
FullBox: mp4.FullBox{
|
||||
_, err = w.WriteBox(&gomp4.Url{ // <url/>
|
||||
FullBox: gomp4.FullBox{
|
||||
Flags: [3]byte{0, 0, 1},
|
||||
},
|
||||
})
|
||||
@ -358,32 +360,32 @@ func mp4InitGenerateAudioTrack(w *mp4Writer, trackID int, audioTrack *gortsplib.
|
||||
return err
|
||||
}
|
||||
|
||||
err = w.writeBoxEnd() // </dref>
|
||||
err = w.WriteBoxEnd() // </dref>
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = w.writeBoxEnd() // </dinf>
|
||||
err = w.WriteBoxEnd() // </dinf>
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.writeBoxStart(&mp4.Stbl{}) // <stbl>
|
||||
_, err = w.WriteBoxStart(&gomp4.Stbl{}) // <stbl>
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.writeBoxStart(&mp4.Stsd{ // <stsd>
|
||||
_, err = w.WriteBoxStart(&gomp4.Stsd{ // <stsd>
|
||||
EntryCount: 1,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.writeBoxStart(&mp4.AudioSampleEntry{ // <mp4a>
|
||||
SampleEntry: mp4.SampleEntry{
|
||||
AnyTypeBox: mp4.AnyTypeBox{
|
||||
Type: mp4.BoxTypeMp4a(),
|
||||
_, err = w.WriteBoxStart(&gomp4.AudioSampleEntry{ // <mp4a>
|
||||
SampleEntry: gomp4.SampleEntry{
|
||||
AnyTypeBox: gomp4.AnyTypeBox{
|
||||
Type: gomp4.BoxTypeMp4a(),
|
||||
},
|
||||
DataReferenceIndex: 1,
|
||||
},
|
||||
@ -406,14 +408,14 @@ func mp4InitGenerateAudioTrack(w *mp4Writer, trackID int, audioTrack *gortsplib.
|
||||
decSpecificInfoTagSize := uint8(len(conf))
|
||||
decSpecificInfoTag := append(
|
||||
[]byte{
|
||||
mp4.DecSpecificInfoTag,
|
||||
gomp4.DecSpecificInfoTag,
|
||||
0x80, 0x80, 0x80, decSpecificInfoTagSize, // size
|
||||
},
|
||||
conf...,
|
||||
)
|
||||
|
||||
esDescrTag := []byte{
|
||||
mp4.ESDescrTag,
|
||||
gomp4.ESDescrTag,
|
||||
0x80, 0x80, 0x80, 32 + decSpecificInfoTagSize, // size
|
||||
0x00,
|
||||
byte(trackID), // ES_ID
|
||||
@ -421,7 +423,7 @@ func mp4InitGenerateAudioTrack(w *mp4Writer, trackID int, audioTrack *gortsplib.
|
||||
}
|
||||
|
||||
decoderConfigDescrTag := []byte{
|
||||
mp4.DecoderConfigDescrTag,
|
||||
gomp4.DecoderConfigDescrTag,
|
||||
0x80, 0x80, 0x80, 18 + decSpecificInfoTagSize, // size
|
||||
0x40, // object type indicator (MPEG-4 Audio)
|
||||
0x15, 0x00,
|
||||
@ -431,7 +433,7 @@ func mp4InitGenerateAudioTrack(w *mp4Writer, trackID int, audioTrack *gortsplib.
|
||||
}
|
||||
|
||||
slConfigDescrTag := []byte{
|
||||
mp4.SLConfigDescrTag,
|
||||
gomp4.SLConfigDescrTag,
|
||||
0x80, 0x80, 0x80, 0x01, // size (1)
|
||||
0x02,
|
||||
}
|
||||
@ -444,14 +446,14 @@ func mp4InitGenerateAudioTrack(w *mp4Writer, trackID int, audioTrack *gortsplib.
|
||||
pos += copy(data[pos:], decSpecificInfoTag)
|
||||
copy(data[pos:], slConfigDescrTag)
|
||||
|
||||
_, err = w.writeBox(&myEsds{ // <esds/>
|
||||
_, err = w.WriteBox(&myEsds{ // <esds/>
|
||||
Data: data,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.writeBox(&mp4.Btrt{ // <btrt/>
|
||||
_, err = w.WriteBox(&gomp4.Btrt{ // <btrt/>
|
||||
MaxBitrate: 128825,
|
||||
AvgBitrate: 128825,
|
||||
})
|
||||
@ -459,56 +461,56 @@ func mp4InitGenerateAudioTrack(w *mp4Writer, trackID int, audioTrack *gortsplib.
|
||||
return err
|
||||
}
|
||||
|
||||
err = w.writeBoxEnd() // </mp4a>
|
||||
err = w.WriteBoxEnd() // </mp4a>
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = w.writeBoxEnd() // </stsd>
|
||||
err = w.WriteBoxEnd() // </stsd>
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.writeBox(&mp4.Stts{ // <stts>
|
||||
_, err = w.WriteBox(&gomp4.Stts{ // <stts>
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.writeBox(&mp4.Stsc{ // <stsc>
|
||||
_, err = w.WriteBox(&gomp4.Stsc{ // <stsc>
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.writeBox(&mp4.Stsz{ // <stsz>
|
||||
_, err = w.WriteBox(&gomp4.Stsz{ // <stsz>
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.writeBox(&mp4.Stco{ // <stco>
|
||||
_, err = w.WriteBox(&gomp4.Stco{ // <stco>
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = w.writeBoxEnd() // </stbl>
|
||||
err = w.WriteBoxEnd() // </stbl>
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = w.writeBoxEnd() // </minf>
|
||||
err = w.WriteBoxEnd() // </minf>
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = w.writeBoxEnd() // </mdia>
|
||||
err = w.WriteBoxEnd() // </mdia>
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = w.writeBoxEnd() // </trak>
|
||||
err = w.WriteBoxEnd() // </trak>
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -528,12 +530,12 @@ func mp4InitGenerate(videoTrack *gortsplib.TrackH264, audioTrack *gortsplib.Trac
|
||||
- trex (audio)
|
||||
*/
|
||||
|
||||
w := newMP4Writer()
|
||||
w := mp4.NewWriter()
|
||||
|
||||
_, err := w.writeBox(&mp4.Ftyp{ // <ftyp/>
|
||||
_, err := w.WriteBox(&gomp4.Ftyp{ // <ftyp/>
|
||||
MajorBrand: [4]byte{'m', 'p', '4', '2'},
|
||||
MinorVersion: 1,
|
||||
CompatibleBrands: []mp4.CompatibleBrandElem{
|
||||
CompatibleBrands: []gomp4.CompatibleBrandElem{
|
||||
{CompatibleBrand: [4]byte{'m', 'p', '4', '1'}},
|
||||
{CompatibleBrand: [4]byte{'m', 'p', '4', '2'}},
|
||||
{CompatibleBrand: [4]byte{'i', 's', 'o', 'm'}},
|
||||
@ -544,12 +546,12 @@ func mp4InitGenerate(videoTrack *gortsplib.TrackH264, audioTrack *gortsplib.Trac
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = w.writeBoxStart(&mp4.Moov{}) // <moov>
|
||||
_, err = w.WriteBoxStart(&gomp4.Moov{}) // <moov>
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = w.writeBox(&mp4.Mvhd{ // <mvhd/>
|
||||
_, err = w.WriteBox(&gomp4.Mvhd{ // <mvhd/>
|
||||
Timescale: 1000,
|
||||
Rate: 65536,
|
||||
Volume: 256,
|
||||
@ -578,7 +580,7 @@ func mp4InitGenerate(videoTrack *gortsplib.TrackH264, audioTrack *gortsplib.Trac
|
||||
}
|
||||
}
|
||||
|
||||
_, err = w.writeBoxStart(&mp4.Mvex{}) // <mvex>
|
||||
_, err = w.WriteBoxStart(&gomp4.Mvex{}) // <mvex>
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -586,7 +588,7 @@ func mp4InitGenerate(videoTrack *gortsplib.TrackH264, audioTrack *gortsplib.Trac
|
||||
trackID = 1
|
||||
|
||||
if videoTrack != nil {
|
||||
_, err = w.writeBox(&mp4.Trex{ // <trex/>
|
||||
_, err = w.WriteBox(&gomp4.Trex{ // <trex/>
|
||||
TrackID: uint32(trackID),
|
||||
DefaultSampleDescriptionIndex: 1,
|
||||
})
|
||||
@ -598,7 +600,7 @@ func mp4InitGenerate(videoTrack *gortsplib.TrackH264, audioTrack *gortsplib.Trac
|
||||
}
|
||||
|
||||
if audioTrack != nil {
|
||||
_, err = w.writeBox(&mp4.Trex{ // <trex/>
|
||||
_, err = w.WriteBox(&gomp4.Trex{ // <trex/>
|
||||
TrackID: uint32(trackID),
|
||||
DefaultSampleDescriptionIndex: 1,
|
||||
})
|
||||
@ -607,15 +609,15 @@ func mp4InitGenerate(videoTrack *gortsplib.TrackH264, audioTrack *gortsplib.Trac
|
||||
}
|
||||
}
|
||||
|
||||
err = w.writeBoxEnd() // </mvex>
|
||||
err = w.WriteBoxEnd() // </mvex>
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = w.writeBoxEnd() // </moov>
|
||||
err = w.WriteBoxEnd() // </moov>
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return w.bytes(), nil
|
||||
return w.Bytes(), nil
|
||||
}
|
||||
|
@ -7,9 +7,11 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/abema/go-mp4"
|
||||
gomp4 "github.com/abema/go-mp4"
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/pkg/aac"
|
||||
|
||||
"github.com/aler9/rtsp-simple-server/internal/mp4"
|
||||
)
|
||||
|
||||
func durationGoToMp4(v time.Duration, timescale time.Duration) int64 {
|
||||
@ -17,10 +19,10 @@ func durationGoToMp4(v time.Duration, timescale time.Duration) int64 {
|
||||
}
|
||||
|
||||
func mp4PartGenerateVideoTraf(
|
||||
w *mp4Writer,
|
||||
w *mp4.Writer,
|
||||
trackID int,
|
||||
videoSamples []*fmp4VideoSample,
|
||||
) (*mp4.Trun, int, error) {
|
||||
) (*gomp4.Trun, int, error) {
|
||||
/*
|
||||
traf
|
||||
- tfhd
|
||||
@ -28,15 +30,15 @@ func mp4PartGenerateVideoTraf(
|
||||
- trun
|
||||
*/
|
||||
|
||||
_, err := w.writeBoxStart(&mp4.Traf{}) // <traf>
|
||||
_, err := w.WriteBoxStart(&gomp4.Traf{}) // <traf>
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
flags := 0
|
||||
|
||||
_, err = w.writeBox(&mp4.Tfhd{ // <tfhd/>
|
||||
FullBox: mp4.FullBox{
|
||||
_, err = w.WriteBox(&gomp4.Tfhd{ // <tfhd/>
|
||||
FullBox: gomp4.FullBox{
|
||||
Flags: [3]byte{2, byte(flags >> 8), byte(flags)},
|
||||
},
|
||||
TrackID: uint32(trackID),
|
||||
@ -45,8 +47,8 @@ func mp4PartGenerateVideoTraf(
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
_, err = w.writeBox(&mp4.Tfdt{ // <tfdt/>
|
||||
FullBox: mp4.FullBox{
|
||||
_, err = w.WriteBox(&gomp4.Tfdt{ // <tfdt/>
|
||||
FullBox: gomp4.FullBox{
|
||||
Version: 1,
|
||||
},
|
||||
// sum of decode durations of all earlier samples
|
||||
@ -63,8 +65,8 @@ func mp4PartGenerateVideoTraf(
|
||||
flags |= 0x400 // sample flags present
|
||||
flags |= 0x800 // sample composition time offset present or v1
|
||||
|
||||
trun := &mp4.Trun{ // <trun/>
|
||||
FullBox: mp4.FullBox{
|
||||
trun := &gomp4.Trun{ // <trun/>
|
||||
FullBox: gomp4.FullBox{
|
||||
Version: 1,
|
||||
Flags: [3]byte{0, byte(flags >> 8), byte(flags)},
|
||||
},
|
||||
@ -79,7 +81,7 @@ func mp4PartGenerateVideoTraf(
|
||||
flags |= 1 << 16 // sample_is_non_sync_sample
|
||||
}
|
||||
|
||||
trun.Entries = append(trun.Entries, mp4.TrunEntry{
|
||||
trun.Entries = append(trun.Entries, gomp4.TrunEntry{
|
||||
SampleDuration: uint32(durationGoToMp4(e.duration(), fmp4VideoTimescale)),
|
||||
SampleSize: uint32(len(e.avcc)),
|
||||
SampleFlags: flags,
|
||||
@ -87,12 +89,12 @@ func mp4PartGenerateVideoTraf(
|
||||
})
|
||||
}
|
||||
|
||||
trunOffset, err := w.writeBox(trun)
|
||||
trunOffset, err := w.WriteBox(trun)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
err = w.writeBoxEnd() // </traf>
|
||||
err = w.WriteBoxEnd() // </traf>
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
@ -101,11 +103,11 @@ func mp4PartGenerateVideoTraf(
|
||||
}
|
||||
|
||||
func mp4PartGenerateAudioTraf(
|
||||
w *mp4Writer,
|
||||
w *mp4.Writer,
|
||||
trackID int,
|
||||
audioTrack *gortsplib.TrackAAC,
|
||||
audioSamples []*fmp4AudioSample,
|
||||
) (*mp4.Trun, int, error) {
|
||||
) (*gomp4.Trun, int, error) {
|
||||
/*
|
||||
traf
|
||||
- tfhd
|
||||
@ -117,15 +119,15 @@ func mp4PartGenerateAudioTraf(
|
||||
return nil, 0, nil
|
||||
}
|
||||
|
||||
_, err := w.writeBoxStart(&mp4.Traf{}) // <traf>
|
||||
_, err := w.WriteBoxStart(&gomp4.Traf{}) // <traf>
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
flags := 0
|
||||
|
||||
_, err = w.writeBox(&mp4.Tfhd{ // <tfhd/>
|
||||
FullBox: mp4.FullBox{
|
||||
_, err = w.WriteBox(&gomp4.Tfhd{ // <tfhd/>
|
||||
FullBox: gomp4.FullBox{
|
||||
Flags: [3]byte{2, byte(flags >> 8), byte(flags)},
|
||||
},
|
||||
TrackID: uint32(trackID),
|
||||
@ -134,8 +136,8 @@ func mp4PartGenerateAudioTraf(
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
_, err = w.writeBox(&mp4.Tfdt{ // <tfdt/>
|
||||
FullBox: mp4.FullBox{
|
||||
_, err = w.WriteBox(&gomp4.Tfdt{ // <tfdt/>
|
||||
FullBox: gomp4.FullBox{
|
||||
Version: 1,
|
||||
},
|
||||
// sum of decode durations of all earlier samples
|
||||
@ -150,8 +152,8 @@ func mp4PartGenerateAudioTraf(
|
||||
flags |= 0x100 // sample duration present
|
||||
flags |= 0x200 // sample size present
|
||||
|
||||
trun := &mp4.Trun{ // <trun/>
|
||||
FullBox: mp4.FullBox{
|
||||
trun := &gomp4.Trun{ // <trun/>
|
||||
FullBox: gomp4.FullBox{
|
||||
Version: 0,
|
||||
Flags: [3]byte{0, byte(flags >> 8), byte(flags)},
|
||||
},
|
||||
@ -159,18 +161,18 @@ func mp4PartGenerateAudioTraf(
|
||||
}
|
||||
|
||||
for _, e := range audioSamples {
|
||||
trun.Entries = append(trun.Entries, mp4.TrunEntry{
|
||||
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)
|
||||
trunOffset, err := w.WriteBox(trun)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
err = w.writeBoxEnd() // </traf>
|
||||
err = w.WriteBoxEnd() // </traf>
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
@ -192,14 +194,14 @@ func mp4PartGenerate(
|
||||
mdat
|
||||
*/
|
||||
|
||||
w := newMP4Writer()
|
||||
w := mp4.NewWriter()
|
||||
|
||||
moofOffset, err := w.writeBoxStart(&mp4.Moof{}) // <moof>
|
||||
moofOffset, err := w.WriteBoxStart(&gomp4.Moof{}) // <moof>
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = w.writeBox(&mp4.Mfhd{ // <mfhd/>
|
||||
_, err = w.WriteBox(&gomp4.Mfhd{ // <mfhd/>
|
||||
SequenceNumber: 0,
|
||||
})
|
||||
if err != nil {
|
||||
@ -208,7 +210,7 @@ func mp4PartGenerate(
|
||||
|
||||
trackID := 1
|
||||
|
||||
var videoTrun *mp4.Trun
|
||||
var videoTrun *gomp4.Trun
|
||||
var videoTrunOffset int
|
||||
if videoTrack != nil {
|
||||
var err error
|
||||
@ -221,7 +223,7 @@ func mp4PartGenerate(
|
||||
trackID++
|
||||
}
|
||||
|
||||
var audioTrun *mp4.Trun
|
||||
var audioTrun *gomp4.Trun
|
||||
var audioTrunOffset int
|
||||
if audioTrack != nil {
|
||||
var err error
|
||||
@ -231,12 +233,12 @@ func mp4PartGenerate(
|
||||
}
|
||||
}
|
||||
|
||||
err = w.writeBoxEnd() // </moof>
|
||||
err = w.WriteBoxEnd() // </moof>
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mdat := &mp4.Mdat{} // <mdat/>
|
||||
mdat := &gomp4.Mdat{} // <mdat/>
|
||||
|
||||
dataSize := 0
|
||||
videoDataSize := 0
|
||||
@ -269,14 +271,14 @@ func mp4PartGenerate(
|
||||
}
|
||||
}
|
||||
|
||||
mdatOffset, err := w.writeBox(mdat)
|
||||
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)
|
||||
err = w.RewriteBox(videoTrunOffset, videoTrun)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -284,13 +286,13 @@ func mp4PartGenerate(
|
||||
|
||||
if audioTrack != nil && audioTrun != nil {
|
||||
audioTrun.DataOffset = int32(videoDataSize + mdatOffset - moofOffset + 8)
|
||||
err = w.rewriteBox(audioTrunOffset, audioTrun)
|
||||
err = w.RewriteBox(audioTrunOffset, audioTrun)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return w.bytes(), nil
|
||||
return w.Bytes(), nil
|
||||
}
|
||||
|
||||
func fmp4PartName(id uint64) string {
|
||||
|
100
internal/mp4/writer.go
Normal file
100
internal/mp4/writer.go
Normal file
@ -0,0 +1,100 @@
|
||||
package mp4
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
gomp4 "github.com/abema/go-mp4"
|
||||
"github.com/orcaman/writerseeker"
|
||||
)
|
||||
|
||||
// Writer is a MP4 writer.
|
||||
type Writer struct {
|
||||
buf *writerseeker.WriterSeeker
|
||||
w *gomp4.Writer
|
||||
}
|
||||
|
||||
// NewWriter allocates a Writer.
|
||||
func NewWriter() *Writer {
|
||||
w := &Writer{
|
||||
buf: &writerseeker.WriterSeeker{},
|
||||
}
|
||||
|
||||
w.w = gomp4.NewWriter(w.buf)
|
||||
|
||||
return w
|
||||
}
|
||||
|
||||
// WriteBoxStart writes a box start.
|
||||
func (w *Writer) WriteBoxStart(box gomp4.IImmutableBox) (int, error) {
|
||||
bi := &gomp4.BoxInfo{
|
||||
Type: box.GetType(),
|
||||
}
|
||||
var err error
|
||||
bi, err = w.w.StartBox(bi)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
_, err = gomp4.Marshal(w.w, box, gomp4.Context{})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return int(bi.Offset), nil
|
||||
}
|
||||
|
||||
// WriteBoxEnd writes a box end.
|
||||
func (w *Writer) WriteBoxEnd() error {
|
||||
_, err := w.w.EndBox()
|
||||
return err
|
||||
}
|
||||
|
||||
// WriteBox writes a self-closing box.
|
||||
func (w *Writer) WriteBox(box gomp4.IImmutableBox) (int, error) {
|
||||
off, err := w.WriteBoxStart(box)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
err = w.WriteBoxEnd()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return off, nil
|
||||
}
|
||||
|
||||
// RewriteBox rewrites a box.
|
||||
func (w *Writer) RewriteBox(off int, box gomp4.IImmutableBox) error {
|
||||
prevOff, err := w.w.Seek(0, io.SeekCurrent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.w.Seek(int64(off), io.SeekStart)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.WriteBoxStart(box)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = w.WriteBoxEnd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.w.Seek(prevOff, io.SeekStart)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Bytes returns the MP4 content.
|
||||
func (w *Writer) Bytes() []byte {
|
||||
return w.buf.Bytes()
|
||||
}
|
Loading…
Reference in New Issue
Block a user