@@ -14,13 +14,14 @@ import (
1414 "github.com/Eyevinn/mp4ff/internal"
1515 "github.com/Eyevinn/mp4ff/mp4"
1616 "github.com/Eyevinn/mp4ff/sei"
17+ "github.com/Eyevinn/mp4ff/vvc"
1718)
1819
1920const (
2021 appName = "mp4ff-nallister"
2122)
2223
23- var usg = `%s lists NAL units and slice types of AVC or HEVC tracks of an mp4 (ISOBMFF) file
24+ var usg = `%s lists NAL units and slice types of AVC, HEVC, or VVC tracks of an mp4 (ISOBMFF) file
2425or a file containing a byte stream in Annex B format.
2526
2627Takes first video track in a progressive file and the first track in a fragmented file.
@@ -51,7 +52,7 @@ func parseOptions(fs *flag.FlagSet, args []string) (*options, error) {
5152 opts := options {}
5253
5354 fs .IntVar (& opts .maxNrSamples , "m" , - 1 , "Max nr of samples to parse" )
54- fs .StringVar (& opts .codec , "c" , "avc" , "Codec to parse (avc or hevc )" )
55+ fs .StringVar (& opts .codec , "c" , "avc" , "Codec to parse (avc, hevc, or vvc )" )
5556 fs .IntVar (& opts .seiLevel , "sei" , 0 , "Level of SEI information (1 is interpret, 2 is dump hex)" )
5657 fs .IntVar (& opts .printRaw , "raw" , 0 , "nr raw NAL unit bytes to print" )
5758 fs .BoolVar (& opts .annexB , "annexb" , false , "Input is Annex B stream file" )
@@ -108,7 +109,8 @@ func run(args []string, stdout io.Writer) error {
108109 }
109110 var avcSPS * avc.SPS
110111 for i , frame := range frames {
111- if o .codec == "avc" {
112+ switch o .codec {
113+ case "avc" :
112114 if avcSPS == nil {
113115 for _ , nalu := range frame {
114116 if avc .GetNaluType (nalu [0 ]) == avc .NALU_SPS {
@@ -120,8 +122,12 @@ func run(args []string, stdout io.Writer) error {
120122 }
121123 }
122124 err = printAVCNalus (stdout , avcSPS , frame , i + 1 , 0 , o .seiLevel , o .printPsHex , o .printRaw )
123- } else {
125+ case "hevc" :
124126 err = printHEVCNalus (stdout , frame , i + 1 , 0 , o .seiLevel , o .printPsHex , o .printRaw )
127+ case "vvc" :
128+ err = printVVCNalus (stdout , frame , i + 1 , 0 , o .seiLevel , o .printPsHex , o .printRaw )
129+ default :
130+ return fmt .Errorf ("unsupported codec: %s" , o .codec )
125131 }
126132 if err != nil {
127133 return fmt .Errorf ("printing error: %w" , err )
@@ -175,6 +181,8 @@ func parseProgressiveMp4(w io.Writer, f *mp4.File, maxNrSamples int, codec strin
175181 }
176182 } else if stbl .Stsd .HvcX != nil {
177183 codec = "hevc"
184+ } else if stbl .Stsd .VvcX != nil {
185+ codec = "vvc"
178186 }
179187 nrSamples := stbl .Stsz .SampleNumber
180188 mdat := f .Mdat
@@ -194,9 +202,9 @@ func parseProgressiveMp4(w io.Writer, f *mp4.File, maxNrSamples int, codec strin
194202 }
195203 size := stbl .Stsz .GetSampleSize (sampleNr )
196204 decTime , _ := stbl .Stts .GetDecodeTime (uint32 (sampleNr ))
197- var cto int32 = 0
205+ var cto int64 = 0
198206 if stbl .Ctts != nil {
199- cto = stbl .Ctts .GetCompositionTimeOffset (uint32 (sampleNr ))
207+ cto = int64 ( stbl .Ctts .GetCompositionTimeOffset (uint32 (sampleNr ) ))
200208 }
201209 // Next find sample bytes as slice in mdat
202210 offsetInMdatData := uint64 (offset ) - mdatPayloadStart
@@ -217,9 +225,11 @@ func parseProgressiveMp4(w io.Writer, f *mp4.File, maxNrSamples int, codec strin
217225 }
218226 }
219227 }
220- err = printAVCNalus (w , avcSPS , nalus , sampleNr , decTime + uint64 ( cto ) , seiLevel , parameterSets , nrRaw )
228+ err = printAVCNalus (w , avcSPS , nalus , sampleNr , int64 ( decTime ) + cto , seiLevel , parameterSets , nrRaw )
221229 case "hevc" , "h.265" , "h265" :
222- err = printHEVCNalus (w , nalus , sampleNr , decTime + uint64 (cto ), seiLevel , parameterSets , nrRaw )
230+ err = printHEVCNalus (w , nalus , sampleNr , int64 (decTime )+ cto , seiLevel , parameterSets , nrRaw )
231+ case "vvc" , "h.266" , "h266" :
232+ err = printVVCNalus (w , nalus , sampleNr , int64 (decTime )+ cto , seiLevel , parameterSets , nrRaw )
223233 default :
224234 return fmt .Errorf ("unknown codec: %s" , codec )
225235 }
@@ -258,6 +268,7 @@ func parseFragmentedMp4(w io.Writer, f *mp4.File, maxNrSamples int, codec string
258268 var trex * mp4.TrexBox
259269 var avcSPS * avc.SPS
260270 var err error
271+ var editListOffset int64 = 0
261272 if f .Init != nil { // Auto-detect codec if moov box is there
262273 moov := f .Init .Moov
263274 videoTrak , ok := findFirstVideoTrak (moov )
@@ -275,8 +286,11 @@ func parseFragmentedMp4(w io.Writer, f *mp4.File, maxNrSamples int, codec string
275286 }
276287 } else if stbl .Stsd .HvcX != nil {
277288 codec = "hevc"
289+ } else if stbl .Stsd .VvcX != nil {
290+ codec = "vvc"
278291 }
279292 trex , _ = moov .Mvex .GetTrex (videoTrak .Tkhd .TrackID )
293+ editListOffset = getVideoListOffset (moov , videoTrak )
280294 }
281295 iSamples := make ([]mp4.FullSample , 0 )
282296 for _ , iSeg := range f .Segments {
@@ -295,9 +309,11 @@ func parseFragmentedMp4(w io.Writer, f *mp4.File, maxNrSamples int, codec string
295309 }
296310 switch codec {
297311 case "avc" , "h.264" , "h264" :
298- err = printAVCNalus (w , avcSPS , nalus , i + 1 , s .PresentationTime (), seiLevel , parameterSets , nrRaw )
312+ err = printAVCNalus (w , avcSPS , nalus , i + 1 , s .PresentationTime ()+ editListOffset , seiLevel , parameterSets , nrRaw )
299313 case "hevc" , "h.265" , "h265" :
300- err = printHEVCNalus (w , nalus , i + 1 , s .PresentationTime (), seiLevel , parameterSets , nrRaw )
314+ err = printHEVCNalus (w , nalus , i + 1 , s .PresentationTime ()+ editListOffset , seiLevel , parameterSets , nrRaw )
315+ case "vvc" , "h.266" , "h266" :
316+ err = printVVCNalus (w , nalus , i + 1 , s .PresentationTime ()+ editListOffset , seiLevel , parameterSets , nrRaw )
301317 default :
302318 return fmt .Errorf ("unknown codec: %s" , codec )
303319 }
@@ -312,7 +328,24 @@ func parseFragmentedMp4(w io.Writer, f *mp4.File, maxNrSamples int, codec string
312328 return nil
313329}
314330
315- func printAVCNalus (w io.Writer , avcSPS * avc.SPS , nalus [][]byte , nr int , pts uint64 , seiLevel int , parameterSets bool , nrRaw int ) error {
331+ func getVideoListOffset (moov * mp4.MoovBox , videoTrak * mp4.TrakBox ) int64 {
332+ editListOffset := int64 (0 )
333+ for _ , ch := range videoTrak .Children {
334+ if ch .Type () == "edts" {
335+ edts := ch .(* mp4.EdtsBox )
336+ for _ , el := range edts .Elst {
337+ for _ , entry := range el .Entries {
338+ if entry .MediaTime < 0 {
339+ editListOffset = int64 (entry .SegmentDuration ) * int64 (videoTrak .Mdia .Mdhd .Timescale ) / int64 (moov .Mvhd .Timescale )
340+ }
341+ }
342+ }
343+ }
344+ }
345+ return editListOffset
346+ }
347+
348+ func printAVCNalus (w io.Writer , avcSPS * avc.SPS , nalus [][]byte , nr int , pts int64 , seiLevel int , parameterSets bool , nrRaw int ) error {
316349 msg := ""
317350 var seiNALUs [][]byte
318351 totLen := 0
@@ -363,7 +396,7 @@ func printAVCNalus(w io.Writer, avcSPS *avc.SPS, nalus [][]byte, nr int, pts uin
363396 return nil
364397}
365398
366- func printHEVCNalus (w io.Writer , nalus [][]byte , nr int , pts uint64 , seiLevel int , parameterSets bool , nrRaw int ) error {
399+ func printHEVCNalus (w io.Writer , nalus [][]byte , nr int , pts int64 , seiLevel int , parameterSets bool , nrRaw int ) error {
367400 msg := ""
368401 var seiNALUs [][]byte
369402 totLen := 0
@@ -401,6 +434,52 @@ func printHEVCNalus(w io.Writer, nalus [][]byte, nr int, pts uint64, seiLevel in
401434 return nil
402435}
403436
437+ func printVVCNalus (w io.Writer , nalus [][]byte , nr int , pts int64 , seiLevel int , parameterSets bool , nrRaw int ) error {
438+ msg := ""
439+ var seiNALUs [][]byte
440+ totLen := 0
441+ for i , nalu := range nalus {
442+ totLen += 4 + len (nalu )
443+ if i > 0 {
444+ msg += ","
445+ }
446+ header , err := vvc .ParseNaluHeader (nalu )
447+ if err != nil {
448+ return fmt .Errorf ("error parsing VVC NALU header: %w" , err )
449+ }
450+ naluType := header .NaluType
451+ if nrRaw > 0 {
452+ msg += fmt .Sprintf ("\n %s (%dB)" , naluType , len (nalu ))
453+ msg += fmt .Sprintf (" raw: %s" , bytesToStringN (nalu , nrRaw ))
454+ } else {
455+ msg += fmt .Sprintf (" %s (%dB)" , naluType , len (nalu ))
456+ }
457+ if seiLevel > 0 && (naluType == vvc .NALU_SEI_PREFIX || naluType == vvc .NALU_SEI_SUFFIX ) {
458+ seiNALUs = append (seiNALUs , nalu )
459+ }
460+ }
461+ fmt .Fprintf (w , "Sample %d, pts=%d (%dB):%s\n " , nr , pts , totLen , msg )
462+ printSEINALus (w , seiNALUs , "vvc" , seiLevel , nil )
463+ if parameterSets {
464+ for _ , nalu := range nalus {
465+ header , err := vvc .ParseNaluHeader (nalu )
466+ if err != nil {
467+ continue
468+ }
469+ naluType := header .NaluType
470+ switch naluType {
471+ case vvc .NALU_VPS :
472+ fmt .Fprintf (w , " VPS: %s\n " , hex .EncodeToString (nalu ))
473+ case vvc .NALU_SPS :
474+ fmt .Fprintf (w , " SPS: %s\n " , hex .EncodeToString (nalu ))
475+ case vvc .NALU_PPS :
476+ fmt .Fprintf (w , " PPS: %s\n " , hex .EncodeToString (nalu ))
477+ }
478+ }
479+ }
480+ return nil
481+ }
482+
404483// printSEINALus - print interpreted information if seiLevel is >= 1. Add hex dump if seiLevel >= 2
405484func printSEINALus (w io.Writer , seiNALUs [][]byte , codec string , seiLevel int , avcSPS * avc.SPS ) {
406485 if seiLevel < 1 {
@@ -412,7 +491,7 @@ func printSEINALus(w io.Writer, seiNALUs [][]byte, codec string, seiLevel int, a
412491 case "avc" :
413492 seiCodec = sei .AVC
414493 hdrLen = 1
415- case "hevc" :
494+ case "hevc" , "vvc" : // VVC uses same SEI format as HEVC
416495 seiCodec = sei .HEVC
417496 hdrLen = 2
418497 }
@@ -476,6 +555,8 @@ func findAnnexBFrames(nalus [][]byte, codec string) ([][][]byte, error) {
476555 isAUD = isAvcAudNalu
477556 case "hevc" :
478557 isAUD = isHEVCAudNalu
558+ case "vvc" :
559+ isAUD = isVVCAudNalu
479560 default :
480561 return nil , fmt .Errorf ("unknown codec: %s" , codec )
481562 }
@@ -502,3 +583,11 @@ func isAvcAudNalu(nalu []byte) bool {
502583func isHEVCAudNalu (nalu []byte ) bool {
503584 return hevc .GetNaluType (nalu [0 ]) == hevc .NALU_AUD
504585}
586+
587+ func isVVCAudNalu (nalu []byte ) bool {
588+ header , err := vvc .ParseNaluHeader (nalu )
589+ if err != nil {
590+ return false
591+ }
592+ return header .NaluType == vvc .NALU_AUD
593+ }
0 commit comments