MPlatform and MFormats SDK can expose the clock timestamp associated with each received video frame through a Windows named pipe. This is a low-level option for applications that need timing information closer to the device capture callback than the normal frame-retrieval path.
The application enables the feature with the genlock.pipe_name property, starts the live source, connects to the named pipe, and reads a sequence of 8-byte signed integers. Each value uses a 10,000,000-ticks-per-second time base, so one tick is 100 nanoseconds.
Important: This feature exposes source timestamps. It does not configure a genlock input, lock a capture card to a reference signal, or generate a COM frame event.
Supported sources and SDK versions
| Source | Value written to the pipe | Notes |
|---|---|---|
| Blackmagic Design DeckLink input | The frame time returned by the DeckLink hardware-reference timestamp API, requested with a 10,000,000-ticks-per-second time scale. | Currently available only through the legacy Blackmagic capture backend. Set bmd.new_api=false before enumerating or selecting the device. |
| NDI Receiver | The NDI video-frame receive timestamp. NDI defines it as the sender-side submission time in 100-nanosecond units. | NDI is provided as a network timing and test path; it is not a physical genlock source. If the sender does not provide this value, NDI returns Int64.MaxValue. |
The property is available in SDK 2.11.0 and later. The current implementation does not provide this named-pipe output for AJA devices or for SDI output renderers.
When this can be useful
- Correlating application events, sensor data, or control messages with the capture-device clock.
- Measuring input cadence, clock drift, or timing jitter without using the arrival time of
SourceFrameGetas the clock value. - Driving an application-owned scheduler from the timing of a DeckLink input, after calibrating the device clock against the application's clock domain.
- Comparing NDI sender timestamps with local receipt or processing times during diagnostics.
Do not treat the received integer as UTC time. The value belongs to the source's clock domain. Use differences between timestamps for cadence measurements, or calculate and maintain an offset if it must be compared with another clock.
Configure the source
Use a unique pipe name for every live-source instance. A short name is recommended, for example decklink_clock_1. The SDK converts it to the full local Windows path \\.\pipe\decklink_clock_1.
MPlatform SDK
For MPlatform, use object::genlock.pipe_name. The following fragment shows the required ordering for a DeckLink input. Keep the normal device and input-line enumeration from your application; the names below are placeholders.
using MPLATFORMLib;
const string pipeName = "decklink_clock_1";
MLiveClass live = new MLiveClass();
// Required by the current DeckLink named-pipe implementation.
// Set this before DeviceGetCount, DeviceGetByIndex, or DeviceSet.
live.PropsSet("bmd.new_api", "false");
// MPlatform forwards this property to the selected live-input backend.
live.PropsSet("object::genlock.pipe_name", pipeName);
live.DeviceSet("video", selectedDeckLinkDeviceName, "");
live.DeviceSet("video::line-in", selectedInputLineName, "");
live.ObjectStart(null);
// Connect to and read the pipe on a worker thread after ObjectStart.
For an NDI input, do not change bmd.new_api. Set the pipe property, select NDI Receiver and the required NDI input line, and start the object normally:
const string pipeName = "ndi_clock_1";
MLiveClass live = new MLiveClass();
live.PropsSet("object::genlock.pipe_name", pipeName);
live.DeviceSet("video", "NDI Receiver", "");
live.DeviceSet("video::line-in", selectedNdiInputLine, "");
live.ObjectStart(null);
MFormats SDK
For MFormats, use the property without the object:: prefix. Set the properties before device enumeration because bmd.new_api determines which Blackmagic capture backend is enumerated.
using MFORMATSLib;
const string pipeName = "decklink_clock_1";
MFLiveClass live = new MFLiveClass();
live.PropsSet("bmd.new_api", "false");
live.PropsSet("genlock.pipe_name", pipeName);
// Enumerate devices and select the required DeckLink input.
int deviceCount;
live.DeviceGetCount(eMFDeviceType.eMFDT_Video, out deviceCount);
live.DeviceSet(eMFDeviceType.eMFDT_Video, selectedDeviceIndex, "");
// With the default device.start_at_deviceset=true, capture starts at DeviceSet.
// If that property is disabled, capture starts on the first SourceFrameGet call.
See Initialization of a device for input for complete device, input-line, and input-format enumeration examples.
Read timestamps in C#
The SDK is the named-pipe server and the application is the client. Start the capture first, then connect with the short pipe name. Read until exactly 8 bytes have been received; a single Read call is not guaranteed to return the whole value.
using System;
using System.IO;
using System.IO.Pipes;
static void ReadSourceClock(string pipeName)
{
using (var pipe = new NamedPipeClientStream(
".",
pipeName,
PipeDirection.In,
PipeOptions.Asynchronous))
{
// Perform connection and reading on a worker thread, not on the UI thread.
pipe.Connect(5000);
var data = new byte[sizeof(long)];
while (ReadExactly(pipe, data))
{
long timestamp100ns = BitConverter.ToInt64(data, 0);
// NDI uses Int64.MaxValue when a receive timestamp is unavailable.
if (timestamp100ns == long.MaxValue)
continue;
double timestampSeconds = timestamp100ns / 10000000.0;
ProcessSourceTimestamp(timestamp100ns, timestampSeconds);
}
}
}
static bool ReadExactly(Stream stream, byte[] buffer)
{
int offset = 0;
while (offset < buffer.Length)
{
int read = stream.Read(buffer, offset, buffer.Length - offset);
if (read == 0)
return false;
offset += read;
}
return true;
}
BitConverter.ToInt64 is appropriate for a Windows client because the SDK writes the native 64-bit integer representation. If the pipe is consumed by another runtime, decode an 8-byte signed little-endian integer.
Read timestamps in C++
const wchar_t* pipePath = L"\\\\.\\pipe\\decklink_clock_1";
HANDLE pipe = CreateFileW(
pipePath,
GENERIC_READ,
0,
nullptr,
OPEN_EXISTING,
0,
nullptr);
if (pipe != INVALID_HANDLE_VALUE)
{
int64_t timestamp100ns = 0;
DWORD bytesRead = 0;
while (ReadFile(
pipe,
×tamp100ns,
sizeof(timestamp100ns),
&bytesRead,
nullptr) &&
bytesRead == sizeof(timestamp100ns))
{
const double timestampSeconds =
static_cast<double>(timestamp100ns) / 10'000'000.0;
ProcessSourceTimestamp(timestamp100ns, timestampSeconds);
}
CloseHandle(pipe);
}
Delivery behavior and limitations
- The pipe supports one connected client. A second client cannot subscribe to the same source pipe.
- Use a unique name for each live source. The SDK creates the first instance of that pipe name; a name collision prevents the pipe from being created.
- Delivery is best effort. If no client is connected or the previous asynchronous write is still pending, that frame's timestamp is not queued for later delivery. Do not use the pipe as a lossless frame counter.
- The pipe carries timestamps for video frames only. It does not contain audio timestamps, frame pixels, timecode, a source identifier, or a protocol-version header.
- Restarting or reinitializing the capture device closes and recreates the pipe. The client must detect the broken connection and reconnect.
- DeckLink and NDI values are not guaranteed to share the same clock origin. Do not compare their absolute values without clock-domain calibration.
- The current implementation does not publish pipe state or dropped timestamp counts through SDK statistics.
Troubleshooting
The client cannot connect
- Make sure capture has started before connecting.
- Pass only the short name to
NamedPipeClientStream, for exampledecklink_clock_1, not the full\\.\pipe\...path. - Verify that no other source or process is using the same pipe name.
- For DeckLink, confirm that
bmd.new_api=falsewas set before device enumeration.
The NDI value is 9223372036854775807
This is Int64.MaxValue, which NDI uses when the sender-side receive timestamp is unavailable. Ignore the value or use another timing source.
Some timestamps are missing
Keep the read loop continuously active on a dedicated worker. The pipe is a low-latency notification path rather than a durable queue, so slow or interrupted readers can miss values.