101 lines
		
	
	
		
			2.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			101 lines
		
	
	
		
			2.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package fmp4
 | |
| 
 | |
| import (
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"sync"
 | |
| 	"time"
 | |
| 
 | |
| 	"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"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	shdrOnce sync.Once
 | |
| 	shdr     []byte
 | |
| )
 | |
| 
 | |
| // MovieFragmenter breaks a stream into segments each containing both tracks from the original stream
 | |
| type MovieFragmenter struct {
 | |
| 	tracks []*TrackFragmenter
 | |
| 	fhdr   []byte
 | |
| 	vidx   int
 | |
| 	seqNum uint32
 | |
| 	shdrw  bool
 | |
| }
 | |
| 
 | |
| // NewMovie creates a movie fragmenter from a stream
 | |
| func NewMovie(streams []av.CodecData) (*MovieFragmenter, error) {
 | |
| 	f := &MovieFragmenter{
 | |
| 		tracks: make([]*TrackFragmenter, len(streams)),
 | |
| 		vidx:   -1,
 | |
| 	}
 | |
| 	atoms := make([]*fmp4io.Track, len(streams))
 | |
| 	var err error
 | |
| 	for i, cd := range streams {
 | |
| 		f.tracks[i], err = NewTrack(cd)
 | |
| 		if err != nil {
 | |
| 			return nil, fmt.Errorf("track %d: %w", i, err)
 | |
| 		}
 | |
| 		atoms[i] = f.tracks[i].atom
 | |
| 		if cd.Type().IsVideo() {
 | |
| 			f.vidx = i
 | |
| 		}
 | |
| 	}
 | |
| 	if f.vidx < 0 {
 | |
| 		return nil, errors.New("no video track found")
 | |
| 	}
 | |
| 	f.fhdr, err = MovieHeader(atoms)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return f, err
 | |
| }
 | |
| 
 | |
| // Fragment produces a fragment out of the currently-queued packets.
 | |
| func (f *MovieFragmenter) Fragment() (fragment.Fragment, error) {
 | |
| 	dur := f.tracks[f.vidx].Duration()
 | |
| 	var tracks []fragmentWithData
 | |
| 	for _, track := range f.tracks {
 | |
| 		tf := track.makeFragment()
 | |
| 		if tf.trackFrag != nil {
 | |
| 			tracks = append(tracks, tf)
 | |
| 		}
 | |
| 	}
 | |
| 	if len(tracks) == 0 {
 | |
| 		return fragment.Fragment{}, nil
 | |
| 	}
 | |
| 	f.seqNum++
 | |
| 	initial := !f.shdrw
 | |
| 	f.shdrw = true
 | |
| 	frag := marshalFragment(tracks, f.seqNum, initial)
 | |
| 	frag.Duration = dur
 | |
| 	return frag, nil
 | |
| }
 | |
| 
 | |
| // WritePacket formats and queues a packet for the next fragment to be written
 | |
| func (f *MovieFragmenter) WritePacket(pkt av.Packet) error {
 | |
| 	return f.tracks[pkt.Idx].WritePacket(pkt)
 | |
| }
 | |
| 
 | |
| // Duration calculates the elapsed duration between the first and last pending video frame
 | |
| func (f *MovieFragmenter) Duration() time.Duration {
 | |
| 	return f.tracks[f.vidx].Duration()
 | |
| }
 | |
| 
 | |
| // MovieHeader marshals an init.mp4 for the fragmenter's tracks
 | |
| func (f *MovieFragmenter) MovieHeader() (filename, contentType string, blob []byte) {
 | |
| 	return "init.mp4", "video/mp4", f.fhdr
 | |
| }
 | |
| 
 | |
| // NewSegment indicates that a new segment has begun and  the next call to
 | |
| // Fragment() should include a leading FTYP header
 | |
| func (f *MovieFragmenter) NewSegment() {
 | |
| 	f.shdrw = false
 | |
| }
 | |
| 
 | |
| func (f *MovieFragmenter) TimeScale() uint32 {
 | |
| 	return 90000
 | |
| }
 | 
