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:
- Congestion Watchdog Queue: Incoming frames are queued asynchronously. If the queue length exceeds
60elements (usually caused by slow disk I/O or high encoder complexity), new frames are dropped immediately to protect system memory, incrementing thedroppedFramesmetric. - Master Audio Clock Synchronization: Video frames are treated as master triggers. When a video frame is popped from the queue,
LRecorderqueries theaudioHistorybuffer usingpopAudioSamplesForPtsto extract and encode audio samples matching the exact timestamp span up to the current video PTS. This technique guarantees drift-free A/V sync. - Fragmented Container Writing (Play-While-Recording): When
play_while_recis active,LRecorderconfigures specific muxer flags (frag_keyframe+empty_moov+default_base_mooffor MP4/MOV) to flush container atoms to disk incrementally. This prevents file corruption during unexpected crashes and allows other playback applications to stream the file while it is still being recorded. - Dynamic Timecode Insertion: Supports writing standard SMPTE timecode tracks directly into container metadata headers or encoder registers, calculated from system clock offsets, metadata flags, or user-defined start points.
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, libx264libopenh264, 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_standardprores_hq, prores_4444, dnxhddnxhr_lb, dnxhr_sq, dnxhr_hq, dnxhr_hqx, dnxhr_444h264_nvenc, hevc_nvenc, av1_nvenc, libx264libopenh264, 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:
setPropsvscustomSet
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: Bilinear3: 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));
}