HEVC / H.265 Encoder (Pull)¶
This article explains how to use TTranscoder::pull to encode a raw YUV video file to an HEVC / H.265 Annex B elementary stream.
The code snippets in this article are from the enc_hevc_pull macOS sample.
Linux and Windows samples are also available:
Source Video¶
For source we use the foreman_qcif.yuv file from the AVBlocks Assets repository. After downloading and unzipping you will find foreman_qcif.yuv in the vid subdirectory.
Code¶
This code takes a raw YUV video file and encodes it to HEVC / H.265 Annex B format using the pull method. The encoded samples are pulled from the transcoder and written to the output file by the application.
Initialize AVBlocks¶
Initialize the AVBlocks library before creating the transcoder, and shut it down after encoding is complete.
int main(int argc, char* argv[])
{
Options opt;
switch(prepareOptions(opt, argc, argv))
{
case Command: return 0;
case Error: return 1;
case Parsed: break;
}
TLibrary library;
return encode(opt) ? 0 : 1;
}
Configure Input Socket¶
The input socket describes the raw YUV source file. The frame size, frame rate, and color format must match the source video.
TMediaSocket()
.file(opt.yuv_file)
.streamType(StreamType::UncompressedVideo)
.addPin(
TMediaPin()
.streamInfo(TVideoStreamInfo()
.streamType(StreamType::UncompressedVideo)
.frameWidth(opt.frame_size.width_)
.frameHeight(opt.frame_size.height_)
.colorFormat(opt.yuv_color.Id)
.frameRate(opt.fps)
.scanType(ScanType::Progressive)
)
)
Configure Output Socket¶
The output socket describes HEVC / H.265 Annex B output. The pull sample does not set a file on the output socket because the application writes pulled samples to the output stream.
TMediaSocket()
.streamType(StreamType::H265)
.streamSubType(StreamSubType::HEVC_Annex_B)
.addPin(
TMediaPin()
.streamInfo(TVideoStreamInfo()
.streamType(StreamType::H265)
.streamSubType(StreamSubType::HEVC_Annex_B)
)
)
Configure Transcoder and Pull Encoded Samples¶
After creating the input and output sockets, the sample creates a transcoder, enables demo mode, opens it, pulls encoded samples, writes each sample buffer to the output file, and checks for end-of-stream.
bool encode(const Options& opt)
{
deleteFile(opt.h265_file.c_str());
std::ofstream outfile(opt.h265_file, std::ios_base::binary);
if (!outfile.is_open())
{
std::println(stderr, "Could not open output file: {}", opt.h265_file);
return false;
}
try {
TTranscoder transcoder;
transcoder
.allowDemoMode(true)
.addInput(
TMediaSocket()
.file(opt.yuv_file)
.streamType(StreamType::UncompressedVideo)
.addPin(
TMediaPin()
.streamInfo(TVideoStreamInfo()
.streamType(StreamType::UncompressedVideo)
.frameWidth(opt.frame_size.width_)
.frameHeight(opt.frame_size.height_)
.colorFormat(opt.yuv_color.Id)
.frameRate(opt.fps)
.scanType(ScanType::Progressive)
)
)
)
.addOutput(
TMediaSocket()
.streamType(StreamType::H265)
.streamSubType(StreamSubType::HEVC_Annex_B)
.addPin(
TMediaPin()
.streamInfo(TVideoStreamInfo()
.streamType(StreamType::H265)
.streamSubType(StreamSubType::HEVC_Annex_B)
)
)
)
.open();
int32_t outputIndex = 0;
TMediaSample sample;
while (transcoder.pull(outputIndex, sample))
{
auto buf = sample.buffer();
outfile.write(reinterpret_cast<const char*>(buf.data()), buf.dataSize());
}
const auto error = transcoder.error();
bool success = error.facility() == primo::error::ErrorFacility::Codec &&
error.code() == primo::codecs::CodecError::EOS;
if (!success)
{
printError("Transcoder pull", error);
transcoder.close();
return false;
}
transcoder.close();
std::println("Output: {}", opt.h265_file);
return true;
} catch (const TAVBlocksException& ex) {
std::println(stderr, "AVBlocks error: {}", ex.what());
return false;
} catch (const std::exception& ex) {
std::println(stderr, "Error: {}", ex.what());
return false;
}
}
Complete C++ Code¶
Here’s the complete working example from enc_hevc_pull.cpp:
#include <primo/avblocks/avb++.h>
#include <print>
#include <fstream>
#include "options.h"
#include "util.h"
using namespace primo::codecs;
using namespace primo::avblocks::modern;
bool encode(const Options& opt)
{
deleteFile(opt.h265_file.c_str());
std::ofstream outfile(opt.h265_file, std::ios_base::binary);
if (!outfile.is_open())
{
std::println(stderr, "Could not open output file: {}", opt.h265_file);
return false;
}
try {
TTranscoder transcoder;
transcoder
.allowDemoMode(true)
.addInput(
TMediaSocket()
.file(opt.yuv_file)
.streamType(StreamType::UncompressedVideo)
.addPin(
TMediaPin()
.streamInfo(TVideoStreamInfo()
.streamType(StreamType::UncompressedVideo)
.frameWidth(opt.frame_size.width_)
.frameHeight(opt.frame_size.height_)
.colorFormat(opt.yuv_color.Id)
.frameRate(opt.fps)
.scanType(ScanType::Progressive)
)
)
)
.addOutput(
TMediaSocket()
.streamType(StreamType::H265)
.streamSubType(StreamSubType::HEVC_Annex_B)
.addPin(
TMediaPin()
.streamInfo(TVideoStreamInfo()
.streamType(StreamType::H265)
.streamSubType(StreamSubType::HEVC_Annex_B)
)
)
)
.open();
int32_t outputIndex = 0;
TMediaSample sample;
while (transcoder.pull(outputIndex, sample))
{
auto buf = sample.buffer();
outfile.write(reinterpret_cast<const char*>(buf.data()), buf.dataSize());
}
const auto error = transcoder.error();
bool success = error.facility() == primo::error::ErrorFacility::Codec &&
error.code() == primo::codecs::CodecError::EOS;
if (!success)
{
printError("Transcoder pull", error);
transcoder.close();
return false;
}
transcoder.close();
std::println("Output: {}", opt.h265_file);
return true;
} catch (const TAVBlocksException& ex) {
std::println(stderr, "AVBlocks error: {}", ex.what());
return false;
} catch (const std::exception& ex) {
std::println(stderr, "Error: {}", ex.what());
return false;
}
}
int main(int argc, char* argv[])
{
Options opt;
switch(prepareOptions(opt, argc, argv))
{
case Command: return 0;
case Error: return 1;
case Parsed: break;
}
TLibrary library;
return encode(opt) ? 0 : 1;
}
How to Run¶
See the build instructions for macOS and the enc_hevc_pull example for details.
Command Line¶
./enc_hevc_pull --frame <width>x<height> --rate <fps> --color <COLOR> --input <file.yuv> --output <file.h265> [--colors] [--help]
Examples¶
List options:
./bin/x64/enc_hevc_pull --help
enc_hevc_pull --frame <width>x<height> --rate <fps> --color <COLOR> --input <file.yuv> --output <file.h265> [--colors]
-h, --help
-i, --input input YUV file
-o, --output output H265 file
-r, --rate input frame rate
-f, --frame input frame sizes <width>x<height>
-c, --color input color format. Use --colors to list all supported color
formats
--colors list COLOR constants
Encode a raw YUV video from ./assets/vid/foreman_qcif.yuv to an H.265 video in ./output/enc_hevc_pull/foreman_qcif.h265:
mkdir -p ./output/enc_hevc_pull
./bin/x64/enc_hevc_pull \
--input ./assets/vid/foreman_qcif.yuv \
--output ./output/enc_hevc_pull/foreman_qcif.h265 \
--frame 176x144 \
--rate 30 \
--color yuv420