HEVC / H.265 Encoder (Pull)¶
This article explains how to use Transcoder::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;
}
Library::initialize();
bool encodeResult = encode(opt);
Library::shutdown();
return encodeResult ? 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.
primo::ref<MediaSocket> createInputSocket(Options& opt)
{
auto socket = primo::make_ref(Library::createMediaSocket());
socket->setStreamType(StreamType::UncompressedVideo);
socket->setFile(primo::ustring(opt.yuv_file));
auto pin = primo::make_ref(Library::createMediaPin());
socket->pins()->add(pin.get());
auto vsi = primo::make_ref(Library::createVideoStreamInfo());
pin->setStreamInfo(vsi.get());
vsi->setStreamType(StreamType::UncompressedVideo);
vsi->setFrameWidth(opt.frame_size.width_);
vsi->setFrameHeight(opt.frame_size.height_);
vsi->setColorFormat(opt.yuv_color.Id);
vsi->setFrameRate(opt.fps);
vsi->setScanType(ScanType::Progressive);
return socket;
}
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.
primo::ref<MediaSocket> createOutputSocket(Options& opt)
{
auto socket = primo::make_ref(Library::createMediaSocket());
socket->setStreamType(StreamType::H265);
socket->setStreamSubType(StreamSubType::HEVC_Annex_B);
auto pin = primo::make_ref(Library::createMediaPin());
socket->pins()->add(pin.get());
auto vsi = primo::make_ref(Library::createVideoStreamInfo());
pin->setStreamInfo(vsi.get());
vsi->setStreamType(StreamType::H265);
vsi->setStreamSubType(StreamSubType::HEVC_Annex_B);
return socket;
}
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(Options& opt)
{
// transcoder will fail if output exists (by design)
deleteFile(primo::ustring(opt.h265_file));
ofstream outfile(opt.h265_file, ios_base::binary);
if (!outfile.is_open())
{
cout << "Could not open file " << opt.h265_file << endl;
return false;
}
// create input socket
auto inSocket = createInputSocket(opt);
// create output socket
auto outSocket = createOutputSocket(opt);
// create transcoder
auto transcoder = primo::make_ref(Library::createTranscoder());
transcoder->setAllowDemoMode(true);
transcoder->inputs()->add(inSocket.get());
transcoder->outputs()->add(outSocket.get());
if (!transcoder->open())
{
printError("Transcoder::open", transcoder->error());
return false;
}
// encode by pulling encoded samples
int32_t outputIndex = 0;
auto sample = primo::make_ref(Library::createMediaSample());
while (transcoder->pull(outputIndex, sample.get()))
{
outfile.write((const char *)sample->buffer()->data(), sample->buffer()->dataSize());
}
const primo::error::ErrorInfo *error = transcoder->error();
printError("Transcoder::pull", error);
bool success = false;
if ((error->facility() == primo::error::ErrorFacility::Codec) &&
(error->code() == primo::codecs::CodecError::EOS))
{
// ok
success = true;
}
transcoder->close();
return success;
}
Complete C++ Code¶
Here’s the complete working example from enc_hevc_pull.cpp:
/*
* Copyright (c) Primo Software. All Rights Reserved.
*
* Use of this source code is governed by a MIT License
* that can be found in the LICENSE file in the root of the source
* tree.
*/
#include <unistd.h>
#include <iostream>
#include <string>
#include <fstream>
#include <filesystem>
#include <primo/platform/reference++.h>
#include <primo/platform/error_facility.h>
#include <primo/platform/ustring.h>
#include <primo/avblocks/avb.h>
#include "options.h"
#include "util.h"
using namespace std;
using namespace primo::avblocks;
using namespace primo::codecs;
namespace fs = std::filesystem;
namespace av = primo::avblocks;
namespace pc = primo::codecs;
void printStatus(const primo::error::ErrorInfo* e)
{
if (primo::error::ErrorFacility::Success == e->facility())
{
cout << "Success";
}
else
{
if (e->message())
{
cout << primo::ustring(e->message()) << " ";
}
cout << "(facility:" << e->facility() << " error:" << e->code() << ")" << endl;
}
cout << endl;
}
primo::ref<MediaSocket> createInputSocket(Options& opt)
{
auto socket = primo::make_ref(Library::createMediaSocket());
socket->setStreamType(StreamType::UncompressedVideo);
socket->setFile(primo::ustring(opt.yuv_file));
auto pin = primo::make_ref(Library::createMediaPin());
socket->pins()->add(pin.get());
auto vsi = primo::make_ref(Library::createVideoStreamInfo());
pin->setStreamInfo(vsi.get());
vsi->setStreamType(StreamType::UncompressedVideo);
vsi->setFrameWidth(opt.frame_size.width_);
vsi->setFrameHeight(opt.frame_size.height_);
vsi->setColorFormat(opt.yuv_color.Id);
vsi->setFrameRate(opt.fps);
vsi->setScanType(ScanType::Progressive);
return socket;
}
primo::ref<MediaSocket> createOutputSocket(Options& opt)
{
auto socket = primo::make_ref(Library::createMediaSocket());
socket->setStreamType(StreamType::H265);
socket->setStreamSubType(StreamSubType::HEVC_Annex_B);
auto pin = primo::make_ref(Library::createMediaPin());
socket->pins()->add(pin.get());
auto vsi = primo::make_ref(Library::createVideoStreamInfo());
pin->setStreamInfo(vsi.get());
vsi->setStreamType(StreamType::H265);
vsi->setStreamSubType(StreamSubType::HEVC_Annex_B);
return socket;
}
bool encode(Options& opt)
{
// transcoder will fail if output exists (by design)
deleteFile(primo::ustring(opt.h265_file));
ofstream outfile(opt.h265_file, ios_base::binary);
if (!outfile.is_open())
{
cout << "Could not open file " << opt.h265_file << endl;
return false;
}
// create input socket
auto inSocket = createInputSocket(opt);
// create output socket
auto outSocket = createOutputSocket(opt);
// create transcoder
auto transcoder = primo::make_ref(Library::createTranscoder());
transcoder->setAllowDemoMode(true);
transcoder->inputs()->add(inSocket.get());
transcoder->outputs()->add(outSocket.get());
if (!transcoder->open())
{
printError("Transcoder::open", transcoder->error());
return false;
}
// encode by pulling encoded samples
int32_t outputIndex = 0;
auto sample = primo::make_ref(Library::createMediaSample());
while (transcoder->pull(outputIndex, sample.get()))
{
outfile.write((const char *)sample->buffer()->data(), sample->buffer()->dataSize());
}
const primo::error::ErrorInfo *error = transcoder->error();
printError("Transcoder::pull", error);
bool success = false;
if ((error->facility() == primo::error::ErrorFacility::Codec) &&
(error->code() == primo::codecs::CodecError::EOS))
{
// ok
success = true;
}
transcoder->close();
return success;
}
int main(int argc, char* argv[])
{
Options opt;
switch(prepareOptions(opt, argc, argv))
{
case Command: return 0;
case Error: return 1;
case Parsed: break;
}
Library::initialize();
bool encodeResult = encode(opt);
Library::shutdown();
return encodeResult ? 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