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

LRecorder Reference Manual & Playout Recording Guide

The LRecorder class is the central real-time program stream recording engine of the now2sdk framework, derived from the LSink base class. It is designed to capture, format, compress, and multiplex live audio-visual feeds into standard container formats (such as MP4, MKV, and MOV) on the fly.

LRecorder operates asynchronously using an internal thread-safe packet queue to isolate disk write operations and CPU-heavy encoding passes from the rendering and live capture loops.


1. Asynchronous Ingestion & Multiplexing Architecture

graph TD
    A[Program Stream Source - LObject] -->|pushVideoFrame / pushAudioFrame| B(LRecorder Sink)
    
    subgraph Video Ingest & Scale
        B -->|AVFrame*| C{Frame Queue Size > 60?}
        C -->|Yes| D[Drop Frame / Increment droppedFrames]
        C -->|No| E[Push to Frame Queue]
        E -->|Video Frame Packet| F[encodeLoop Thread]
        F --> G[SwsContext Normalization / Scale]
        G --> H[Video Encoder - e.g. libx264]
    end
    
    subgraph Audio Buffer & Resample
        B -->|Audio AVFrame*| I[Audio resample & buffer]
        I --> J[SwrContext Normalization / 48kHz Stereo S16]
        J --> K[Push to audioHistory deque]
    end
    
    H --> L[Sync Controller - Master Audio Clock]
    K -->|popAudioSamplesForPts| L
    L --> M[Audio Encoder - e.g. AAC]
    
    H -->|Encoded AVPacket| N[Muxer - av_interleaved_write_frame]
    M -->|Encoded AVPacket| N
    N --> O[Physical Storage Disk]

Ingestion & Processing Details:


2. API Reference & Public Methods

📌 Target Encoding Controls

void setContainer(const std::string& container);

Sets the destination container format (e.g. "mp4", "mkv", "mov").

void setVideoCodec(const std::string& codec);

Sets the target video codec (e.g. "libx264", "h264_nvenc", "mjpeg").

void setAudioCodec(const std::string& codec);

Sets the target audio codec (e.g. "aac", "pcm_s16le").

void setVideoBitrate(int bitrateKbps);

Sets the video stream target encoding bitrate (e.g. 5000 for 5 Mbps).

void setAudioBitrate(int bitrateKbps);

Sets the audio stream target encoding bitrate (e.g. 192 for 192 kbps).

void setVideoFormat(const videoFormatProps& props);
void getVideoFormat(videoFormatProps& props);

Configures or retrieves the target physical video resolution, frame rate, and colorspace normalization.

void setAudioFormat(const audioFormatProps& props);
void getAudioFormat(audioFormatProps& props);

Configures or retrieves the target audio sampling rate, bits per sample, and layout channels.


📌 Custom Configuration & Properties

void customSet(const std::string& params);

Parses a space-separated string of key-value options (e.g. "preset=fast tune=zerolatency g=50") and injects them directly into the underlying FFmpeg encoder context before initialization.

void setProps(const std::string& key, const std::string& value);

Updates dynamic configuration properties at runtime. Key options are detailed in Section 3.


📌 Execution Control

void setFilePath(const std::string& filePath);

Sets the absolute destination output path.

void recordObject(LObject* source);

Binds LRecorder to a stream source (e.g. LLive, LMixer, LFile).

bool Record();

Starts the recording thread loop. Returns true if FFmpeg context initialization succeeds.

void Pause(bool pause);

Pauses or resumes the active recording process. Pausing calculates and shifts timestamps internally so that the final clip is contiguous.

void Stop();

Ceases recording, flushes left-over audio packets, writes container trailers to complete indexing, and releases all resources.

void statusGet(int& status);

Retrieves current recording state: 1 representing actively recording, 0 representing idle.

void setSplitMinute(int minutes);

Configures automatic time-based file rotation interval in minutes. Setting minutes = 0 disables auto-split (default: 0). When active, LRecorder seamlessly closes the current segment and creates a new indexed file (filename_001.mp4, filename_002.mp4) every N minutes without dropping frames.

void Split();

Triggers an immediate, seamless manual file split while recording is active. Rotates to the next indexed destination file.


📌 Recording Performance Metrics

struct Stats {
    int64_t fileSizeBytes = 0;   // Current file size on disk in bytes
    double durationWrittenMs = 0; // Total duration written in milliseconds
    int width = 0;               // Encoded video frame width
    int height = 0;              // Encoded video frame height
    double fps = 0.0;            // Frame rate standard of the written file
    int audioChannels = 0;       // Audio channels
    int audioSampleRate = 0;     // Audio sample rate in Hz
    int droppedFrames = 0;       // Total dropped frames during congestion
    int bufferedFrames = 0;      // Count of raw frames pending write in queue
    double avSyncMs = 0.0;       // Current Audio-Video PTS delta sync
    std::string timecode = "";   // Current active timecode string (HH:MM:SS:FF)
};

void getStats(Stats& stats);

Fills a Stats structure containing real-time file size, latency, buffer, and synchronization diagnostics.


📌 Dynamic Capability Discovery (Static Methods)

static std::vector<std::string> getAvailableContainers();

Queries the system for all write-supported multiplexers (containers).

static std::vector<std::string> getAvailableVideoCodecs(const std::string& container);

Returns valid video encoder codecs compatible with the specified container.

static std::vector<std::string> getAvailableAudioCodecs(const std::string& container);

Returns valid audio encoder codecs compatible with the specified container.

static std::vector<std::string> getAvailableVideoFormats(const std::string& container, const std::string& codec);

Returns valid, standard-compliant resolution and FPS preset strings (e.g. "HD1080_60p", "625i_50") compatible with the selected container and codec. Constrains UI choices for strict broadcast formats (such as dnxhd, dvvideo, or mxf_d10).

static std::vector<std::string> getAvailableBitrates(const std::string& container, const std::string& codec, const std::string& videoFormat = "");

Returns official broadcast and web bitrate presets (e.g., "145 Mbps", "220 Mbps" for dnxhd; "50 Mbps" for mxf_d10; "5 Mbps" to "200 Mbps" for H.264/HEVC) dynamically filtered by container, codec, and resolution.


2.1 Broadcast & Web Container / Codec Compatibility Matrix

The table below details all supported containers, video codecs, supported resolutions/FPS limits, official bitrate options, and compatible audio codecs based on Medialooks broadcast specifications:

Container Video Codec(s) Supported Resolutions & FPS Official Bitrate Options Audio Codec(s)
mp4 h264_nvenc, hevc_nvenc, av1_nvenc, libx264
libopenh264, mpeg4, mjpeg, mpeg2video
Modern (NVENC/x264/hevc): SD, 720p, 1080p (23.976 to 120fps), 4K (up to 120p)
Legacy (mpeg4/mjpeg/openh264/mpeg2): SD to 1080p60
Full Range: 5 Mbps to 200 Mbps aac, mp3, pcm_s16le
mov prores_proxy, prores_lt, prores_standard
prores_hq, prores_4444, dnxhd
dnxhr_lb, dnxhr_sq, dnxhr_hq, dnxhr_hqx, dnxhr_444
h264_nvenc, hevc_nvenc, av1_nvenc, libx264
libopenh264, mpeg4, mjpeg
ProRes & DNxHR: SD, 720p, 1080p, 4K (23.976 to 120p)
DNxHD: 720p50/59.94/60, 1080i50/59.94/60, 1080p23.976..30
NVENC/x264: SD to 4K 120p
Legacy: SD to 1080p60
DNxHD: 36, 45, 60, 75, 90, 115, 120, 145, 175, 185, 220, 440 Mbps
DNxHR / ProRes / NVENC / x264: 5 Mbps to 440 Mbps
pcm_s16le, pcm_s24le, aac
mxf mpeg2video, dnxhd, dnxhr_sq, dnxhr_hq, prores_hq, libx264 DNxHD: Strict VC-3 720p/1080i/1080p
MPEG2: SD, 720p, 1080i/1080p (up to 60fps)
DNxHR & ProRes: SD to 4K 120p
DNxHD: 36, 45, 60, 75, 90, 115, 120, 145, 175, 185, 220, 440 Mbps
MPEG2 / ProRes / x264: 5 Mbps to 200 Mbps
pcm_s16le, pcm_s24le (PCM required)
mxf_d10 mpeg2video (50 Mbps IMX) Strict Tape Standards: 525i_5994 (720x480), 625i_50 (720x576) Strict IMX Tape Standard: 50 Mbps CBR pcm_s16le
avi rawvideo, dvvideo, h264_nvenc, libx264, libopenh264, mpeg4, mjpeg, mpeg2video dvvideo: SD PAL/NTSC only
rawvideo/x264/nvenc: SD to 4K 120p
Legacy: SD to 1080p60
DV / DVCPRO: 25 Mbps, 50 Mbps
General Codecs (x264/MJPEG): 5 Mbps to 200 Mbps
pcm_s16le, mp3
mkv h264_nvenc, hevc_nvenc, av1_nvenc, libx264, libopenh264, prores_hq, dnxhd, dnxhr_hq ProRes/DNxHR/NVENC/x264: SD to 4K 120p
DNxHD: Strict VC-3 720p/1080i/1080p
DNxHD: 36 to 440 Mbps
General Codecs: 5 Mbps to 200 Mbps
pcm_s16le, aac, flac, mp3
webm vp8, vp9, libx264 vp9 / libx264: SD to 4K 120p
vp8: SD to 1080p60
Web Media: 5 Mbps to 200 Mbps opus, vorbis
ts / mpg libx264, h264_nvenc, mpeg2video x264 / NVENC: SD to 4K 120p
mpeg2video: SD to 1080p60
Broadcast Streaming: 5 Mbps to 200 Mbps mp2, aac, ac3 (mpg auto-converts AAC to mp2)
dv dvvideo Strict Tape Standards: 525i_5994 (720x480), 625i_50 (720x576) DV / DVCPRO: 25 Mbps, 50 Mbps pcm_s16le
gxf mpeg2video, dvvideo dvvideo: SD PAL/NTSC
mpeg2video: SD to 1080p60 broadcast
DV: 25 Mbps
MPEG2: 5 Mbps to 50 Mbps
pcm_s16le

3. Properties & Configuration Options

[!IMPORTANT] API Design Distinction: setProps vs customSet

  • setProps(key, value): Controls LRecorder Object & Pipeline Properties (e.g. durationMs, play_while_rec, scaling_quality, timecode).
  • customSet(params): Controls FFmpeg & Codec-Specific Fine Tuning in a single space-separated string (e.g. profile=high preset=veryfast g=25 bf=0 tune=zerolatency).
Key Value Type Default Value Description
durationMs double "0.0" Configures an automatic stop limit in milliseconds. If set to 0.0, recording runs indefinitely until Stop() is called.
play_while_rec bool "false" Toggles fragmented container creation (frag_keyframe+empty_moov+default_base_moof for MP4/MOV) to allow real-time playback by external video players during capture.
scaling_quality int "2" Resolution scaler algorithm selector:
1: Point (Nearest Neighbor)
2: Bilinear
3: Lanczos
timecode bool "false" Toggles creation of the SMPTE timecode track.
timecodeSource int "0" Selects timecode timing clock source:
0: Current local system clock timecode.
1: Inherits metadata timecode from the input LObject source.
2: Offsets timecode using the value set in timecodeStart.
timecodeStart string "01:00:00:00" Starting timecode offset value used when timecodeSource = 2 (format: HH:MM:SS:FF).

3.1 Advanced FFmpeg Codec Tuning (customSet)

For fine-grained encoder control (GOP size, B-frames, low-latency tuning, profiles), pass space-separated parameters directly via customSet:

// Example: Low-Latency H.264 Live Ingest Settings
m_lRecorder->customSet("profile=high preset=veryfast g=25 bf=0 tune=zerolatency");

4. Ingest Recording Dashboard Example

This comprehensive Qt C++ dashboard implementation (adapted from the Recorder Example) demonstrates how to scan hardware, populate container/codec selections cascadingly, configure recording pipelines, start/pause/stop operations, and display statistics in real time.

#include "mainwindow.h"
#include "./ui_mainwindow.h"
#include "LLive.h"
#include "LPreview.h"
#include "LRecorder.h"
#include <QTimer>
#include <QFileDialog>
#include <QMessageBox>
#include <sstream>
#include <iomanip>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent),
      ui(new Ui::MainWindow),
      m_lLive(new LLive()),
      m_lPreview(new LPreview()),
      m_lRecorder(new LRecorder()),
      m_statsTimer(new QTimer(this)),
      m_outputDir("/home/alsaberk/Development/LinuxMediaLibrary"),
      m_isRecording(false),
      m_isPaused(false)
{
    ui->setupUi(this);

    // 1. Configure default input camera capture properties
    m_lLive->setProps("gpu", "true");
    m_lLive->setProps("timecode", "true");
    m_lLive->setProps("timecodeSource", "0");

    // 2. Bind rendering monitor widget (Must call previewEnable before previewObject!)
    m_lPreview->previewEnable(ui->livePreview, true, true);
    m_lPreview->previewObject(m_lLive);
    m_lPreview->setProps("audio_meter", "true");
    m_lPreview->setProps("timecode.preview", "true");

    // 3. Query and populate containers list
    std::vector<std::string> containers = LRecorder::getAvailableContainers();
    for (const auto& format : containers) {
        ui->containerCombo->addItem(QString::fromStdString(format));
    }
    int mp4Index = ui->containerCombo->findText("mp4", Qt::MatchExactly);
    if (mp4Index != -1) ui->containerCombo->setCurrentIndex(mp4Index);

    // 4. Populate standard quality profiles
    ui->standardVideoFormatCombo->addItem("HD 1080p 50", static_cast<int>(vF::HD1080_50p));
    ui->standardVideoFormatCombo->addItem("HD 1080p 25", static_cast<int>(vF::HD1080_25p));
    ui->standardVideoFormatCombo->addItem("HD 720p 50", static_cast<int>(vF::HD720_50p));
    ui->standardVideoFormatCombo->setCurrentIndex(0);

    ui->standardAudioFormatCombo->addItem("16-Bit / 48kHz / Stereo", static_cast<int>(aF::_16B_48K_2CH));
    ui->standardAudioFormatCombo->addItem("24-Bit / 48kHz / Stereo", static_cast<int>(aF::_24B_48K_2CH));
    ui->standardAudioFormatCombo->setCurrentIndex(0);

    // 5. Connect cascade events
    connect(ui->containerCombo, &QComboBox::currentTextChanged, this, &MainWindow::onContainerChanged);
    connect(ui->outputFolderBtn, &QPushButton::clicked, this, &MainWindow::onSelectFolderClicked);
    
    // Recording controls
    connect(ui->startRecBtn, &QPushButton::clicked, this, &MainWindow::onStartRecordingClicked);
    connect(ui->pauseRecBtn, &QPushButton::clicked, this, &MainWindow::onPauseRecordingClicked);
    connect(ui->stopRecBtn, &QPushButton::clicked, this, &MainWindow::onStopRecordingClicked);

    // Monitor statistics updates at 200ms
    connect(m_statsTimer, &QTimer::timeout, this, &MainWindow::updateUIStats);

    // Trigger initial settings loading
    onContainerChanged(ui->containerCombo->currentText());
}

MainWindow::~MainWindow() {
    if (m_isRecording) m_lRecorder->Stop();
    m_lLive->Stop();
    delete m_statsTimer;
    delete m_lRecorder;
    delete m_lPreview;
    delete m_lLive;
    delete ui;
}

// ─── Step 1: Cascading Container-Codec Selection ───
void MainWindow::onContainerChanged(const QString& container) {
    ui->videoCodecCombo->blockSignals(true);
    ui->audioCodecCombo->blockSignals(true);
    ui->videoCodecCombo->clear();
    ui->audioCodecCombo->clear();

    std::string fmt = container.toLower().toStdString();
    
    // Dynamic query compatible encoders
    std::vector<std::string> vCodecs = LRecorder::getAvailableVideoCodecs(fmt);
    std::vector<std::string> aCodecs = LRecorder::getAvailableAudioCodecs(fmt);

    for (const auto& c : vCodecs) ui->videoCodecCombo->addItem(QString::fromStdString(c));
    for (const auto& c : aCodecs) ui->audioCodecCombo->addItem(QString::fromStdString(c));

    // Select standard presets
    int h264Idx = ui->videoCodecCombo->findText("libx264", Qt::MatchContains);
    if (h264Idx != -1) ui->videoCodecCombo->setCurrentIndex(h264Idx);
    
    int aacIdx = ui->audioCodecCombo->findText("aac", Qt::MatchContains);
    if (aacIdx != -1) ui->audioCodecCombo->setCurrentIndex(aacIdx);

    ui->videoCodecCombo->blockSignals(false);
    ui->audioCodecCombo->blockSignals(false);
    applyRecorderSettings();
}

// ─── Step 2: Bind Configuration Specifications ───
void MainWindow::applyRecorderSettings() {
    if (m_isRecording) return; // Properties locked during active recording

    // Normalizations
    videoFormatProps vFormat;
    vFormat.setVideoFormat = static_cast<vF_Type>(ui->standardVideoFormatCombo->currentData().toInt());
    m_lRecorder->setVideoFormat(vFormat);

    audioFormatProps aFormat;
    aFormat.setAudioFormat = static_cast<aF_Type>(ui->standardAudioFormatCombo->currentData().toInt());
    m_lRecorder->setAudioFormat(aFormat);

    // Bitrates & Encoders
    m_lRecorder->setVideoBitrate(10000); // 10 Mbps
    m_lRecorder->setAudioBitrate(192);   // 192 kbps
    m_lRecorder->setContainer(ui->containerCombo->currentText().toLower().toStdString());
    m_lRecorder->setVideoCodec(ui->videoCodecCombo->currentText().toStdString());
    m_lRecorder->setAudioCodec(ui->audioCodecCombo->currentText().toStdString());

    // Dynamic props parameters
    m_lRecorder->setProps("play_while_rec", "true"); // Enable fragmented playing
    m_lRecorder->setProps("timecode", "true");       // Embed timecode
    m_lRecorder->setProps("timecodeSource", "0");    // System clock source
    m_lRecorder->customSet("preset=fast tune=zerolatency g=50"); // x264 custom parameters

    // Assemble file destination
    QString formatStr = ui->containerCombo->currentText().toLower();
    QString fullPath = m_outputDir + "/REC_FEED." + formatStr;
    m_lRecorder->setFilePath(fullPath.toStdString());

    // Bind Ingestion Source
    m_lRecorder->recordObject(m_lLive);
}

// ─── Step 3: Trigger Record ───
void MainWindow::onStartRecordingClicked() {
    if (m_isRecording) return;
    applyRecorderSettings();

    if (m_lRecorder->Record()) {
        m_isRecording = true;
        m_isPaused = false;

        ui->startRecBtn->setEnabled(false);
        ui->startRecBtn->setText("🔴 RECORDING...");
        ui->pauseRecBtn->setEnabled(true);
        ui->stopRecBtn->setEnabled(true);
        
        m_statsTimer->start(200);
    } else {
        QMessageBox::critical(this, "Recording Error", "Failed to start record encoding context. Verify settings.");
    }
}

// ─── Step 4: Manage Pause/Resume ───
void MainWindow::onPauseRecordingClicked() {
    if (!m_isRecording) return;

    if (!m_isPaused) {
        m_lRecorder->Pause(true);
        m_isPaused = true;
        ui->pauseRecBtn->setText("▶ RESUME");
    } else {
        m_lRecorder->Pause(false);
        m_isPaused = false;
        ui->pauseRecBtn->setText("⏸ PAUSE");
    }
}

// ─── Step 5: Stop & Finalize ───
void MainWindow::onStopRecordingClicked() {
    if (!m_isRecording) return;
    
    m_statsTimer->stop();
    m_lRecorder->Stop(); // Flushes remaining queue and writes file index trailers safely

    m_isRecording = false;
    m_isPaused = false;

    ui->startRecBtn->setEnabled(true);
    ui->startRecBtn->setText("🔴 START RECORDING");
    ui->pauseRecBtn->setEnabled(false);
    ui->stopRecBtn->setEnabled(false);

    QMessageBox::information(this, "Playout Recorder", "File captured and index finalized successfully.");
}

// ─── Step 6: Update Stats Metrics ───
void MainWindow::updateUIStats() {
    if (!m_isRecording) return;

    LRecorder::Stats stats;
    m_lRecorder->getStats(stats);

    // Format Duration
    double writtenSec = stats.durationWrittenMs / 1000.0;
    int hrs = static_cast<int>(writtenSec) / 3600;
    int mins = (static_cast<int>(writtenSec) % 3600) / 60;
    int secs = static_cast<int>(writtenSec) % 60;

    std::stringstream ss;
    ss << std::setfill('0') << std::setw(2) << hrs << ":"
       << std::setfill('0') << std::setw(2) << mins << ":"
       << std::setfill('0') << std::setw(2) << secs;
    ui->statsDurationVal->setText(QString::fromStdString(ss.str()));

    // Format File size
    double sizeMB = static_cast<double>(stats.fileSizeBytes) / (1024.0 * 1024.0);
    ui->statsSizeVal->setText(QString("%1 MB").arg(sizeMB, 0, 'f', 2));
    
    // Display current Timecode
    ui->statsTCVal->setText(QString::fromStdString(stats.timecode));
}