<rss version="2.0">
  <channel>
    <title>Streaming on LLBBL Blog</title>
    <link>https://llbbl.blog/categories/streaming/</link>
    <description></description>
    
    <language>en</language>
    
    <lastBuildDate>Sun, 23 Aug 2026 10:00:00 -0500</lastBuildDate>
    
    <item>
      <title>An AVIF Is an MP4 With One Frame</title>
      <link>https://llbbl.blog/2026/08/23/an-avif-is-an-mp.html</link>
      <pubDate>Sun, 23 Aug 2026 10:00:00 -0500</pubDate>
      
      <guid>http://llbbl.micro.blog/2026/08/23/an-avif-is-an-mp.html</guid>
      <description>&lt;p&gt;XML&amp;rsquo;s answer to structure was a schema language, a query language, and a namespace system. MP4&amp;rsquo;s answer is eight bytes, and it turned out to be enough to absorb an entire industry. Every byte in the file lives inside a box, and every box starts the same way:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;4 bytes   size, big-endian, including this header
4 bytes   type, four ASCII characters
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That is it. Eight bytes, and any parser that knows nothing else about the format can walk the entire file, skipping what it doesn&amp;rsquo;t understand. Some boxes contain other boxes. Some contain payload. There is no data outside a box anywhere in the file.&lt;/p&gt;
&lt;p&gt;That design is why the container outgrew video entirely.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;same-header-different-extension&#34;&gt;Same Header, Different Extension&lt;/h2&gt;
&lt;p&gt;Here is a ten-second 640x360 H.264 clip:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ftyp  32 bytes  @ 0
free  8 bytes  @ 32
mdat  63602 bytes  @ 40
moov  4424 bytes  @ 63642
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here is a three-second AAC audio file:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ftyp  28 bytes  @ 0
free  8 bytes  @ 28
mdat  26304 bytes  @ 36
moov  1287 bytes  @ 26340
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And here is a still image:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ftyp  32 bytes  @ 0
meta  235 bytes  @ 32
  hdlr  33 bytes  @ 44
  pitm  14 bytes  @ 77
  iloc  30 bytes  @ 91
  iinf  40 bytes  @ 121
  iprp  106 bytes  @ 161
    ipco  75 bytes  @ 169
    ipma  23 bytes  @ 244
mdat  17667 bytes  @ 267
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That last one is an AVIF. A photograph. It has the same box header format, the same &lt;code&gt;ftyp&lt;/code&gt; first, the same &lt;code&gt;mdat&lt;/code&gt; holding the payload. What changed is that a still image has no timeline, so instead of &lt;code&gt;moov&lt;/code&gt; with its sample tables it uses &lt;code&gt;meta&lt;/code&gt; with an item structure: &lt;code&gt;pitm&lt;/code&gt; names the primary item, &lt;code&gt;iloc&lt;/code&gt; says where in &lt;code&gt;mdat&lt;/code&gt; that item&amp;rsquo;s bytes live, &lt;code&gt;iprp&lt;/code&gt; carries its properties.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;ftyp&lt;/code&gt; box says which dialect you are reading:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ xxd -g 1 -l 32 shot.avif
00000000: 00 00 00 20 66 74 79 70 61 76 69 66 00 00 00 00  ... ftypavif....
00000010: 61 76 69 66 6d 69 66 31 6d 69 61 66 4d 41 31 42  avifmif1miafMA1B
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Size 32, type &lt;code&gt;ftyp&lt;/code&gt;, major brand &lt;code&gt;avif&lt;/code&gt;, then four compatible brands: &lt;code&gt;avif&lt;/code&gt;, &lt;code&gt;mif1&lt;/code&gt;, &lt;code&gt;miaf&lt;/code&gt;, &lt;code&gt;MA1B&lt;/code&gt;. The &lt;code&gt;.m4a&lt;/code&gt; file above declares &lt;code&gt;M4A &lt;/code&gt; with &lt;code&gt;isom&lt;/code&gt; as a compatible brand. The MP4 declares &lt;code&gt;isom&lt;/code&gt; with &lt;code&gt;iso2&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So &lt;code&gt;.mp4&lt;/code&gt;, &lt;code&gt;.mov&lt;/code&gt;, &lt;code&gt;.m4a&lt;/code&gt;, &lt;code&gt;.m4v&lt;/code&gt;, &lt;code&gt;.heic&lt;/code&gt;, and &lt;code&gt;.avif&lt;/code&gt; are one format with six extensions. Your iPhone photo library and your video library are the same container. That happened because in February 1998, ISO picked Apple&amp;rsquo;s QuickTime file format as the basis for MPEG-4&amp;rsquo;s container, and the box model turned out to be general enough that everyone who needed a container afterward just used it.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;the-box-in-the-wrong-place&#34;&gt;The Box in the Wrong Place&lt;/h2&gt;
&lt;p&gt;Now the flaw that shaped an entire decade of web video.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;moov&lt;/code&gt; box holds the sample tables: which frame starts at which byte, how long it lasts, which chunk it belongs to. &lt;code&gt;mdat&lt;/code&gt; holds the frames. A player can decode nothing until it has read &lt;code&gt;moov&lt;/code&gt;, because &lt;code&gt;mdat&lt;/code&gt; has no internal framing at all. It is one undifferentiated run of bytes, and the only thing that says where frame 0 begins is a number inside &lt;code&gt;moov&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Look at where &lt;code&gt;moov&lt;/code&gt; ended up in that first file. Byte 63,642 of a 68,066-byte file.&lt;/p&gt;
&lt;p&gt;An encoder writing sequentially cannot know the byte offset of the last chunk until it has written the last chunk, so the natural thing is to write all of &lt;code&gt;mdat&lt;/code&gt; and then append &lt;code&gt;moov&lt;/code&gt; at the end. That is what nearly every encoder did by default, and it means the player must reach the last 6% of the file before it can show you the first frame.&lt;/p&gt;
&lt;p&gt;The fix is a post-processing pass:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ ffmpeg -i input -c:v libx264 -movflags +faststart out.mp4
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ftyp  32 bytes  @ 0
moov  4424 bytes  @ 32
free  8 bytes  @ 4456
mdat  63602 bytes  @ 4464
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Same boxes. Same sizes. Same total file length, 68,066 bytes both times, and I checked the &lt;code&gt;mdat&lt;/code&gt; payloads byte for byte: identical. All that changed is the order.&lt;/p&gt;
&lt;p&gt;It is not quite a memmove, though, because &lt;code&gt;moov&lt;/code&gt;&amp;rsquo;s offsets are absolute positions in the file:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;plain.mp4    stco has 1 chunk offsets; first five: [48]
fast.mp4     stco has 1 chunk offsets; first five: [4472]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Moving &lt;code&gt;moov&lt;/code&gt; in front of &lt;code&gt;mdat&lt;/code&gt; pushed every byte of media 4,424 places later, so every entry in the chunk offset table had to be rewritten by exactly that amount. On a real file with thousands of chunks, that is thousands of pointers, all of which must be corrected, and if the rewrite changes the size of &lt;code&gt;moov&lt;/code&gt; (32-bit offsets overflowing into &lt;code&gt;co64&lt;/code&gt;) the whole thing has to be recomputed again.&lt;/p&gt;
&lt;p&gt;Absolute offsets are the design decision underneath most of MP4&amp;rsquo;s awkwardness. You cannot concatenate two MP4s. You cannot insert a second of video in the middle. You cannot append to a file that is still being written and have it remain playable. Everything is pointer arithmetic against byte zero.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;fragments-are-the-actual-answer&#34;&gt;Fragments Are the Actual Answer&lt;/h2&gt;
&lt;p&gt;Fragmented MP4 fixes it by giving up on the single index. Instead of one &lt;code&gt;moov&lt;/code&gt; describing the whole timeline, you get an initialization segment and then a run of self-describing chunks:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;[ ftyp + moov ]  [ moof + mdat ]  [ moof + mdat ]  [ moof + mdat ] ...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Each &lt;code&gt;moof&lt;/code&gt; carries the sample table for the &lt;code&gt;mdat&lt;/code&gt; that follows it, with offsets relative to the fragment rather than the file. Which means you can start writing before you know how long the video is, cut the stream anywhere, serve any fragment independently, and switch bitrates between fragments without the player noticing.&lt;/p&gt;
&lt;p&gt;That property is the entire basis of HLS and DASH. Every adaptive-bitrate stream you have watched is this: a manifest, an init segment, and a pile of &lt;code&gt;moof&lt;/code&gt;/&lt;code&gt;mdat&lt;/code&gt; pairs that a player stitches together while quietly swapping quality levels based on your bandwidth.&lt;/p&gt;
&lt;p&gt;It also means the &lt;code&gt;moov&lt;/code&gt; placement problem is now mostly historical for streaming and still completely current for files. Anything you upload, download, or store as a single &lt;code&gt;.mp4&lt;/code&gt; still has one &lt;code&gt;moov&lt;/code&gt;, and it is still in whichever place the encoder happened to put it.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;what-to-do-about-it&#34;&gt;What To Do About It&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Always pass &lt;code&gt;-movflags +faststart&lt;/code&gt;&lt;/strong&gt; when producing MP4 for the web. It costs one extra pass over the file at encode time and nothing at all afterward.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Check where &lt;code&gt;moov&lt;/code&gt; landed&lt;/strong&gt; before blaming the network. Eight bytes of parsing tells you: read the size at offset 0, jump, read the type, repeat. If &lt;code&gt;moov&lt;/code&gt; is last, that is your slow start.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use fMP4 for anything live or adaptive.&lt;/strong&gt; A single-file MP4 cannot be written and played at the same time, no matter how you order the boxes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Don&amp;rsquo;t concatenate MP4 files.&lt;/strong&gt; &lt;code&gt;cat a.mp4 b.mp4 &amp;gt; c.mp4&lt;/code&gt; produces a file whose first &lt;code&gt;moov&lt;/code&gt; describes only the first video and whose second &lt;code&gt;moov&lt;/code&gt; has offsets pointing into the wrong place. Remux with &lt;code&gt;ffmpeg -f concat&lt;/code&gt; instead.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Treat &lt;code&gt;.heic&lt;/code&gt; and &lt;code&gt;.avif&lt;/code&gt; as the same problem space.&lt;/strong&gt; If your image pipeline calls &lt;code&gt;identify&lt;/code&gt; or sniffs magic bytes, those files start with a box header, not a signature, and the four bytes that matter are at offset 4 rather than offset 0.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Read &lt;code&gt;ftyp&lt;/code&gt; compatible brands, not the extension.&lt;/strong&gt; A file named &lt;code&gt;.mp4&lt;/code&gt; can declare &lt;code&gt;qt  &lt;/code&gt;, and a file named &lt;code&gt;.mov&lt;/code&gt; can declare &lt;code&gt;isom&lt;/code&gt;. The brands are the truth.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Everything in this series so far has failed by underspecifying something. MP4 does not have that problem. The box model is rigorous, self-describing, and general enough that it absorbed still images without anyone having to redesign it. What it got wrong was one thing: it wrote down byte offsets instead of relative ones, and made the index a single object that has to be complete before it can be written. Two decades of streaming infrastructure exist to work around that decision.&lt;/p&gt;
&lt;h2 id=&#34;sources&#34;&gt;Sources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.iso.org/standard/68960.html&#34;&gt;ISO/IEC 14496-12&lt;/a&gt; — the ISO Base Media File Format; the box definition is clause 4.2&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://mp4ra.org/&#34;&gt;MP4 Registration Authority&lt;/a&gt; — the registry of every legal FourCC brand and box type&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://developer.apple.com/standards/classic-quicktime/&#34;&gt;Apple&amp;rsquo;s QuickTime File Format documentation&lt;/a&gt; — the atom model MP4 inherited&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://aomediacodec.github.io/av1-isobmff/&#34;&gt;AVIF specification&lt;/a&gt; — how AV1 intra frames map onto ISOBMFF items&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.rfc-editor.org/rfc/rfc8216&#34;&gt;RFC 8216&lt;/a&gt; — HTTP Live Streaming, which is fMP4 plus a text manifest&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ffmpeg.org/ffmpeg-formats.html&#34;&gt;ffmpeg movflags documentation&lt;/a&gt; — &lt;code&gt;+faststart&lt;/code&gt; and the fragmentation options&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;I&amp;rsquo;d appreciate a follow. You can subscribe with your email below. The emails go out once a week, or you can find me on Mastodon at &lt;a href=&#34;https://micro.blog/llbbl?remote_follow=1&#34;&gt;@logan@llbbl.blog&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
</description>
    </item>
    
  </channel>
</rss>