Create Video From JPEG Images

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

Slideshow Sample

The code snippets in this article are from the Slideshow sample app. The complete source code of the Slideshow app is available in the following BitBucket repositories:

.NET

Create a Transcoder object

Use the standard new operator to create a Transcoder object.

using (var transcoder = new Transcoder())
{
    // Transcoder demo mode must be enabled, 
    // in order to use the OEM release for testing (without a valid license).
    transcoder.AllowDemoMode = true;

    // Code that uses Transcoder goes here
}

Configure inputs and outputs

Use the MediaSocket and MediaPin classes to configure the Trascoder inputs and outputs.

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.

// Configure Input
{
    MediaInfo medInfo = new MediaInfo();
    medInfo.InputFile = GetImagePath(0);

    result = medInfo.Load();
}
Configure the input socket

This requires the following steps:

  1. Get a VideoStreamInfo object from MediaInfo
  2. Create a MediaPin object and set the MediaPin.StreamInfo property.
  3. Create a MediaSocket object and add the pin to MediaSocket.Pins.
  4. Add the socket to Transcoder.Inputs.
// Configure Input
{
    VideoStreamInfo vidInfo = (VideoStreamInfo)medInfo.Streams[0];
    vidInfo.FrameRate = inputFrameRate;

    MediaPin pin = new MediaPin();
    pin.StreamInfo = vidInfo;

    MediaSocket socket = new MediaSocket();
    socket.Pins.Add(pin);

    transcoder.Inputs.Add(socket);
}

Configure outputs

This requires the following steps:

  1. Call the MediaSocket.FromPreset static method to create a MediaSocket object from a predefined preset
  2. Set the MediaSocket.File property to the output filename.
  3. Add the socket to Transcoder.Outputs.
MediaSocket socket = MediaSocket.FromPreset(opt.Preset);
socket.File = outFilename;

transcoder.Outputs.Add(socket);

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
result = transcoder.Open();
PrintError("Open Transcoder", transcoder.Error);
if (!result)
    return;

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

transcoder.Close();

Pushing image data

The code that pushes the image data is fairly simple.

For each image:

  1. Create a MediaBuffer object and load it with the image data.
  2. Create a MediaSample, set the sample start time, and set the buffer with the image data.
  3. Call Transcoder.Push and provide the media sample object.
for (int i = 0; i < imageCount; i++)
{
    string imagePath = GetImagePath(i);

    MediaBuffer mediaBuffer = new MediaBuffer(File.ReadAllBytes(imagePath));

    MediaSample mediaSample = new MediaSample();
    mediaSample.StartTime = i / inputFrameRate;
    mediaSample.Buffer = mediaBuffer;

    if (!transcoder.Push(0, mediaSample))
    {
        PrintError("Push Transcoder", transcoder.Error);
        return;
    }
}

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

result = transcoder.Flush();
PrintError("Flush Transcoder", transcoder.Error);
if (!result)
    return;

Complete .NET code

Here is the complete sample:

Library.Initialize();

// Set license information. To run AVBlocks in demo mode, comment the next line out
// Library.SetLicense("<license-string>");

string outFilename = "cube." + opt.FileExtension;
const int imageCount = 250;
const double inputFrameRate = 25.0;

using (var transcoder = new Transcoder())
{
    // In order to use the OEM release for testing (without a valid license),
    // the transcoder demo mode must be enabled.
    transcoder.AllowDemoMode = true;

    try
    {
        bool result;

        File.Delete(outFilename);

        // Configure Input
        {
            MediaInfo medInfo = new MediaInfo();
            medInfo.InputFile = GetImagePath(0);

            result = medInfo.Load();
            PrintError("Load MediaInfo", medInfo.Error);
            if (!result)
                return;

            VideoStreamInfo vidInfo = (VideoStreamInfo)medInfo.Streams[0];
            vidInfo.FrameRate = inputFrameRate;

            MediaPin pin = new MediaPin();
            pin.StreamInfo = vidInfo;

            MediaSocket socket = new MediaSocket();
            socket.Pins.Add(pin);

            transcoder.Inputs.Add(socket);
        }

        // Configure Output
        {
            MediaSocket socket = MediaSocket.FromPreset(opt.Preset);
            socket.File = outFilename;

            transcoder.Outputs.Add(socket);
        }

        // Encode Images
        result = transcoder.Open();
        PrintError("Open Transcoder", transcoder.Error);
        if (!result)
            return;

        for (int i = 0; i < imageCount; i++)
        {
            string imagePath = GetImagePath(i);

            MediaBuffer mediaBuffer = new MediaBuffer(File.ReadAllBytes(imagePath));

            MediaSample mediaSample = new MediaSample();
            mediaSample.StartTime = i / inputFrameRate;
            mediaSample.Buffer = mediaBuffer;

            if (!transcoder.Push(0, mediaSample))
            {
                PrintError("Push Transcoder", transcoder.Error);
                return;
            }
        }

        result = transcoder.Flush();
        PrintError("Flush Transcoder", transcoder.Error);
        if (!result)
            return;

        transcoder.Close();
        Console.WriteLine("Output video: \"{0}\"", outFilename);
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.ToString());
    }
}

Library.Shutdown();

C++

Create a Transcoder object

Use the createTranscoder function from the primo::avblocks::Library namespace to create a new Transcoder* object. When the object is no longer needed, free it with the release method.

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;

Pushing 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:

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

    if (opt.error || opt.help)
    {
        help();
        return 1;
    }

    if (opt.preset.empty())
    {
        wcout << "No preset!" << endl;
        help();
        return 1;
    }

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

    const double inputFramerate = 25.0;
    const int imageCount = 250;
    bool_t res;

    wstring imgdir( getExeDir() );
    imgdir.append(L"\\..\\sample-resources\\img");

    wstring outFilename (getExeDir());
    outFilename.append(L"\\cube.");
    outFilename.append(opt.fileExtension);
    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->setInputFile(firstImage.c_str());

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

    // 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 output
    {
        auto socket = primo::make_ref(Library::createMediaSocket(opt.preset.c_str()));
        socket->setFile(outFilename.c_str());

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

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

    // 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;
        }
    }

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

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

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

    return 0;
}
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;
}


Last updated on April 8th, 2017 02:55:54 PM