Free now2sdk Core is completely free — no license key required   See what's included →
SDK Modules Docs Release Notes Pricing GitHub
Get the SDK →

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:

  1. Broadcast Hardware Ports: Blackmagic Design (BMD) DeckLink outputs (SDI, HDMI, Component, Composite, S-Video).
  2. 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:


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:

  1. Scanning and listing DeckLink and NDI output devices.
  2. Building cascading dropdown selections for connection channels and resolution standards.
  3. Enabling and disabling transmission dynamically.
  4. 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));
}