Video files, specifically MP4 files, have a curious head of encoded data at the start of the file which can be seen in a hex editor or even Notepad. Below is a screenshot of a hex editor of just such a header.
(In actual fact this is a DASH fragmented mp4 initialization vector IV file, i.e. the first file in a long stream. So is without any encoded media data but it serves as an example)
I have written some VBA code to load this file and parse it out into what are known as ATOMS. I have placed this code into Appendix A because actually I found better source code in the form of the VLC Media Player code. VLC handles all manner of mp4 files and so is a infinitely better bet.
MP4 Files are based on Apple's QuickTime Movie files
During my reaearch, a fantastic discovery was a treasure trove of documentation regarding Apple's QuickTime File Format Specification. Below is a beautiful diagram showing the nested nature of the data structure. Clicking on the image will take you Apple's website. But a word of warning, whilst MP4 is based on QuickTime it may diverge at some points. In fact there is a warning here which says...
Important: The QuickTime File Format has been used as the basis of the MPEG-4 standard and the JPEG-2000 standard, developed by the International Organization for Standardization (ISO). Although these file types have similar structures and contain many functionally identical elements, they are distinct file types.
Warning: Do not use this specification to interpret a file that conforms to a different specification, however similar.
Despite this Apple's documentation is free and well-written and definitely serves as a starting point.
MPEG Specificaation ISO/IEC 14496 parts 12 & 14
There is an official specification published by International Standards Organisation (ISO). In the document ISO/IEC 14496-12 there is a very good table which also shows the nested box/atom structure. It is reproduced below.
ftyp | * | 4.3 | file type and compatibility | |||||
pdin | 8.43 | progressive download information | ||||||
moov | * | 8.1 | container for all the metadata | |||||
mvhd | * | 8.3 | movie header, overall declarations | |||||
trak | * | 8.4 | container for an individual track or stream | |||||
tkhd | * | 8.5 | track header, overall information about the track | |||||
tref | 8.6 | track reference container | ||||||
edts | 8.25 | edit list container | ||||||
elst | 8.26 | an edit list | ||||||
mdia | * | 8.7 | container for the media information in a track | |||||
mdhd | * | 8.8 | media header, overall information about the media | |||||
hdlr | * | 8.9 | handler, declares the media (handler) type | |||||
minf | * | 8.10 | media information container | |||||
vmhd | 8.11.2 | video media header, overall information (video track only) | ||||||
smhd | 8.11.3 | sound media header, overall information (sound track only) | ||||||
hmhd | 8.11.4 | hint media header, overall information (hint track only) | ||||||
nmhd | 8.11.5 | Null media header, overall information (some tracks only) | ||||||
dinf | * | 8.12 | data information box, container | |||||
dref | * | 8.13 | data reference box, declares source(s) of media data in track | |||||
stbl | * | 8.14 | sample table box, container for the time/space map | |||||
stsd | * | 8.16 | sample descriptions (codec types, initialization etc.) | |||||
stts | * | 8.15.2 | (decoding) time-to-sample | |||||
ctts | 8.15.3 | (composition) time to sample | ||||||
stsc | * | 8.18 | sample-to-chunk, partial data-offset information | |||||
stsz | 8.17.2 | sample sizes (framing) | ||||||
stz2 | 2 8.17.3 | compact sample sizes (framing) | ||||||
stco | * | 8.19 | chunk offset, partial data-offset information | |||||
co64 | 8.19 | 64 -bit chunk offset | ||||||
stss | 8.20 | sync sample table (random access points) | ||||||
stsh | 8.21 | shadow sync sample table | ||||||
padb | 8.23 | sample padding bits | ||||||
stdp | 8.22 | sample degradation priority | ||||||
sdtp | 8.40.2 | independent and disposable samples | ||||||
sbgp | 8.40.3.2 | sample-to-group | ||||||
sgpd | 8.40.3.3 | sample group description | ||||||
subs | 8.42 | sub-sample information | ||||||
mvex | 8.29 | movie extends box | ||||||
mehd | 8.30 | movie extends header box | ||||||
trex | * | 8.31 | track extends defaults | |||||
ipmc | 8.45.4 | IPMP Control Box | ||||||
moof | 8.32 | movie fragment | ||||||
mfhd | * | 8.33 | movie fragment header | |||||
traf | 8.34 | track fragment | ||||||
tfhd | * | 8.35 | track fragment header | |||||
trun | 8.36 | track fragment run | ||||||
sdtp | 8.40.2 | independent and disposable samples | ||||||
sbgp | 8.40.3.2 | sample-to-group | ||||||
subs | 8.42 | sub-sample information | ||||||
mfra | 8.37 | movie fragment random access | ||||||
tfra | 8.38 | track fragment random access | ||||||
mfro | * | 8.39 | movie fragment random access offset | |||||
mdat | 8.2 | media data container | ||||||
free | 8.24 | free space | ||||||
skip | 8.24 | free space | ||||||
udta | 8.27 | user-data | ||||||
cprt | 8.28 | copyright etc. | ||||||
meta | 8.44.1 | metadata | ||||||
hdlr | * | 8.9 | handler, declares the metadata (handler) type | |||||
dinf | 8.12 | data information box, container | ||||||
dref | 8.13 | data reference box, declares source(s) of metadata items | ||||||
ipmc | 8.45.4 | IPMP Control Box | ||||||
iloc | 8.44.3 | item location | ||||||
ipro | 8.44.5 | item protection | ||||||
sinf | 8.45.1 | protection scheme information box | ||||||
frma | 8.45.2 | original format box | ||||||
imif | 8.45.3 | IPMP Information box | ||||||
schm | 8.45.5 | scheme type box | ||||||
schi | 8.45.6 | scheme information box | ||||||
iinf | 8.44.6 | item information | ||||||
xml | 8.44.2 | XML container | ||||||
bxml | 8.44.2 | binary XML container | ||||||
pitm | 8.44.4 | primary item reference |
VLC Media Player Source Code
The Github page for VLC Media Player is where to find the source code. The atoms can be seen to be defined in libmp4.h, here is an excerpt ...
#define ATOM_ftyp VLC_FOURCC( 'f', 't', 'y', 'p' )
#define ATOM_moov VLC_FOURCC( 'm', 'o', 'o', 'v' )
Also defined in the libmp4.h header file are the C structs ...
typedef struct MP4_Box_data_ftyp_s
{
uint32_t i_major_brand;
uint32_t i_minor_version;
uint32_t i_compatible_brands_count;
uint32_t *i_compatible_brands;
} MP4_Box_data_ftyp_t;
You can compare the above C code with my VBA code in Appendix A. In the vlc_libmp4.c implementation file you can see the actual code to populate the data structures. So for example, the following code populates the data structure given above.
static int MP4_ReadBox_ftyp( stream_t *p_stream, MP4_Box_t *p_box )
{
MP4_READBOX_ENTER( MP4_Box_data_ftyp_t, MP4_FreeBox_ftyp );
MP4_GETFOURCC( p_box->data.p_ftyp->i_major_brand );
MP4_GET4BYTES( p_box->data.p_ftyp->i_minor_version );
p_box->data.p_ftyp->i_compatible_brands_count = i_read / 4;
if( p_box->data.p_ftyp->i_compatible_brands_count > 0 )
{
uint32_t *tab = p_box->data.p_ftyp->i_compatible_brands =
vlc_alloc( p_box->data.p_ftyp->i_compatible_brands_count,
sizeof(uint32_t) );
if( unlikely( tab == NULL ) )
MP4_READBOX_EXIT( 0 );
for( unsigned i = 0; i < p_box->data.p_ftyp->i_compatible_brands_count; i++ )
{
MP4_GETFOURCC( tab[i] );
}
}
else
{
p_box->data.p_ftyp->i_compatible_brands = NULL;
}
MP4_READBOX_EXIT( 1 );
}
Use Unix Tool Grep in Windows
I'm very impressed with Windows Subsystem for Linux. I used Grep to search the VLC Media Player source code. I did this by pressing SHIFT and right mouse button clicking in a Windows Explorer window and taking the context menu option 'Open Linux shell here' and type in the following ("hdlr" was my search term, substitute for yours) ...
grep -rl hdlr
So Grep is a fantastic tool which is available to Windows 10 users. There are plenty of other great Unix tools as well, looking forward to using them for my everyday Windows tasks.
Links
- Introduction to QuickTime File Format Specification
- Github page for VLC Media Player
- vlc_libmp4.h at master · videolan_vlc · GitHub
- vlc_libmp4.c at master · videolan_vlc · GitHub
- Official download of VLC media player, the best Open Source player - VideoLAN
- FFmpeg Formats Documentation - 4.7 dash
- video - Anyone familiar with mp4 data structure_ - Stack Overflow
- MP4 File Format Specification - Stack Overflow
- MP4 file format specification - part of the ISO/IEC 14496 standard.
- specification of the ISO base format, ISO/IEC 14496 standard
- Understanding the MPEG-4 movie atom _ Adobe Developer Connection
- Atomic Parsley MPEG-4 files
- ISO 14496-1 Media Format
- MPEG-4 File Sink - Windows applications _ Microsoft Docs
- 17 Free MPEG-DASH example and HLS m3u8 sample test streams
- ISO base media file format - Wikipedia
- mp4dump - Bento4
- pymp4parse · PyPI
- mp4-reader
- YAMB 1.6 _ 2.1.0.0 beta 2 Free Download - VideoHelp
- VideoHelp Forum
- GPAC is an Open Source multimedia framework - that includes a multimedia packager, called MP4Box.
- GPAC - MP4Box DASH Support
- SourceForge - GPAC - Discussion - Help_ Merging m4s segments in one playable file
- Stack Overflow - video - Combine MPEG-DASH segments (ex, init.mp4 + segments.m4s) back to a full source.mp4_
- Building a Dash-264 Player
- 3 Ways to Add Two or More Subtitles to Video • Raymond.CC
- Matsu Blog_ MPEG4コンテナのmoovボックスについて
- ffmpeg - Sending periodic metadata in fragmented live MP4 stream_ - Stack Overflow
- GitHub - beardypig_pymp4_ A Python MP4 Parser and toolkit
- ffmpeg - Sending periodic metadata in fragmented live MP4 stream_ - Stack Overflow
- media - What exactly is Fragmented mp4(fMP4)_ How is it different from normal mp4_ - Stack Overflow
- Online MPEG4 Parser
- Mp4 Explorer - CodePlex Archive
- DASH Support in MP4Box _ GPAC
- What's in the box! - YouTube
- [MS-SSTR]_ Smooth Streaming Protocol _ Microsoft Docs
- ETSI TS 126 244 (for HTTP streaming extensions)
- RubyDoc Gems - Documentation for bmff (0.1.2)
- iso-bmff - npm
- node.js - Accessing live video stream midway using websockets - Stack Overflow
- How to make your media streams smarter using timed metadata - Unified Streaming
Appendix A - VBA Code to read DASH IV
The listing below is my admittedly buggy and work-in-progress code for parsing a DASH IV file. I'll confess that in places I have triaged the code to skip some bytes here and there on a 'fake it 'til you make it' basis. Given its bugs and triages this is definitely not production ready so absolutely no warranty. (As a matter of fact, all code on this blog carries no warranty and you use at your own risk). But writing this code forced me to research the problem and led me to discover the VLC Media Player source code. I give the code below for illustrative purposes.
For better source code one should look at the VLC Media Player source, detailed above.
Anyway at least one can get a handle on the nested nature on the data structure. Here is a screenshot of the locals window after the parsing is complete, you can see the nested atoms.
Option Explicit
Private fso As New Scripting.FileSystemObject
Private Type udtFtyp
lSize As Long
type As String * 4
major_brand As String
minor_version As Long
compatible_brand As String
compatible_brand2 As String
End Type
Private Type udtFree
size As Long
type As String * 4
text As String
End Type
Private Type udtStco
'* Chunk Offset Atom
'* https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-BBCHAEEA
size As Long
type As String * 4
version As Byte
flags As Long ' 3 bytes
numberofentries As Long
End Type
Private Type udtStsz
'* Sample Size Atom
'* https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-BBCBBCGB
size As Long
type As String * 4
version As Byte
flags As Long ' 3 bytes
numberofentries As Long
End Type
Private Type udtStsc
'* Sample-to-Chunk Atom
'* https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-BBCIDAFD
size As Long
type As String * 4
version As Byte
flags As Long ' 3 bytes
numberofentries As Long
End Type
Private Type udtStsdSampleDescriptions
size As Long
dataformat As Long
reserved(0 To 5)
datareferenceindex As Integer
additionaldata() As Byte
End Type
Private Type udtStsd
'* Sample Description Atom
'* https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-BBCHHGBH
size As Long
type As String * 4
version As Byte
flags As Long ' 3 bytes
numberofentries As Long
sampleDescriptions() As udtStsdSampleDescriptions
End Type
Private Type udtStts
'* Time-to-Sample Atom
'* https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-BBCGFJII
size As Long
type As String * 4
version As Byte
flags As Long ' 3 bytes
numberofentries As Long
End Type
Private Type udtStbl
'* Sample Table Atom
'* https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-BBCBFDFF
type As String * 4
stsd As udtStsd
stts As udtStts
stsc As udtStsc
stsz As udtStsz
stco As udtStco
End Type
Private Type udtDrefDetails
size As Long
type As String * 4
version As Byte
flags As Long ' 3 bytes
data() As Byte
End Type
Private Type udtDref
'* Data Reference Atom
'* https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-BBCGGDAE
size As Long
type As String * 4
version As Byte
flags As Long '3 bytes
numberofentries As Long
details() As udtDrefDetails
End Type
Private Type udtDinf
'* Data Information Atom
'* https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-BBCIFAIC
size As Long
type As String * 4
dref As udtDref
End Type
Private Type udtSmhd
'* Sound Media Information Header Atom
'* https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-BBCHEIJG
size As Long
type As String * 4
version As Byte
flags As Long '3 bytes
balance As Integer
reserved As Integer
End Type
Private Type udtMinf
'* Media Information Atoms
'* https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-25647
type As String * 4
smhd As udtSmhd
dinf As udtDinf
stbl As udtStbl
End Type
Private Type udtSoun
'* Sound Data (Media Data Atom Type)
'* https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-BBCIBHFD
size As Long
type As String * 4
End Type
Private Type udtHdlr
'* handler reference ('hdlr') atom (Media Atom)
'* https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-DontLinkElementID_147
size As Long
type As String * 4
version As Byte
flags As Long '3 bytes
componenttype As String * 4
componentsubtype As String * 4
componentmanufacturer As Long
componentflags As Long
componentflagsmask As Long
componentname As String
End Type
Private Type udtMdhd
'* Media Header Atom
'* https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-SW34
size As Long
type As String * 4
End Type
Private Type udtMdia
'* Media Atom
'* https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-BBCHFJFA
type As String * 4
mdhd As udtMdhd
hdlr As udtHdlr
soun As udtSoun
minf As udtMinf
End Type
Private Type udtTkhd
'* Track Header Atom
'* https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-BBCEIDFA
size As Long
type As String * 4
version As Byte
flags As Long '3 bytes
creationtime As Long
modificationtime As Long
trackid As Long
reserved0 As Long
duration As Long
reserved1(0 To 1) As Long
layer As Integer
alternationgroup As Integer
volume As Integer
Reserved2 As Integer
matrixstructure(0 To 35) As Byte
trackwidth As Long
trackheight As Long
End Type
Private Type udtTrak
'* Track Atom
'* https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-BBCBEAIF
size As Long
type As String * 4
tkhd As udtTkhd
mdia As udtMdia
End Type
Private Type udtMvhd
'* Movie Header Atom
'* https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-BBCGFGJG
size As Long
type As String * 4
version As Long
flags As Long
creationtime As Long
modificationtime As Long
timescale As Long
duration As Long
preferredrate As Long
preferredvolume As Long
reserved As String
matrix As String
previewtime As Long
previewduration As Long
postertime As Long
selectiontime As Long
selectionduration As Long
currenttime As Long
nexttrackid As Long
End Type
Private Type udtMoov
'* Movie Atom
'* https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-55911
size As Long
type As String * 4
mvhd As udtMvhd
trak As udtTrak
End Type
Private Type udtWholeFile
ftyp As udtFtyp
free As udtFree
moov As udtMoov
End Type
Dim wholefile As udtWholeFile
Private Sub Start()
Dim sFileName As String
sFileName = TEST_FILENAME
Dim bytes() As Byte
bytes() = ReadByteFile(sFileName)
Dim idx As Long
idx = Read_ftyp(bytes(), 0)
idx = Read_free(bytes(), idx)
idx = Read_moov(bytes(), idx)
End Sub
Private Function Read_ftyp(ByRef bytes() As Byte, ByVal lPosition As Long)
Debug.Assert Chr$(bytes(lPosition + 4)) + Chr$(bytes(lPosition + 5)) + Chr$(bytes(lPosition + 6)) + Chr$(bytes(lPosition + 7)) = "ftyp"
Dim sBuffer As String
sBuffer = BytesToString(bytes, 50, (lPosition))
wholefile.ftyp.lSize = 16777216# * bytes(lPosition) + 65536# * bytes(lPosition + 1) + 256# * bytes(lPosition + 2) + bytes(lPosition + 3)
lPosition = lPosition + 8
wholefile.ftyp.major_brand = BytesToString(bytes(), 4, lPosition)
wholefile.ftyp.minor_version = BytesToLong(bytes(), 4, lPosition)
wholefile.ftyp.compatible_brand = BytesToString(bytes(), 4, lPosition)
wholefile.ftyp.compatible_brand2 = BytesToString(bytes(), 4, lPosition)
Debug.Print "[ftyp] size=8+" & CStr(wholefile.ftyp.lSize - 8)
Debug.Print " major_brand = " & wholefile.ftyp.major_brand
Debug.Print " minor_version = " & wholefile.ftyp.minor_version
Debug.Print " compatible_brand = " & wholefile.ftyp.compatible_brand
Debug.Print " compatible_brand = " & wholefile.ftyp.compatible_brand2
Read_ftyp = lPosition ' + wholefile.ftyp.lSize
End Function
Private Function Read_free(ByRef bytes() As Byte, ByVal lPosition As Long)
Dim sBuffer As String
sBuffer = BytesToString(bytes, 50, (lPosition))
Debug.Assert Chr$(bytes(lPosition + 4)) + Chr$(bytes(lPosition + 5)) + Chr$(bytes(lPosition + 6)) + Chr$(bytes(lPosition + 7)) = "free"
wholefile.free.size = 16777216# * bytes(lPosition) + 65536# * bytes(lPosition + 1) + 256# * bytes(lPosition + 2) + bytes(lPosition + 3)
lPosition = lPosition + 8
wholefile.free.text = BytesToString(bytes(), wholefile.free.size - 8, lPosition)
Debug.Print "[free] size=8+" & CStr(wholefile.free.size - 8)
Debug.Print " text=" & wholefile.free.text
Read_free = lPosition '+ wholefile.free.size
End Function
Private Function Read_moov(ByRef bytes() As Byte, ByVal lPosition As Long)
Dim sBuffer As String
sBuffer = BytesToString(bytes, 50, (lPosition))
Debug.Assert Chr$(bytes(lPosition + 4)) + Chr$(bytes(lPosition + 5)) + Chr$(bytes(lPosition + 6)) + Chr$(bytes(lPosition + 7)) = "moov"
wholefile.moov.size = 16777216# * bytes(lPosition) + 65536# * bytes(lPosition + 1) + 256# * bytes(lPosition + 2) + bytes(lPosition + 3)
wholefile.moov.type = "moov"
lPosition = lPosition + 8
Debug.Print "[moov] size=8+" & CStr(wholefile.moov.size - 8)
lPosition = Read_mvhd(bytes(), lPosition) 'mvhd element starts 8 bytes in, i.e. it is first child
lPosition = Read_trak(bytes(), lPosition)
Read_moov = lPosition
End Function
Private Function Read_mvhd(ByRef bytes() As Byte, ByVal lPosition As Long)
Debug.Assert Chr$(bytes(lPosition + 4)) + Chr$(bytes(lPosition + 5)) + Chr$(bytes(lPosition + 6)) + Chr$(bytes(lPosition + 7)) = "mvhd"
Dim lSize As Long
lSize = 16777216# * bytes(lPosition) + 65536# * bytes(lPosition + 1) + 256# * bytes(lPosition + 2) + bytes(lPosition + 3)
Dim idx As Long
idx = lPosition
'Dim sBuffer As String
'sBuffer = BytesToString(bytes, lSize + 12, (idx))
wholefile.moov.mvhd.size = lSize
Debug.Print "[mvhd] size=8+" & CStr(lSize - 8)
'https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-BBCGFGJG
idx = lPosition + 8
wholefile.moov.mvhd.version = BytesToLong(bytes, 1, idx)
wholefile.moov.mvhd.flags = BytesToLong(bytes, 3, idx)
wholefile.moov.mvhd.creationtime = BytesToLong(bytes, 4, idx)
wholefile.moov.mvhd.modificationtime = BytesToLong(bytes, 4, idx)
wholefile.moov.mvhd.timescale = BytesToLong(bytes, 4, idx)
wholefile.moov.mvhd.duration = BytesToLong(bytes, 4, idx)
wholefile.moov.mvhd.preferredrate = BytesToLong(bytes, 4, idx)
wholefile.moov.mvhd.preferredvolume = BytesToLong(bytes, 2, idx)
wholefile.moov.mvhd.reserved = BytesToString(bytes, 10, idx)
wholefile.moov.mvhd.matrix = BytesToString(bytes, 36, idx)
wholefile.moov.mvhd.previewtime = BytesToLong(bytes, 4, idx)
wholefile.moov.mvhd.previewduration = BytesToLong(bytes, 4, idx)
wholefile.moov.mvhd.postertime = BytesToLong(bytes, 4, idx)
wholefile.moov.mvhd.selectiontime = BytesToLong(bytes, 4, idx)
wholefile.moov.mvhd.selectionduration = BytesToLong(bytes, 4, idx) '94
wholefile.moov.mvhd.currenttime = BytesToLong(bytes, 4, idx)
wholefile.moov.mvhd.nexttrackid = BytesToLong(bytes, 4, idx)
Debug.Print " version = " & wholefile.moov.mvhd.version
Debug.Print " flags = " & wholefile.moov.mvhd.flags
Debug.Print " creationtime = " & wholefile.moov.mvhd.creationtime
Debug.Print " modificationtime = " & wholefile.moov.mvhd.modificationtime
Debug.Print " timescale = " & wholefile.moov.mvhd.timescale
Debug.Print " duration = " & wholefile.moov.mvhd.duration
Debug.Print " preferredrate = " & wholefile.moov.mvhd.preferredrate
Debug.Print " preferredvolume = " & wholefile.moov.mvhd.preferredvolume
Debug.Print " reserved = " & wholefile.moov.mvhd.reserved
Debug.Print " matrix = " & wholefile.moov.mvhd.matrix
Debug.Print " previewtime = " & wholefile.moov.mvhd.previewtime
Debug.Print " previewduration = " & wholefile.moov.mvhd.previewduration
Debug.Print " postertime = " & wholefile.moov.mvhd.postertime
Debug.Print " selectiontime = " & wholefile.moov.mvhd.selectiontime
Debug.Print " selectionduration = " & wholefile.moov.mvhd.selectionduration
Debug.Print " currenttime = " & wholefile.moov.mvhd.currenttime
Debug.Print " nexttrackid = " & wholefile.moov.mvhd.nexttrackid
'mvhd is a leaf atom and has no children
Read_mvhd = lPosition + lSize
End Function
Private Function Read_trak(ByRef bytes() As Byte, ByVal lPosition As Long)
lPosition = lPosition + 2 '* shim, fake it till we make it
Debug.Assert Chr$(bytes(lPosition + 4)) + Chr$(bytes(lPosition + 5)) + Chr$(bytes(lPosition + 6)) + Chr$(bytes(lPosition + 7)) = "trak"
Dim lSize As Long
lSize = 16777216# * bytes(lPosition) + 65536# * bytes(lPosition + 1) + 256# * bytes(lPosition + 2) + bytes(lPosition + 3)
wholefile.moov.trak.type = "trak"
wholefile.moov.trak.size = lSize
lPosition = lPosition + 8
Debug.Print "[trak] size=8+" & CStr(lSize - 8)
lPosition = Read_tkhd(bytes(), lPosition) 'tkhd element starts 8 bytes in, i.e. it is first child
lPosition = Read_mdia(bytes(), lPosition)
Read_trak = lPosition
End Function
Private Function Read_mdia(ByRef bytes() As Byte, ByVal lPosition As Long)
Debug.Assert Chr$(bytes(lPosition + 4)) + Chr$(bytes(lPosition + 5)) + Chr$(bytes(lPosition + 6)) + Chr$(bytes(lPosition + 7)) = "mdia"
Dim lSize As Long
lSize = 16777216# * bytes(lPosition) + 65536# * bytes(lPosition + 1) + 256# * bytes(lPosition + 2) + bytes(lPosition + 3)
wholefile.moov.trak.mdia.type = "mdia"
lPosition = Read_mdhd(bytes(), lPosition + 8) 'mdhd element starts 8 bytes in, i.e. it is first child
lPosition = Read_mdia_hdlr(bytes(), lPosition)
lPosition = Read_minf(bytes(), lPosition)
Read_mdia = lPosition
End Function
Private Function Read_mdhd(ByRef bytes() As Byte, ByVal lPosition As Long)
Debug.Assert Chr$(bytes(lPosition + 4)) + Chr$(bytes(lPosition + 5)) + Chr$(bytes(lPosition + 6)) + Chr$(bytes(lPosition + 7)) = "mdhd"
Dim lSize As Long
lSize = 16777216# * bytes(lPosition) + 65536# * bytes(lPosition + 1) + 256# * bytes(lPosition + 2) + bytes(lPosition + 3)
wholefile.moov.trak.mdia.mdhd.size = lSize
wholefile.moov.trak.mdia.mdhd.type = "mdhd"
Read_mdhd = lPosition + lSize
End Function
Private Function Read_minf(ByRef bytes() As Byte, ByVal lPosition As Long)
lPosition = lPosition + 6
Debug.Assert Chr$(bytes(lPosition + 0)) + Chr$(bytes(lPosition + 1)) + Chr$(bytes(lPosition + 2)) + Chr$(bytes(lPosition + 3)) = "minf"
'Dim sBuffer As String
'sBuffer = BytesToString(bytes, 50, (lPosition))
wholefile.moov.trak.mdia.minf.type = "minf"
'*https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-25647
lPosition = Read_smhd(bytes, lPosition + 4)
lPosition = Read_dinf(bytes, lPosition)
Read_minf = lPosition + 44
End Function
Private Function Read_dinf(ByRef bytes() As Byte, ByVal lPosition As Long)
Debug.Assert Chr$(bytes(lPosition + 4)) + Chr$(bytes(lPosition + 5)) + Chr$(bytes(lPosition + 6)) + Chr$(bytes(lPosition + 7)) = "dinf"
Dim lSize As Long
lSize = 16777216# * bytes(lPosition) + 65536# * bytes(lPosition + 1) + 256# * bytes(lPosition + 2) + bytes(lPosition + 3)
'Dim sBuffer As String
'sBuffer = BytesToString(bytes, 50, (lPosition))
wholefile.moov.trak.mdia.minf.dinf.size = lSize
wholefile.moov.trak.mdia.minf.dinf.type = "dinf"
lPosition = Read_dref(bytes, lPosition + 8)
lPosition = Read_stbl(bytes, lPosition)
Read_dinf = lPosition + lSize
End Function
Private Function Read_stbl(ByRef bytes() As Byte, ByVal lPosition As Long)
lPosition = lPosition + 6
'Dim sBuffer As String
'sBuffer = BytesToString(bytes, 50, (lPosition))
Debug.Assert Chr$(bytes(lPosition + 0)) + Chr$(bytes(lPosition + 1)) + Chr$(bytes(lPosition + 2)) + Chr$(bytes(lPosition + 3)) = "stbl"
lPosition = Read_stsd(bytes(), lPosition + 4)
lPosition = Read_stts(bytes(), lPosition)
lPosition = Read_stsc(bytes(), lPosition)
lPosition = Read_stsz(bytes(), lPosition)
lPosition = Read_stco(bytes(), lPosition)
Read_stbl = lPosition
End Function
Private Function Read_stsd(ByRef bytes() As Byte, ByVal lPosition As Long)
Debug.Assert Chr$(bytes(lPosition + 4)) + Chr$(bytes(lPosition + 5)) + Chr$(bytes(lPosition + 6)) + Chr$(bytes(lPosition + 7)) = "stsd"
Dim lSize As Long
lSize = 16777216# * bytes(lPosition) + 65536# * bytes(lPosition + 1) + 256# * bytes(lPosition + 2) + bytes(lPosition + 3)
'Dim sBuffer As String
'sBuffer = BytesToString(bytes, 50, (lPosition))
wholefile.moov.trak.mdia.minf.stbl.stsd.type = "stsd"
wholefile.moov.trak.mdia.minf.stbl.stsd.size = lSize
Dim idx As Long
idx = lPosition + 8
wholefile.moov.trak.mdia.minf.stbl.stsd.version = BytesToLong(bytes, 1, idx)
wholefile.moov.trak.mdia.minf.stbl.stsd.flags = BytesToLong(bytes, 3, idx)
Dim lEntries As Long
lEntries = BytesToLong(bytes, 4, idx)
wholefile.moov.trak.mdia.minf.stbl.stsd.numberofentries = lEntries
If lEntries > 0 Then
ReDim wholefile.moov.trak.mdia.minf.stbl.stsd.sampleDescriptions(0 To lEntries - 1)
Dim lLoop As Long
For lLoop = 0 To lEntries - 1
Dim lDetailsSize As Long
lDetailsSize = 16777216# * bytes(idx) + 65536# * bytes(idx + 1) + 256# * bytes(idx + 2) + bytes(idx + 3)
wholefile.moov.trak.mdia.minf.stbl.stsd.sampleDescriptions(lLoop).size = lDetailsSize
idx = idx + 4
wholefile.moov.trak.mdia.minf.stbl.stsd.sampleDescriptions(lLoop).dataformat = BytesToLong(bytes, 4, idx)
idx = idx + 6 ' reserved 6 bytes
wholefile.moov.trak.mdia.minf.stbl.stsd.sampleDescriptions(lLoop).datareferenceindex = BytesToLong(bytes, 2, idx)
Dim lDataSize As Long
lDataSize = lDetailsSize - 24
If lDataSize > 0 Then
ReDim wholefile.moov.trak.mdia.minf.stbl.stsd.sampleDescriptions(lLoop).additionaldata(0 To lDataSize - 1)
CopyBytes bytes(), wholefile.moov.trak.mdia.minf.stbl.stsd.sampleDescriptions(lLoop).additionaldata, idx, lDataSize
End If
idx = idx + lDataSize
Next lLoop
End If
Read_stsd = lPosition + lSize
End Function
Private Function Read_stts(ByRef bytes() As Byte, ByVal lPosition As Long)
lPosition = lPosition + 9 '* fake it till we make it
Debug.Assert Chr$(bytes(lPosition + 4)) + Chr$(bytes(lPosition + 5)) + Chr$(bytes(lPosition + 6)) + Chr$(bytes(lPosition + 7)) = "stts"
Dim lSize As Long
lSize = 16777216# * bytes(lPosition) + 65536# * bytes(lPosition + 1) + 256# * bytes(lPosition + 2) + bytes(lPosition + 3)
'Dim sBuffer As String
'sBuffer = BytesToString(bytes, 50, (lPosition))
wholefile.moov.trak.mdia.minf.stbl.stts.type = "stts"
wholefile.moov.trak.mdia.minf.stbl.stts.size = lSize
Dim idx As Long
idx = lPosition + 8
wholefile.moov.trak.mdia.minf.stbl.stts.version = BytesToLong(bytes, 1, idx)
wholefile.moov.trak.mdia.minf.stbl.stts.flags = BytesToLong(bytes, 3, idx)
Dim lEntries As Long
lEntries = BytesToLong(bytes, 4, idx)
wholefile.moov.trak.mdia.minf.stbl.stts.numberofentries = lEntries
Read_stts = lPosition + lSize
End Function
Private Function Read_stsc(ByRef bytes() As Byte, ByVal lPosition As Long)
Debug.Assert Chr$(bytes(lPosition + 4)) + Chr$(bytes(lPosition + 5)) + Chr$(bytes(lPosition + 6)) + Chr$(bytes(lPosition + 7)) = "stsc"
Dim lSize As Long
lSize = 16777216# * bytes(lPosition) + 65536# * bytes(lPosition + 1) + 256# * bytes(lPosition + 2) + bytes(lPosition + 3)
'Dim sBuffer As String
'sBuffer = BytesToString(bytes, 50, (lPosition))
wholefile.moov.trak.mdia.minf.stbl.stsc.type = "stsc"
wholefile.moov.trak.mdia.minf.stbl.stsc.size = lSize
Dim idx As Long
idx = lPosition + 8
wholefile.moov.trak.mdia.minf.stbl.stsc.version = BytesToLong(bytes, 1, idx)
wholefile.moov.trak.mdia.minf.stbl.stsc.flags = BytesToLong(bytes, 3, idx)
Dim lEntries As Long
lEntries = BytesToLong(bytes, 4, idx)
wholefile.moov.trak.mdia.minf.stbl.stsc.numberofentries = lEntries
Read_stsc = lPosition + lSize
End Function
Private Function Read_stsz(ByRef bytes() As Byte, ByVal lPosition As Long)
Debug.Assert Chr$(bytes(lPosition + 4)) + Chr$(bytes(lPosition + 5)) + Chr$(bytes(lPosition + 6)) + Chr$(bytes(lPosition + 7)) = "stsz"
Dim lSize As Long
lSize = 16777216# * bytes(lPosition) + 65536# * bytes(lPosition + 1) + 256# * bytes(lPosition + 2) + bytes(lPosition + 3)
'Dim sBuffer As String
'sBuffer = BytesToString(bytes, 50, (lPosition))
wholefile.moov.trak.mdia.minf.stbl.stsz.type = "stsz"
wholefile.moov.trak.mdia.minf.stbl.stsz.size = lSize
Dim idx As Long
idx = lPosition + 8
wholefile.moov.trak.mdia.minf.stbl.stsz.version = BytesToLong(bytes, 1, idx)
wholefile.moov.trak.mdia.minf.stbl.stsz.flags = BytesToLong(bytes, 3, idx)
Dim lEntries As Long
lEntries = BytesToLong(bytes, 4, idx)
wholefile.moov.trak.mdia.minf.stbl.stsz.numberofentries = lEntries
Read_stsz = lPosition + lSize
End Function
Private Function Read_stco(ByRef bytes() As Byte, ByVal lPosition As Long)
Debug.Assert Chr$(bytes(lPosition + 4)) + Chr$(bytes(lPosition + 5)) + Chr$(bytes(lPosition + 6)) + Chr$(bytes(lPosition + 7)) = "stco"
Dim lSize As Long
lSize = 16777216# * bytes(lPosition) + 65536# * bytes(lPosition + 1) + 256# * bytes(lPosition + 2) + bytes(lPosition + 3)
'Dim sBuffer As String
'sBuffer = BytesToString(bytes, 50, (lPosition))
wholefile.moov.trak.mdia.minf.stbl.stco.type = "stco"
wholefile.moov.trak.mdia.minf.stbl.stco.size = lSize
Dim idx As Long
idx = lPosition + 8
wholefile.moov.trak.mdia.minf.stbl.stco.version = BytesToLong(bytes, 1, idx)
wholefile.moov.trak.mdia.minf.stbl.stco.flags = BytesToLong(bytes, 3, idx)
Dim lEntries As Long
lEntries = BytesToLong(bytes, 4, idx)
wholefile.moov.trak.mdia.minf.stbl.stco.numberofentries = lEntries
Read_stco = lPosition + lSize
End Function
Private Function Read_dref(ByRef bytes() As Byte, ByVal lPosition As Long)
Debug.Assert Chr$(bytes(lPosition + 4)) + Chr$(bytes(lPosition + 5)) + Chr$(bytes(lPosition + 6)) + Chr$(bytes(lPosition + 7)) = "dref"
Dim lSize As Long
lSize = 16777216# * bytes(lPosition) + 65536# * bytes(lPosition + 1) + 256# * bytes(lPosition + 2) + bytes(lPosition + 3)
'Dim sBuffer As String
'sBuffer = BytesToString(bytes, 50, (lPosition))
wholefile.moov.trak.mdia.minf.dinf.dref.size = lSize
wholefile.moov.trak.mdia.minf.dinf.dref.type = "dref"
Dim idx As Long
idx = lPosition + 8
wholefile.moov.trak.mdia.minf.dinf.dref.version = BytesToLong(bytes, 1, idx)
wholefile.moov.trak.mdia.minf.dinf.dref.flags = BytesToLong(bytes, 3, idx)
Dim lEntries As Long
lEntries = BytesToLong(bytes, 4, idx)
wholefile.moov.trak.mdia.minf.dinf.dref.numberofentries = lEntries
If lEntries > 0 Then
ReDim wholefile.moov.trak.mdia.minf.dinf.dref.details(0 To lEntries - 1)
Dim lLoop As Long
For lLoop = 0 To lEntries - 1
Dim lDetailsSize As Long
lDetailsSize = 16777216# * bytes(idx) + 65536# * bytes(idx + 1) + 256# * bytes(idx + 2) + bytes(idx + 3)
wholefile.moov.trak.mdia.minf.dinf.dref.details(lLoop).size = lDetailsSize
wholefile.moov.trak.mdia.minf.dinf.dref.details(lLoop).type = _
Chr$(bytes(idx + 4)) + Chr$(bytes(idx + 5)) + Chr$(bytes(idx + 6)) + Chr$(bytes(idx + 7))
idx = idx + 8
wholefile.moov.trak.mdia.minf.dinf.dref.details(lLoop).version = BytesToLong(bytes, 1, idx)
wholefile.moov.trak.mdia.minf.dinf.dref.details(lLoop).flags = BytesToLong(bytes, 3, idx)
Dim lDataSize As Long
lDataSize = lDetailsSize - 12
If lDataSize > 0 Then
Stop
CopyBytes bytes(), wholefile.moov.trak.mdia.minf.dinf.dref.details(lLoop).data, idx, lDataSize
End If
idx = idx + lDataSize
Next lLoop
End If
Read_dref = lPosition + lSize
End Function
Private Function Read_smhd(ByRef bytes() As Byte, ByVal lPosition As Long)
'Dim sBuffer As String
'sBuffer = BytesToString(bytes, 50, (lPosition))
Dim lSize As Long
lSize = 16777216# * bytes(lPosition) + 65536# * bytes(lPosition + 1) + 256# * bytes(lPosition + 2) + bytes(lPosition + 3)
Debug.Assert Chr$(bytes(lPosition + 4)) + Chr$(bytes(lPosition + 5)) + Chr$(bytes(lPosition + 6)) + Chr$(bytes(lPosition + 7)) = "smhd"
Dim idx As Long
idx = lPosition + 8
wholefile.moov.trak.mdia.minf.smhd.type = "smhd"
wholefile.moov.trak.mdia.minf.smhd.size = lSize
wholefile.moov.trak.mdia.minf.smhd.version = BytesToLong(bytes, 1, idx)
wholefile.moov.trak.mdia.minf.smhd.flags = BytesToLong(bytes, 3, idx)
wholefile.moov.trak.mdia.minf.smhd.balance = BytesToLong(bytes, 2, idx)
wholefile.moov.trak.mdia.minf.smhd.reserved = BytesToLong(bytes, 2, idx)
Read_smhd = lPosition + lSize
End Function
Private Function Read_mdia_hdlr(ByRef bytes() As Byte, ByVal lPosition As Long)
lPosition = lPosition + 6 '* fake it 'til we make it
'Dim sBuffer As String
'sBuffer = BytesToString(bytes, 50, (lPosition))
Debug.Assert Chr$(bytes(lPosition + 4)) + Chr$(bytes(lPosition + 5)) + Chr$(bytes(lPosition + 6)) + Chr$(bytes(lPosition + 7)) = "hdlr"
Dim lSize As Long
lSize = 16777216# * bytes(lPosition) + 65536# * bytes(lPosition + 1) + 256# * bytes(lPosition + 2) + bytes(lPosition + 3)
wholefile.moov.trak.mdia.hdlr.size = lSize
wholefile.moov.trak.mdia.hdlr.type = "hdlr"
Dim idx As Long
idx = lPosition + 8
wholefile.moov.trak.mdia.hdlr.version = BytesToLong(bytes, 1, idx)
wholefile.moov.trak.mdia.hdlr.flags = BytesToLong(bytes, 3, idx)
wholefile.moov.trak.mdia.hdlr.componenttype = BytesToLong(bytes, 4, idx)
wholefile.moov.trak.mdia.hdlr.componentsubtype = BytesToString(bytes, 4, idx)
idx = idx + 12 'skip the reserved
Dim sRemainder As String
sRemainder = BytesToString(bytes, lPosition + lSize - idx, (idx))
wholefile.moov.trak.mdia.hdlr.componentname = sRemainder
Read_mdia_hdlr = lPosition + lSize
End Function
Private Function Read_tkhd(ByRef bytes() As Byte, ByVal lPosition As Long)
Debug.Assert Chr$(bytes(lPosition + 4)) + Chr$(bytes(lPosition + 5)) + Chr$(bytes(lPosition + 6)) + Chr$(bytes(lPosition + 7)) = "tkhd"
Dim lSize As Long
lSize = 16777216# * bytes(lPosition) + 65536# * bytes(lPosition + 1) + 256# * bytes(lPosition + 2) + bytes(lPosition + 3)
Dim idx As Long
idx = lPosition + 8
wholefile.moov.trak.tkhd.size = lSize
wholefile.moov.trak.tkhd.type = "tkhd"
wholefile.moov.trak.tkhd.version = BytesToLong(bytes, 1, idx)
wholefile.moov.trak.tkhd.flags = BytesToLong(bytes, 3, idx)
wholefile.moov.trak.tkhd.creationtime = BytesToLong(bytes, 4, idx)
wholefile.moov.trak.tkhd.modificationtime = BytesToLong(bytes, 4, idx)
wholefile.moov.trak.tkhd.trackid = BytesToLong(bytes, 4, idx)
wholefile.moov.trak.tkhd.reserved0 = BytesToLong(bytes, 4, idx)
wholefile.moov.trak.tkhd.duration = BytesToLong(bytes, 4, idx)
wholefile.moov.trak.tkhd.reserved1(0) = BytesToLong(bytes, 4, idx)
wholefile.moov.trak.tkhd.reserved1(1) = BytesToLong(bytes, 4, idx)
wholefile.moov.trak.tkhd.layer = BytesToLong(bytes, 2, idx)
wholefile.moov.trak.tkhd.alternationgroup = BytesToLong(bytes, 2, idx)
wholefile.moov.trak.tkhd.volume = BytesToLong(bytes, 2, idx)
wholefile.moov.trak.tkhd.Reserved2 = BytesToLong(bytes, 2, idx)
idx = idx + CopyBytes(bytes, wholefile.moov.trak.tkhd.matrixstructure, idx, 36)
wholefile.moov.trak.tkhd.trackwidth = BytesToLong(bytes, 4, idx)
wholefile.moov.trak.tkhd.trackheight = BytesToLong(bytes, 4, idx)
Debug.Print "[trak] size=8+" & CStr(lSize - 8)
Read_tkhd = lPosition + lSize
End Function
Private Function CopyBytes(ByRef srcbytes() As Byte, ByRef destbytes() As Byte, ByVal lIdx As Long, ByVal cBytes As Long) As Long
Dim x
For x = 0 To cBytes - 1
destbytes(x) = srcbytes(x)
Next x
CopyBytes = cBytes
End Function
Private Function BytesToLong(ByRef bytes() As Byte, ByVal cBytes As Long, ByRef plPosition As Long, Optional bLittleEndian As Boolean = False) As Long
BytesToLong = 0
Dim lLoop As Long
If bLittleEndian Then
For lLoop = cBytes - 1 To 0 Step -1
BytesToLong = BytesToLong * 256 + bytes(plPosition + lLoop)
Next
Else
For lLoop = 0 To cBytes - 1
BytesToLong = BytesToLong * 256 + bytes(plPosition + lLoop)
Next
End If
plPosition = plPosition + cBytes
End Function
Private Function BytesToString(ByRef bytes() As Byte, ByVal cBytes As Long, ByRef plPosition As Long) As String
Dim lLoop As Long
For lLoop = 0 To cBytes - 1
BytesToString = BytesToString & Chr$(bytes(plPosition + lLoop))
Next
plPosition = plPosition + cBytes
End Function
Private Function ReadByteFile(ByVal sFileName As String) As Byte()
Debug.Assert fso.FileExists(sFileName)
Dim fileNum As Integer
Dim bytes() As Byte
fileNum = FreeFile
Open sFileName For Binary As fileNum
ReDim bytes(LOF(fileNum) - 1)
Get fileNum, , bytes
Close fileNum
ReadByteFile = bytes
End Function