179 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			179 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package fmp4
 | |
| 
 | |
| import (
 | |
| 	"git.r-2.top/kunmeng/vdk/av"
 | |
| 	"git.r-2.top/kunmeng/vdk/format/fmp4/fmp4io"
 | |
| 	"git.r-2.top/kunmeng/vdk/format/fmp4/fragment"
 | |
| 	"git.r-2.top/kunmeng/vdk/format/fmp4/timescale"
 | |
| 	"git.r-2.top/kunmeng/vdk/utils/bits/pio"
 | |
| )
 | |
| 
 | |
| type fragmentWithData struct {
 | |
| 	trackFrag   *fmp4io.TrackFrag
 | |
| 	packets     []av.Packet
 | |
| 	independent bool
 | |
| }
 | |
| 
 | |
| func (f *TrackFragmenter) makeFragment() fragmentWithData {
 | |
| 	if len(f.pending) < 2 {
 | |
| 		return fragmentWithData{}
 | |
| 	}
 | |
| 	entryCount := len(f.pending) - 1
 | |
| 	// timescale for first packet
 | |
| 	startTime := f.pending[0].Time
 | |
| 	startDTS := timescale.ToScale(startTime, f.timeScale)
 | |
| 	// build fragment metadata
 | |
| 	defaultFlags := fmp4io.SampleNoDependencies
 | |
| 	if f.codecData.Type().IsVideo() {
 | |
| 		defaultFlags = fmp4io.SampleNonKeyframe
 | |
| 	}
 | |
| 	track := &fmp4io.TrackFrag{
 | |
| 		Header: &fmp4io.TrackFragHeader{
 | |
| 			Flags:   fmp4io.TrackFragDefaultBaseIsMOOF,
 | |
| 			TrackID: f.trackID,
 | |
| 		},
 | |
| 		DecodeTime: &fmp4io.TrackFragDecodeTime{
 | |
| 			Version: 1,
 | |
| 			Time:    startDTS,
 | |
| 		},
 | |
| 		Run: &fmp4io.TrackFragRun{
 | |
| 			Flags:   fmp4io.TrackRunDataOffset,
 | |
| 			Entries: make([]fmp4io.TrackFragRunEntry, entryCount),
 | |
| 		},
 | |
| 	}
 | |
| 	// add samples to the fragment run
 | |
| 	curDTS := startDTS
 | |
| 	for i, pkt := range f.pending[:entryCount] {
 | |
| 		// calculate the absolute DTS of the next sample and use the difference as the duration
 | |
| 		nextTime := f.pending[i+1].Time
 | |
| 		nextDTS := timescale.ToScale(nextTime, f.timeScale)
 | |
| 		entry := fmp4io.TrackFragRunEntry{
 | |
| 			Duration: uint32(nextDTS - curDTS),
 | |
| 			Flags:    defaultFlags,
 | |
| 			Size:     uint32(len(pkt.Data)),
 | |
| 		}
 | |
| 		if pkt.IsKeyFrame {
 | |
| 			entry.Flags = fmp4io.SampleNoDependencies
 | |
| 		}
 | |
| 		if i == 0 {
 | |
| 			// Optimistically use the first sample's fields as defaults.
 | |
| 			// If a later sample has different values, then the default will be cleared and per-sample values will be used for that field.
 | |
| 			track.Header.DefaultDuration = entry.Duration
 | |
| 			track.Header.DefaultSize = entry.Size
 | |
| 			track.Header.DefaultFlags = entry.Flags
 | |
| 			track.Run.FirstSampleFlags = entry.Flags
 | |
| 		} else {
 | |
| 			if entry.Duration != track.Header.DefaultDuration {
 | |
| 				track.Header.DefaultDuration = 0
 | |
| 			}
 | |
| 			if entry.Size != track.Header.DefaultSize {
 | |
| 				track.Header.DefaultSize = 0
 | |
| 			}
 | |
| 			// The first sample's flags can be specified separately if all other samples have the same flags.
 | |
| 			// Thus the default flags come from the second sample.
 | |
| 			if i == 1 {
 | |
| 				track.Header.DefaultFlags = entry.Flags
 | |
| 			} else if entry.Flags != track.Header.DefaultFlags {
 | |
| 				track.Header.DefaultFlags = 0
 | |
| 			}
 | |
| 		}
 | |
| 		if pkt.CompositionTime != 0 {
 | |
| 			// add composition time to entries in this run
 | |
| 			track.Run.Flags |= fmp4io.TrackRunSampleCTS
 | |
| 			relCTS := timescale.Relative(pkt.CompositionTime, f.timeScale)
 | |
| 			if relCTS < 0 {
 | |
| 				// negative composition time needs version 1
 | |
| 				track.Run.Version = 1
 | |
| 			}
 | |
| 			entry.CTS = relCTS
 | |
| 		}
 | |
| 		// log.Printf("%3d %d -> %d = %d  %s -> %s = %s  comp %s %d", f.trackID, curDTS, nextDTS, nextDTS-curDTS, pkt.Time, nextTime, nextTime-pkt.Time, pkt.CompositionTime, entry.CTS)
 | |
| 		curDTS = nextDTS
 | |
| 		track.Run.Entries[i] = entry
 | |
| 	}
 | |
| 	if track.Header.DefaultSize != 0 {
 | |
| 		// all samples are the same size
 | |
| 		track.Header.Flags |= fmp4io.TrackFragDefaultSize
 | |
| 	} else {
 | |
| 		track.Run.Flags |= fmp4io.TrackRunSampleSize
 | |
| 	}
 | |
| 	if track.Header.DefaultDuration != 0 {
 | |
| 		// all samples are the same duration
 | |
| 		track.Header.Flags |= fmp4io.TrackFragDefaultDuration
 | |
| 	} else {
 | |
| 		track.Run.Flags |= fmp4io.TrackRunSampleDuration
 | |
| 	}
 | |
| 	if track.Header.DefaultFlags != 0 {
 | |
| 		// all samples are the same duration
 | |
| 		track.Header.Flags |= fmp4io.TrackFragDefaultFlags
 | |
| 		if track.Run.FirstSampleFlags != track.Header.DefaultFlags {
 | |
| 			// except the first one
 | |
| 			track.Run.Flags |= fmp4io.TrackRunFirstSampleFlags
 | |
| 		}
 | |
| 	} else {
 | |
| 		track.Run.Flags |= fmp4io.TrackRunSampleFlags
 | |
| 	}
 | |
| 	d := fragmentWithData{
 | |
| 		trackFrag:   track,
 | |
| 		packets:     f.pending[:entryCount],
 | |
| 		independent: track.Run.FirstSampleFlags&fmp4io.SampleNoDependencies != 0,
 | |
| 	}
 | |
| 	f.pending = []av.Packet{f.pending[entryCount]}
 | |
| 	return d
 | |
| }
 | |
| 
 | |
| func marshalFragment(tracks []fragmentWithData, seqNum uint32, initial bool) fragment.Fragment {
 | |
| 	// fill out fragment header
 | |
| 	moof := &fmp4io.MovieFrag{
 | |
| 		Header: &fmp4io.MovieFragHeader{
 | |
| 			Seqnum: seqNum,
 | |
| 		},
 | |
| 		Tracks: make([]*fmp4io.TrackFrag, len(tracks)),
 | |
| 	}
 | |
| 	independent := true
 | |
| 	for i, track := range tracks {
 | |
| 		moof.Tracks[i] = track.trackFrag
 | |
| 		if !track.independent {
 | |
| 			independent = false
 | |
| 		}
 | |
| 	}
 | |
| 	// calculate track data offsets relative to the start of the MOOF
 | |
| 	dataBase := moof.Len() + 8 // MOOF plus the MDAT header
 | |
| 	dataOffset := dataBase
 | |
| 	for i, track := range tracks {
 | |
| 		moof.Tracks[i].Run.DataOffset = uint32(dataOffset)
 | |
| 		for _, pkt := range track.packets {
 | |
| 			dataOffset += len(pkt.Data)
 | |
| 		}
 | |
| 	}
 | |
| 	// marshal MOOF and MDAT header
 | |
| 	var shdrSize int
 | |
| 	if initial {
 | |
| 		shdrOnce.Do(func() {
 | |
| 			shdr = FragmentHeader()
 | |
| 		})
 | |
| 		shdrSize = len(shdr)
 | |
| 	}
 | |
| 	b := make([]byte, shdrSize+dataBase, shdrSize+dataOffset)
 | |
| 	var n int
 | |
| 	if initial {
 | |
| 		copy(b, shdr)
 | |
| 		n = len(shdr)
 | |
| 	}
 | |
| 	n += moof.Marshal(b[n:])
 | |
| 	pio.PutU32BE(b[n:], uint32(dataOffset-dataBase+8))
 | |
| 	pio.PutU32BE(b[n+4:], uint32(fmp4io.MDAT))
 | |
| 	// write MDAT contents
 | |
| 	for i, track := range tracks {
 | |
| 		moof.Tracks[i].Run.DataOffset = uint32(dataOffset)
 | |
| 		for _, pkt := range track.packets {
 | |
| 			b = append(b, pkt.Data...)
 | |
| 		}
 | |
| 	}
 | |
| 	return fragment.Fragment{
 | |
| 		Bytes:       b,
 | |
| 		Length:      len(b),
 | |
| 		Independent: independent,
 | |
| 	}
 | |
| }
 | 
