SDI (Blackmagic DeckLink)
bilbycast-edge can capture and play out SDI directly on Blackmagic
DeckLink cards — video plus embedded audio, with no external SDI→IP
converter in the path. It talks to the Blackmagic SDK through a first-party C++
shim rather than FFmpeg’s decklink avdevice, because the avdevice hides
bmdFrameHasNoInputSource, which makes a pulled cable indistinguishable from a
live feed.
SDI lives behind the sdi-decklink build feature, which is off in a
plain cargo build. This page is about turning it on.
Why there is a build step at all
Section titled “Why there is a build step at all”The Blackmagic SDK is EULA-gated: you have to accept Blackmagic’s licence to download it, and it cannot be redistributed inside this project. Only the SDK headers are needed, and only while compiling.
| Needed at build time | Needed at run time | |
|---|---|---|
SDK headers (DeckLinkAPI.h, DeckLinkAPIDispatch.cpp) |
Yes | No |
libDeckLinkAPI.so (part of Desktop Video) |
No | Yes |
| A physical DeckLink card | No | Yes |
The SDK’s DeckLinkAPIDispatch.cpp loads libDeckLinkAPI.so dynamically at
runtime, so you can compile an SDI-capable binary on a machine with no card and
no driver installed. Conversely, an SDI-capable binary runs perfectly well on a
host with no card: the boot probe finds nothing, the node never advertises the
sdi-decklink capability, and the manager UI hides the SDI input and output
types for it. There is no runtime cost to carrying SDI on a non-SDI host.
Checklist A — build it yourself
Section titled “Checklist A — build it yourself”For anyone building bilbycast-edge from source on their own machine. No GitHub account, private repository, or credential is involved.
-
Download the SDK. Go to blackmagicdesign.com/support, search for “Desktop Video SDK”, choose version 16 or newer, and download it. You will need to complete Blackmagic’s registration form and accept their EULA.
-
Unzip it and locate
Linux/include. That single directory contains bothDeckLinkAPI.handDeckLinkAPIDispatch.cpp. Nothing else from the SDK is used by the build. -
Point
DECKLINK_SDK_DIRat that directory — the directory itself, not its parent:Terminal window export DECKLINK_SDK_DIR=/path/to/Blackmagic_DeckLink_SDK_16.0/Linux/include# Both of these must list. If they don't, you are one level off.ls "$DECKLINK_SDK_DIR"/DeckLinkAPI.h "$DECKLINK_SDK_DIR"/DeckLinkAPIDispatch.cpp -
Build with the feature enabled, alongside whatever else you already use:
Terminal window cargo build --release --features sdi-decklink,video-encoder-x264SDI also needs
media-codecs, which is on by default. If you disable it, both the SDI input and output refuse to start withsdi_no_media_codecs— neither can encode or decode without it. -
Install Desktop Video on the machine with the card, from the same support page. This provides the kernel driver and
libDeckLinkAPI.so. -
Check the operating system sees the card before suspecting the build:
Terminal window ls /dev/blackmagic/ -
Check the edge sees it. Start the edge, then confirm
sdi-decklinkappears in the node’s advertised capabilities and that per-port hardware status is populated. In the manager UI, the SDI input and output types become selectable for that node.
Checklist B — build it in GitHub Actions
Section titled “Checklist B — build it in GitHub Actions”For project maintainers, and for anyone running their own fork’s CI. The
release workflow already requests sdi-decklink for the two *-linux-full
artefacts (the *-rockchip row omits it deliberately) — it strips the feature
at build time when the SDK is unavailable, warns, and continues. So this is
entirely about making the headers reachable from CI. No workflow edit is
required. Once a credential does reach the run, the Verify binary step
asserts the shim symbol is linked in and fails the release if it is not, so the
feature cannot silently rot back out.
The SDK is not vendored into the public repository. It lives in a separate private repository that CI checks out.
-
Download the SDK and locate
Linux/include, exactly as in Checklist A steps 1–2. -
Create a private repository for the headers — for this project,
Bilbycast/bilbycast-decklink-sdk. Private is the point: nothing is redistributed. -
Commit only
Linux/include. NotSamples/, not the shared library, not the installer or PDFs:Terminal window mkdir bilbycast-decklink-sdk && cd bilbycast-decklink-sdkgit initcp -r /path/to/SDK/Linux/include .# Both must exist before you commit.ls include/DeckLinkAPI.h include/DeckLinkAPIDispatch.cppgit add -A && git commit -m "DeckLink SDK 16.0 headers"git tag sdk-16.0git remote add origin git@github.com:Bilbycast/bilbycast-decklink-sdk.gitgit push -u origin main --tagsCI searches for
DeckLinkAPI.hrather than assuming a fixed path, so you can keep the vendor’s directory nesting or flatten it. -
Generate a deploy key — an SSH keypair scoped to that one repository:
Terminal window ssh-keygen -t ed25519 -N "" -C "bilbycast-decklink-sdk deploy key" \-f ~/.ssh/decklink_sdk_deployThis produces
decklink_sdk_deploy(private) anddecklink_sdk_deploy.pub(public). -
Add the public half to the SDK repository: Settings → Deploy keys → Add deploy key, and paste
decklink_sdk_deploy.pub. Leave Allow write access unchecked — CI only needs to read. -
Add the private half to the edge repository: Settings → Secrets and variables → Actions → New repository secret. Name it exactly
DECKLINK_SDK_DEPLOY_KEYand paste the whole private key file, including the-----BEGIN OPENSSH PRIVATE KEY-----and-----END …-----lines. -
Verify before releasing. Push a commit and open the CI run. The
cargo check (sdi-decklink)step should run and pass. If you instead see “SDI compile gate skipped”, the secret is not visible to the run — check the name from step 6. -
Cut a release. In the release run’s build log the preflight prints
OK: DECKLINK_SDK_DEPLOY_KEY is set, and the binary verification printsOK: SDI compiled in (dl_read_frame present). The release notes switch to the SDI-present wording automatically.
To move to a newer SDK later, commit the new headers to the SDK repository. Nothing in bilbycast-edge changes.
Checklist C — allow the device nodes under systemd
Section titled “Checklist C — allow the device nodes under systemd”Only applies if you run the edge from the packaged systemd service. That unit is
hardened with DevicePolicy=closed, so device access is deny-by-default.
Skipping this makes an SDI-capable node look exactly like a host with no card.
The API probe succeeds — it only needs libDeckLinkAPI.so, which loads fine —
but the sandbox denies the DeckLink device nodes, so enumeration returns zero
cards. The sdi-decklink capability is gated on that live device list, so the
node never advertises SDI at all and the manager hides the SDI input and
output types, exactly as it would for a card-less host.
-
See what your card exposes:
Terminal window ls /dev/blackmagic/ -
Check the shipped unit covers them. It grants the
char-blackmagicdevice class plus/dev/blackmagic/io0throughio7. If your host shows nodes outside that set — different names, or more than eight — add matching lines to the unit:DeviceAllow=/dev/blackmagic/<node> rwPaths that don’t exist are ignored with a log warning, so extra entries are harmless.
-
Reload and restart:
Terminal window sudo systemctl daemon-reloadsudo systemctl restart bilbycast-edge -
Confirm the node’s per-port SDI hardware status is now populated.
Ad markers — SCTE-104 ↔ SCTE-35
Section titled “Ad markers — SCTE-104 ↔ SCTE-35”Both directions are off by default and are a single boolean each. They are the reason to take SDI natively rather than through a converter: an ad marker that only lives in VANC is lost the moment a converter re-frames the signal.
Capture — set scte35_extraction: true on an sdi input. Generic VANC is
copied before the SDK frame is released, the packets carrying DID/SDID
0x41/0x07 are picked out, and the message is decoded — both the
single_operation_message and the multiple_operation_message framing, the
latter being what real ad-splice inserters emit. Splice start, splice end and
cancel become a SCTE-35 splice_insert() section muxed straight into the
egress TS — PID 0x01FC, advertised in the PMT as stream type 0x86;
anything else is ignored.
Pre-roll, break duration and auto-return survive the translation; zero pre-roll
becomes an immediate splice. Each translated cue raises an Info
sdi_scte35_emitted event carrying the splice event ID and opcode, and bumps
InputStats.sdi_stats.scte35_cues_emitted.
Playout — set scte35_injection: true on an sdi output. The selected
program’s SCTE-35 PID is section-reassembled, CRC-valid splice_insert() cues
are decoded back into SCTE-104 — a multiple_operation_message carrying one
splice operation — and the packet is attached as DID/SDID
0x41/0x07 VANC to the next three successfully scheduled frames (receiver
reliability; line number 0 lets the SDK place it). Component splices,
encrypted sections and non-splice_insert commands are ignored.
OutputStats.sdi_stats.scte35_cues_injected counts once per cue, not once per
frame carrying it.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Cause | Fix |
|---|---|---|
DECKLINK_SDK_DIR is not set at build |
Feature enabled without the headers | Checklist A step 3 |
missing …/DeckLinkAPI.h — DECKLINK_SDK_DIR does not look like the SDK's Linux/include |
Pointing at the SDK root, not Linux/include |
Point one level deeper |
Compile errors about IDeckLinkVideoBuffer / StartAccess |
SDK older than 16 | Download SDK 16 or newer |
sdi_no_media_codecs at flow start |
Built with media-codecs disabled |
Rebuild with default features |
Node does not advertise SDI, and ls /dev/blackmagic/ does list nodes |
systemd sandbox denying the device nodes — enumeration returns zero cards, so the capability is withheld | Checklist C |
Node does not advertise SDI; boot log sdi.decklink says “the DeckLink API is unreachable” |
Desktop Video missing, or binary built without the feature | Install Desktop Video; confirm the feature was enabled |
Node does not advertise SDI; boot log sdi.decklink says “no devices enumerated” |
Either the sandbox above or no card fitted — the log line alone cannot tell them apart | ls /dev/blackmagic/: nodes listed → Checklist C; nothing listed → seat/fit the card |
| Released binary has no SDI | A *-rockchip artefact (never carries it), or a *-full artefact from v0.102.0 or earlier. Confirm with --print-capabilities | grep '^feature sdi-decklink' |
Upgrade to v0.103.0+ for *-full; otherwise Checklist A, or Checklist B then re-release |
| CI logs “SDI compile gate skipped” | No SDK credential visible to that run | Expected on fork PRs; otherwise recheck the secret name |
Related
Section titled “Related”- Supported Protocols — where SDI sits among the input and output types
- Codec Matrix — which encoders you can pair with SDI capture
- Install as a Linux Service — the packaged systemd unit referenced in Checklist C