Table of Contents

MP4 Fragment Writer Sample

The IsoFragmentWriterSample demonstrates how to use IsoFormatParser to generate fragmented MP4 (fMP4) segments from a remote media source. Unlike regular MP4 recording where the entire file is written as a single unit, fragmented MP4 produces an initialization segment followed by a sequence of independent media fragments — the same format used by HLS and MPEG-DASH streaming.

This approach is useful when you need to produce fMP4 segments for a custom streaming pipeline, CDN integration, or segment-based storage.

Overview

The IsoFragmentWriterSample class performs the following:

  1. Creates a media source from a URI using SourceFactory
  2. On the Opened state, initializes an IsoFormatParser in write mode with segmented output
  3. Writes the initialization fragment containing codec configuration (ftyp + moov boxes)
  4. Queues incoming samples and processes them on a background task
  5. Produces a new media fragment (moof + mdat boxes) at each video key frame

Creating the Source

this.source = VAST.Media.SourceFactory.Create(uri);

this.source.Uri = uri;
this.source.NewSample += source_NewSample;
this.source.Error += source_Error;
this.source.StateChanged += source_StateChanged;

this.source.Open();

SourceFactory creates the appropriate source for the URI protocol (rtsp://, rtmp://, srt://, etc.).

Initializing the Fragment Writer

When the source reaches the Opened state, the fragment writer is created and the initialization segment is produced:

case VAST.Media.MediaState.Opened:

    this.writer = new VAST.File.ISO.IsoFormatParser(false);
    this.writer.Parameters = new VAST.File.ISO.ParsingParameters { UseSegments = true };

    var outputStream = new VAST.Common.VersatileBufferStream { AutoGrowth = true };
    this.writer.SetOutputStream(outputStream);

    var sd = new List<VAST.Common.MediaType>();
    for (int i = 0; i < this.source.StreamCount; i++)
    {
        VAST.Common.MediaType mt = this.source.GetMediaType(i);
        this.streams.Add(new StreamContext { InputMediaType = mt });
        sd.Add(mt);
    }
    this.writer.StreamDescriptor = sd;

    this.writer.Flush();
    this.initializationFragment = outputStream;
    this.initializationFragment.SetLength(this.initializationFragment.Position);
    this.initializationFragment.Seek(0, System.IO.SeekOrigin.Begin);

    // TODO: process initializationFragment accordingly

    this.source.Start();
    break;

Setup Steps

  1. Create the parsernew IsoFormatParser(false) creates a parser in write mode. UseSegments = true enables fragmented MP4 output.
  2. Set output stream — a VersatileBufferStream with AutoGrowth is used as an in-memory output buffer.
  3. Set stream descriptors — the source's media types are collected via GetMediaType and assigned to StreamDescriptor. This tells the writer about the video and audio tracks.
  4. Flush the initialization fragment — the first Flush() writes the ftyp and moov boxes. The resulting stream contains the initialization segment that must be sent to clients before any media fragments.

Queuing Samples

private void source_NewSample(object sender, VAST.Media.NewSampleEventArgs e)
{
    this.inputSampleQueue.Enqueue(e.Sample);
    e.Sample.AddRef();
}

Incoming samples are queued with AddRef() to keep them alive until the background task processes them.

Producing Fragments

The background task dequeues samples, writes them to the fragment writer, and produces a new fragment at each video key frame:

if (originalInputSample.KeyFrame
    && streamContext.InputMediaType.ContentType == VAST.Common.ContentType.Video)
{
    if (this.currentFragmentStream != null)
    {
        // flush the current fragment
        this.writer.Flush();

        this.currentFragmentStream.SetLength(this.currentFragmentStream.Position);
        this.currentFragmentStream.Seek(0, System.IO.SeekOrigin.Begin);
        // TODO: currentFragmentStream contains fragment, process it accordingly

        this.currentFragmentStream.Dispose();
        this.currentFragmentStream = null;
    }

    // create new stream for the next fragment
    this.currentFragmentStream = new VAST.Common.VersatileBufferStream { AutoGrowth = true };
    this.writer.SetOutputStream(this.currentFragmentStream);
}

this.writer.Encode(originalInputSample, originalInputSample.StreamIndex);

Fragment Lifecycle

  1. Wait for key frame — all samples before the first video key frame are dropped to ensure fragments start with a clean reference point.
  2. Write samplesEncode writes each media sample (video or audio) to the current output stream.
  3. Key frame boundary — when a new video key frame arrives, Flush() finalizes the current fragment (moof + mdat boxes). The completed fragment stream is available for processing.
  4. New output stream — a fresh VersatileBufferStream is created for the next fragment, and SetOutputStream redirects the writer to it.

Each fragment contains all video and audio samples between two consecutive video key frames. Fragment duration is therefore determined by the source's key frame interval (GOP size).

Output Structure

The writer produces the following fMP4 structure:

Output Content Description
Initialization fragment ftyp + moov Codec configuration, track definitions. Sent once before any media fragments.
Media fragment 1 moof + mdat Media samples from 1st key frame to 2nd key frame
Media fragment 2 moof + mdat Media samples from 2nd key frame to 3rd key frame
... ... One fragment per GOP

See Also