HEVC / H.265 Annex B Encoder (Run)

This article explains how to use Transcoder::run to encode a raw YUV video file to HEVC / H.265 Annex B elementary stream format.

The code snippets are from the enc_hevc_file macOS sample.

Linux and Windows samples are also available:

Source Video

The sample uses foreman_qcif.yuv from the AVBlocks Assets repository. After downloading and unzipping the assets, the file is in the vid subdirectory.

Code

Initialize AVBlocks

Initialize the library before using AVBlocks and shut it down when 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 file. Its dimensions, frame rate, and color format must match the source file.

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

Set the output stream type to H265 and the subtype to HEVC_Annex_B to produce an elementary HEVC stream.

primo::ref<MediaSocket> createOutputSocket(Options& opt)
{
    auto socket = primo::make_ref(Library::createMediaSocket());
    socket->setFile(primo::ustring(opt.h265_file));
    socket->setStreamType(StreamType::H265);

    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 Encode

Create the sockets, enable demo mode, add the sockets to a transcoder, and run it. The sample removes an existing output because the transcoder intentionally fails when the output file already exists.

bool encode(Options& opt)
{
    // 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());

    // transcoder will fail if output exists (by design)
    deleteFile(primo::ustring(opt.h265_file));

    cout << "Transcoder open: ";
    if(transcoder->open())
    {
        printStatus(transcoder->error());
        if(!transcoder->run())
            return false;

        cout << "Transcoder run: ";
        printStatus(transcoder->error());

        transcoder->close();
        cout << "Transcoder close: ";
        printStatus(transcoder->error());
    }
    else
    {
        printStatus(transcoder->error());
        return false;
    }

    return true;
}

Complete C++ Code

Here’s the complete working example from enc_hevc_file.cpp:

#include <unistd.h>

#include <iostream>
#include <string>
#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->setFile(primo::ustring(opt.h265_file));
    socket->setStreamType(StreamType::H265);

    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)
{
    // 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());

    // transcoder will fail if output exists (by design)
    deleteFile(primo::ustring(opt.h265_file));

    cout << "Transcoder open: ";
    if(transcoder->open())
    {
        printStatus(transcoder->error());
        if(!transcoder->run())
            return false;

        cout << "Transcoder run: ";
        printStatus(transcoder->error());

        transcoder->close();
        cout << "Transcoder close: ";
        printStatus(transcoder->error());
    }
    else
    {
        printStatus(transcoder->error());
        return false;
    }

    return true;
}

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 and the enc_hevc_file macOS sample for details.

Command Line

./enc_hevc_file --frame <width>x<height> --rate <fps> --color <COLOR> --input <file.yuv> --output <file.h265> [--colors] [--help]

Examples

List options:

./bin/x64/enc_hevc_file --help

enc_hevc_file --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 the QCIF source to an HEVC file:

mkdir -p ./output/enc_hevc_file

./bin/x64/enc_hevc_file \
  --input ./assets/vid/foreman_qcif.yuv \
  --output ./output/enc_hevc_file/foreman_qcif.h265 \
  --frame 176x144 \
  --rate 30 \
  --color yuv420