MFormats SDK and MPlatform SDK carry timecode together with media frames. A frame has media timestamps and a main timecode in M_TIME.tcFrame. Some readers also attach individual embedded timecodes as frame metadata. These values serve different purposes: use the main timecode for ordinary processing, or inspect the metadata when you need a particular embedded source.
On the FFmpeg decoding path, the main timecode can be derived from source timecode and media position. If no source timecode is available, it can be generated from media position. Do not assume that every embedded timecode is selected automatically as tcFrame.
Timecode basic concepts
M_TIMECODE contains the following fields:
| Field | Meaning |
|---|---|
nHours |
Hours. |
nMinutes |
Minutes. |
nSeconds |
Seconds. |
nFrames |
Frame-label component; interpret it together with the rate and progressive-frame flags. |
eTCFlags |
eMTimecodeFlags: validity, drop-frame mode, rate, provenance and progressive-frame markers. |
nExtraCounter |
Additional source-dependent information, such as a frame/device counter, playlist file index or timecode user bits. It has no universal interpretation. |
M_TIME contains:
| Field | Type and meaning |
|---|---|
rtStartTime |
64-bit frame start timestamp in 100-nanosecond units. |
rtEndTime |
64-bit frame end timestamp in 100-nanosecond units. |
tcFrame |
The main frame timecode, as M_TIMECODE. |
eFFlags |
Frame-state flags, such as break, live and network flags. |
Media timestamps describe frame timing; timecode supplies frame labels. Changing the timecode does not require changing the timestamps.
Drop-frame and non-drop-frame timecode
The SDK uses 01:02:03:04 for non-drop-frame timecode and 01:02:03;04 for drop-frame timecode. Drop-frame numbering compensates for the clock difference at 29.97 and 59.94 fps; it does not drop video frames. For 23.98 fps, use non-drop-frame timecode.
At 1000/1001 rates, non-drop-frame labels are not elapsed wall-clock time. Do not interpret a label such as 01:00:00:00 as exactly one elapsed hour.
Timecode flags
The following groups are useful when reading and setting timecode. Rate and provenance aliases combine several flags; they are not separate embedded-source identifiers.
| Group | Public names | Meaning |
|---|---|---|
| Validity and numbering |
eMTCF_Invalid, eMTCF_DropFrame, eMTCF_NonDropFrame
|
Ignore invalid timecode. The drop-frame bit selects drop-frame numbering; non-drop-frame has value zero. |
| Base rate |
eMTCF_Rate_24, eMTCF_Rate_25, eMTCF_Rate_30, eMTCF_Rate_30_DF
|
24/23.98, 25, 30/29.97 non-drop-frame, and 29.97 drop-frame respectively. |
| Progressive rates |
eMTCF_Rate_50, eMTCF_Rate_60, eMTCF_Rate_60_DF, and their _Odd variants |
50, 60/59.94 non-drop-frame, and 59.94 drop-frame, including the progressive-frame marker. |
| Progressive-frame markers |
eMTCF_Progressive_Even_Frame, eMTCF_Progressive_Odd_Frame
|
Distinguish frames in a high-frame-rate pair. The odd representation can include both marker bits. |
| Provenance |
eMTCF_SrcTC, eMTCF_UserTC
|
Source or user-specified timecode. Combined rate/progressive aliases also exist, such as eMTCF_SrcTC_Rate_25 and eMTCF_UserTC_Progressive_Odd_Frame. |
| User bits | eMTCF_SrcTCUserbits |
Source timecode user bits are carried in nExtraCounter. |
| Local time |
eMTCF_LocalTimeTC, eMTCF_LocalTimeSyncTC
|
Timecode initialized from local time, or continuously synchronized with local time. |
Test the drop-frame bit rather than using HasFlag(eMTCF_NonDropFrame), because the non-drop-frame value is zero.
Get start timecode
Read start-timecode properties after opening the source. A file-level start timecode does not list all embedded timecode sources and does not identify System Scheme 1.
| SDK and owner | Property | Use |
|---|---|---|
MFormats: MFReader
|
start_timecode |
Start-timecode value or an explicitly configured input override. |
MFormats: MFReader
|
info::start_timecode |
Detected source start-timecode metadata, when available. |
MPlatform: MFile
|
file::start_timecode |
File start timecode; the getter falls back to file::info::start_timecode when no explicit value is present. |
These C# fragments require already-open objects:
// MFormats: reader is an open MFReader.
reader.PropsGet("start_timecode", out string startTimecode);
if (string.IsNullOrEmpty(startTimecode))
reader.PropsGet("info::start_timecode", out startTimecode);
// MPlatform: file is an open MFile.
file.PropsGet("file::start_timecode", out string fileStartTimecode);A value may be absent if the source does not supply this metadata. Some timecode information becomes available only during decoding.
Get current frame timecode
In MFormats, call MFFrame.MFTimeGet. In MPlatform, call MFrame.FrameTimeGet. Both return M_TIME; use its tcFrame field and check eMTCF_Invalid.
// MFormats: frame is an acquired MFFrame.
frame.MFTimeGet(out M_TIME time);
M_TIMECODE tc = time.tcFrame;
// MPlatform: platformFrame is an acquired MFrame.
platformFrame.FrameTimeGet(out M_TIME platformTime);
M_TIMECODE platformTc = platformTime.tcFrame;The examples below show frame acquisition and cleanup. Release each acquired frame after processing it. A cast to another interface on that same frame is an alias, not another frame to release separately.
Timecode and fast playback
During rate conversion, distinguish output timing from the original source position. MFormats attaches SRMT_START_TIME as a decimal string containing the original frame start timestamp in 100-nanosecond units. It is a media timestamp, not a formatted SMPTE timecode.
// frame is a live, acquired MFFrame.
frame.MFStrGet("SRMT_START_TIME", out string sourceStartText);
if (long.TryParse(sourceStartText, out long sourceStart))
Console.WriteLine($"Source position: {sourceStart / 10000000.0:F3} s");Use MFTimeGet().tcFrame for the main timecode and SRMT_START_TIME when you need the source-position timestamp before rate conversion.
Embedded MXF/SMPTE timecodes
The FFmpeg MXF reader can expose several timecode sources on the same decoded frame: Material Package and Source Package tracks, RP 188 LTC/VITC, SDTI-CP System Item timecode, and MXF GC System Scheme 1 timecode.
System Scheme 1 support described here applies to frame-wrapped MXF with a single timecode element in its TimecodeArray. Multi-frame arrays used with clip/custom wrapping are not handled by this path. Availability depends on the installed SDK, matching interop definitions and codec modules.
Request the FFmpeg MXF path when opening the source:
// MFormats
reader.ReaderOpen(filePath, "mxf.force_ffmpeg=true");
// MPlatform
file.FileNameSet(filePath, "mxf.force_ffmpeg=true");The same unprefixed option is passed in each method's source-open parameters. Do not add the file:: property prefix inside these parameter strings. The feature described here concerns decoded frames on this backend; changing reader backends or requesting compressed packets is a different workflow.
Frame-data reference
Read these entries with IMFFrame.MFDataGet. MFormats frames expose this interface directly. MPlatform MFrame also exposes IMFFrame, so cast the frame to read the same metadata.
| Public frame-data name | Representation | Meaning and access |
|---|---|---|
s12m_tc_sourceN |
32-bit eMFTimecodeSource; 4 bytes. |
Reader-produced identifier for entry N. Read it to identify the source; it is not a reader configuration property. |
s12m_tc_dataN |
M_TIMECODE; check Marshal.SizeOf<M_TIMECODE>(). |
Reader-produced timecode paired with s12m_tc_sourceN. Copy the structure while the frame is alive. |
N starts at zero. Read consecutive pairs until an entry is missing or its size/pointer is invalid. There is no fixed public entry count: availability depends on the file and decoded frame. Never identify a source by its index, and never retain a returned native pointer after releasing the frame.
Timecode source values
The public eMFTimecodeSource identifiers are listed below. The enum describes source identity; it does not mean every source occurs in every file or reader path.
| Public enum name | Value | Meaning |
|---|---|---|
eMF_Timecode_Unknown |
0 | Source not identified. |
eMF_Timecode_Material |
1 | MXF Material Package timecode track. |
eMF_Timecode_Source |
2 | MXF Source Package timecode track. |
eMF_Timecode_LTC |
3 | RP 188 Linear Time Code, SDID 0x61. |
eMF_Timecode_VITC |
4 | RP 188 Vertical Interval Time Code, SDID 0x60. |
eMF_Timecode_SDTI_CP |
5 | SDTI-CP System Item timecode, SMPTE 385M / 326M. |
eMF_Timecode_SEI |
6 | H.264/H.265 SEI picture-timing source identifier. |
eMF_Timecode_SystemScheme1 |
7 | MXF GC System Scheme 1, ST 394/405. |
System Scheme 1 can be returned alongside other sources. Its presence does not automatically make it the main tcFrame timecode. Read it by source identity, use the copied value directly, or explicitly assign it to a frame you own. There is no Scheme 1 source-selection reader setting in this workflow.
Read the pairs and find System Scheme 1
The following C# methods are shared by the two examples below. Place them in your application class. Use the namespace for your SDK interop assembly: MFORMATSLib or MPLATFORMLib, together with System and System.Runtime.InteropServices.
static string FormatTimecode(M_TIMECODE tc)
{
if ((tc.eTCFlags & eMTimecodeFlags.eMTCF_Invalid) != 0)
return "invalid";
string separator =
(tc.eTCFlags & eMTimecodeFlags.eMTCF_DropFrame) != 0 ? ";" : ":";
return $"{tc.nHours:D2}:{tc.nMinutes:D2}:{tc.nSeconds:D2}" +
$"{separator}{tc.nFrames:D2}";
}
static bool ReadTimecodes(IMFFrame frame, out M_TIMECODE scheme1)
{
scheme1 = new M_TIMECODE();
bool found = false;
for (int i = 0; ; i++)
{
frame.MFDataGet($"s12m_tc_source{i}",
out int sourceSize, out long sourcePointer);
if (sourceSize != sizeof(int) || sourcePointer == 0)
break;
frame.MFDataGet($"s12m_tc_data{i}",
out int dataSize, out long dataPointer);
if (dataSize != Marshal.SizeOf<M_TIMECODE>() || dataPointer == 0)
break;
var source = (eMFTimecodeSource)Marshal.ReadInt32(
new IntPtr(sourcePointer));
var tc = Marshal.PtrToStructure<M_TIMECODE>(
new IntPtr(dataPointer));
Console.WriteLine($"{source}: {FormatTimecode(tc)}, flags={tc.eTCFlags}");
if (!found && source == eMFTimecodeSource.eMF_Timecode_SystemScheme1)
{
scheme1 = tc;
found = true;
}
}
return found;
}MFormats: inspect a pulled frame
This method opens a local MXF file and inspects one decoded frame. Set useScheme1 to replace that frame's main timecode when a valid Scheme 1 entry exists. In a processing loop, make the replacement before sending the frame to your receiver.
static void InspectMFormats(string filePath, bool useScheme1)
{
MFReaderClass reader = null;
MFFrame frame = null;
try
{
reader = new MFReaderClass();
reader.ReaderOpen(filePath, "mxf.force_ffmpeg=true");
reader.SourceFrameGet(50000000, out frame, ""); // 5-second wait
if (frame == null)
return;
frame.MFTimeGet(out M_TIME time);
Console.WriteLine($"Main: {FormatTimecode(time.tcFrame)}");
bool found = ReadTimecodes((IMFFrame)frame, out M_TIMECODE scheme1);
if (useScheme1 && found &&
(scheme1.eTCFlags & eMTimecodeFlags.eMTCF_Invalid) == 0)
{
time.tcFrame = scheme1;
frame.MFTimeSet(ref time);
// In a frame loop, deliver this frame to your receiver here.
}
}
finally
{
try
{
if (frame != null)
Marshal.ReleaseComObject(frame);
}
finally
{
if (reader != null)
{
try { reader.ReaderClose(); }
finally { Marshal.ReleaseComObject(reader); }
}
}
}
}MPlatform: inspect an isolated file frame
MPlatform normally runs a connected source-to-receiver pipeline. The method below uses FileFrameGet to inspect a single local-file frame; it is not an application-owned playback loop. Replacing timecode on this returned frame does not change the source's running connected playback.
static void InspectMPlatform(string filePath, bool useScheme1)
{
MFileClass file = null;
MFrame frame = null;
try
{
file = new MFileClass();
file.FileNameSet(filePath, "mxf.force_ffmpeg=true");
file.FileFrameGet(0.0, 0.0, out frame);
if (frame == null)
return;
frame.FrameTimeGet(out M_TIME time);
Console.WriteLine($"Main: {FormatTimecode(time.tcFrame)}");
IMFFrame metadata = (IMFFrame)frame; // Alias of the same frame.
bool found = ReadTimecodes(metadata, out M_TIMECODE scheme1);
if (useScheme1 && found &&
(scheme1.eTCFlags & eMTimecodeFlags.eMTCF_Invalid) == 0)
{
time.tcFrame = scheme1;
frame.FrameTimeSet(ref time);
}
}
finally
{
try
{
if (frame != null)
Marshal.ReleaseComObject(frame);
}
finally
{
if (file != null)
{
try { file.ObjectClose(); }
finally { Marshal.ReleaseComObject(file); }
}
}
}
}Both examples read the existing M_TIME and change only tcFrame. This preserves rtStartTime, rtEndTime and eFFlags. Passing a newly zero-initialized M_TIME to the setter would overwrite those fields.
H.264 timecodes
The FFmpeg decoding path can recognize H.264 SEI picture-timing timecode and use it for frame timecode. The following source-open options control this behavior:
| Option | Type | Meaning |
|---|---|---|
h264_timecode.force |
Boolean | Allows H.264 timecode recognition to replace an existing container-derived start timecode. |
h264_timecode.every_frame_update |
Boolean | Updates frame timecode from recognized H.264 timecode on subsequent frames, useful when network timing differs from source timing. |
Set the options explicitly when needed, rather than depending on a backend fallback:
// MFormats
reader.ReaderOpen(inputPathOrUrl,
"h264_timecode.force=true h264_timecode.every_frame_update=true");
// MPlatform
file.FileNameSet(inputPathOrUrl,
"h264_timecode.force=true h264_timecode.every_frame_update=true");These are input options. To embed H.264 timecode on output, use the writer configuration described below.
Seeking by timecode
Timecode seeking maps a timecode to media position using the source start timecode and frame rate. It is not a search through every s12m_tc_dataN entry. Reading or assigning Scheme 1 metadata does not change that seek mapping.
MPlatform SDK
| Method on IMFile | Purpose |
|---|---|
FilePosSetTC |
Set playback position by timecode. |
FilePosGetTC |
Get playback position represented as timecode. |
FileInOutSetTC |
Set in/out points by timecode. |
FileInOutGetTC |
Get in/out timecodes and whether an out point was specified. |
FileFrameGetByTC |
Retrieve an isolated file frame at the mapped position. |
These fragments require an open local-file MFile. Choose labels that belong to that file's timecode range.
M_TIMECODE target = new M_TIMECODE
{
nHours = 1, nMinutes = 2, nSeconds = 3, nFrames = 4,
eTCFlags = eMTimecodeFlags.eMTCF_NonDropFrame
};
file.FilePosSetTC(ref target);
file.FilePosGetTC(out M_TIMECODE currentTc);
M_TIMECODE tcIn = new M_TIMECODE
{
nHours = 1, nMinutes = 0, nSeconds = 0, nFrames = 0,
eTCFlags = eMTimecodeFlags.eMTCF_NonDropFrame
};
M_TIMECODE tcOut = new M_TIMECODE
{
nHours = 1, nMinutes = 1, nSeconds = 0, nFrames = 0,
eTCFlags = eMTimecodeFlags.eMTCF_NonDropFrame
};
file.FileInOutSetTC(ref tcIn, ref tcOut);
file.FileInOutGetTC(out M_TIMECODE actualIn, out M_TIMECODE actualOut,
out int outSpecified);
MFrame selectedFrame = null;
try
{
file.FileFrameGetByTC(ref target, out selectedFrame);
selectedFrame.FrameTimeGet(out M_TIME selectedTime);
Console.WriteLine(FormatTimecode(selectedTime.tcFrame));
}
finally
{
if (selectedFrame != null)
Marshal.ReleaseComObject(selectedFrame);
}If rate flags are omitted, files use the original file frame rate. For playlists, specify the appropriate rate flags explicitly. Close and release the owning file when your application finishes using it.
MFormats SDK
On an open MFReader, set tc_pos and request the next frame without an explicit time or frame number:
reader.PropsSet("tc_pos", "06:01:02:21");
MFFrame selectedFrame = null;
try
{
reader.SourceFrameGet(50000000, out selectedFrame, "");
selectedFrame.MFTimeGet(out M_TIME selectedTime);
Console.WriteLine(FormatTimecode(selectedTime.tcFrame));
}
finally
{
if (selectedFrame != null)
Marshal.ReleaseComObject(selectedFrame);
}The pending property is consumed during frame retrieval. The reader converts the requested label to a position relative to its start timecode. A request at or before the start maps to position zero. Do not rely on an arbitrary invalid or missing label always returning the first frame; inspect the returned frame and handle retrieval errors. Close and release the reader after processing.
Timecode encoding
Writer timecode depends on the selected container, codec and muxer. A container start timecode and an embedded H.264 timecode are separate output mechanisms. Reading Scheme 1 metadata does not by itself enable Scheme 1 output.
For the FFmpeg writer path, include start_timecode in the encoding configuration before starting output:
| Value | Meaning |
|---|---|
auto or omitted |
Uses the first input frame's main timecode when a first frame is supplied at encoding initialization. If initialization has no first frame, local system time is used. Do not assume a universal live/file fallback. |
disabled |
Suppresses the SDK's automatic start-timecode metadata. It does not guarantee that the container contains no timecode; for example, an MXF muxer can generate its own default timecode. |
local_time |
Initializes the encoded start timecode from local system time. |
A timecode string, such as 10:00:00:00
|
Uses the specified start label. |
Container timecode
Containers such as MOV and MXF have native timecode support. The muxer interprets the configured start-timecode metadata according to the output format; this is not a guarantee that all per-frame embedded source entries are preserved.
H.264 stream timecode
On the FFmpeg H.264 output path, embed_tc=true enables timecode embedding in the encoded stream. It is disabled by default. Combine it with start_timecode to choose an output start label. Do not infer H.265 embedding support from the SEI source enum.
string encoderConfig =
"format='mpegts' protocol='udp://' " +
"video::codec='libopenh264' video::b='5M' audio::codec='aac' " +
"start_timecode='01:01:01:01' embed_tc=true";The following are configuration fragments; the application supplies the destination, compatible source media and lifecycle management:
// MPlatform: writer and source are application-owned objects.
writer.WriterNameSet(destinationUrl, encoderConfig);
writer.ObjectStart(source);
source.FilePlayStart();
// The connected pipeline supplies frames to the writer.
// MFormats: configure the writer before your frame loop.
mfWriter.WriterSet(destinationUrl, 1, encoderConfig);
// For each acquired MFFrame, after any timecode replacement:
mfWriter.ReceiverFramePut(frame, 50000000, "");For a UDP example, destinationUrl can be udp://127.0.0.1:1234. Use a compatible receiver, enable the H.264 input options on that receiver when needed, and inspect timecode on its decoded frames. A Running state alone does not verify received timecode.
During shutdown, stop production and drain application workers. Close the MPlatform writer before its source with ObjectClose, then release the owned objects. In MFormats, release each frame after delivery, call WriterClose(0) to flush and close the writer synchronously, then close the reader with ReaderClose and release the owned objects in finally blocks. Inspect the output file after the synchronous close returns successfully. The argument selects asynchronous closing: WriterClose(1) can return while queued frames are still being finalized.
To request output without optional timecode, use start_timecode=disabled, leave embed_tc disabled, and check the selected format's output. Some containers or copied compressed streams can still carry timecode.
Device output timecode
MFRenderer supports a configurable start_timecode that replaces outgoing frame timecode with a sequence starting at the supplied label. The MPlatform MRenderer path using the MFormats renderer forwards this property. Set it before delivering frames or starting connected output:
renderer.PropsSet("start_timecode", "11:11:11:11");Transmission of that timecode depends on the selected output device and its capabilities.
High frame rates and timecode
At 50p, 59.94p and 60p, the SDK can represent a pair of progressive frames with one base frame label plus a progressive-frame marker. Preserve the flags when copying timecode; the four numeric components alone can lose the distinction between adjacent frames.
For a 50p pair representation, a suffix of .0 or .1 can illustrate the even and odd member:
00:06:35:15.0 corresponds to full-rate frame label 00:06:35:30
00:06:35:15.1 corresponds to full-rate frame label 00:06:35:31
00:06:35:16.0 corresponds to full-rate frame label 00:06:35:32
00:06:35:16.1 corresponds to full-rate frame label 00:06:35:33This illustrates the pair mapping; do not add the suffix based only on nFrames parity or assume every SDK text formatter emits this notation. Interpret the progressive-frame flags and the actual media rate.
Recommended workflow
- Open the source with options for the intended reader backend.
- Read available start metadata, then read
M_TIMEon each frame. - Use
tcFramefor ordinary processing. For a specific embedded source, enumerate the timecode pairs and match its source identifier. - If replacing the main timecode on an owned frame, preserve the rest of
M_TIMEand make the change before downstream delivery. - Configure output timecode separately, then inspect the resulting file or received frames.
- Copy metadata before releasing its frame, and close and release all application-owned SDK objects.
Still have questions?
Tell us what didn’t work or what you’d like clarified.