---
title: "Create a DASH MPD from MP4 in C#"
url: https://recursiveai.org/onemedia/cookbook/create-dash-mpd-from-mp4/
description: "Create a DASH MPD from an MP4 in C# without re-encoding: OneMedia (a preview) writes CMAF segments and a manifest.mpd with a segment timeline. Real output, ffprobe packet check and dash.js playback. No FFmpeg or MP4Box needed."
version: "0.7.0-preview.1"
---

# Create a DASH MPD from MP4 in C#

`Media.PackageDash("in.mp4", "dash")` cuts a video into CMAF segments, one directory per track, and writes the DASH manifest, `manifest.mpd`, that describes them. Nothing is re-encoded: the segments carry the MP4’s own coded pictures and audio frames. Packaging is new in 0.7.0-preview.1 and is a preview.

> **A preview that no validator has passed.** In the README’s words:
>
> - “**No validator has passed the output.** Apple `mediastreamvalidator` was not run (it needs an Apple ID sign-in). The DASH-IF conformance tool v3.1.0 ran once (two findings, both fixed) and then fails in its own base-URI handling on every run.”
> - “**What did check it:** packet equality with the input through ffprobe (28 CMAF and 19 transport-stream inputs, 0 unexplained), TSDuck continuity on 38 of 38 transport-stream segment sets, and playback in hls.js 1.7.4 and dash.js 5.2.1 in Chrome 155, Firefox 141 and Playwright WebKit 26 (one failure: HEVC in transport-stream segments through hls.js in Chrome, not investigated) plus native HLS in macOS WebKit (7 of 7).”

```shell
dotnet new console -o Mp4ToDash && cd Mp4ToDash
dotnet add package RecursiveAI.OneMedia --version 0.7.0-preview.1
curl -L -o sintel.mp4 https://download.blender.org/durian/trailer/sintel_trailer-480p.mp4
```

The input is the Sintel trailer (© copyright Blender Foundation, durian.blender.org, CC BY 3.0): 52 seconds of H.264 video and AAC audio. Put this in `Program.cs` and run `dotnet run`:

```csharp
using RecursiveAI.OneMedia;
using RecursiveAI.OneMedia.Streaming;

// CMAF segments of about six seconds, cut on keyframes, and a DASH manifest.
PackagingReport report = Media.PackageDash("sintel.mp4", "dash");

foreach (PackagedTrack track in report.Tracks)
    Console.WriteLine($"{track.Name}: {track.Kind} {track.Codecs}, {track.Segments} segments, " +
                      $"{track.Bytes:N0} bytes, {track.DurationSeconds:0.###} s");
foreach (string manifest in report.Manifests)
    Console.WriteLine($"wrote {manifest}");
foreach (var loss in report.Losses)
    Console.WriteLine($"not carried: {loss.Kind} {loss.Subject}: {loss.Reason}");
foreach (string note in report.Notes)
    Console.WriteLine($"note: {note}");
```

```text
video0: Video avc1.64001E, 6 segments, 3,525,834 bytes, 52.208 s
audio0: Audio mp4a.40.2, 6 segments, 842,751 bytes, 51.947 s
wrote manifest.mpd
```

## The manifest

```shell
cat dash/manifest.mpd
```

```text
<?xml version="1.0" encoding="utf-8"?>
<MPD type="static" profiles="urn:mpeg:dash:profile:isoff-live:2011" minBufferTime="PT2S" mediaPresentationDuration="PT52.208334S" maxSegmentDuration="PT10.432S" xmlns="urn:mpeg:dash:schema:mpd:2011">
  <Period id="0" start="PT0S">
    <AdaptationSet id="0" contentType="video" segmentAlignment="true" startWithSAP="1" mimeType="video/mp4">
      <Role schemeIdUri="urn:mpeg:dash:role:2011" value="main" />
      <Representation id="video0" codecs="avc1.64001E" width="854" height="480" bandwidth="1080650">
        <SegmentTemplate timescale="24" startNumber="1" media="video0/seg-$Number%05d$.m4s" initialization="video0/init.mp4">
          <SegmentTimeline>
            <S t="0" d="224" />
            <S d="172" />
            <S d="175" />
            <S d="201" />
            <S d="250" />
            <S d="231" />
          </SegmentTimeline>
        </SegmentTemplate>
      </Representation>
    </AdaptationSet>
    <AdaptationSet id="1" contentType="audio" segmentAlignment="true" startWithSAP="1" mimeType="audio/mp4">
      <Role schemeIdUri="urn:mpeg:dash:role:2011" value="main" />
      <Representation id="audio0" codecs="mp4a.40.2" audioSamplingRate="48000" bandwidth="132045">
        <AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2" />
        <SegmentTemplate timescale="48000" startNumber="1" media="audio0/seg-$Number%05d$.m4s" initialization="audio0/init.mp4">
          <SegmentTimeline>
            <S t="0" d="448512" />
            <S d="344064" />
            <S d="350208" />
            <S d="401408" />
            <S d="500736" />
            <S d="448512" />
          </SegmentTimeline>
        </SegmentTemplate>
      </Representation>
    </AdaptationSet>
  </Period>
</MPD>
```

A static manifest in the DASH live profile, which is the one for segment templates: each representation names its `init.mp4` and its numbered segments, and the `SegmentTimeline` lists each segment’s length in the track’s own timescale (224 of 24 is the first video segment’s 9.33 s). A segment ends at the first keyframe at or after six seconds. The files:

```shell
find dash -type f | sort | tr '\n' ' ' | fold -s -w 100
```

```text
dash/audio0/init.mp4 dash/audio0/seg-00001.m4s dash/audio0/seg-00002.m4s dash/audio0/seg-00003.m4s
dash/audio0/seg-00004.m4s dash/audio0/seg-00005.m4s dash/audio0/seg-00006.m4s dash/manifest.mpd
dash/video0/init.mp4 dash/video0/seg-00001.m4s dash/video0/seg-00002.m4s dash/video0/seg-00003.m4s
dash/video0/seg-00004.m4s dash/video0/seg-00005.m4s dash/video0/seg-00006.m4s
```

`PackagingOptions.SingleFile` writes one `media.mp4` per track instead, with a `sidx` index, and the manifest uses the on-demand profile with byte ranges. To write HLS playlists for the same segments, call `Packager.Package` with `Formats = PackagingFormats.Both`, or see [Convert MP4 to HLS](https://recursiveai.org/onemedia/cookbook/convert-mp4-to-hls/).

## Check it with ffprobe

ffprobe reads the manifest itself (give it an absolute path: its DASH reader fails on `dash/manifest.mpd`). Every packet of both tracks has the time, keyframe flag, size and bytes (MD5) it has in the MP4:

```shell
for s in v:0 a:0; do
  for f in sintel.mp4 "$PWD/dash/manifest.mpd"; do
    ffprobe -v error -select_streams $s -show_entries packet=pts_time,flags,size,data_hash -show_data_hash MD5 -of csv=p=0 $f > $(basename $f).csv
  done
  cmp -s sintel.mp4.csv manifest.mpd.csv && echo "$s: $(wc -l < manifest.mpd.csv) packets equal to the MP4's"
done
```

```text
v:0: 1253 packets equal to the MP4's
a:0: 2435 packets equal to the MP4's
```

And one segment, the fourth video segment after its initialization segment:

```shell
cat dash/video0/init.mp4 dash/video0/seg-00004.m4s > seg4.mp4
ffprobe -v error -count_packets -show_entries stream=codec_name,width,height,nb_read_packets -of compact seg4.mp4
ffprobe -v error -show_entries packet=pts_time,flags -of compact seg4.mp4 | head -n 2
```

```text
stream|codec_name=h264|width=854|height=480|nb_read_packets=201
packet|pts_time=23.791667|flags=K__
packet|pts_time=23.875000|flags=___
```

It starts at 23.79 s, where the timeline’s first three segments end (224 + 172 + 175 = 571 of 24), on a keyframe.

## Play it in a browser

Serve the `dash` folder from any web server and give [dash.js](https://github.com/Dash-Industry-Forum/dash.js) the manifest:

```html
<video id="video" controls muted></video>
<script src="https://cdn.jsdelivr.net/npm/dashjs@5.2.1/dist/modern/umd/dash.all.min.js"></script>
<script>
  dashjs.MediaPlayer().create().initialize(document.getElementById("video"), "dash/manifest.mpd", true);
</script>
```

Served from a local web server, dash.js 5.2.1 in headless Chrome 149 played this output with no errors: 854 × 480, duration 52.208 s, `readyState` 4, the time advancing from 0.50 s to 2.50 s over two seconds, then a seek to 30 s that landed at 30.000 and went on playing (31.96 s two seconds later).

From the command line, `onemedia package sintel.mp4 dash --dash` does the same (`dotnet tool install --global RecursiveAI.OneMedia.Tool --version 0.7.0-preview.1`).

## Limits

- **A preview that no validator has passed.** The project verifies DASH “by the packet differential and by dash.js playback, not by the conformance tool. This is a known gap.” ([Packaging](https://recursiveai.org/onemedia/docs/packaging/)).
- **No encryption.** From the README: “encryption is not written; live mode takes one input and holds the current segment in memory”.
- **No re-encoding.** Segments can only start on the input’s keyframes. A bit-rate ladder needs inputs already encoded at each rate (`Packager.Package(new[] { "1080p.mp4", "720p.mp4" }, "dash")`), and renditions switch cleanly only when their keyframes coincide.
- **CMAF only.** DASH is written with CMAF segments; transport-stream segments are for HLS.
- **Reading DASH back.** `Media.Open("dash/manifest.mpd")` reads the presentation as one file ([Read an HLS stream](https://recursiveai.org/onemedia/cookbook/read-hls-stream/), which works the same for an MPD). From the README: “live DASH is not reloaded”.
