The SDK processes timecode together with media frames. If the source contains embedded timecode, the SDK reads and propagates that source timecode. If the source does not contain timecode data, the SDK generates frame timecode from the media position.
Timecode support is available in both MFormats SDK and MPlatform SDK. The same core structures are used in both SDKs:
M_TIME— frame timing information;M_TIMECODE— SMPTE-style timecode value;eMTimecodeFlags— flags that describe the timecode type, source, rate, and high-frame-rate markers.
Timecode basic concepts
A timecode value identifies a frame by hours, minutes, seconds, and frame number. The SDK stores this value in the M_TIMECODE structure.
public struct M_TIMECODE
{
public int nHours;
public int nMinutes;
public int nSeconds;
public int nFrames;
public eMTimecodeFlags eTCFlags;
public int nExtraCounter;
}The fields are:
nHours— hours part of the timecode;nMinutes— minutes part of the timecode;nSeconds— seconds part of the timecode;nFrames— frame number part of the timecode;eTCFlags— timecode flags, including drop-frame, non-drop-frame, rate, source timecode, user timecode, local-time timecode, and high-frame-rate markers;nExtraCounter— optional extra counter. Depending on the source, this can be used for additional data such as a device counter, file frame number, playlist file index, or timecode user bits.
Frame timing information is stored in the M_TIME structure.
public struct M_TIME
{
public long rtStartTime;
public long rtEndTime;
public M_TIMECODE tcFrame;
public eMFrameFlags eFFlags;
}The fields are:
rtStartTime— frame start time, usually the frame position in the file or stream, in 100-nanosecond units;rtEndTime— frame end time, in 100-nanosecond units;tcFrame— frame timecode as anM_TIMECODEstructure;eFFlags— frame flags, such as break, live, network, and other frame state flags.
Drop-frame and non-drop-frame timecode
The SDK distinguishes drop-frame and non-drop-frame timecode with eMTimecodeFlags and with the textual separator used in timecode strings.
01:02:03:04means non-drop-frame timecode;01:02:03;04means drop-frame timecode.
Non-drop-frame timecode uses eMTCF_NonDropFrame. Drop-frame timecode uses eMTCF_DropFrame. Drop-frame timecode is used for 29.97 and 59.94 rates to compensate the difference between frame count and clock time. For 23.98, drop-frame timecode is not used.
Important note: for 1000/1001 rates such as 23.98, 29.97, and 59.94, non-drop-frame timecode values are frame labels and are not equal to real elapsed clock time. For example, 01:00:00:00 at a 1000/1001 clock is not exactly one real hour.
Timecode flags
The eTCFlags field in M_TIMECODE describes how the timecode should be interpreted.
Basic flags
eMTCF_NonDropFrame— non-drop-frame timecode;eMTCF_DropFrame— drop-frame timecode;eMTCF_Invalid— timecode is not set and should be ignored.
Rate flags
Rate flags can be present in timecode values, especially when seeking by timecode or when interpreting returned timecode:
eMTCF_Rate_24— 24 or 23.98 fps;eMTCF_Rate_25— 25 fps;eMTCF_Rate_30— 30 or 29.97 non-drop-frame;eMTCF_Rate_30_DF— 29.97 drop-frame;eMTCF_Rate_50— 50p;eMTCF_Rate_60— 60p or 59.94 non-drop-frame;eMTCF_Rate_60_DF— 59.94 drop-frame.
Source, user, and local timecode flags
eMTCF_SrcTC— the source has an embedded timecode stream or embedded source timecode;eMTCF_UserTC— the timecode was specified by the user;eMTCF_SrcTCUserbits— source timecode contains user bits stored innExtraCounter;eMTCF_LocalTimeTC— timecode is generated from local system time;eMTCF_LocalTimeSyncTC— timecode is generated from local system time and continues to synchronize with local time.
High-frame-rate flags
For progressive frame rates greater than 30 fps, the SDK uses progressive frame flags:
eMTCF_Progressive_Even_Frame— even frame in a progressive frame pair;eMTCF_Progressive_Odd_Frame— odd frame in a progressive frame pair.
Source and user timecode variants also exist, for example eMTCF_SrcTC_Progressive_Even_Frame, eMTCF_SrcTC_Progressive_Odd_Frame, eMTCF_UserTC_Progressive_Even_Frame, and eMTCF_UserTC_Progressive_Odd_Frame.
Get start timecode
The start timecode is stored as a property.
- For an
MFileobject in MPlatform SDK, use thefile::start_timecodeproperty. - For an
MFReaderobject in MFormats SDK, use thestart_timecodeproperty.
The resulting string also indicates whether the value is drop-frame or non-drop-frame timecode:
01:02:03:04— non-drop-frame timecode;01:02:03;04— drop-frame timecode.
MFormats SDK example
MFReader reader = null;
try
{
reader = new MFReaderClass();
reader.ReaderOpen(filePath, "");
reader.PropsGet("start_timecode", out string startTimecode);
Console.WriteLine($"Start timecode: {startTimecode}");
reader.ReaderClose();
}
finally
{
if (reader != null)
Marshal.ReleaseComObject(reader);
}MPlatform SDK example
MFile file = null;
try
{
file = new MFileClass();
file.FileNameSet(filePath, "");
file.PropsGet("file::start_timecode", out string startTimecode);
Console.WriteLine($"Start timecode: {startTimecode}");
}
finally
{
if (file != null)
Marshal.ReleaseComObject(file);
}Get current frame timecode
For regular frame processing, use the frame timecode stored in M_TIME.tcFrame. This is the effective frame timecode selected by the SDK for the current frame.
MFormats SDK
In MFormats SDK, use IMFFrame.MFTimeGet to get the M_TIME structure from an MFFrame.
MFReader reader = null;
try
{
reader = new MFReaderClass();
reader.ReaderOpen(filePath, "");
MFFrame frame = null;
try
{
((IMFSource)reader).SourceFrameGet(-1, out frame, "");
M_TIME time;
frame.MFTimeGet(out time);
M_TIMECODE tc = time.tcFrame;
Console.WriteLine(
$"{tc.nHours:D2}:{tc.nMinutes:D2}:{tc.nSeconds:D2}:{tc.nFrames:D2}, " +
$"flags={tc.eTCFlags}");
}
finally
{
if (frame != null)
Marshal.ReleaseComObject(frame);
}
reader.ReaderClose();
}
finally
{
if (reader != null)
Marshal.ReleaseComObject(reader);
}MPlatform SDK
In MPlatform SDK, use IMFrame.FrameTimeGet to get the M_TIME structure from an MFrame.
MFile file = null;
try
{
file = new MFileClass();
file.FileNameSet(filePath, "");
MFrame frame = null;
try
{
file.FileFrameGet(-1, 0, out frame);
M_TIME time;
frame.FrameTimeGet(out time);
M_TIMECODE tc = time.tcFrame;
Console.WriteLine(
$"{tc.nHours:D2}:{tc.nMinutes:D2}:{tc.nSeconds:D2}:{tc.nFrames:D2}, " +
$"flags={tc.eTCFlags}");
}
finally
{
if (frame != null)
Marshal.ReleaseComObject(frame);
}
}
finally
{
if (file != null)
Marshal.ReleaseComObject(file);
}Timecode and fast playback
During fast playback, the timecode from M_TIME.tcFrame may not match the actual displayed frame timecode. In this case, for MFormats frames, use the SRMT_START_TIME string attribute. This value considers the play rate.
frame.MFStrGet("SRMT_START_TIME", out string srmtStartTime);MXF SMPTE S12M / RP 188 / SMPTE 385M timecodes
MXF files may contain several SMPTE timecode sources. The SDK can expose these timecodes as frame data and normalize them to the regular M_TIMECODE representation used by M_TIME.tcFrame.
This is used for MXF files that contain MXF package timecode tracks, SMPTE RP 188 ancillary timecode, or SMPTE 385M / 326M SDTI-CP System Item timecode.
When available, source timecode entries are attached to each frame as data pairs:
s12m_tc_source0,s12m_tc_source1,s12m_tc_source2, ... — the source identifier stored as aneMFTimecodeSourcevalue;s12m_tc_data0,s12m_tc_data1,s12m_tc_data2, ... — the correspondingM_TIMECODEvalue.
The indexes are zero-based. Read entries sequentially until the source key or the data key is missing, has a null pointer, or has an unexpected size.
Timecode source values
The source identifier is represented by the eMFTimecodeSource enum:
enum eMFTimecodeSource
{
eMF_Timecode_Unknown = 0, // Source not identified.
eMF_Timecode_Material, // Material Package timecode track (MXF).
eMF_Timecode_Source, // Source Package timecode track (MXF).
eMF_Timecode_LTC, // Linear Time Code (RP 188 SDID=0x61).
eMF_Timecode_VITC, // Vertical Interval Time Code (RP 188 SDID=0x60).
eMF_Timecode_SDTI_CP, // SDTI-CP System Item (SMPTE 385M / 326M).
}eMF_Timecode_Unknownmeans that the timecode source was not identified.eMF_Timecode_Materialidentifies an MXF Material Package timecode track.eMF_Timecode_Sourceidentifies an MXF Source Package timecode track.eMF_Timecode_LTCidentifies Linear Time Code from SMPTE RP 188 ancillary data.eMF_Timecode_VITCidentifies Vertical Interval Time Code from SMPTE RP 188 ancillary data.eMF_Timecode_SDTI_CPidentifies SDTI-CP System Item timecode according to SMPTE 385M / 326M.
The s12m_tc_data* entries contain the individual source timecodes found in the frame. The M_TIME.tcFrame value returned by MFTimeGet is the normalized frame timecode selected by the SDK for regular frame processing.
Applications that only need the effective frame timecode can use M_TIME.tcFrame. Applications that need to inspect all embedded MXF/SMPTE timecode sources should read the s12m_tc_source* and s12m_tc_data* entries.
For the FFmpeg-based MXF reader path, open the file with:
string props = "mxf.force_ffmpeg='true'";
reader.ReaderOpen(filePath, props);Read MXF SMPTE timecode entries
IMFReader reader = null;
try
{
reader = new MFReaderClass();
reader.ReaderOpen(filePath, "mxf.force_ffmpeg='true'");
for (int frameIndex = 0; frameIndex < 10; frameIndex++)
{
MFFrame frame = null;
try
{
((IMFSource)reader).SourceFrameGet(-1, out frame, "");
M_TIME frameTime;
frame.MFTimeGet(out frameTime);
M_TIMECODE effectiveTc = frameTime.tcFrame;
Console.WriteLine(
$"frame={frameIndex}, effective tc=" +
$"{effectiveTc.nHours:D2}:{effectiveTc.nMinutes:D2}:" +
$"{effectiveTc.nSeconds:D2}:{effectiveTc.nFrames:D2}, " +
$"flags={effectiveTc.eTCFlags}");
int sourceCount = 0;
for (int tcIndex = 0; ; tcIndex++)
{
string sourceKey = $"s12m_tc_source{tcIndex}";
frame.MFDataGet(sourceKey, out int sourceSize, out long sourcePtrRaw);
if (sourcePtrRaw == 0 || sourceSize != sizeof(int))
break;
string dataKey = $"s12m_tc_data{tcIndex}";
frame.MFDataGet(dataKey, out int dataSize, out long dataPtrRaw);
if (dataPtrRaw == 0 ||
dataSize != Marshal.SizeOf<M_TIMECODE>())
{
break;
}
var source = (eMFTimecodeSource)Marshal.ReadInt32(
new IntPtr(sourcePtrRaw));
var sourceTc = Marshal.PtrToStructure<M_TIMECODE>(
new IntPtr(dataPtrRaw));
Console.WriteLine(
$" source={source}, tc=" +
$"{sourceTc.nHours:D2}:{sourceTc.nMinutes:D2}:" +
$"{sourceTc.nSeconds:D2}:{sourceTc.nFrames:D2}, " +
$"flags={sourceTc.eTCFlags}");
sourceCount++;
}
Console.WriteLine(
$"frame={frameIndex}, source timecode entries={sourceCount}");
}
finally
{
if (frame != null)
Marshal.ReleaseComObject(frame);
}
}
reader.ReaderClose();
}
finally
{
if (reader != null)
Marshal.ReleaseComObject(reader);
}The number of available SMPTE timecode entries depends on the MXF file and the embedded metadata. A file may expose one or several source entries per frame. For example, an MXF file can expose four SMPTE timecode source entries on each frame.
H.264 timecodes
If an H.264 source stores timecode in frame fields, the SDK converts it to SMPTE timecode and processes it as regular frame timecode.
For reading H.264 timecode from a file or network stream, use:
h264_timecode.force=true h264_timecode.every_frame_update=trueh264_timecode.force=true enables forced H.264 timecode recognition. h264_timecode.every_frame_update=true makes timecode recognition update on every frame, which can be useful for network streams where received frame timing may differ from the original source timing.
For writing H.264 timecode, use embed_tc=true in the writer encoding configuration together with start_timecode. This embeds the configured timecode into the encoded H.264 video stream, so it can be recovered by a compatible receiver.
Seeking by timecode
MPlatform SDK
MPlatform SDK provides dedicated timecode methods on IMFile.
FilePosSetTC— set file position by timecode;FilePosGetTC— get current file position as timecode;FileInOutSetTC— set file in and out points by timecode;FileInOutGetTC— get file in and out points as timecode;FileFrameGetByTC— get a frame at the specified timecode.
Set position by timecode
M_TIMECODE tc = new M_TIMECODE
{
nHours = 1,
nMinutes = 2,
nSeconds = 3,
nFrames = 4,
eTCFlags = eMTimecodeFlags.eMTCF_NonDropFrame
};
file.FilePosSetTC(ref tc);Get current position by timecode
M_TIMECODE currentTc;
file.FilePosGetTC(out currentTc);
Console.WriteLine(
$"{currentTc.nHours:D2}:{currentTc.nMinutes:D2}:" +
$"{currentTc.nSeconds:D2}:{currentTc.nFrames:D2}");Set in and out points by timecode
M_TIMECODE tcIn = new M_TIMECODE
{
nHours = 0,
nMinutes = 1,
nSeconds = 0,
nFrames = 0,
eTCFlags = eMTimecodeFlags.eMTCF_NonDropFrame
};
M_TIMECODE tcOut = new M_TIMECODE
{
nHours = 0,
nMinutes = 2,
nSeconds = 0,
nFrames = 0,
eTCFlags = eMTimecodeFlags.eMTCF_NonDropFrame
};
file.FileInOutSetTC(ref tcIn, ref tcOut);Get frame by timecode
MFrame frame = null;
try
{
M_TIMECODE tc = new M_TIMECODE
{
nHours = 0,
nMinutes = 10,
nSeconds = 0,
nFrames = 0,
eTCFlags = eMTimecodeFlags.eMTCF_NonDropFrame
};
file.FileFrameGetByTC(ref tc, out frame);
M_TIME time;
frame.FrameTimeGet(out time);
Console.WriteLine(
$"{time.tcFrame.nHours:D2}:{time.tcFrame.nMinutes:D2}:" +
$"{time.tcFrame.nSeconds:D2}:{time.tcFrame.nFrames:D2}");
}
finally
{
if (frame != null)
Marshal.ReleaseComObject(frame);
}If the rate is not specified in eMTimecodeFlags, files use the original file frame rate. For playlists, specify the frame rate explicitly with the appropriate eMTimecodeFlags value.
MFormats SDK
In MFormats SDK, set the tc_pos property on MFReader to seek by timecode.
reader.PropsSet("tc_pos", "06:01:02:21");The seek is applied on the next frame request. After setting tc_pos, the next SourceFrameGet or SourceFrameConvertedGet call returns the frame with the specified timecode. If the timecode is invalid or cannot be found, the reader returns the first frame.
MFReader reader = null;
try
{
reader = new MFReaderClass();
reader.ReaderOpen(filePath, "");
reader.PropsSet("tc_pos", "06:01:02:21");
MFFrame frame = null;
try
{
((IMFSource)reader).SourceFrameGet(-1, out frame, "");
M_TIME time;
frame.MFTimeGet(out time);
M_TIMECODE tc = time.tcFrame;
Console.WriteLine(
$"{tc.nHours:D2}:{tc.nMinutes:D2}:{tc.nSeconds:D2}:{tc.nFrames:D2}");
}
finally
{
if (frame != null)
Marshal.ReleaseComObject(frame);
}
reader.ReaderClose();
}
finally
{
if (reader != null)
Marshal.ReleaseComObject(reader);
}Timecode encoding
If source frames contain timecode data, the SDK can encode this data into outputs that support timecode. Depending on the selected container, codec, and encoding options, timecode can be written as a container timecode track or embedded into the encoded video stream.
To control the encoded start timecode, use the start_timecode attribute in the encoding configuration.
Supported start_timecode values are:
auto— default value. Uses the original source timecode when available. If no source timecode is available, live sources uselocal_timeand file sources use00:00:00;disabled— disables timecode encoding;local_time— aligns the start timecode with the local system time;- a custom timecode string, for example
10:00:00:00— uses the specified value as the start timecode.
Container timecode
For containers that support a timecode track, such as MOV or MXF, the SDK can write frame timecode into the output container. This is the regular timecode encoding path for file formats with native timecode-track support.
H.264 stream timecode
For H.264 encoding, use embed_tc=true to insert the configured timecode into the encoded H.264 video stream. The timecode can then be restored by a receiver that supports H.264 timecode extraction.
The start_timecode option specifies the timecode value to encode. The embed_tc=true option enables embedding that timecode into the H.264 stream.
format='mpegts' protocol='udp://' video::codec='libopenh264' video::b='5M' audio::codec='aac' start_timecode='01:01:01:01' embed_tc=trueThe following example sends an H.264 stream over UDP with embedded timecode and then reads the stream back to check the received frame timecode:
MWriterClass writer = null;
MFileClass source = null;
MFileClass receiver = null;
try
{
writer = new MWriterClass();
source = new MFileClass();
receiver = new MFileClass();
string sourcePath =
@"\\mLdiskstation\MLFiles\MediaTest\MP4\Adele - Rolling In The Deep (Live WNYC).mp4";
source.FileNameSet(
sourcePath,
"h264_timecode.force=true h264_timecode.every_frame_update=true");
source.FilePlayStart();
source.ObjectStateGet(out eMState sourceState);
if (sourceState != eMState.eMS_Running)
return;
string encoderConfig =
"format='mpegts' protocol='udp://' " +
"video::codec='libopenh264' video::b='5M' " +
"audio::codec='aac' " +
"start_timecode='01:01:01:01' embed_tc=true";
string url = "udp://127.0.0.1:1234";
writer.WriterNameSet(url, encoderConfig);
writer.ObjectStart(source);
writer.ObjectStateGet(out eMState writerState);
if (writerState != eMState.eMS_Running)
return;
Thread.Sleep(1500);
receiver.FileNameSet(url, "");
receiver.FilePlayStart();
receiver.ObjectStateGet(out eMState receiverState);
if (receiverState != eMState.eMS_Running)
return;
MFrame frame = null;
try
{
receiver.SourceFrameGet(-1, out frame, 0);
M_TIME time;
frame.FrameTimeGet(out time);
Console.WriteLine(
$"Received timecode: " +
$"{time.tcFrame.nHours:D2}:{time.tcFrame.nMinutes:D2}:" +
$"{time.tcFrame.nSeconds:D2}:{time.tcFrame.nFrames:D2}");
}
finally
{
if (frame != null)
Marshal.ReleaseComObject(frame);
}
}
finally
{
if (receiver != null)
Marshal.ReleaseComObject(receiver);
if (source != null)
Marshal.ReleaseComObject(source);
if (writer != null)
Marshal.ReleaseComObject(writer);
}On the receiving side, H.264 timecode extraction can be enabled with h264_timecode.force=true and h264_timecode.every_frame_update=true when the stream needs more accurate per-frame timecode recognition.
For MRenderer or MFRenderer, you can also set the start_timecode property with PropsSet.
m_objMFRenderer.PropsSet("start_timecode", "11:11:11:11");High frame rates and timecode
SMPTE timecode has a limited frame number range. For progressive frame rates greater than 30 fps, such as 50p, 59.94p, and 60p, the SDK represents frame pairs with progressive frame flags.
For high-frame-rate sources, the SDK marks the two frame parts as:
.0— even progressive frame;.1— odd progressive frame.
Example for a 50p file:
00:06:35:15.0 = 00:06:35:(15 * 2 + 0) = 00:06:35:30
00:06:35:15.1 = 00:06:35:(15 * 2 + 1) = 00:06:35:31
00:06:35:16.0 = 00:06:35:(16 * 2 + 0) = 00:06:35:32
00:06:35:16.1 = 00:06:35:(16 * 2 + 1) = 00:06:35:33
00:06:35:17.0 = 00:06:35:(17 * 2 + 0) = 00:06:35:34
00:06:35:17.1 = 00:06:35:(17 * 2 + 1) = 00:06:35:35Recommended workflow
Reading timecode
- Read the start timecode from
start_timecodeorfile::start_timecode. - For every frame, read
M_TIMEwithMFTimeGetin MFormats SDK orFrameTimeGetin MPlatform SDK. - Use
M_TIME.tcFrameas the effective frame timecode. - Check
M_TIMECODE.eTCFlagsto determine whether the timecode is drop-frame, non-drop-frame, source-based, user-defined, local-time-based, or high-frame-rate progressive. - For MXF SMPTE S12M / RP 188 / SMPTE 385M sources, read
s12m_tc_source*ands12m_tc_data*frame data when access to all embedded source timecode entries is required. - For H.264 sources or network streams, use
h264_timecode.force=trueandh264_timecode.every_frame_update=truewhen forced or more accurate per-frame H.264 timecode recognition is required.
Seeking by timecode
- In MPlatform SDK, use
FilePosSetTC,FileInOutSetTC, andFileFrameGetByTC. - In MFormats SDK, set the
tc_posproperty and request the next frame.
Encoding timecode
- Use source timecode automatically when available.
- Use
start_timecodeto force a specific encoded start timecode. - Use
disabledwhen the output must not contain encoded timecode. - Use
local_timefor live workflows that need wall-clock-aligned timecode. - For H.264 encoding, use
embed_tc=truetogether withstart_timecodeto embed the timecode into the encoded H.264 video stream.