github.com/bluenviron/mediacommon@v1.9.3/pkg/formats/fmp4/parts.go (about)

     1  package fmp4
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  
     8  	"github.com/abema/go-mp4"
     9  )
    10  
    11  // Parts is a sequence of fMP4 parts.
    12  type Parts []*Part
    13  
    14  // Unmarshal decodes one or more fMP4 parts.
    15  func (ps *Parts) Unmarshal(byts []byte) error {
    16  	type readState int
    17  
    18  	const (
    19  		waitingMoof readState = iota
    20  		waitingMfhd
    21  		waitingTraf
    22  		waitingTfdtTfhdTrun
    23  	)
    24  
    25  	state := waitingMoof
    26  	var curPart *Part
    27  	var moofOffset uint64
    28  	var curTrack *PartTrack
    29  	var tfdt *mp4.Tfdt
    30  	var tfhd *mp4.Tfhd
    31  
    32  	_, err := mp4.ReadBoxStructure(bytes.NewReader(byts), func(h *mp4.ReadHandle) (interface{}, error) {
    33  		if h.BoxInfo.IsSupportedType() {
    34  			switch h.BoxInfo.Type.String() {
    35  			case "moof":
    36  				if state != waitingMoof {
    37  					return nil, fmt.Errorf("unexpected moof")
    38  				}
    39  
    40  				curPart = &Part{}
    41  				*ps = append(*ps, curPart)
    42  				moofOffset = h.BoxInfo.Offset
    43  				state = waitingMfhd
    44  				return h.Expand()
    45  
    46  			case "mfhd":
    47  				if state != waitingMfhd {
    48  					return nil, fmt.Errorf("unexpected mfhd")
    49  				}
    50  
    51  				box, _, err := h.ReadPayload()
    52  				if err != nil {
    53  					return nil, err
    54  				}
    55  				mfhd := box.(*mp4.Mfhd)
    56  
    57  				curPart.SequenceNumber = mfhd.SequenceNumber
    58  				state = waitingTraf
    59  
    60  			case "traf":
    61  				if state != waitingTraf && state != waitingTfdtTfhdTrun {
    62  					return nil, fmt.Errorf("unexpected traf")
    63  				}
    64  
    65  				if curTrack != nil {
    66  					if tfdt == nil || tfhd == nil || curTrack.Samples == nil {
    67  						return nil, fmt.Errorf("parse error")
    68  					}
    69  				}
    70  
    71  				curTrack = &PartTrack{}
    72  				curPart.Tracks = append(curPart.Tracks, curTrack)
    73  				tfdt = nil
    74  				tfhd = nil
    75  				state = waitingTfdtTfhdTrun
    76  				return h.Expand()
    77  
    78  			case "tfhd":
    79  				if state != waitingTfdtTfhdTrun || tfhd != nil {
    80  					return nil, fmt.Errorf("unexpected tfhd")
    81  				}
    82  
    83  				box, _, err := h.ReadPayload()
    84  				if err != nil {
    85  					return nil, err
    86  				}
    87  				tfhd = box.(*mp4.Tfhd)
    88  
    89  				curTrack.ID = int(tfhd.TrackID)
    90  
    91  			case "tfdt":
    92  				if state != waitingTfdtTfhdTrun || tfdt != nil {
    93  					return nil, fmt.Errorf("unexpected tfdt")
    94  				}
    95  
    96  				box, _, err := h.ReadPayload()
    97  				if err != nil {
    98  					return nil, err
    99  				}
   100  				tfdt = box.(*mp4.Tfdt)
   101  
   102  				if tfdt.FullBox.Version != 1 {
   103  					return nil, fmt.Errorf("unsupported tfdt version")
   104  				}
   105  
   106  				curTrack.BaseTime = tfdt.BaseMediaDecodeTimeV1
   107  
   108  			case "trun":
   109  				if state != waitingTfdtTfhdTrun || tfhd == nil {
   110  					return nil, fmt.Errorf("unexpected trun")
   111  				}
   112  
   113  				box, _, err := h.ReadPayload()
   114  				if err != nil {
   115  					return nil, err
   116  				}
   117  				trun := box.(*mp4.Trun)
   118  
   119  				trunFlags := uint16(trun.Flags[1])<<8 | uint16(trun.Flags[2])
   120  				if (trunFlags & trunFlagDataOffsetPreset) == 0 {
   121  					return nil, fmt.Errorf("unsupported flags")
   122  				}
   123  
   124  				existing := len(curTrack.Samples)
   125  				tmp := make([]*PartSample, existing+len(trun.Entries))
   126  				copy(tmp, curTrack.Samples)
   127  				curTrack.Samples = tmp
   128  
   129  				pos := uint64(trun.DataOffset) + moofOffset
   130  				if uint64(len(byts)) < pos {
   131  					return nil, fmt.Errorf("invalid data_offset / moof_offset")
   132  				}
   133  
   134  				ptr := byts[pos:]
   135  
   136  				for i, e := range trun.Entries {
   137  					s := &PartSample{}
   138  
   139  					if (trunFlags & trunFlagSampleDurationPresent) != 0 {
   140  						s.Duration = e.SampleDuration
   141  					} else {
   142  						s.Duration = tfhd.DefaultSampleDuration
   143  					}
   144  
   145  					s.PTSOffset = e.SampleCompositionTimeOffsetV1
   146  
   147  					var sampleFlags uint32
   148  					if (trunFlags & trunFlagSampleFlagsPresent) != 0 {
   149  						sampleFlags = e.SampleFlags
   150  					} else {
   151  						sampleFlags = tfhd.DefaultSampleFlags
   152  					}
   153  					s.IsNonSyncSample = ((sampleFlags & sampleFlagIsNonSyncSample) != 0)
   154  
   155  					var size uint32
   156  					if (trunFlags & trunFlagSampleSizePresent) != 0 {
   157  						size = e.SampleSize
   158  					} else {
   159  						size = tfhd.DefaultSampleSize
   160  					}
   161  
   162  					if len(ptr) < int(size) {
   163  						return nil, fmt.Errorf("invalid sample size")
   164  					}
   165  
   166  					s.Payload = ptr[:size]
   167  					ptr = ptr[size:]
   168  
   169  					curTrack.Samples[existing+i] = s
   170  				}
   171  
   172  			case "mdat":
   173  				if state != waitingTraf && state != waitingTfdtTfhdTrun {
   174  					return nil, fmt.Errorf("unexpected mdat")
   175  				}
   176  
   177  				if curTrack != nil {
   178  					if tfdt == nil || tfhd == nil || curTrack.Samples == nil {
   179  						return nil, fmt.Errorf("parse error")
   180  					}
   181  				}
   182  
   183  				state = waitingMoof
   184  			}
   185  		}
   186  
   187  		return nil, nil
   188  	})
   189  	if err != nil {
   190  		return err
   191  	}
   192  
   193  	if state != waitingMoof {
   194  		return fmt.Errorf("decode error")
   195  	}
   196  
   197  	return nil
   198  }
   199  
   200  // Marshal encodes a one or more fMP4 part.
   201  func (ps *Parts) Marshal(w io.WriteSeeker) error {
   202  	for _, p := range *ps {
   203  		err := p.Marshal(w)
   204  		if err != nil {
   205  			return err
   206  		}
   207  	}
   208  	return nil
   209  }