
VOutputPipe C++ library
v1.0.0
Table of contents
- Overview
- Versions
- Library files
- VOutputPipe class description
- Read video from pipe
- Build and connect to your project
- Simple example
- Test application
Overview
VOutputPipe C++ library provides an interface to write video frames to named pipe (FIFO) in Linux OS. Third-party applications such as ffmpeg and GStreamer read the pipe as an ordinary raw video stream, so user’s application can be a video source for them. Unlike V4L2 video output devices a named pipe requires no kernel module, no video device node and no root privileges, so the library works on any Linux system. The library provides a simple interface. It depends on open source Frame library (describes video frame structure and pixel formats, source code included, Apache 2.0 license). The library supports C++17 standard and works only on Linux. The library supports only NV12 pixel format.
IMPORTANT: a named pipe transfers data only while somebody reads it, so a video consumer (e.g. ffmpeg or GStreamer) must be running to make the library work. Until a consumer opens the pipe for reading the write(…) method returns FALSE and no video is transferred. The startup order is:
- Start user’s application. The open(…) method creates the pipe file (e.g. /tmp/video0). The write(…) method returns FALSE for every frame at this point.
- Start the video consumer on the same pipe file, giving it the frame resolution, pixel format and frame rate (see Read video from pipe). From this moment write(…) returns TRUE and frames reach the consumer.
The consumer can be started and stopped at any moment: the library reattaches to a new consumer automatically. The consumer can also be started before user’s application if the pipe file is created in advance with the mkfifo /tmp/video0 command.
Versions
Table 1 - Library versions.
| Version | Release date | What’s new |
|---|---|---|
| 1.0.0 | 08.08.2026 | First version. |
Library files
The library is supplied only by source code. The user is given a set of files in the form of a CMake project (repository). The repository structure is shown below:
CMakeLists.txt --------------- Main CMake file of the library.
README.md -------------------- Library documentation.
3rdparty --------------------- Folder with third-party libraries.
CMakeLists.txt ----------- CMake file to include third-party libraries.
Frame -------------------- Folder with files of Frame library.
src -------------------------- Folder with library source code.
CMakeLists.txt ----------- CMake file of the library.
VOutputPipe.h ------------ Main library header file.
VOutputPipeVersion.h ----- Header file with library version.
VOutputPipeVersion.h.in -- File for CMake to generate version header.
VOutputPipe.cpp ---------- C++ implementation file.
example ---------------------- Folder for example application files.
CMakeLists.txt ----------- CMake file for example application.
main.cpp ----------------- Source C++ file of example application.
test ------------------------- Folder for test application files.
CMakeLists.txt ----------- CMake file for test application.
main.cpp ----------------- Source C++ file of test application.
VOutputPipe class description
VOutputPipe class declaration
VOutputPipe class declared in VOutputPipe.h file. Class declaration:
namespace cr
{
namespace video
{
/// Named pipe video output class.
class VOutputPipe
{
public:
/// Get library version.
static std::string getVersion();
/// Class constructor.
VOutputPipe();
/// Class destructor. Closes pipe.
~VOutputPipe();
/// Copy constructor. Prohibited.
VOutputPipe(const VOutputPipe& src) = delete;
/// Operator "=". Prohibited.
VOutputPipe& operator= (const VOutputPipe& src) = delete;
/// Create pipe file.
bool open(std::string device);
/// Write video frame to pipe.
bool write(Frame& frame);
/// Close pipe.
void close(void);
};
}
}
getVersion method
The getVersion() method returns string of current version of VOutputPipe class. Method declaration:
static std::string getVersion();
Method can be used without VOutputPipe class instance:
cout << "VOutputPipe class version: " << VOutputPipe::getVersion();
Console output:
VOutputPipe class version: 1.0.0
open method
The open(…) method creates the pipe file. The method does not wait for a consumer: the pipe is opened for writing later, on first write(…) call, as soon as a consumer appears. The method closes the previously opened pipe if the method is called twice.
bool open(std::string device);
| Parameter | Value |
|---|---|
| device | full pipe file name. For example: “/tmp/video0”. If the file already exists it must be a pipe, otherwise the method returns FALSE and the existing file is left untouched. A symbolic link is rejected as well. |
Returns: TRUE if the pipe file is created (or an existing pipe file reused) or FALSE if not.
NOTE: the pipe file is created with 0644 access rights regardless of the process umask, so a consumer running as another user can read it. Write access stays with the owner, so no other user can inject data into the video stream.
write method
The write(…) method writes video frame to pipe. Frame object defines frame size: the method writes exactly frame.size bytes. The method does not add any header, so the consumer gets a continuous stream of raw frames.
bool write(Frame& frame);
| Parameter | Value |
|---|---|
| frame | Frame class object. Supported format (Fourcc enum of Frame class): NV12 only. Frames of any other pixel format are rejected. |
Returns: TRUE if the video frame is written to pipe or FALSE if not.
The method returns FALSE while no consumer reads the pipe. The consumer may start, stop and start again at any moment: the method reattaches to the new consumer automatically. The method retries short writes, so a frame is handed to the pipe as a whole and never truncated by a partial write.
NOTE: the write call blocks while the consumer is behind, so the consumer defines the maximum frame rate and no frame is lost while the consumer stays connected.
NOTE: a raw stream has no frame markers, so a consumer must start on a frame boundary. If the previous consumer disconnected while a frame was still in the pipe buffer, the bytes it did not read stay there until the next write(…) call fails and detaches the pipe. A new consumer started within that interval may begin in the middle of a frame and stay shifted. Give the producer one frame interval to detect the disconnect (the write(…) call returns FALSE) before starting the next consumer.
NOTE: class constructor sets SIGPIPE signal to be ignored for the whole process. Without it a consumer which stopped reading would terminate user’s application on the next write.
close method
The close() method closes the pipe and removes the pipe file if the file was created by the open(…) method. A pipe file which already existed before open(…) was called is reused, not owned, and is left in place. Method declaration:
void close();
Read video from pipe
A consumer is mandatory: the library transfers video only while an application reads the pipe. The commands below must be started after user’s application created the pipe file (or after the pipe file was created manually with mkfifo /tmp/video0).
The consumer has to be told frame size and pixel format, since a raw stream carries no header. For a 1920x1080 NV12 stream at 30 fps written to /tmp/video0:
Read by ffmpeg and write mp4 file:
ffmpeg -f rawvideo -pixel_format nv12 -video_size 1920x1080 -framerate 30 \
-i /tmp/video0 -c:v libx264 -preset veryfast -crf 20 -pix_fmt yuv420p out.mp4
Read by GStreamer and write mp4 file:
gst-launch-1.0 -e filesrc location=/tmp/video0 \
! rawvideoparse width=1920 height=1080 format=nv12 framerate=30/1 \
! videoconvert ! x264enc ! h264parse ! mp4mux ! filesink location=out.mp4
NOTE: a pipe carries exactly one consumer’s worth of data. Two processes reading the same pipe split the byte stream between them instead of each getting a copy.
Build and connect to your project
The library includes Frame library as a git submodule, so the repository must be cloned with submodules. Typical commands to build VOutputPipe library:
git clone --recursive https://github.com/ConstantRobotics-Ltd/VOutputPipe.git
cd VOutputPipe
mkdir build
cd build
cmake ..
make
If the repository was cloned without --recursive, run git submodule update --init --recursive in the repository folder before building.
If you want connect VOutputPipe library to your CMake project as source code you can make follow. For example, if your repository has structure:
CMakeLists.txt
src
CMakeList.txt
yourLib.h
yourLib.cpp
Create folder 3rdparty in your repository. Copy repository folder VOutputPipe to 3rdparty folder. New structure of your repository:
CMakeLists.txt
src
CMakeList.txt
yourLib.h
yourLib.cpp
3rdparty
VOutputPipe
Create CMakeLists.txt file in 3rdparty folder. CMakeLists.txt should contain:
cmake_minimum_required(VERSION 3.13)
################################################################################
## 3RD-PARTY
## dependencies for the project
################################################################################
project(3rdparty LANGUAGES CXX)
################################################################################
## SETTINGS
## basic 3rd-party settings before use
################################################################################
# To inherit the top-level architecture when the project is used as a submodule.
SET(PARENT ${PARENT}_YOUR_PROJECT_3RDPARTY)
# Disable self-overwriting of parameters inside included subdirectories.
SET(${PARENT}_SUBMODULE_CACHE_OVERWRITE OFF CACHE BOOL "" FORCE)
################################################################################
## INCLUDING SUBDIRECTORIES
## Adding subdirectories according to the 3rd-party configuration
################################################################################
add_subdirectory(VOutputPipe)
File 3rdparty/CMakeLists.txt adds folder VOutputPipe to your project and excludes example application from compiling (by default example application excluded from compiling if VOutputPipe included as sub-repository). The new structure of your repository:
CMakeLists.txt
src
CMakeList.txt
yourLib.h
yourLib.cpp
3rdparty
CMakeLists.txt
VOutputPipe
Next you need include folder 3rdparty in main CMakeLists.txt file of your repository. Add string at the end of your main CMakeLists.txt:
add_subdirectory(3rdparty)
Next you have to include VOutputPipe library in your src/CMakeLists.txt file:
target_link_libraries(${PROJECT_NAME} VOutputPipe)
Simple example
Below is the code for a simple application for streaming generated video frames to /tmp/video0 pipe. The same code is supplied in example folder.
#include <iostream>
#include <thread>
#include <chrono>
#include <cstdint>
#include <cstring>
#include "VOutputPipe.h"
int main(void)
{
// Open pipe.
cr::video::VOutputPipe videoOutput;
if (!videoOutput.open("/tmp/video0"))
return -1;
// Create NV12 frame with certain resolution.
cr::video::Frame frame(1920, 1080, cr::video::Fourcc::NV12);
// Main loop.
for (int frameId = 0; true; ++frameId)
{
// Fill Y plane by black and UV plane by neutral chroma.
memset(frame.data, 16, frame.width * frame.height);
memset(frame.data + frame.width * frame.height, 128, frame.width * (frame.height / 2));
// Draw moving 200x200 white rectangle in Y plane. The loop never ends,
// so the offset is calculated in 64 bits to keep it from overflowing.
int x = (int)((int64_t)frameId * 8 % (frame.width - 200));
for (int y = 300; y < 500; ++y)
memset(frame.data + y * frame.width + x, 235, 200);
// Write frame to pipe. Write fails while no consumer reads the pipe.
if (!videoOutput.write(frame))
std::cerr << "Can't write frame" << std::endl;
// 33ms delay for 30 fps stream
std::this_thread::sleep_for(std::chrono::milliseconds(33));
}
return 0;
}
Run the example first (it creates the pipe file) and then start the consumer to record 60 frames to mp4 file:
./build/bin/VOutputPipeExample &
ffmpeg -f rawvideo -pixel_format nv12 -video_size 1920x1080 -framerate 30 \
-i /tmp/video0 -frames:v 60 -c:v libx264 -crf 20 -pix_fmt yuv420p out.mp4
The example prints Can't write frame until a consumer opens the pipe. The recorded out.mp4 file contains the white rectangle moving 8 pixels per frame.
Test Application
The test directory contains an application that generates artificial NV12 video frames and writes them to the VOutputPipe named pipe. The application is built as VOutputPipeTest and has no OpenCV or video-file dependency.
Test Application Workflow
The application allocates one reusable NV12 frame after it opens the pipe. For every frame it:
- Fills the Y plane with limited-range black (
16) and the UV plane with neutral chroma (128). - Draws a white square in the Y plane and it moves horizontally.
- Writes the complete NV12 frame to the named pipe.
- Waits for the requested frame interval.
The output width and height must be even numbers in range 2 - 8192 (NV12 uses 2x2 chroma sampling, and the upper bound keeps the frame size inside int range) and FPS must be in range 1 - 1000. The application checks the entered values and exits with an error message if they are out of range. The size of the white square and its vertical position are derived from the frame size, so any valid resolution works.
Generated NV12 Frame
The test application creates NV12 data directly. For a frame with width W and height H, the layout is:
NV12: Y plane | UV plane (interleaved U V values)
The Y plane contains W * H bytes and the UV plane contains W * (H / 2) bytes. The test application initializes both planes and then changes the Y values inside the square to make it white:
memset(outputFrame.data, 16, width * height);
memset(outputFrame.data + width * height, 128, width * (height / 2));
The resulting outputFrame already has the Fourcc::NV12 format required by videoOutput.write(outputFrame).
Run the Test Application
Run it from the project root:
./build/bin/VOutputPipeTest
The application first asks whether to use the default values. Enter 1 to use the defaults or 0 to enter the width, height, FPS, and device name one by one. For example:
Use default params (0 - no, 1 - yes): 0
Enter width: 960
Enter height: 540
Enter FPS: 60
Enter device name: /tmp/video0
Start a consumer in a second terminal using the same values. Without a consumer the application transfers no video at all. For example, to record the stream to an MP4 file:
ffmpeg -f rawvideo -pixel_format nv12 -video_size 960x540 -framerate 60 \
-i /tmp/video0 -c:v libx264 -crf 20 -pix_fmt yuv420p out.mp4
The test application prints Can't write frame for every frame while no consumer is connected. Once FFmpeg opens the pipe, the application prints Frame sent : <id> for every frame written to the consumer.