LOutput Reference Manual & Broadcast Transmission Guide
The LOutput class is the central physical and network broadcast transmission engine of the now2sdk framework, derived from the LSink base class. It is designed to capture, format, and transmit mixed audio-visual program feeds in real time to:
- Broadcast Hardware Ports: Blackmagic Design (BMD) DeckLink outputs (SDI, HDMI, Component, Composite, S-Video).
- Network IP Video Channels: Network Device Interface (NDI) streams broadcasted to the local subnetwork.
It connects to any stream source (such as LFile, LLive, or LMixer) using the sink-source pipeline model, processing incoming buffers asynchronously to maintain stable broadcast frame rates.
1. Transmission & Resampling Architecture
graph TD
A[Program Feed Source - LObject] -->|pushVideoFrame / pushAudioFrame| B(LOutput Sink)
subgraph Video Transmission Pipeline
B -->|Asynchronous Video Queue| C[outputLoop Thread]
C --> D{Active Device?}
D -->|DeckLink Index| E[Convert to BMD pixel format BGRA/UYVY]
D -->|NDI Index| F[Pack NDI video frames]
E --> G[IDeckLinkOutput::DisplayVideoFrame]
F --> H[NDIlib_send_send_video_v2]
end
subgraph Audio Resampling Pipeline
B -->|Audio Buffer Queue| I[audioLoop Thread]
I --> J[Phase 1: Resample to target quality - bits/sample rate]
J --> K[Phase 2: Resample to hardware standard 48kHz S16 Stereo]
K --> L{Active Device?}
L -->|DeckLink Index| M[IDeckLinkOutput::WriteAudioSamples]
L -->|NDI Index| N[NDIlib_send_send_audio_v3]
end
Ingestion & Processing Details:
- Asynchronous Buffer Queue: Video frames are buffered in an internal queue (
m_videoQueue). To prevent stream memory bloat and latency drift, if the queue size exceeds5frames, the oldest frame is dropped automatically and tracked in the statistics registry (m_droppedFrames). - Dual-Phase Audio Resampling:
- Phase 1 (Target Quality): Normalizes incoming audio samples to user-specified rates and qualities using a dedicated software resampler (
m_swrAudio). - Phase 2 (Hardware Standards): Upsamples and aligns the samples to the exact standard expected by DeckLink and NDI outputs (48kHz 16-bit Stereo PCM) using the hardware context resampler (
m_swrHardware), applying gain adjustments dynamically before transmission.
- Phase 1 (Target Quality): Normalizes incoming audio samples to user-specified rates and qualities using a dedicated software resampler (
- Synchronous Thread Loops:
LOutputoperates two background loops (outputLoopandaudioLoop) to handle asynchronous network and card outputs without blocking the source's render loop.
2. API Reference & Public Methods
📌 Output Management & Ingestion
void setEnabled(bool enabled);
Starts or stops the broadcast transmission thread loop for the selected device interface.
void setProps(const std::string& key, const std::string& value);
Sets dynamic configuration properties at runtime. Parameter options are detailed in Section 3.
void setSource(LObject* source);
Registers LOutput as a sink to the program stream source (e.g. LMixer or LFile). If another source was previously registered, it detaches automatically first.
void setDevice(int index);
Selects the active broadcast hardware or stream target device. Switching devices during active transmission automatically stops the current stream, binds the new index, and resumes the output loop.
void DeviceFormatAudioSet(const audioFormatProps& props);
Forces target audio output channel and sample rate Normalizations.
📌 Device, Connection, and Format Queries
void DeviceGetCount(int& count);
Scans for hardware and returns the count of output devices: $\text{Total Device Count} = \text{DeckLink Output Cards} + 1\ (\text{NDI Output Channel})$
void DeviceGetByIndex(int index, std::string& name, std::string& desc);
Retrieves the model identifier (e.g. "DeckLink Studio 4K") and descriptions for the device index.
void DeviceChannelGetCount(int deviceIndex, int& count);
Returns available physical connections/outputs (SDI, HDMI, etc.) for DeckLink cards, or 1 for NDI channels.
void DeviceChannelGetByIndex(int deviceIndex, int channelIndex, std::string& name, std::string& desc);
Queries naming strings (e.g. "SDI", "HDMI") for connection ports.
void DeviceChannelSet(int index);
Binds the active output connection port. For DeckLink cards, it sets the hardware register dynamically via IDeckLinkConfiguration.
void DeviceFormatVideoGetCount(int deviceIndex, int channelIndex, int& count);
Queries display standards supported by the hardware connection.
void DeviceFormatVideoGetByIndex(int deviceIndex, int channelIndex, int formatIndex, std::string& name, videoFormatProps& props);
Retrieves specific resolution and target framerates for a display mode index.
void DeviceFormatVideoSet(int index);
Configures the output resolution profile. If changed during active playout, it safely restarts DeckLink/NDI outputs to apply the new format on-the-fly.
📌 Playout Transmission Metrics
struct OutputStats {
int width;
int height;
double fps;
int audioChannels;
int audioSampleRate;
int droppedFrames;
int bufferedFrames;
int displayedFrames;
};
void getStats(OutputStats& stats);
Populates an OutputStats structure containing frame statistics, output resolutions, buffer load queues, and total dropped frames.
3. Properties & Configuration Options
All options listed below are configured at runtime via the setProps(const std::string& key, const std::string& value) interface:
| Key | Value Type | Default Value | Description |
|---|---|---|---|
enabled |
bool |
"false" |
Toggles the active transmission loop for the selected output device. |
stream_name |
string |
"NDI_Output" |
Configures the output stream identifier broadcasted over NDI. |
NDI.color_format |
string |
"UYVY" |
Pixel format for NDI network streams:"UYVY": 16-bit 4:2:2 (Default Broadcast)"BGRA": 32-bit Alpha-supported format. |
DeckLink.color_format |
string |
"UYVY" |
Pixel format for DeckLink hardware outputs:"UYVY": 16-bit 4:2:2"BGRA": 32-bit RGBA. |
scaling_quality |
string |
"bilinear" |
Normalization scaling filter:"bilinear": Bilinear scaling."bicubic": Bicubic scaling."lanczos": High-quality Lanczos scaling. |
timecode |
bool |
"false" |
Toggles embedding timecode metadata inside NDI blocks / BMD RP188 flags. |
timecode_format |
string |
"auto" |
Selects formatting scheme. |
audio_gain |
int |
"100" |
Ingestion audio volume level coefficient percentage (0 to 100). |
4. Qt Broadcast Transmission Dashboard Example
This implementation (adapted from the Output Example mainwindow.cpp) shows how to configure an LOutput broadcast dashboard. It manages:
- Scanning and listing DeckLink and NDI output devices.
- Building cascading dropdown selections for connection channels and resolution standards.
- Enabling and disabling transmission dynamically.
- Monitoring performance statistics (displayed vs. dropped frames) in real time.
#include "mainwindow.h"
#include "./ui_mainwindow.h"
#include "LOutput.h"
#include "LFile.h"
#include "LFormat.h"
#include <QTimer>
#include <QFileDialog>
#include <QFileInfo>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent),
ui(new Ui::MainWindow),
m_lFile(new LFile()),
m_lOutput(new LOutput()),
m_statsTimer(new QTimer(this))
{
ui->setupUi(this);
// 1. Hook Playout Player to Output Transmitter
m_lOutput->setSource(m_lFile);
// 2. Scan and list available broadcast hardware
refreshDevices();
// 3. Connect cascade UI selectors
connect(ui->deviceCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &MainWindow::onDeviceChanged);
connect(ui->channelCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &MainWindow::onChannelChanged);
connect(ui->formatCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &MainWindow::onFormatChanged);
connect(ui->enableOutputCheck, &QCheckBox::toggled,
this, &MainWindow::onOutputToggled);
// 4. Start statistics update timer (500ms interval)
connect(m_statsTimer, &QTimer::timeout, this, &MainWindow::updateOutputStats);
m_statsTimer->start(500);
}
MainWindow::~MainWindow() {
m_statsTimer->stop();
m_lOutput->setEnabled(false); // Cease transmission on exit
m_lFile->stop();
delete m_lOutput;
delete m_lFile;
delete ui;
}
// ─── Step 1: Query and Populate Devices ───
void MainWindow::refreshDevices() {
ui->deviceCombo->blockSignals(true);
ui->deviceCombo->clear();
int deviceCount = 0;
m_lOutput->DeviceGetCount(deviceCount); // Scans DeckLink cards + NDI channels
for (int i = 0; i < deviceCount; i++) {
std::string name, desc;
m_lOutput->DeviceGetByIndex(i, name, desc);
ui->deviceCombo->addItem(QString::fromStdString(name), i);
}
ui->deviceCombo->blockSignals(false);
if (deviceCount > 0) {
ui->deviceCombo->setCurrentIndex(0);
onDeviceChanged(0);
}
}
// ─── Step 2: Query Connection Port Lines (SDI / HDMI / etc.) ───
void MainWindow::onDeviceChanged(int index) {
if (index < 0) return;
int deviceIndex = ui->deviceCombo->itemData(index).toInt();
m_lOutput->setDevice(deviceIndex);
ui->channelCombo->blockSignals(true);
ui->channelCombo->clear();
int channelCount = 0;
m_lOutput->DeviceChannelGetCount(deviceIndex, channelCount);
for (int i = 0; i < channelCount; i++) {
std::string name, desc;
m_lOutput->DeviceChannelGetByIndex(deviceIndex, i, name, desc);
ui->channelCombo->addItem(QString::fromStdString(name), i);
}
ui->channelCombo->blockSignals(false);
if (channelCount > 0) {
ui->channelCombo->setCurrentIndex(0);
onChannelChanged(0);
}
}
// ─── Step 3: Populate Output Formats ───
void MainWindow::onChannelChanged(int index) {
if (index < 0) return;
int channelIndex = ui->channelCombo->itemData(index).toInt();
m_lOutput->DeviceChannelSet(channelIndex);
int deviceIndex = ui->deviceCombo->currentData().toInt();
ui->formatCombo->blockSignals(true);
ui->formatCombo->clear();
int formatCount = 0;
m_lOutput->DeviceFormatVideoGetCount(deviceIndex, channelIndex, formatCount);
for (int i = 0; i < formatCount; i++) {
std::string name;
videoFormatProps props;
m_lOutput->DeviceFormatVideoGetByIndex(deviceIndex, channelIndex, i, name, props);
ui->formatCombo->addItem(QString::fromStdString(name), i);
}
ui->formatCombo->blockSignals(false);
if (formatCount > 0) {
ui->formatCombo->setCurrentIndex(0);
onFormatChanged(0);
}
}
// ─── Step 4: Bind Output Format Standard ───
void MainWindow::onFormatChanged(int index) {
if (index < 0) return;
int formatIndex = ui->formatCombo->itemData(index).toInt();
m_lOutput->DeviceFormatVideoSet(formatIndex);
}
// ─── Step 5: Toggle Transmission (On-Air / Standby) ───
void MainWindow::onOutputToggled(bool checked) {
// Configure transmission properties
m_lOutput->setProps("stream_name", "MAIN_PROGRAM_FEED");
m_lOutput->setProps("NDI.color_format", "UYVY"); // Standard 4:2:2 NDI
m_lOutput->setProps("DeckLink.color_format", "UYVY"); // Standard 4:2:2 SDI
m_lOutput->setProps("scaling_quality", "lanczos"); // Use high-quality scaler
m_lOutput->setProps("timecode", "true"); // Embed timecode
m_lOutput->setProps("audio_gain", "90"); // 90% output gain
// Enable/disable transmission
m_lOutput->setEnabled(checked);
// Toggle UI selectors to avoid conflicts while active
ui->deviceCombo->setEnabled(!checked);
ui->channelCombo->setEnabled(!checked);
ui->formatCombo->setEnabled(!checked);
}
// ─── Step 6: Monitor Statistics ───
void MainWindow::updateOutputStats() {
LOutput::OutputStats stats;
m_lOutput->getStats(stats);
ui->resStatsVal->setText(QString("%1 x %2").arg(stats.width).arg(stats.height));
ui->fpsStatsVal->setText(QString("%1 FPS").arg(stats.fps, 0, 'f', 2));
ui->audioStatsVal->setText(QString("%1 Hz, %2 CH").arg(stats.audioSampleRate).arg(stats.audioChannels));
ui->sentFramesStatsVal->setText(QString::number(stats.displayedFrames));
ui->droppedFramesStatsVal->setText(QString::number(stats.droppedFrames));
ui->bufferQueueStatsVal->setText(QString::number(stats.bufferedFrames));
}