package hls 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 { 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 mp4InitGenerateVideoTrack(w *mp4.Writer, 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{}) // 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{ // 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{}) // if err != nil { return err } _, err = w.WriteBox(&gomp4.Mdhd{ // Timescale: fmp4VideoTimescale, // 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{ // HandlerType: [4]byte{'v', 'i', 'd', 'e'}, Name: "VideoHandler", }) if err != nil { return err } _, err = w.WriteBoxStart(&gomp4.Minf{}) // if err != nil { return err } _, err = w.WriteBox(&gomp4.Vmhd{ // FullBox: gomp4.FullBox{ Flags: [3]byte{0, 0, 1}, }, }) if err != nil { return err } _, err = w.WriteBoxStart(&gomp4.Dinf{}) // if err != nil { return err } _, err = w.WriteBoxStart(&gomp4.Dref{ // EntryCount: 1, }) if err != nil { return err } _, err = w.WriteBox(&gomp4.Url{ // FullBox: gomp4.FullBox{ Flags: [3]byte{0, 0, 1}, }, }) if err != nil { return err } err = w.WriteBoxEnd() // if err != nil { return err } err = w.WriteBoxEnd() // if err != nil { return err } _, err = w.WriteBoxStart(&gomp4.Stbl{}) // if err != nil { return err } _, err = w.WriteBoxStart(&gomp4.Stsd{ // EntryCount: 1, }) if err != nil { return err } _, err = w.WriteBoxStart(&gomp4.VisualSampleEntry{ // 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{ // 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{ // MaxBitrate: 1000000, AvgBitrate: 1000000, }) if err != nil { return err } err = w.WriteBoxEnd() // if err != nil { return err } err = w.WriteBoxEnd() // if err != nil { return err } _, err = w.WriteBox(&gomp4.Stts{ // }) if err != nil { return err } _, err = w.WriteBox(&gomp4.Stsc{ // }) if err != nil { return err } _, err = w.WriteBox(&gomp4.Stsz{ // }) if err != nil { return err } _, err = w.WriteBox(&gomp4.Stco{ // }) if err != nil { return err } err = w.WriteBoxEnd() // if err != nil { return err } err = w.WriteBoxEnd() // if err != nil { return err } err = w.WriteBoxEnd() // if err != nil { return err } err = w.WriteBoxEnd() // if err != nil { return err } return nil } func mp4InitGenerateAudioTrack(w *mp4.Writer, trackID int, audioTrack *gortsplib.TrackAAC) 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{}) // if err != nil { return err } _, err = w.WriteBox(&gomp4.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{}) // if err != nil { return err } _, err = w.WriteBox(&gomp4.Mdhd{ // Timescale: uint32(audioTrack.ClockRate()), Language: [3]byte{'u', 'n', 'd'}, }) if err != nil { return err } _, err = w.WriteBox(&gomp4.Hdlr{ // HandlerType: [4]byte{'s', 'o', 'u', 'n'}, Name: "SoundHandler", }) if err != nil { return err } _, err = w.WriteBoxStart(&gomp4.Minf{}) // if err != nil { return err } _, err = w.WriteBox(&gomp4.Smhd{ // }) if err != nil { return err } _, err = w.WriteBoxStart(&gomp4.Dinf{}) // if err != nil { return err } _, err = w.WriteBoxStart(&gomp4.Dref{ // EntryCount: 1, }) if err != nil { return err } _, err = w.WriteBox(&gomp4.Url{ // FullBox: gomp4.FullBox{ Flags: [3]byte{0, 0, 1}, }, }) if err != nil { return err } err = w.WriteBoxEnd() // if err != nil { return err } err = w.WriteBoxEnd() // if err != nil { return err } _, err = w.WriteBoxStart(&gomp4.Stbl{}) // if err != nil { return err } _, err = w.WriteBoxStart(&gomp4.Stsd{ // EntryCount: 1, }) if err != nil { return err } _, err = w.WriteBoxStart(&gomp4.AudioSampleEntry{ // 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{ // Data: data, }) if err != nil { return err } _, err = w.WriteBox(&gomp4.Btrt{ // MaxBitrate: 128825, AvgBitrate: 128825, }) if err != nil { return err } err = w.WriteBoxEnd() // if err != nil { return err } err = w.WriteBoxEnd() // if err != nil { return err } _, err = w.WriteBox(&gomp4.Stts{ // }) if err != nil { return err } _, err = w.WriteBox(&gomp4.Stsc{ // }) if err != nil { return err } _, err = w.WriteBox(&gomp4.Stsz{ // }) if err != nil { return err } _, err = w.WriteBox(&gomp4.Stco{ // }) if err != nil { return err } err = w.WriteBoxEnd() // if err != nil { return err } err = w.WriteBoxEnd() // if err != nil { return err } err = w.WriteBoxEnd() // if err != nil { return err } err = w.WriteBoxEnd() // if err != nil { return err } return nil } func mp4InitGenerate(videoTrack *gortsplib.TrackH264, audioTrack *gortsplib.TrackAAC) ([]byte, error) { /* - ftyp - moov - mvhd - trak (video) - trak (audio) - mvex - trex (video) - trex (audio) */ w := mp4.NewWriter() _, err := w.WriteBox(&gomp4.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{}) // if err != nil { return nil, err } _, err = w.WriteBox(&gomp4.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 := mp4InitGenerateVideoTrack(w, trackID, videoTrack) if err != nil { return nil, err } trackID++ } if audioTrack != nil { err := mp4InitGenerateAudioTrack(w, trackID, audioTrack) if err != nil { return nil, err } } _, err = w.WriteBoxStart(&gomp4.Mvex{}) // if err != nil { return nil, err } trackID = 1 if videoTrack != nil { _, err = w.WriteBox(&gomp4.Trex{ // TrackID: uint32(trackID), DefaultSampleDescriptionIndex: 1, }) if err != nil { return nil, err } trackID++ } if audioTrack != nil { _, err = w.WriteBox(&gomp4.Trex{ // TrackID: uint32(trackID), DefaultSampleDescriptionIndex: 1, }) if err != nil { return nil, err } } err = w.WriteBoxEnd() // if err != nil { return nil, err } err = w.WriteBoxEnd() // if err != nil { return nil, err } return w.Bytes(), nil }