Create Video From JPEG Images#

This topic describes how to use the Transcoder class to make a video from a sequence of images.

The code snippets in this article are from the slideshow sample.

Create a Transcoder object#

Use the createTranscoder function from the primo::avblocks::Library namespace to create a new Transcoder object.

auto transcoder = primo::make_ref(Library::createTranscoder());

// Transcoder demo mode must be enabled, 
// in order to use the production release for testing (without a valid license)
transcoder->setAllowDemoMode(TRUE);

Configure inputs#

Detect the input stream#

If the input is a media file, you can use MediaInfo to read the stream information from the file. For images MediaInfo will return a VideoStreamInfo object readily configured with the image attributes. AVBlocks supports BMP, JPEG, PNG, and TIFF images.

// Load image info
auto info = primo::make_ref(Library::createMediaInfo());
{
    wstring firstImage(imgdir);
    firstImage.append(L"\\cube0000.jpeg");

    info->setInputFile(firstImage.c_str());

    res = info->load();
    printError(L"Load Info", info->error());
    if (!res)
        return 1;
}

Configure the input socket#

This requires the following steps:

  1. Get a VideoStreamInfo object from MediaInfo

  2. Create a MediaPin object and call the MediaPin::setStreamInfo method.

  3. Create a MediaSocket object and add the pin to MediaSocket::pins.

  4. Add the socket to Transcoder::inputs.

// Configure input
{
    VideoStreamInfo *vinfo = (VideoStreamInfo*) info->streams()->at(0);
    vinfo->setFrameRate(inputFramerate);

    auto pin = primo::make_ref(Library::createMediaPin());
    pin->setStreamInfo(vinfo);

    auto socket = primo::make_ref(Library::createMediaSocket());
    socket->pins()->add(pin.get());

    transcoder->inputs()->add(socket.get());
}

Configure outputs#

This requires the following steps:

  1. Call the Library::createMediaSocket static method to create a MediaSocket object from a predefined preset

  2. Call the MediaSocket::setFile method with the output filename

  3. Add the socket to Transcoder::outputs

// Configure output
{
    auto socket = primo::make_ref(Library::createMediaSocket(opt.preset.c_str()));
    socket->setFile(outFilename.c_str());

    transcoder->outputs()->add(socket.get());
}

Encode images#

This requires the following steps:

  1. Call Transcoder::open

  2. Call Transcoder::push for each image

  3. Call Transcoder::flush (always needed after pushing)

  4. Call Transcoder::close

res = transcoder->open();
printError(L"Open Transcoder", transcoder->error());
if (!res)
    return 1;

// Code that pushes image data goes here ...

res = transcoder->flush();
printError(L"Flush Transcoder", transcoder->error());

transcoder->close();
wcout << L"Output video: \"" << outFilename << L"\"" << endl;

Push image data#

The code that pushes the image data is fairly simple.

For each image:

  1. Create a MediaSample object

  2. Create a MediaBuffer object and load it with the image data

  3. Set the sample start time, and set the sample buffer with the image data

  4. Call Transcoder::push and with the media sample object

// Encode images
auto mediaSample = primo::make_ref(Library::createMediaSample());

for(int i = 0; i < imageCount; i++)
{
    WCHAR imgfile[MAX_PATH];
    wsprintf(imgfile, L"%s\\cube%04d.jpeg", imgdir.c_str(), i);
    
    auto buffer = primo::make_ref(createMediaBufferForFile(imgfile));

    mediaSample->setBuffer(buffer.get());
    
    // the correct start time is required by the transcoder
    mediaSample->setStartTime(i / inputFramerate);

    res = transcoder->push(0, mediaSample.get());
    if (!res)
    {
        printError(L"Push Transcoder", transcoder->error());
        return 1;
    }
}
MediaBuffer* createMediaBufferForFile(const wchar_t* filename)
{
    HANDLE h = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
    if (INVALID_HANDLE_VALUE == h)
        return NULL;

    int32_t imgsize = GetFileSize(h, NULL);

    MediaBuffer* mediaBuffer = Library::createMediaBuffer(imgsize);
    
    DWORD bytesRead;
    ReadFile(h, mediaBuffer->start(), imgsize, &bytesRead, NULL);
    
    mediaBuffer->setData(0, imgsize);
    
    CloseHandle(h);
    
    return mediaBuffer;
}

When you are done pushing, call Transcoder::flush to make sure all buffered data is processed.

res = transcoder->flush();
printError(L"Flush Transcoder", transcoder->error());

Complete C++ code#

Here is the complete sample:

#include "stdafx.h"
#include "options.h"
#include "util.h"

using namespace std;
using namespace primo::avblocks;
using namespace primo::codecs;

void printError(const wchar_t* action, const primo::error::ErrorInfo* e)
{
    if (action)
    {
        wcout << action << L": ";
    }

    if (primo::error::ErrorFacility::Success == e->facility())
    {
        wcout << L"Success" << endl;
        return;
    }

    if (e->message())
    {
        wcout << e->message() << L", ";
    }

    wcout << L"facility:" << e->facility() << L", error:" << e->code() << endl;
}

MediaBuffer* createMediaBufferForFile(const wchar_t* filename)
{
    HANDLE h = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
    if (INVALID_HANDLE_VALUE == h)
        return NULL;

    int32_t imgsize = GetFileSize(h, NULL);

    MediaBuffer* mediaBuffer = Library::createMediaBuffer(imgsize);
    
    DWORD bytesRead;
    ReadFile(h, mediaBuffer->start(), imgsize, &bytesRead, NULL);
    
    mediaBuffer->setData(0, imgsize);
    
    CloseHandle(h);
    
    return mediaBuffer;
}

bool slideshow(Options& opt)
{
    const double inputFramerate = 25.0;
    const int imageCount = 250;
    bool_t res;
    
    wstring imgdir( opt.input_dir);

    wstring outFilename (opt.output_file);
    DeleteFile(outFilename.c_str());

    auto transcoder = primo::make_ref(Library::createTranscoder());

    // Transcoder demo mode must be enabled, 
    // in order to use the production release for testing (without a valid license)
    transcoder->setAllowDemoMode(TRUE);

    // Load image info
    auto info = primo::make_ref(Library::createMediaInfo());
    {
        wstring firstImage(imgdir);
        firstImage.append(L"\\cube0000.jpeg");

        info->inputs()->at(0)->setFile(firstImage.c_str());

        res = info->open();
        printError(L"Load Info", info->error());
        if (!res)
            return false;
    }

    // Configure input
    {
        auto vinfo = primo::make_ref((VideoStreamInfo*) info->outputs()->at(0)->pins()->at(0)->streamInfo()->clone());
        vinfo->setFrameRate(inputFramerate);

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

        auto socket = primo::make_ref(Library::createMediaSocket());
        socket->pins()->add(pin.get());

        transcoder->inputs()->add(socket.get());
    }
    

    // Configure output
    {
        auto socket = primo::make_ref(Library::createMediaSocket(opt.preset.name));
        socket->setFile(outFilename.c_str());

        transcoder->outputs()->add(socket.get());
    }

    res = transcoder->open();
    printError(L"Open Transcoder", transcoder->error());
    if (!res)
        return false;

    // Encode images
    auto mediaSample = primo::make_ref(Library::createMediaSample());

    for(int i = 0; i < imageCount; i++)
    {
        WCHAR imgfile[MAX_PATH];
        wsprintf(imgfile, L"%s\\cube%04d.jpeg", imgdir.c_str(), i);
        
        auto buffer = primo::make_ref(createMediaBufferForFile(imgfile));

        mediaSample->setBuffer(buffer.get());
        
        // the correct start time is required by the transcoder
        mediaSample->setStartTime(i / inputFramerate);

        res = transcoder->push(0, mediaSample.get());
        if (!res)
        {
            printError(L"Push Transcoder", transcoder->error());
            return false;
        }
    }

    if(!transcoder->flush())
    {
        printError(L"Flush Transcoder", transcoder->error());
        return false;
    }

    transcoder->close();
    wcout << L"Output video: \"" << outFilename << L"\"" << endl;

    return true;
}

int _tmain(int argc, wchar_t* argv[])
{
    Options opt;

    switch(prepareOptions( opt, argc, argv))
    {
        case Command: return 0;
        case Error: return 1;
    }

    primo::avblocks::Library::initialize();

    bool slideshowResult = slideshow(opt);

    primo::avblocks::Library::shutdown();

    return slideshowResult ? 0 : 1;
}