1
0
mirror of https://github.com/bluenviron/mediamtx synced 2025-03-11 06:47:58 +00:00
mediamtx/internal/hls/fmp4/init.go
2022-09-17 21:19:45 +02:00

617 lines
11 KiB
Go

// Package fmp4 contains a fMP4 writer.
package fmp4
import (
gomp4 "github.com/abema/go-mp4"
"github.com/aler9/gortsplib"
"github.com/aler9/gortsplib/pkg/h264"
)
type myEsds struct {
gomp4.FullBox `mp4:"0,extend"`
Data []byte `mp4:"1,size=8"`
}
func (*myEsds) GetType() gomp4.BoxType {
return gomp4.StrToBoxType("esds")
}
func init() { //nolint:gochecknoinits
gomp4.AddBoxDef(&myEsds{}, 0)
}
func generateInitVideoTrack(w *mp4Writer, trackID int, videoTrack *gortsplib.TrackH264) error {
/*
trak
- tkhd
- mdia
- mdhd
- hdlr
- minf
- vmhd
- dinf
- dref
- url
- stbl
- stsd
- avc1
- avcC
- pasp
- btrt
- stts
- stsc
- stsz
- stco
*/
_, err := w.WriteBoxStart(&gomp4.Trak{}) // <trak>
if err != nil {
return err
}
sps := videoTrack.SafeSPS()
pps := videoTrack.SafePPS()
var spsp h264.SPS
err = spsp.Unmarshal(sps)
if err != nil {
return err
}
width := spsp.Width()
height := spsp.Height()
_, err = w.WriteBox(&gomp4.Tkhd{ // <tkhd/>
FullBox: gomp4.FullBox{
Flags: [3]byte{0, 0, 3},
},
TrackID: uint32(trackID),
Width: uint32(width * 65536),
Height: uint32(height * 65536),
Matrix: [9]int32{0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000},
})
if err != nil {
return err
}
_, err = w.WriteBoxStart(&gomp4.Mdia{}) // <mdia>
if err != nil {
return err
}
_, err = w.WriteBox(&gomp4.Mdhd{ // <mdhd/>
Timescale: videoTimescale, // the number of time units that pass per second
Language: [3]byte{'u', 'n', 'd'},
})
if err != nil {
return err
}
_, err = w.WriteBox(&gomp4.Hdlr{ // <hdlr/>
HandlerType: [4]byte{'v', 'i', 'd', 'e'},
Name: "VideoHandler",
})
if err != nil {
return err
}
_, err = w.WriteBoxStart(&gomp4.Minf{}) // <minf>
if err != nil {
return err
}
_, err = w.WriteBox(&gomp4.Vmhd{ // <vmhd/>
FullBox: gomp4.FullBox{
Flags: [3]byte{0, 0, 1},
},
})
if err != nil {
return err
}
_, err = w.WriteBoxStart(&gomp4.Dinf{}) // <dinf>
if err != nil {
return err
}
_, err = w.WriteBoxStart(&gomp4.Dref{ // <dref>
EntryCount: 1,
})
if err != nil {
return err
}
_, err = w.WriteBox(&gomp4.Url{ // <url/>
FullBox: gomp4.FullBox{
Flags: [3]byte{0, 0, 1},
},
})
if err != nil {
return err
}
err = w.WriteBoxEnd() // </dref>
if err != nil {
return err
}
err = w.WriteBoxEnd() // </dinf>
if err != nil {
return err
}
_, err = w.WriteBoxStart(&gomp4.Stbl{}) // <stbl>
if err != nil {
return err
}
_, err = w.WriteBoxStart(&gomp4.Stsd{ // <stsd>
EntryCount: 1,
})
if err != nil {
return err
}
_, err = w.WriteBoxStart(&gomp4.VisualSampleEntry{ // <avc1>
SampleEntry: gomp4.SampleEntry{
AnyTypeBox: gomp4.AnyTypeBox{
Type: gomp4.BoxTypeAvc1(),
},
DataReferenceIndex: 1,
},
Width: uint16(width),
Height: uint16(height),
Horizresolution: 4718592,
Vertresolution: 4718592,
FrameCount: 1,
Depth: 24,
PreDefined3: -1,
})
if err != nil {
return err
}
_, err = w.WriteBox(&gomp4.AVCDecoderConfiguration{ // <avcc/>
AnyTypeBox: gomp4.AnyTypeBox{
Type: gomp4.BoxTypeAvcC(),
},
ConfigurationVersion: 1,
Profile: spsp.ProfileIdc,
ProfileCompatibility: sps[2],
Level: spsp.LevelIdc,
LengthSizeMinusOne: 3,
NumOfSequenceParameterSets: 1,
SequenceParameterSets: []gomp4.AVCParameterSet{
{
Length: uint16(len(sps)),
NALUnit: sps,
},
},
NumOfPictureParameterSets: 1,
PictureParameterSets: []gomp4.AVCParameterSet{
{
Length: uint16(len(pps)),
NALUnit: pps,
},
},
})
if err != nil {
return err
}
_, err = w.WriteBox(&gomp4.Btrt{ // <btrt/>
MaxBitrate: 1000000,
AvgBitrate: 1000000,
})
if err != nil {
return err
}
err = w.WriteBoxEnd() // </avc1>
if err != nil {
return err
}
err = w.WriteBoxEnd() // </stsd>
if err != nil {
return err
}
_, err = w.WriteBox(&gomp4.Stts{ // <stts>
})
if err != nil {
return err
}
_, err = w.WriteBox(&gomp4.Stsc{ // <stsc>
})
if err != nil {
return err
}
_, err = w.WriteBox(&gomp4.Stsz{ // <stsz>
})
if err != nil {
return err
}
_, err = w.WriteBox(&gomp4.Stco{ // <stco>
})
if err != nil {
return err
}
err = w.WriteBoxEnd() // </stbl>
if err != nil {
return err
}
err = w.WriteBoxEnd() // </minf>
if err != nil {
return err
}
err = w.WriteBoxEnd() // </mdia>
if err != nil {
return err
}
err = w.WriteBoxEnd() // </trak>
if err != nil {
return err
}
return nil
}
func generateInitAudioTrack(w *mp4Writer, trackID int, audioTrack *gortsplib.TrackMPEG4Audio) error {
/*
trak
- tkhd
- mdia
- mdhd
- hdlr
- minf
- smhd
- dinf
- dref
- url
- stbl
- stsd
- mp4a
- esds
- btrt
- stts
- stsc
- stsz
- stco
*/
_, err := w.WriteBoxStart(&gomp4.Trak{}) // <trak>
if err != nil {
return err
}
_, err = w.WriteBox(&gomp4.Tkhd{ // <tkhd/>
FullBox: gomp4.FullBox{
Flags: [3]byte{0, 0, 3},
},
TrackID: uint32(trackID),
AlternateGroup: 1,
Volume: 256,
Matrix: [9]int32{0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000},
})
if err != nil {
return err
}
_, err = w.WriteBoxStart(&gomp4.Mdia{}) // <mdia>
if err != nil {
return err
}
_, err = w.WriteBox(&gomp4.Mdhd{ // <mdhd/>
Timescale: uint32(audioTrack.ClockRate()),
Language: [3]byte{'u', 'n', 'd'},
})
if err != nil {
return err
}
_, err = w.WriteBox(&gomp4.Hdlr{ // <hdlr/>
HandlerType: [4]byte{'s', 'o', 'u', 'n'},
Name: "SoundHandler",
})
if err != nil {
return err
}
_, err = w.WriteBoxStart(&gomp4.Minf{}) // <minf>
if err != nil {
return err
}
_, err = w.WriteBox(&gomp4.Smhd{ // <smhd/>
})
if err != nil {
return err
}
_, err = w.WriteBoxStart(&gomp4.Dinf{}) // <dinf>
if err != nil {
return err
}
_, err = w.WriteBoxStart(&gomp4.Dref{ // <dref>
EntryCount: 1,
})
if err != nil {
return err
}
_, err = w.WriteBox(&gomp4.Url{ // <url/>
FullBox: gomp4.FullBox{
Flags: [3]byte{0, 0, 1},
},
})
if err != nil {
return err
}
err = w.WriteBoxEnd() // </dref>
if err != nil {
return err
}
err = w.WriteBoxEnd() // </dinf>
if err != nil {
return err
}
_, err = w.WriteBoxStart(&gomp4.Stbl{}) // <stbl>
if err != nil {
return err
}
_, err = w.WriteBoxStart(&gomp4.Stsd{ // <stsd>
EntryCount: 1,
})
if err != nil {
return err
}
_, err = w.WriteBoxStart(&gomp4.AudioSampleEntry{ // <mp4a>
SampleEntry: gomp4.SampleEntry{
AnyTypeBox: gomp4.AnyTypeBox{
Type: gomp4.BoxTypeMp4a(),
},
DataReferenceIndex: 1,
},
ChannelCount: uint16(audioTrack.Config.ChannelCount),
SampleSize: 16,
SampleRate: uint32(audioTrack.ClockRate() * 65536),
})
if err != nil {
return err
}
enc, _ := audioTrack.Config.Marshal()
decSpecificInfoTagSize := uint8(len(enc))
decSpecificInfoTag := append(
[]byte{
gomp4.DecSpecificInfoTag,
0x80, 0x80, 0x80, decSpecificInfoTagSize, // size
},
enc...,
)
esDescrTag := []byte{
gomp4.ESDescrTag,
0x80, 0x80, 0x80, 32 + decSpecificInfoTagSize, // size
0x00,
byte(trackID), // ES_ID
0x00,
}
decoderConfigDescrTag := []byte{
gomp4.DecoderConfigDescrTag,
0x80, 0x80, 0x80, 18 + decSpecificInfoTagSize, // size
0x40, // object type indicator (MPEG-4 Audio)
0x15, 0x00,
0x00, 0x00, 0x00, 0x01,
0xf7, 0x39, 0x00, 0x01,
0xf7, 0x39,
}
slConfigDescrTag := []byte{
gomp4.SLConfigDescrTag,
0x80, 0x80, 0x80, 0x01, // size (1)
0x02,
}
data := make([]byte, len(esDescrTag)+len(decoderConfigDescrTag)+len(decSpecificInfoTag)+len(slConfigDescrTag))
pos := 0
pos += copy(data[pos:], esDescrTag)
pos += copy(data[pos:], decoderConfigDescrTag)
pos += copy(data[pos:], decSpecificInfoTag)
copy(data[pos:], slConfigDescrTag)
_, err = w.WriteBox(&myEsds{ // <esds/>
Data: data,
})
if err != nil {
return err
}
_, err = w.WriteBox(&gomp4.Btrt{ // <btrt/>
MaxBitrate: 128825,
AvgBitrate: 128825,
})
if err != nil {
return err
}
err = w.WriteBoxEnd() // </mp4a>
if err != nil {
return err
}
err = w.WriteBoxEnd() // </stsd>
if err != nil {
return err
}
_, err = w.WriteBox(&gomp4.Stts{ // <stts>
})
if err != nil {
return err
}
_, err = w.WriteBox(&gomp4.Stsc{ // <stsc>
})
if err != nil {
return err
}
_, err = w.WriteBox(&gomp4.Stsz{ // <stsz>
})
if err != nil {
return err
}
_, err = w.WriteBox(&gomp4.Stco{ // <stco>
})
if err != nil {
return err
}
err = w.WriteBoxEnd() // </stbl>
if err != nil {
return err
}
err = w.WriteBoxEnd() // </minf>
if err != nil {
return err
}
err = w.WriteBoxEnd() // </mdia>
if err != nil {
return err
}
err = w.WriteBoxEnd() // </trak>
if err != nil {
return err
}
return nil
}
// GenerateInit generates a FMP4 initialization file.
func GenerateInit(videoTrack *gortsplib.TrackH264, audioTrack *gortsplib.TrackMPEG4Audio) ([]byte, error) {
/*
- ftyp
- moov
- mvhd
- trak (video)
- trak (audio)
- mvex
- trex (video)
- trex (audio)
*/
w := newMP4Writer()
_, err := w.WriteBox(&gomp4.Ftyp{ // <ftyp/>
MajorBrand: [4]byte{'m', 'p', '4', '2'},
MinorVersion: 1,
CompatibleBrands: []gomp4.CompatibleBrandElem{
{CompatibleBrand: [4]byte{'m', 'p', '4', '1'}},
{CompatibleBrand: [4]byte{'m', 'p', '4', '2'}},
{CompatibleBrand: [4]byte{'i', 's', 'o', 'm'}},
{CompatibleBrand: [4]byte{'h', 'l', 's', 'f'}},
},
})
if err != nil {
return nil, err
}
_, err = w.WriteBoxStart(&gomp4.Moov{}) // <moov>
if err != nil {
return nil, err
}
_, err = w.WriteBox(&gomp4.Mvhd{ // <mvhd/>
Timescale: 1000,
Rate: 65536,
Volume: 256,
Matrix: [9]int32{0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000},
NextTrackID: 2,
})
if err != nil {
return nil, err
}
trackID := 1
if videoTrack != nil {
err := generateInitVideoTrack(w, trackID, videoTrack)
if err != nil {
return nil, err
}
trackID++
}
if audioTrack != nil {
err := generateInitAudioTrack(w, trackID, audioTrack)
if err != nil {
return nil, err
}
}
_, err = w.WriteBoxStart(&gomp4.Mvex{}) // <mvex>
if err != nil {
return nil, err
}
trackID = 1
if videoTrack != nil {
_, err = w.WriteBox(&gomp4.Trex{ // <trex/>
TrackID: uint32(trackID),
DefaultSampleDescriptionIndex: 1,
})
if err != nil {
return nil, err
}
trackID++
}
if audioTrack != nil {
_, err = w.WriteBox(&gomp4.Trex{ // <trex/>
TrackID: uint32(trackID),
DefaultSampleDescriptionIndex: 1,
})
if err != nil {
return nil, err
}
}
err = w.WriteBoxEnd() // </mvex>
if err != nil {
return nil, err
}
err = w.WriteBoxEnd() // </moov>
if err != nil {
return nil, err
}
return w.Bytes(), nil
}