package fmp4 import ( gomp4 "github.com/abema/go-mp4" "github.com/aler9/gortsplib/v2/pkg/format" "github.com/aler9/gortsplib/v2/pkg/h264" ) // InitTrack is a track of Init. type InitTrack struct { ID int TimeScale uint32 Format format.Format } func (track *InitTrack) marshal(w *mp4Writer) error { /* trak - tkhd - mdia - mdhd - hdlr - minf - vmhd (video only) - smhd (audio only) - dinf - dref - url - stbl - stsd - avc1 (h264 only) - avcC - pasp - btrt - mp4a (mpeg4audio only) - esds - btrt - stts - stsc - stsz - stco */ _, err := w.writeBoxStart(&gomp4.Trak{}) // if err != nil { return err } var sps []byte var pps []byte var spsp h264.SPS var width int var height int switch ttrack := track.Format.(type) { case *format.H264: sps = ttrack.SafeSPS() pps = ttrack.SafePPS() 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(track.ID), 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 } case *format.MPEG4Audio: _, err = w.WriteBox(&gomp4.Tkhd{ // FullBox: gomp4.FullBox{ Flags: [3]byte{0, 0, 3}, }, TrackID: uint32(track.ID), 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: track.TimeScale, Language: [3]byte{'u', 'n', 'd'}, }) if err != nil { return err } switch track.Format.(type) { case *format.H264: _, err = w.WriteBox(&gomp4.Hdlr{ // HandlerType: [4]byte{'v', 'i', 'd', 'e'}, Name: "VideoHandler", }) if err != nil { return err } case *format.MPEG4Audio: _, 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 } switch track.Format.(type) { case *format.H264: _, err = w.WriteBox(&gomp4.Vmhd{ // FullBox: gomp4.FullBox{ Flags: [3]byte{0, 0, 1}, }, }) if err != nil { return err } case *format.MPEG4Audio: _, 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 } switch ttrack := track.Format.(type) { case *format.H264: _, 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 } case *format.MPEG4Audio: _, err = w.writeBoxStart(&gomp4.AudioSampleEntry{ // SampleEntry: gomp4.SampleEntry{ AnyTypeBox: gomp4.AnyTypeBox{ Type: gomp4.BoxTypeMp4a(), }, DataReferenceIndex: 1, }, ChannelCount: uint16(ttrack.Config.ChannelCount), SampleSize: 16, SampleRate: uint32(ttrack.ClockRate() * 65536), }) if err != nil { return err } enc, _ := ttrack.Config.Marshal() _, err = w.WriteBox(&gomp4.Esds{ // FullBox: gomp4.FullBox{ Version: 0, Flags: [3]byte{0x00, 0x00, 0x00}, }, Descriptors: []gomp4.Descriptor{ { Tag: gomp4.ESDescrTag, Size: 32 + uint32(len(enc)), ESDescriptor: &gomp4.ESDescriptor{ ESID: uint16(track.ID), }, }, { Tag: gomp4.DecoderConfigDescrTag, Size: 18 + uint32(len(enc)), DecoderConfigDescriptor: &gomp4.DecoderConfigDescriptor{ ObjectTypeIndication: 0x40, StreamType: 0x05, UpStream: false, Reserved: true, MaxBitrate: 128825, AvgBitrate: 128825, }, }, { Tag: gomp4.DecSpecificInfoTag, Size: uint32(len(enc)), Data: enc, }, { Tag: gomp4.SLConfigDescrTag, Size: 1, Data: []byte{0x02}, }, }, }) 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 }