Media.Remux("in.mp4", "out.ts") copies the tracks of a video into an MPEG transport stream: one program with PAT and PMT, PES packets with PTS and DTS, and a PCR, written strictly forward. H.264 and HEVC get start codes in place of their length prefixes and the parameter sets in front of each keyframe, and AAC gets ADTS headers; the coded pictures and audio frames themselves are copied, not re-encoded. Use .m2ts or .mts for 192-byte M2TS packets. TS and M2TS writing are new in 0.6.0-preview.1.
dotnet new console -o Mp4ToTs && cd Mp4ToTs dotnet add package RecursiveAI.OneMedia --version 0.6.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): H.264 video and AAC audio. Put this in Program.cs and run dotnet run:
using RecursiveAI.OneMedia; using RecursiveAI.OneMedia.Remuxing; // The extension picks the target: .ts (188-byte packets), .m2ts or .mts (192-byte packets). string input = args.Length > 0 ? args[0] : "sintel.mp4"; string output = args.Length > 1 ? args[1] : "sintel.ts"; RemuxReport report = Media.Remux(input, output); foreach (RemuxTrackReport track in report.Tracks) if (track.OutputTrack >= 0) Console.WriteLine($"track {track.InputTrack} -> {track.OutputTrack}: {track.Packets} packets copied"); foreach (RemuxLoss loss in report.Losses) Console.WriteLine($"not carried: {loss.Kind} {loss.Subject}: {loss.Reason}: {loss.Detail}"); foreach (string change in report.Changes) Console.WriteLine($"changed: {change}"); Console.WriteLine($"{report.Mux.BytesWritten:N0} bytes written"); if (report.LostMediaTracks.Count > 0) Console.WriteLine($"LOST: input track(s) {string.Join(", ", report.LostMediaTracks)}");
track 0 -> 0: 1253 packets copied track 1 -> 1: 2435 packets copied not carried: Tag ilst: NotWritten: 5 tags: ©nam, ©ART, ©too, ©cpy, desc. not carried: Disposition Default: NotRepresentable: A transport stream has no field for track dispositions. not carried: Disposition Default: NotRepresentable: A transport stream has no field for track dispositions. changed: Input track 0: times were moved by -2/24 s, the start of its first edit and its priming, because a transport stream has no edit list. 5,083,332 bytes written
Every packet of both tracks is written. A transport stream has no place for the MP4’s iTunes tags or for track dispositions, so the report lists them, and it has no edit list, so the video’s start offset is moved into the packet times and the report says by how much.
Check it with ffmpeg
ffprobe -v error -count_packets -show_entries stream=codec_name,nb_read_packets,duration:format=format_name,duration -of compact sintel.tsprogram|stream|codec_name=h264|duration=52.208333|nb_read_packets=1253 stream|codec_name=aac|duration=51.946667|nb_read_packets=2435 stream|codec_name=h264|duration=52.208333|nb_read_packets=1253 stream|codec_name=aac|duration=51.946667|nb_read_packets=2435 format|format_name=mpegts|duration=52.208333
The audio packets differ from the MP4’s by their ADTS headers, and the video by its start codes and parameter sets, so compare what they decode to. Every one of the 1,253 pictures and the whole soundtrack decode identically from both files:
for f in sintel.mp4 sintel.ts; do echo "$f video $(ffmpeg -v error -i $f -map 0:v:0 -f framemd5 - | grep -v '^#' | cut -d, -f6 | md5sum)" echo "$f audio $(ffmpeg -v error -i $f -map 0:a:0 -f s16le - | md5sum)" done
sintel.mp4 video 8290074c3615185b9bfb28105932785d - sintel.mp4 audio d58b078f881cebc4d6b932a6cbf62924 - sintel.ts video 8290074c3615185b9bfb28105932785d - sintel.ts audio d58b078f881cebc4d6b932a6cbf62924 -
Browsers don’t play a .ts file in a <video> element (Chrome 149 refused this one); transport streams are for broadcast and recording tools, set-top boxes and players such as VLC and ffplay. HLS uses TS segments, but HLS packaging is not in this release.
Limits
- What a TS refuses. From the README: “TS refuses Opus and AAC whose layout is in a program config element, and writes a decode-order Matroska VFW H.264 track with PTS equal to DTS”. Vorbis, FLAC, VP8/VP9/AV1, PCM, ALAC, AMR and WMA are refused too (MPEG-TS muxer); a refused track is listed in the report, and
LostMediaTracksnames it when it was video or all of the audio. - One program. Captions inside the video, teletext, DVB subtitles, timed ID3 and SCTE 35 are carried; tags, dispositions, multi-edit edit lists and rotation have no TS form and are listed as not carried.
- No packaging. In the README’s words, “HLS/DASH packaging and HLS/DASH sources are planned and not in this release.”
- No re-encoding. To go the other way,
Media.Remux("in.ts", "out.mp4")re-frames the TS’s Annex B H.264 and ADTS AAC for MP4 (Read MPEG-TS info).