
VDecoderH265 C++ library
v1.1.0
Table of contents
- Overview
- Versions
- Library files
- VDecoderH265 class description
- Build and connect to your project
- SIMD acceleration and portability
- Simple example
Overview
The VDecoderH265 C++ library provides a standalone software H.265 (HEVC) video decoder for Linux, Windows, and embedded platforms. Key features:
- Zero external dependencies: Contains complete, self-contained implementations of HEVC NAL parsing (VPS, SPS, PPS, Slice headers), Exp-Golomb bitstream reading, CABAC entropy decoding, inverse quantization, 4x4 / 8x8 / 16x16 / 32x32 DCT and DST transforms, Intra prediction (Planar, DC, and 33 directional Angular modes), Inter motion compensation (fractional 7/8-tap luma and 4-tap chroma sub-pel interpolation and bi-prediction), DPB (Decoded Picture Buffer) management, Sample Adaptive Offset (SAO), and an adaptive in-loop deblocking filter. Zero third-party media frameworks or OS-specific decoding services are required.
- Pure C++ Source Architecture for Certification: Built 100% from pure C++17 source code with native SIMD intrinsics (AVX2, SSE4.1 on x86 / x86-64, NEON on ARM64) and a portable scalar fallback. Uses no precompiled libraries, preloaded shared objects, or runtime dynamic loading (
.dll,.so,LoadLibrary,dlopen), meeting all strict software certification standards. - Runs on any CPU, builds with any C++17 compiler: SIMD kernels are compiled per source file and selected at run time (AVX2 -> SSE4.1 -> scalar on x86, NEON on ARM64, scalar on every other architecture), so a binary never requires an instruction set the CPU does not have. See SIMD acceleration and portability.
- Cross-platform: Windows (MSVC 2022, x86 and x86-64), Linux x86 / x86-64 (GCC, Clang) and Linux ARM64 / AArch64 (GCC, Clang, NEON). Any other platform with a C++17 compiler and
std::threadis built with the scalar code path. The decoded output is bit-identical on all platforms, SIMD levels and thread counts. - Supported streams: progressive 8-bit 4:2:0 Main profile streams: CABAC entropy decoding, I, P and B slices, weighted prediction, 4x4 to 32x32 transform hierarchy (including transform skip and explicit scaling lists), PCM and lossless (
cu_transquant_bypass) coding units, multiple and dependent slices, tiles, WPP entry points, all IRAP types (IDR, CRA, BLA, RASL / RADL handling) and the output order process (pic_output_flag,no_output_of_prior_pics_flag, DPB bumping). Not supported: bit depth above 8, 4:2:2 / 4:4:4 / monochrome (such streams are rejected with an error, never decoded wrongly), multilayer / 3D extensions (only the base layer,nuh_layer_id = 0, is decoded). - Verified against the conformance suite: all 136 8-bit 4:2:0 streams of the JCT-VC
HEVC_v1conformance suite decode bit-exactly (MD5 of the output equals the official MD5 shipped with each stream) in single- and multi-threaded mode. The remaining 11 streams of the suite use more than 8 bits and are rejected. - Motion Vector Extraction: Provides spatial motion vector extraction through
getMotionVector(), returning[x, y, dx, dy]for one sample per 16x16 display-grid cell, with conformance-window cropping accounted for. - NV12 Output: Decodes Annex B elementary streams into standard NV12 uncompressed video frames (Y plane followed by interleaved UV plane), ready for display or downstream computer vision / AI processing.
- Fast MV-Only Extraction: Supports lightweight motion vector extraction mode (
CUSTOM_1 = 1.0f), which extracts motion vectors while bypassing NV12 frame allocation and memory copying. - Single-threaded by default, optional multi-threading: By default (
CUSTOM_2 = 1.0f) the decoder works single-threaded with zero background thread overhead. Optionally (CUSTOM_2 = 0.0ffor automatic, orN >= 2) worker threads perform multi-threaded Wavefront Parallel Processing (WPP) and parallel in-loop filtering, achieving up to 2.5x acceleration while maintaining 100% bit-identical output. - Standard VCodec Interface: Inherits from the open-source
cr::video::VCodecinterface (3rdparty/VCodec), enabling drop-in compatibility across the RapidPixel SDK ecosystem. - C++17 Architecture: Adheres strictly to the PIMPL pattern, shielding public headers from internal decoder structures and ensuring rapid compilation and clean ABI stability. The VDecoderH265 class depends on the VCodec class, which determines video frame structures. The example application uses SimpleUI for window display and drawing vector overlays, and a lightweight built-in H.265 elementary stream parser (
H265Parser). The library uses the C++17 standard.
Versions
Table 1 - Library versions.
| Version | Release date | What’s new |
|---|---|---|
| 1.0.0 | 30.09.2026 | - First version. |
| 1.1.0 | 06.10.2026 | - Decoder restructured and largely rewritten. - Removed MD5 / SEI hash verification, debug output and picture dumps, alloca with stream dependent size; range checks of all parsed values. - Fixes: QP prediction with WPP and tiles, transform skip rotation, NoBackwardPredFlag, uninitialised motion data, hostile POC values. - No compiler warnings (MSVC /W4, GCC, Clang), about 20 % faster. |
Library files
The repository structure is organized as follows:
CMakeLists.txt ------------------ Main CMake build configuration.
README.md ----------------------- Documentation and usage guide.
3rdparty ------------------------ External dependencies.
CMakeLists.txt -------------- CMake inclusion script for third-party libraries.
VCodec ---------------------- VCodec interface and Frame submodule.
src ----------------------------- Library source code.
CMakeLists.txt -------------- CMake script for VDecoderH265 library.
VDecoderH265.h -------------- Public library interface (PIMPL).
VDecoderH265.cpp ------------ Public library implementation wrapper.
VDecoderH265Version.h ------- Version definition header.
VDecoderH265Version.h.in ---- Template for CMake version generation.
impl ------------------------ Internal decoder implementation files.
VDecoderH265Impl.h ------ Internal PIMPL class declaration.
VDecoderH265Impl.cpp ---- PIMPL class implementation & frame buffer management.
H265Engine.h ------------ High-performance pure C++ H.265 decoder engine interface.
H265Engine.cpp ---------- Engine initialization, NAL dispatching, and decode loop.
H265Common.h ------------ Core types, error codes, and HEVC constants.
H265Bitstream.h / .cpp -- Bitstream reader, Exp-Golomb decoding, and CABAC bits.
H265Cabac.h / .cpp ------ Context Adaptive Binary Arithmetic Coding (CABAC) engine.
H265ContextModel.h / .cpp CABAC context state and probability tables.
H265Nal.h / .cpp -------- NAL unit header, nal_unit_type values and their picture categories.
H265NalParser.h / .cpp -- Annex B byte-stream and packet NAL unit parser.
H265Slice.h / .cpp ------ Slice header parsing, CTU processing, and reconstruct loop.
H265Vps.h / .cpp -------- profile_tier_level() skipping (the VPS itself is not needed for decoding).
H265Sps.h / .cpp -------- Sequence Parameter Set (SPS) parsing and conformance window.
H265Pps.h / .cpp -------- Picture Parameter Set (PPS) parsing and tile/slice config.
H265Vui.h / .cpp -------- Video Usability Information (VUI) and timing parameters.
H265Image.h / .cpp ------ Decoded picture: sample planes, conformance window, block metadata grids.
H265Dpb.h / .cpp -------- Decoded Picture Buffer (DPB) and reference picture marking.
H265RefPic.h / .cpp ----- Short-term reference picture sets: st_ref_pic_set() and its derivation.
H265Motion.h / .cpp ----- Motion compensation, sub-pel interpolation, bi-prediction.
H265IntraPred.h / .cpp -- Intra prediction (Planar, DC, and 33 Angular modes).
H265Transform.h / .cpp -- Inverse quantization and transform hierarchy (DCT/DST).
H265Deblock.h / .cpp ---- Adaptive in-loop deblocking filter.
H265Sao.h / .cpp -------- Sample Adaptive Offset (SAO) in-loop filtering.
H265Scan.h / .cpp ------- Coefficient scan orders (diagonal, horizontal, vertical).
H265AllocPool.h / .cpp -- High-speed chunk memory allocator for decoder frames.
H265DecCtx.h / .cpp ----- Thread context, slice decoding state, and DPB tracking.
H265Threads.h / .cpp ---- Thread synchronization and multi-threaded decoding primitives.
H265Util.h / .cpp ------- Clip3, ceilLog2 and the checked syntax element reader of the headers.
H265Acceleration.h ------ Kernel table of the pixel processing functions, filled at run time.
H265Fallback*.h / .cpp -- High-performance scalar fallback DSP implementations.
x86/ -------------------- x86 / x86-64 SIMD kernels and CPU feature detection.
H265Sse.h / .cpp ---- CPUID / XGETBV detection and run-time dispatch (AVX2 -> SSE4.1 -> scalar).
H265SseDct / Deblk / Intra / Motion.h / .cpp - SSE4.1 kernels (compiled with -msse4.1).
H265TransformAvx2.h / .cpp - AVX2 inverse transform (compiled with -mavx2 or /arch:AVX2).
H265TransformDctTables.h - DCT coefficient tables shared by the SIMD kernels.
arm/ -------------------- ARM NEON kernels (AArch64).
example ------------------------- Standalone sample application.
SimpleUI -------------------- Lightweight window and drawing library.
H265Parser.h ---------------- Standalone H.265 / HEVC elementary stream parser header.
H265Parser.cpp -------------- Standalone H.265 / HEVC elementary stream parser implementation.
CMakeLists.txt -------------- Example build configuration.
main.cpp -------------------- Example demonstrating video decoding and MV extraction.
VDecoderH265 class description
VDecoderH265 class declaration
The VDecoderH265 class is declared in VDecoderH265.h:
namespace cr
{
namespace video
{
class VDecoderH265 : public VCodec
{
public:
/// Class constructor.
VDecoderH265();
/// Class destructor.
~VDecoderH265() override;
/// The decoder is not copyable.
VDecoderH265(const VDecoderH265&) = delete;
VDecoderH265& operator=(const VDecoderH265&) = delete;
/// Get library version.
static std::string getVersion();
/// Decode H.265 video frame to NV12.
bool transcode(Frame& src, Frame& dst) override;
/// Get motion vectors from the most recently decoded frame.
const std::vector<std::vector<float>>& getMotionVector();
/// Set parameter value.
bool setParam(VCodecParam id, float value) override;
/// Get parameter value.
float getParam(VCodecParam id) override;
/// Execute codec command.
bool executeCommand(VCodecCommand id) override;
};
}
}
getVersion method
The getVersion() method returns a string with the current version of the library:
static std::string getVersion();
Example usage:
std::cout << "VDecoderH265 version: " << cr::video::VDecoderH265::getVersion() << std::endl;
Terminal output:
VDecoderH265 version: 1.1.0
transcode method
The transcode(…) method decodes an incoming H.265 Annex B bitstream packet into an uncompressed NV12 video frame.
bool transcode(Frame& src, Frame& dst) override;
| Parameter | Type | Description |
|---|---|---|
src | Frame& | Source compressed video frame. Field src.fourcc must be Fourcc::HEVC. Submit one complete Annex B access unit per call, with src.data and src.size describing its bytes. H.264 and unsupported chroma/bit-depth formats are rejected. |
dst | Frame& | Destination uncompressed video frame. Upon successful decode, the decoder populates dst.data with raw NV12 pixel data, sets dst.width, dst.height, dst.size = width * height * 3 / 2, and dst.fourcc = Fourcc::NV12. |
Returns: true if a frame was successfully decoded and available; false otherwise (e.g., parameter set NAL units without slice data, or buffered frame in DPB).
At EOF, repeatedly call transcode() with an empty HEVC frame (src.size = 0) until it returns false to retrieve delayed B-frames. Empty input marks the end of the stream, not an access-unit boundary. Execute RESET before feeding a new stream. Output dst.frameId identifies the source access unit that produced the displayed frame, including after reordering. Input buffering is bounded (16 MiB per NAL, 64 MiB total queued encoded bytes by default).
cr::video::Frame eof;
eof.fourcc = cr::video::Fourcc::HEVC;
while (decoder.transcode(eof, dst))
{
processFrame(dst);
}
getMotionVector method
The getMotionVector() method retrieves the spatial motion vectors for the most recently decoded video frame.
const std::vector<std::vector<float>>& getMotionVector();
Returns: A reference to a two-dimensional vector where each element is a 4-component vector [x, y, dx, dy]. The reference stays valid until the next transcode(...) call, RESET command or destruction of the decoder: copy the vector if you need it longer. Before the first decoded frame (and after RESET) the vector is empty.
x: Horizontal pixel coordinate of the top-left corner of the macroblock / sub-block.y: Vertical pixel coordinate of the top-left corner of the macroblock / sub-block.dx: Horizontal motion displacement in pixels.dy: Vertical motion displacement in pixels.
Intra-coded cells have zero displacement dx=0, dy=0. For predicted cells, the decoder samples the center of each 16x16 cell, clamped to the visible picture at partial edges. It returns L0 when available, otherwise L1. This is a display-grid summary, not all coded sub-partitions or both bi-prediction vectors. Coordinates are relative to the cropped, displayed picture.
Example:
const auto& mvs = decoder.getMotionVector();
for (const auto& mv : mvs)
{
float x = mv[0];
float y = mv[1];
float dx = mv[2];
float dy = mv[3];
// Process or visualize motion vector
}
setParam method
The setParam(…) method configures decoder parameters.
bool setParam(VCodecParam id, float value) override;
| Parameter | Type | Description |
|---|---|---|
id | VCodecParam | Parameter identifier from the VCodecParam enum. |
value | float | Value to assign to the parameter. |
Supported parameters:
VCodecParam::LOG_LEVEL: Logging verbosity (0- disabled,1- console).VCodecParam::TYPE: Codec mode (1- software decoder).VCodecParam::CUSTOM_1: Decoding mode (default0.0f):0.0f- full video decoding to NV12 + motion vectors.1.0f- motion vectors only: the frame is fully decoded, but NV12 frame buffer copy is skipped (dstgetswidth,height,fourcc = NV12andsize = 0).
VCodecParam::CUSTOM_2: Number of decoding threads (default1.0f):1.0f- single-threaded decoding (default): everything is decoded in the thread which callstranscode(...), no worker threads are created.0.0f- automatic: worker thread pool depending on hardware concurrency (at most 16 workers).N >= 2- worker pool ofNthreads, capped at 32 workers.getParam(CUSTOM_2)reports the actual applied count.- The output is bit-identical for every value.
VCodecParam::CUSTOM_3: Color range control for output NV12 frame (default1.0f):1.0f: Automatic (default). If bitstream signals or infers Limited Range and frame luminance data analysis confirms limited dynamic range [16..235], automatically expands luma [16..235] and chroma [16..240] to Full Range [0..255] during NV12 assembly. Full-range frames are preserved without clipping or alteration.0.0f: Disabled / pass-through: pass-through raw decoded samples without range expansion.2.0f: Forced expansion: always expand Limited Range to Full Range regardless of bitstream headers or pixel statistics.
Returns: true if the parameter was accepted, false otherwise.
Values must be finite integers representable as int. CUSTOM_1 accepts only 0/1, CUSTOM_2 accepts nonnegative counts (0 selects automatic), and CUSTOM_3 accepts only 0/1/2. Initialization failure is reported as false.
getParam method
The getParam(…) method queries parameter values from the decoder.
float getParam(VCodecParam id) override;
Returns: Value of the specified parameter, or -1.0f if unsupported.
executeCommand method
The executeCommand(…) method sends control commands to the decoder.
bool executeCommand(VCodecCommand id) override;
Supported commands:
VCodecCommand::RESET: Flushes the Decoded Picture Buffer (DPB), clears internal state, and resets sequence parameters ready for a new stream.
Returns: true if the command was executed successfully.
Build and connect to your project
The library needs CMake 3.13 or newer and a C++17 compiler with std::thread support (tested with MSVC 2022, GCC 12 and Clang 15). The repository uses git submodules (3rdparty/VCodec, example/SimpleUI): clone it with git clone --recurse-submodules or run git submodule update --init --recursive in an existing clone. The default build type is Release.
Typical commands to build VDecoderH265 library:
cd VDecoderH265
mkdir build
cd build
cmake ..
make
If you want to connect the VDecoderH265 library to your CMake project as source code, you can do the following. For example, if your repository has the structure:
CMakeLists.txt
src
CMakeList.txt
yourLib.h
yourLib.cpp
Create folder 3rdparty in your repository and copy VDecoderH265 folder there. The new structure of your repository:
CMakeLists.txt
src
CMakeList.txt
yourLib.h
yourLib.cpp
3rdparty
VDecoderH265
Create a CMakeLists.txt file in the 3rdparty folder. The CMakeLists.txt file 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(VDecoderH265)
Next, you need to include the 3rdparty folder in the main CMakeLists.txt file of your repository. Add the following line at the end of your main CMakeLists.txt:
add_subdirectory(3rdparty)
Next, you need to include the VDecoderH265 library in your src/CMakeLists.txt file:
target_link_libraries(${PROJECT_NAME} VDecoderH265)
Include the public header in your code:
#include "VDecoderH265.h"
SIMD acceleration and portability
The decoder is plain C++17 with a complete scalar implementation of every DSP routine. SIMD kernels are optional accelerators which are selected at run time, when the decoder is created:
| CPU | Kernels used | How the level is chosen |
|---|---|---|
| x86 / x86-64 | AVX2 -> SSE4.1 -> scalar | CPUID. AVX2 additionally requires OS support for the YMM registers (OSXSAVE + XGETBV). |
| ARM64 / AArch64 | NEON | Always available on AArch64. |
| Any other CPU | scalar | No intrinsics are compiled. |
A binary built on a machine with AVX2 therefore also starts and decodes on a CPU with only SSE4.1 or even only SSE2 (checked with emulated Core 2, Nehalem and Sandy Bridge CPUs), and the decoded output is bit-identical for every level.
Instruction-set flags are applied per source file, never globally:
| Source files | Flags |
|---|---|
src/impl/x86/H265SseDct.cpp, H265SseDeblk.cpp, H265SseIntra.cpp, H265SseMotion.cpp | -msse4.1 (GCC / Clang). MSVC needs no flag. |
src/impl/x86/H265TransformAvx2.cpp | -mavx2 (GCC / Clang), /arch:AVX2 (MSVC). |
| All other files, including CPU detection, the dispatcher and the scalar fallbacks | No instruction-set flags (no -march=native, no global -mavx2 or /arch:AVX2). |
CMake checks with try_compile that the compiler can build the SSE4.1 and AVX2 kernels. If it cannot (very old compiler, unusual toolchain, universal macOS binary), these kernels are left out, a message is printed during configuration, and the dispatcher falls back to the next level. Options:
| Option / macro | Effect |
|---|---|
-DVDECODERH265_ENABLE_SIMD=OFF | Builds the portable scalar decoder only (defines VDECODERH265_NO_SIMD). |
VDECODERH265_NO_SSE41, VDECODERH265_NO_AVX2 | Leave out the x86 kernel sets. CMake defines them automatically when the compiler probe fails. |
CMake exports these macros as public definitions of the VDecoderH265 target. Without CMake, compile all src/**/*.cpp files as C++17, add the flags from the table to the listed files (or define the macros above) and link the system thread library (-pthread).
Simple example
The example application (example/main.cpp) demonstrates opening an H.265 video file, decoding frames to NV12, draining the frames delayed by reordering at the end of the stream, extracting motion vectors, and rendering them using SimpleUI:
#include <iostream>
#include <string>
#include <vector>
#include <cmath>
#include <cstring>
#include "VDecoderH265.h"
#include "H265Parser.h"
#include "SimpleUi.h"
// Link namespaces.
using namespace std;
using namespace cr::ui;
using namespace cr::video;
// Entry point.
int main(int argc, char* argv[])
{
// Print library version.
cout << "VDecoderH265 v" << VDecoderH265::getVersion() << endl;
// Get input H.265 file path.
string videoPath;
if (argc > 1)
{
videoPath = argv[1];
}
else
{
cout << "Enter path to video file (*.h265): ";
getline(cin, videoPath);
}
if (!videoPath.empty() && (videoPath.front() == '"' || videoPath.front() == '\''))
videoPath.erase(0, 1);
if (!videoPath.empty() && (videoPath.back() == '"' || videoPath.back() == '\''))
videoPath.pop_back();
if (videoPath.empty())
videoPath = "test.h265";
// Open H.265 elementary stream file.
H265Parser parser;
if (!parser.open(videoPath))
{
cerr << "Failed to open video file: " << videoPath << endl;
return -1;
}
// Initialize decoder and UI window.
VDecoderH265 decoder;
SimpleUiWindow window("VDecoderH265 - Motion Vectors");
window.show();
// Input H265 frame.
Frame h265Frame(640, 480, Fourcc::HEVC);
// Decoded NV12 frame.
Frame nv12Frame(640, 480, Fourcc::NV12);
// Converted YUV frame.
Frame yuvFrame(640, 480, Fourcc::YUV24);
// Draw the motion vectors of the decoded frame in nv12Frame and show it.
// Returns false when the user asked to exit (ESC).
auto showFrame = [&]()
{
// Convert NV12 frame to YUV format with only Y channel.
if (nv12Frame.width != yuvFrame.width || nv12Frame.height != yuvFrame.height)
yuvFrame = Frame(nv12Frame.width, nv12Frame.height, Fourcc::YUV24);
memset(yuvFrame.data, 128, yuvFrame.size);
for (int i = 0; i < yuvFrame.size; i = i + 3)
yuvFrame.data[i] = nv12Frame.data[i / 3];
// Overlay motion vectors on the frame.
const vector<vector<float>>& vectors = decoder.getMotionVector();
for (const auto& mv : vectors)
if (mv.size() >= 4 && (std::abs(mv[2]) > 0.5f || std::abs(mv[3]) > 0.5f))
SimpleUiDraw::drawLine(yuvFrame, (int)mv[0], (int)mv[1],
(int)(mv[0] + mv[2]), (int)(mv[1] + mv[3]), {0, 255, 0}, 1);
// Render frame and process UI events (ESC to exit).
window.render(yuvFrame);
return window.getKey(30) != 27;
};
// Decode and display video frames.
bool running = true;
while (running && window.isOpen() && parser.getFrame(h265Frame))
{
// Decode H.265 frame to NV12 format.
if (decoder.transcode(h265Frame, nv12Frame))
running = showFrame();
}
// End of stream: an empty HEVC frame flushes the frames delayed by reordering.
Frame eofFrame;
eofFrame.fourcc = Fourcc::HEVC;
while (running && window.isOpen() && decoder.transcode(eofFrame, nv12Frame))
running = showFrame();
return 0;
}