163 lines
4.7 KiB
Go
163 lines
4.7 KiB
Go
|
package fmp4
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
|
||
|
"github.com/deepch/vdk/av"
|
||
|
"github.com/deepch/vdk/codec/aacparser"
|
||
|
"github.com/deepch/vdk/codec/h264parser"
|
||
|
"github.com/deepch/vdk/codec/opusparser"
|
||
|
"github.com/deepch/vdk/format/fmp4/esio"
|
||
|
"github.com/deepch/vdk/format/fmp4/fmp4io"
|
||
|
)
|
||
|
|
||
|
// Track creates a TRAK atom for this stream
|
||
|
func (f *TrackFragmenter) Track() (*fmp4io.Track, error) {
|
||
|
sample := &fmp4io.SampleTable{
|
||
|
SampleDesc: &fmp4io.SampleDesc{},
|
||
|
TimeToSample: &fmp4io.TimeToSample{},
|
||
|
SampleToChunk: &fmp4io.SampleToChunk{},
|
||
|
SampleSize: &fmp4io.SampleSize{},
|
||
|
ChunkOffset: &fmp4io.ChunkOffset{},
|
||
|
}
|
||
|
switch cd := f.codecData.(type) {
|
||
|
case h264parser.CodecData:
|
||
|
f.timeScale = 90000
|
||
|
cd.RecordInfo.LengthSizeMinusOne = 3
|
||
|
conf := make([]byte, cd.RecordInfo.Len())
|
||
|
cd.RecordInfo.Marshal(conf)
|
||
|
sample.SampleDesc.AVC1Desc = &fmp4io.AVC1Desc{
|
||
|
DataRefIdx: 1,
|
||
|
HorizontalResolution: 72,
|
||
|
VorizontalResolution: 72,
|
||
|
Width: int16(cd.Width()),
|
||
|
Height: int16(cd.Height()),
|
||
|
FrameCount: 1,
|
||
|
Depth: 24,
|
||
|
ColorTableId: -1,
|
||
|
Conf: &fmp4io.AVC1Conf{Data: conf},
|
||
|
}
|
||
|
case aacparser.CodecData:
|
||
|
f.timeScale = 48000
|
||
|
dc, err := esio.DecoderConfigFromCodecData(cd)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("decoding AAC configuration: %w", err)
|
||
|
}
|
||
|
sample.SampleDesc.MP4ADesc = &fmp4io.MP4ADesc{
|
||
|
DataRefIdx: 1,
|
||
|
NumberOfChannels: int16(cd.ChannelLayout().Count()),
|
||
|
SampleSize: 16,
|
||
|
SampleRate: float64(cd.SampleRate()),
|
||
|
Conf: &fmp4io.ElemStreamDesc{
|
||
|
StreamDescriptor: &esio.StreamDescriptor{
|
||
|
ESID: uint16(f.trackID),
|
||
|
DecoderConfig: dc,
|
||
|
SLConfig: &esio.SLConfigDescriptor{Predefined: esio.SLConfigMP4},
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
case *opusparser.CodecData:
|
||
|
f.timeScale = 48000
|
||
|
sample.SampleDesc.OpusDesc = &fmp4io.OpusSampleEntry{
|
||
|
DataRefIdx: 1,
|
||
|
NumberOfChannels: uint16(cd.ChannelLayout().Count()),
|
||
|
SampleSize: 16,
|
||
|
SampleRate: float64(cd.SampleRate()),
|
||
|
Conf: &fmp4io.OpusSpecificConfiguration{
|
||
|
OutputChannelCount: uint8(cd.ChannelLayout().Count()),
|
||
|
PreSkip: 3840, // 80ms
|
||
|
},
|
||
|
}
|
||
|
default:
|
||
|
return nil, fmt.Errorf("mp4: codec type=%v is not supported", f.codecData.Type())
|
||
|
}
|
||
|
trackAtom := &fmp4io.Track{
|
||
|
Header: &fmp4io.TrackHeader{
|
||
|
Flags: 0x0003, // Track enabled | Track in movie
|
||
|
Matrix: [9]int32{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000},
|
||
|
TrackID: f.trackID,
|
||
|
},
|
||
|
Media: &fmp4io.Media{
|
||
|
Header: &fmp4io.MediaHeader{
|
||
|
Language: 21956,
|
||
|
TimeScale: f.timeScale,
|
||
|
},
|
||
|
Info: &fmp4io.MediaInfo{
|
||
|
Sample: sample,
|
||
|
Data: &fmp4io.DataInfo{
|
||
|
Refer: &fmp4io.DataRefer{
|
||
|
Url: &fmp4io.DataReferUrl{
|
||
|
Flags: 0x000001, // Self reference
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
if f.codecData.Type().IsVideo() {
|
||
|
vc := f.codecData.(av.VideoCodecData)
|
||
|
trackAtom.Media.Handler = &fmp4io.HandlerRefer{
|
||
|
Type: fmp4io.VideoHandler,
|
||
|
Name: "VideoHandler",
|
||
|
}
|
||
|
trackAtom.Media.Info.Video = &fmp4io.VideoMediaInfo{
|
||
|
Flags: 0x000001,
|
||
|
}
|
||
|
trackAtom.Header.TrackWidth = float64(vc.Width())
|
||
|
trackAtom.Header.TrackHeight = float64(vc.Height())
|
||
|
} else {
|
||
|
trackAtom.Header.Volume = 1
|
||
|
trackAtom.Header.AlternateGroup = 1
|
||
|
trackAtom.Media.Handler = &fmp4io.HandlerRefer{
|
||
|
Type: fmp4io.SoundHandler,
|
||
|
Name: "SoundHandler",
|
||
|
}
|
||
|
trackAtom.Media.Info.Sound = &fmp4io.SoundMediaInfo{}
|
||
|
}
|
||
|
return trackAtom, nil
|
||
|
}
|
||
|
|
||
|
// MovieHeader marshals an init.mp4 for the given tracks
|
||
|
func MovieHeader(tracks []*fmp4io.Track) ([]byte, error) {
|
||
|
ftyp := fmp4io.FileType{
|
||
|
MajorBrand: 0x69736f36, // iso6
|
||
|
CompatibleBrands: []uint32{
|
||
|
0x69736f35, // iso5
|
||
|
0x6d703431, // mp41
|
||
|
},
|
||
|
}
|
||
|
moov := &fmp4io.Movie{
|
||
|
Header: &fmp4io.MovieHeader{
|
||
|
PreferredRate: 1,
|
||
|
PreferredVolume: 1,
|
||
|
Matrix: [9]int32{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000},
|
||
|
TimeScale: 1000,
|
||
|
},
|
||
|
Tracks: tracks,
|
||
|
MovieExtend: &fmp4io.MovieExtend{},
|
||
|
}
|
||
|
for _, track := range tracks {
|
||
|
if track.Header.TrackID >= moov.Header.NextTrackID {
|
||
|
moov.Header.NextTrackID = track.Header.TrackID + 1
|
||
|
}
|
||
|
moov.MovieExtend.Tracks = append(moov.MovieExtend.Tracks,
|
||
|
&fmp4io.TrackExtend{TrackID: track.Header.TrackID, DefaultSampleDescIdx: 1})
|
||
|
}
|
||
|
// marshal init segment
|
||
|
fhdr := make([]byte, ftyp.Len()+moov.Len())
|
||
|
n := ftyp.Marshal(fhdr)
|
||
|
moov.Marshal(fhdr[n:])
|
||
|
return fhdr, nil
|
||
|
}
|
||
|
|
||
|
// FragmentHeader returns the header needed for the beginning of a MP4 segment file
|
||
|
func FragmentHeader() []byte {
|
||
|
styp := fmp4io.SegmentType{
|
||
|
MajorBrand: 0x6d736468, // msdh
|
||
|
CompatibleBrands: []uint32{0x6d736978}, // msix
|
||
|
}
|
||
|
shdr := make([]byte, styp.Len())
|
||
|
styp.Marshal(shdr)
|
||
|
return shdr
|
||
|
}
|