VP9 Encoder (Run)

This article explains how to use Transcoder::run to encode a raw YUV video file to VP9 video in an IVF container.

The code snippets in this article are from the enc_vp9_file 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 VP9 video in an IVF container.

Initialize AVBlocks

The first step in any AVBlocks application is to initialize the library. This must be done before using any other AVBlocks functionality. Always call Library::shutdown() at the end of the program to clean up resources.

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 vsi = primo::make_ref(Library::createVideoStreamInfo());
    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);

    auto pin = primo::make_ref(Library::createMediaPin());
    pin->setStreamInfo(vsi.get());

    auto socket = primo::make_ref(Library::createMediaSocket());
    socket->setStreamType(StreamType::UncompressedVideo);
    socket->setFile(primo::ustring(opt.yuv_file));
    socket->pins()->add(pin.get());

    return socket;
}

Configure Output Socket

The output socket writes VP9 video in an IVF container. The socket stream type is StreamType::IVF, and the output pin stream type is StreamType::VP9.

primo::ref<MediaSocket> createOutputSocket(Options& opt)
{
    auto vsi = primo::make_ref(Library::createVideoStreamInfo());
    vsi->setStreamType(StreamType::VP9);

    auto pin = primo::make_ref(Library::createMediaPin());
    pin->setStreamInfo(vsi.get());

    auto socket = primo::make_ref(Library::createMediaSocket());
    socket->setFile(primo::ustring(opt.ivf_file));
    socket->setStreamType(StreamType::IVF);
    socket->pins()->add(pin.get());

    return socket;
}

Configure Transcoder and Encode

After creating the raw YUV input socket and IVF output socket, the sample creates a transcoder, enables demo mode, adds the sockets, removes any existing output file, opens the transcoder, runs the encode, and closes it.

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.ivf_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_vp9_file.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 <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 vsi = primo::make_ref(Library::createVideoStreamInfo());
    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);

    auto pin = primo::make_ref(Library::createMediaPin());
    pin->setStreamInfo(vsi.get());

    auto socket = primo::make_ref(Library::createMediaSocket());
    socket->setStreamType(StreamType::UncompressedVideo);
    socket->setFile(primo::ustring(opt.yuv_file));
    socket->pins()->add(pin.get());

    return socket;
}

primo::ref<MediaSocket> createOutputSocket(Options& opt)
{
    auto vsi = primo::make_ref(Library::createVideoStreamInfo());
    vsi->setStreamType(StreamType::VP9);

    auto pin = primo::make_ref(Library::createMediaPin());
    pin->setStreamInfo(vsi.get());

    auto socket = primo::make_ref(Library::createMediaSocket());
    socket->setFile(primo::ustring(opt.ivf_file));
    socket->setStreamType(StreamType::IVF);
    socket->pins()->add(pin.get());

    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.ivf_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 for macOS and the enc_vp9_file example for details.

Command Line

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

Examples

List options:

./bin/x64/enc_vp9_file --help
enc_vp9_file --frame <width>x<height> --rate <fps> --color <COLOR> --input <file.yuv> --output <file.ivf> [--colors]
  -h,    --help
  -i,    --input    input YUV file
  -o,    --output   output IVF 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 a VP9 video in ./output/enc_vp9_file/foreman_qcif.ivf:

mkdir -p ./output/enc_vp9_file

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