Create Video From JPEG Images
Contents
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:
Get a VideoStreamInfo object from MediaInfo
Create a MediaPin object and call the
MediaPin::setStreamInfo
method.Create a MediaSocket object and add the pin to
MediaSocket::pins
.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:
Call the
Library::createMediaSocket
static method to create a MediaSocket object from a predefined presetCall the
MediaSocket::setFile
method with the output filenameAdd 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:
Call
Transcoder::open
Call
Transcoder::push
for each imageCall
Transcoder::flush
(always needed after pushing)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:
Create a MediaSample object
Create a MediaBuffer object and load it with the image data
Set the sample start time, and set the sample buffer with the image data
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;
}