
VOutputPipe C++ library
v1.0.1
Table of contents
- Overview
- Versions
- Library files
- VOutputPipe class description
- Read video from pipe
- Build and connect to your project
- Portability
- 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. The library provides a simple interface. It depends on Frame library (describes video frame structure and pixel formats). The library supports C++17 standard and works only on Linux. The library supports only NV12 pixel format.
VOutputPipe is general-purpose software: it is not tied to any sector or application, and the developer of a program decides what the program does with it.
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. |
| 1.0.1 | 15.10.2026 | Git submodules removed: the Frame library is expected in the same folder as the library (stand alone build) or must be added by the parent project. The access rights of a created pipe file are set through the opened pipe (a symbolic link put in place of the pipe was followed by the name based call). The pipe is checked to be a pipe on every attachment of a consumer (frames could be written into a regular file put in place of the pipe). A pipe name with a NUL character is rejected. The move constructor and the move operator “=” are declared as deleted. All compiler warnings fixed (strict warning flags, hardening flags and C++ extensions off in all CMake files). The test application runs self-tests, the generator of video is started by the –stream option. The example uses a 64 bit frame counter. |
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.
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 rapidpixel
{
/// 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;
/// Move constructor. Prohibited.
VOutputPipe(VOutputPipe&& src) = delete;
/// Operator "=". Prohibited.
VOutputPipe& operator= (const VOutputPipe& src) = delete;
/// Move operator "=". Prohibited.
VOutputPipe& operator= (VOutputPipe&& src) = delete;
/// Create pipe file.
bool open(std::string device);
/// Write video frame to pipe.
bool write(Frame& frame);
/// Close pipe.
void close();
};
}
NOTE: the class is not thread-safe: an object must not be accessed from several threads at the same time without external synchronization.
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:
std::cout << "VOutputPipe class version: "
<< rapidpixel::VOutputPipe::getVersion() << std::endl;
Console output:
VOutputPipe class version: 1.0.1
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 currently open pipe if the method is called twice (also when the second call fails).
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. The name must not contain a NUL character. |
Returns: TRUE if the pipe file is created (or an existing pipe file reused) or FALSE if not. No pipe is open after the method returned FALSE.
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. The access rights of an existing pipe file are not changed.
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, as well as frames with nullptr data or with size less than or equal to zero. The data of the frame must be valid for frame.size bytes: the method can not check it. |
Returns: TRUE if the video frame is written to pipe or FALSE if not: open(…) was not called or failed, the frame is not valid, no consumer reads the pipe, the consumer is gone or the pipe file was replaced by a file that is not a pipe.
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. If a connected consumer stops reading at all, the call stays blocked until the consumer reads again or disconnects.
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. Calling the method for a closed object does nothing. 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
Build stand alone
The repository does not contain sub-repositories. The Frame library that VOutputPipe depends on must be cloned into the same folder as VOutputPipe. The folder structure must be:
<work folder>
VOutputPipe
Frame
Typical commands to build the library, the test application and the example (use the URLs of the repositories provided to you):
git clone <VOutputPipe repository URL>
git clone <Frame repository URL>
cd VOutputPipe
mkdir build
cd build
cmake ..
make
If VOutputPipe is the top-level CMake project, the main CMakeLists.txt file adds the folder ../Frame before the library itself. If the folder does not exist CMake stops with an error. The test application and the example are built to the build/bin folder (VOutputPipeTest and VOutputPipeExample), the static library is built to the build/src folder.
CMake options
Table 2 - CMake options of the repository. In the options below ${PARENT} is the prefix defined by the top-level project (empty if VOutputPipe is the top-level project).
| Option | Default | Description |
|---|---|---|
| ${PARENT}_VOUTPUT_PIPE | ON | Build the library. |
| ${PARENT}_VOUTPUT_PIPE_TEST | ON (stand alone), OFF (included in other project) | Build the test application. |
| ${PARENT}_VOUTPUT_PIPE_EXAMPLE | ON (stand alone), OFF (included in other project) | Build the example. |
| ${PARENT}_SUBMODULE_CACHE_OVERWRITE | ON | Forced rewriting of the options above in the CMake cache. The top-level project sets it to OFF when it includes VOutputPipe as a sub-repository. |
| CMAKE_BUILD_TYPE | Release (if not specified) | Build type. |
The library has no other options and no configuration macros.
Connect to your project
If you want to connect VOutputPipe library to your CMake project as source code you can make follow. For example, if your repository has structure:
CMakeLists.txt
src
CMakeLists.txt
yourLib.h
yourLib.cpp
Create folder 3rdparty in your repository. Copy (or add as sub-repositories) the repository folders Frame and VOutputPipe to the 3rdparty folder. Structure of your repository:
CMakeLists.txt
src
CMakeLists.txt
yourLib.h
yourLib.cpp
3rdparty
Frame
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)
################################################################################
## CONFIGURATION
## 3rd-party submodules configuration
################################################################################
SET(${PARENT}_SUBMODULE_FRAME ON CACHE BOOL "" FORCE)
if (${PARENT}_SUBMODULE_FRAME)
SET(${PARENT}_FRAME ON CACHE BOOL "" FORCE)
SET(${PARENT}_FRAME_TEST OFF CACHE BOOL "" FORCE)
endif()
SET(${PARENT}_SUBMODULE_VOUTPUT_PIPE ON CACHE BOOL "" FORCE)
if (${PARENT}_SUBMODULE_VOUTPUT_PIPE)
SET(${PARENT}_VOUTPUT_PIPE ON CACHE BOOL "" FORCE)
SET(${PARENT}_VOUTPUT_PIPE_TEST OFF CACHE BOOL "" FORCE)
SET(${PARENT}_VOUTPUT_PIPE_EXAMPLE OFF CACHE BOOL "" FORCE)
endif()
################################################################################
## INCLUDING SUBDIRECTORIES
## Adding subdirectories according to the 3rd-party configuration
################################################################################
if (${PARENT}_SUBMODULE_FRAME)
add_subdirectory(Frame)
endif()
if (${PARENT}_SUBMODULE_VOUTPUT_PIPE)
add_subdirectory(VOutputPipe)
endif()
File 3rdparty/CMakeLists.txt adds the folders Frame and VOutputPipe (in this order, in order of their dependencies) to your project and excludes the test application and the example from compiling (by default they are excluded if VOutputPipe is included as sub-repository). Structure of your repository:
CMakeLists.txt
src
CMakeLists.txt
yourLib.h
yourLib.cpp
3rdparty
CMakeLists.txt
Frame
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)
NOTE: when VOutputPipe is included in another project it does not add the Frame library by itself: the project must add it (as 3rdparty/CMakeLists.txt above does), in any order relative to VOutputPipe. If the Frame library is not added the build stops with the error Frame.h: No such file or directory.
Portability
The library works only on Linux: CMake stops with an error on other systems. The library uses the POSIX file interface (mkfifo, open, fstat, fchmod, write) and the signal function (see the VOutputPipe constructor). It is written in C++17 without compiler extensions and contains no processor specific code (no SIMD, no assembler). The library writes the bytes of the frame as they are, so the byte order of the host does not affect the stream (the NV12 pixel format is a sequence of 8 bit values). The library was built (Release and Debug) and tested with GCC 12, GCC 13, Clang 15 and Clang 18 on x86-64, and with GCC 13 cross compiler for aarch64 (tests run under emulation).
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 <chrono>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <iostream>
#include <thread>
#include "Frame.h"
#include "VOutputPipe.h"
namespace
{
/// Name of the pipe file. The video consumer reads this file.
constexpr const char* PIPE_NAME{"/tmp/video0"};
/// Frame width, pixels.
constexpr int FRAME_WIDTH{1920};
/// Frame height, pixels.
constexpr int FRAME_HEIGHT{1080};
/// Size of the NV12 Y plane, bytes.
constexpr std::size_t Y_PLANE_SIZE{static_cast<std::size_t>(FRAME_WIDTH) *
static_cast<std::size_t>(FRAME_HEIGHT)};
/// Size of the NV12 UV plane (interleaved U and V, 2x2 subsampling), bytes.
constexpr std::size_t UV_PLANE_SIZE{Y_PLANE_SIZE / 2};
/// Y value of black in limited range.
constexpr int BLACK_Y{16};
/// Y value of white in limited range.
constexpr int WHITE_Y{235};
/// U and V value of neutral chroma.
constexpr int NEUTRAL_UV{128};
/// Side of the moving white square, pixels.
constexpr int RECT_SIZE{200};
/// Y coordinate of the top side of the moving square, pixels.
constexpr int RECT_TOP{300};
/// Horizontal step of the moving square per frame, pixels.
constexpr std::uint64_t RECT_STEP{8};
/// Range of the horizontal position of the moving square, pixels.
constexpr std::uint64_t RECT_SPAN{FRAME_WIDTH - RECT_SIZE};
static_assert(RECT_TOP + RECT_SIZE <= FRAME_HEIGHT,
"the square must be inside the frame");
/// Frame period for 30 fps stream.
constexpr std::chrono::milliseconds FRAME_PERIOD{33};
}
int main(void)
{
// Open pipe.
rapidpixel::VOutputPipe videoOutput;
if (!videoOutput.open(PIPE_NAME))
{
std::cerr << "Can't open pipe" << std::endl;
return -1;
}
// Create NV12 frame with certain resolution.
rapidpixel::Frame frame(FRAME_WIDTH, FRAME_HEIGHT, rapidpixel::Fourcc::NV12);
if (frame.data == nullptr)
{
std::cerr << "Can't allocate frame" << std::endl;
return -1;
}
// Main loop. The frame counter is 64 bit: the loop never ends and a 32 bit
// counter would overflow.
for (std::uint64_t frameId = 0; true; ++frameId)
{
// Fill Y plane by black and UV plane by neutral chroma.
std::memset(frame.data, BLACK_Y, Y_PLANE_SIZE);
std::memset(frame.data + Y_PLANE_SIZE, NEUTRAL_UV, UV_PLANE_SIZE);
// Draw moving white square in Y plane.
const int x = static_cast<int>(frameId * RECT_STEP % RECT_SPAN);
for (int y = RECT_TOP; y < RECT_TOP + RECT_SIZE; ++y)
{
std::memset(frame.data + static_cast<std::ptrdiff_t>(y) *
FRAME_WIDTH + x, WHITE_Y,
static_cast<std::size_t>(RECT_SIZE));
}
// Write frame to pipe. Write fails while no consumer reads the pipe.
if (!videoOutput.write(frame))
{
std::cerr << "Can't write frame" << std::endl;
}
// Wait for the next frame.
std::this_thread::sleep_for(FRAME_PERIOD);
}
}
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. The example runs until it is terminated. The pipe file created by the example stays in place when the process is killed by a signal (the destructor does not run), and the next start reuses it.
Test Application
The test directory contains the test application of the library, built as VOutputPipeTest. The application depends only on the VOutputPipe library and has two modes:
- without arguments it runs the self-tests of the library (default);
- with the
--streamargument it generates artificial NV12 video frames and writes them to a named pipe.
Test Application Workflow
Self-tests. The tests create their files in a temporary directory that is removed at the end, use a deterministic pseudo-random generator and print one block per test with the result OK or ERROR. The application returns 0 if all tests passed and 1 if not. The tests check:
- The version string against the version macros.
- Creation of the pipe file (type, access rights under different umask values) and its removal by
close()and by the destructor. - Rejection of a regular file, a symbolic link, a directory, an empty name, a missing directory, a too long name and a name with a NUL character; a name with special characters is accepted.
- Reuse of an existing pipe file (it is not changed and not removed).
- Calls in the wrong order and frames that are not valid NV12 frames.
- Frames of different sizes (from 2x2 to 1920x1080) sent to a consumer: the consumer must get exactly the bytes that were sent.
- A consumer that leaves and a new consumer that starts.
- A pipe file replaced by a regular file or by a symbolic link.
- A deterministic random sequence of 20000 calls: the object and the files stay consistent.
Stream mode. 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
In the stream mode 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:
const int width = 960;
const int height = 540;
rapidpixel::Frame outputFrame(width, height, rapidpixel::Fourcc::NV12);
const std::size_t ySize = static_cast<std::size_t>(width) *
static_cast<std::size_t>(height);
const std::size_t uvSize = static_cast<std::size_t>(width) *
static_cast<std::size_t>(height / 2);
std::memset(outputFrame.data, 16, ySize);
std::memset(outputFrame.data + ySize, 128, uvSize);
The resulting outputFrame already has the Fourcc::NV12 format required by videoOutput.write(outputFrame).
Run the Test Application
Run the self-tests from the project root:
./build/bin/VOutputPipeTest
Run the stream mode:
./build/bin/VOutputPipeTest --stream
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.