This tutorial shows how to use the LEADTOOLS Multimedia SDK to create a Windows C++ application that uses the ltmmConvert
control and other LEADTOOLS objects to convert images to video.
Overview | |
---|---|
Summary | This tutorial covers how to use the ltmmConvert object to create a video from multiple images in a Windows C++ application. |
Completion Time | 30 minutes |
Visual Studio Project | Download tutorial project (21 KB) |
Platform | Windows API C++ Application |
IDE | Visual Studio 2019+ |
Development License | Download LEADTOOLS |
Try it in another language |
|
Before working on the Create a Video from Still Images - Windows C++ tutorial, complete the Add References and Set a License tutorial.
Start with a copy of the 64-bit Windows API project created in the Add References and Set a License tutorial. If the project is not available, create it by following the steps in that tutorial.
In order to use the ltmmConvert
Object, LEADTOOLS requires additional references. Add the required Multimedia library reference by opening the pre-compiled header file, either pch.h
or stdafx.h
depending on the Visual Studio version used, and add the following lines:
// Add LEADTOOLS Multimedia reference
#include "C:\LEADTOOLS23\Include\ltmm.h"
#include "C:\LEADTOOLS23\Include\ltmm_Errors.h"
//x64 libs
#pragma comment (lib, "C:\\LEADTOOLS23\\Lib\\CDLL\\x64\\Ltmmx.lib")
#pragma comment (lib, "C:\\LEADTOOLS23\\Lib\\CDLL\\x64\\ltmmuuidx.lib")
The code of this tutorial loads image files and performs some processing on them before converting them to video frames, so the following references are required as well:
#pragma comment (lib, "C:\\LEADTOOLS23\\Lib\\CDLL\\x64\\Ltfil_x.lib") // image loading library
#pragma comment (lib, "C:\\LEADTOOLS23\\Lib\\CDLL\\x64\\Ltimgefx_x.lib") // image processing effects library
With the project created, the references added, and the license set, coding can begin.
The steps below are for Visual Studio 2019; they could be different for other versions of Visual Studio.
Go to the Solution Explorer and double-click the resources file (.rc). Expand the menu tab in the resources tree and double-click the menu resource to open it in the designer interface. In the empty item below the Exit item, click and type &Create Video From Images. Drag the new item above Exit. Ensure the item's ID is ID_FILE_CREATEVIDEOFROMIMAGES
.
Go to the main CPP file of the project, which contains the WndProc()
function for the main window. Navigate to the switch (wmId)
statement that is below the WM_COMMAND
case and add the new case shown below.
// In WndProc(), under "case WM_COMMAND:"
switch (wmId)
{
case ID_FILE_CREATEVIDEOFROMIMAGES:
// initialize COM
CoInitialize(NULL);
// Create the video
GenerateVideo(hWnd);
// Cleanup the objects used
if (pMediaType)
pMediaType->Release();
pMediaType = NULL;
if (pSampleSource)
pSampleSource->Release();
pSampleSource = NULL;
if (pConvert)
pConvert->Release();
pConvert = NULL;
CoUninitialize();
break;
// Keep rest of the code as is
Add a new function GenerateVideo(HWND hWnd)
that loads the images and creates an AVI file from them. Below is the code of the function and the variables and data types needed for it. This code can be added above the WndProc
function:
typedef LONGLONG REFERENCE_TIME;
typedef struct tagVIDEOINFOHEADER {
RECT rcSource;
RECT rcTarget;
DWORD dwBitRate;
DWORD dwBitErrorRate;
REFERENCE_TIME AvgTimePerFrame;
BITMAPINFOHEADER bmiHeader;
} VIDEOINFOHEADER;
IltmmConvert* pConvert = NULL;
IltmmSampleSource* pSampleSource = NULL;
IltmmMediaTypeDisp* pMediaType = NULL;
void GenerateVideo(HWND hWnd)
{
TCHAR szFolderIn[1024] = TEXT(""); // Input files location
// Choose input files folder
if (SUCCESS != GetSourceFolder(hWnd, szFolderIn, ARRAYSIZE(szFolderIn)))
return;
TCHAR szFolderSearch[1024] = TEXT(""); // Input files search location and pattern
_tcscpy_s(szFolderSearch, ARRAYSIZE(szFolderSearch), szFolderIn);
_tcscat_s(szFolderSearch, ARRAYSIZE(szFolderSearch), TEXT("\\*.jpg"));
WIN32_FIND_DATAW FindFileData = { 0 };
HANDLE hFind = FindFirstFile(szFolderSearch, &FindFileData);
if (INVALID_HANDLE_VALUE == hFind)
{
MessageBox(hWnd, TEXT("No JPEG files found in folder"), TEXT("LEADTOOLS Demo"), MB_ICONERROR);
return;
}
TCHAR szFileOut[260] = TEXT(""); // Output file name
// Choose output file name
if (SUCCESS != GetTargetName(0, szFileOut, ARRAYSIZE(szFileOut)))
return;
//IltmmConvert* pConvert = NULL;
CoCreateInstance(CLSID_ltmmConvert, NULL, CLSCTX_INPROC_SERVER, IID_IltmmConvert, (void**)&pConvert);
if (!pConvert)
return;
//IltmmSampleSource* pSampleSource = NULL;
CoCreateInstance(CLSID_ltmmSampleSource, NULL, CLSCTX_INPROC_SERVER, IID_IltmmSampleSource, (void**)&pSampleSource);
//IltmmMediaTypeDisp* pMediaType = NULL;
CoCreateInstance(CLSID_ltmmMediaType, NULL, CLSCTX_INPROC_SERVER, IID_IltmmMediaTypeDisp, (void**)&pMediaType);
BSTR bstr = SysAllocString(ltmmMEDIATYPE_Video);
pMediaType->put_Type(bstr);
SysFreeString(bstr);
bstr = SysAllocString(ltmmMEDIASUBTYPE_RGB24);
pMediaType->put_Subtype(bstr);
SysFreeString(bstr);
bstr = SysAllocString(ltmmFORMAT_VideoInfo);
pMediaType->put_FormatType(bstr);
SysFreeString(bstr);
SetVideoCompressor(pConvert);
double framesPerSecond = 1.5;
const int imageWidth = 1024, imageHeight = 768;
VIDEOINFOHEADER vih = { 0 };
vih.bmiHeader.biCompression = BI_RGB;
vih.bmiHeader.biBitCount = 24;
vih.bmiHeader.biSize = sizeof BITMAPINFOHEADER;
vih.bmiHeader.biWidth = imageWidth;
vih.bmiHeader.biHeight = imageHeight;
vih.bmiHeader.biPlanes = 1;
int BytesPerLine = ((imageWidth * 3 + 3) / 4) * 4; //use integer division to calculate byte padding to 4-byte multiples
int bmpSize = BytesPerLine * imageHeight;
vih.bmiHeader.biSizeImage = bmpSize;
vih.bmiHeader.biClrImportant = 0;
vih.AvgTimePerFrame = (REFERENCE_TIME)(10000000.0 / framesPerSecond);
vih.dwBitRate = (DWORD)(bmpSize * 8 * framesPerSecond);
SAFEARRAY sa = { 0 };
VARIANT var;
sa.cbElements = sizeof(unsigned char);
sa.cDims = 1;
sa.fFeatures = (FADF_AUTO | FADF_FIXEDSIZE);
sa.pvData = &vih;
sa.rgsabound[0].cElements = sizeof vih;
VariantInit(&var);
V_VT(&var) = (VT_ARRAY | VT_UI1);
V_ARRAY(&var) = &sa;
pMediaType->SetFormatData(sizeof vih, var);
pMediaType->put_SampleSize(bmpSize);
pMediaType->put_FixedSizeSamples(VARIANT_TRUE);
// assign the source media type
pSampleSource->SetMediaType(pMediaType);
pConvert->put_SourceObject(pSampleSource);
bstr = SysAllocString(szFileOut);
pConvert->put_TargetFile(bstr);
SysFreeString(bstr);
pConvert->put_TargetFormat(ltmmConvert_TargetFormat_Avi);
pConvert->StartConvert();
L_UCHAR* buf = new L_UCHAR[bmpSize];
LARGE_INTEGER starttime;
LARGE_INTEGER stoptime;
LARGE_INTEGER mstarttime;
LARGE_INTEGER mstoptime;
starttime.QuadPart = 0;
mstarttime.QuadPart = 0;
int n = 1;
do // loop to get all images
{
TCHAR szImageFile[1024] = TEXT(""); // Input file full location and name
_tcscpy_s(szImageFile, ARRAYSIZE(szImageFile), szFolderIn);
_tcscat_s(szImageFile, ARRAYSIZE(szImageFile), TEXT("\\"));
_tcscat_s(szImageFile, ARRAYSIZE(szImageFile), FindFileData.cFileName);
TCHAR szProgress[1024];
_stprintf_s(szProgress, ARRAYSIZE(szProgress), TEXT("Loading image number %d "), n++);
HDC hDC = GetDC(hWnd);
TextOut(hDC, 50, 50, szProgress, _tcslen(szProgress));
ReleaseDC(hWnd, hDC);
BITMAPHANDLE BmpLoad = { 0 }, BmpFrame = { 0 };
// resize image and maintain aspect ratio
L_LoadBitmapResize(szImageFile, &BmpLoad, sizeof BITMAPHANDLE, 0, imageHeight, 24, SIZE_NORMAL, ORDER_BGR, NULL, NULL);
/*
L_LoadBitmap(szImageFile, &BmpLoad, sizeof BITMAPHANDLE, 24, ORDER_BGR, NULL, NULL);
int newWidth = imageHeight * BITMAPWIDTH(&BmpLoad) / BITMAPHEIGHT(&BmpLoad);
L_SizeBitmap(&BmpLoad, newWidth, imageHeight, SIZE_RESAMPLE);
*/
L_ChangeBitmapViewPerspective(NULL, &BmpLoad, sizeof BITMAPHANDLE, BOTTOM_LEFT);
L_CreateBitmap(&BmpFrame, sizeof BITMAPHANDLE, TYPE_CONV, imageWidth, imageHeight, 24, ORDER_BGR, NULL, BOTTOM_LEFT, NULL, 0);
L_FillBitmap(&BmpFrame, RGB(255, 160, 220));
L_CombineBitmap(&BmpFrame, (BITMAPWIDTH(&BmpFrame) - BITMAPWIDTH(&BmpLoad)) / 2, 0, BITMAPWIDTH(&BmpLoad), BITMAPHEIGHT(&BmpLoad), &BmpLoad, 0, 0, CB_RAWCOMBINE | CB_OP_ADD | CB_DST_0, 0);
L_FreeBitmap(&BmpLoad);
//L_LoadBitmap(FindFileData.cFileName, &LtBmp, sizeof LtBmp, 24, ORDER_BGR, NULL, NULL);
// convert the frame number to text and draw it to the bitmap
IltmmMediaSampleDisp* pMediaSample = NULL;
pSampleSource->GetSampleBuffer(1000, &pMediaSample);
L_AccessBitmap(&BmpFrame);
L_GetBitmapRow(&BmpFrame, buf, 0, bmpSize);
L_ReleaseBitmap(&BmpFrame);
L_FreeBitmap(&BmpFrame);
// set the time in 100 nanoseconds (based on frame rate)
stoptime.QuadPart = starttime.QuadPart + vih.AvgTimePerFrame;
pMediaSample->SetTime(starttime.HighPart, starttime.LowPart, stoptime.HighPart, stoptime.LowPart);
// media time is equal to the frame number
mstoptime.QuadPart = mstarttime.QuadPart + 1;
pMediaSample->SetMediaTime(mstarttime.HighPart, mstarttime.LowPart, mstoptime.HighPart, mstoptime.LowPart);
// this is a sync point
pMediaSample->put_SyncPoint(VARIANT_TRUE);
// set the sample data
memset(&sa, 0, sizeof sa);
sa.cbElements = sizeof(unsigned char);
sa.cDims = 1;
sa.fFeatures = (FADF_AUTO | FADF_FIXEDSIZE);
sa.pvData = buf;
sa.rgsabound[0].cElements = bmpSize;
V_VT(&var) = (VT_ARRAY | VT_UI1);
V_ARRAY(&var) = &sa;
pMediaSample->SetData(bmpSize, var);
// send the sample downstream
pSampleSource->DeliverSample(1000, pMediaSample);
// adjust the next time values
starttime = stoptime;
mstarttime = mstoptime;
// release the sample buffer
pMediaSample->Release();
} while(FindNextFile(hFind, &FindFileData) != 0);
delete[] buf;
InvalidateRect(hWnd, NULL, TRUE);
MessageBox(hWnd, TEXT("Finished creating AVI"), TEXT("LEADTOOLS Demo"), MB_ICONINFORMATION);
pSampleSource->DeliverEndOfStream(1000);
pConvert->StopConvert();
}
Add three functions named GetTargetName
, GetSourceFolder
and SetVideoCompressor
above the GenerateVideo
function and add the following code to them:
L_INT GetTargetName(HWND hwnd, TCHAR* pszFileName, DWORD nLen)
{
OPENFILENAME OpenFileName = { 0 };
OpenFileName.lStructSize = sizeof OPENFILENAME;
OpenFileName.hwndOwner = hwnd;
OpenFileName.lpstrFilter = TEXT("AVI File\0*.avi\0");
OpenFileName.lpstrFile = pszFileName;
OpenFileName.nMaxFile = nLen;
OpenFileName.lpstrDefExt = TEXT("avi");
OpenFileName.lpstrFileTitle = NULL;
OpenFileName.nMaxFileTitle = 0;
OpenFileName.lpstrInitialDir = NULL;
OpenFileName.Flags = OFN_OVERWRITEPROMPT;
// Show the File Save dialog box
if (!GetSaveFileName(&OpenFileName))
return FAILURE;
return SUCCESS;
}
#include <shobjidl_core.h>
L_INT GetSourceFolder(HWND hWnd, TCHAR* pszFolder, rsize_t string_size)
{
L_INT nRet = FAILURE;
IFileOpenDialog* pFileOpenDialog = NULL;
IShellItem* pShellItem = NULL;
CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, IID_IFileOpenDialog, (void**)&pFileOpenDialog);
if(pFileOpenDialog)
{
pFileOpenDialog->SetOptions(FOS_PICKFOLDERS);
if (SUCCEEDED(pFileOpenDialog->Show(hWnd)))
{
pFileOpenDialog->GetResult(&pShellItem);
LPWSTR pFolder = NULL;
pShellItem->GetDisplayName(SIGDN_FILESYSPATH, &pFolder);
if (pFolder)
{
_tcscpy_s(pszFolder, string_size, pFolder);
CoTaskMemFree(pFolder);
nRet = SUCCESS;
}
}
}
if (pShellItem)
pShellItem->Release();
if (pFileOpenDialog)
pFileOpenDialog->Release();
return nRet;
}
void SetVideoCompressor(IltmmConvert* pConvert)
{
// Select Motion JPEG video compression and disable interleaving because it reduces quality for still images
IltmmCompressors* pCompressors;
pConvert->get_VideoCompressors(&pCompressors);
long MCMP_MJPEG_CodecIndex = -1;
BSTR bstr = SysAllocString(L"@device:sw:{33D9A760-90C8-11D0-BD43-00A0C911CE86}\\LEAD MCMP/MJPEG Codec (2.0)");
pCompressors->Find(bstr, &MCMP_MJPEG_CodecIndex);
SysFreeString(bstr);
if (MCMP_MJPEG_CodecIndex > -1)
{
pCompressors->put_Selection(MCMP_MJPEG_CodecIndex);
IUnknown* pMjpegCodec = NULL;
pConvert->GetSubObject(ltmmConvert_Object_VideoCompressor, &pMjpegCodec);
IMCMPEncoderOption* pOptions = NULL;
pMjpegCodec->QueryInterface(IID_IMCMPEncoderOption, (void**)&pOptions);
pMjpegCodec->Release();
pOptions->put_Format(FORMAT_MJPEG_411);
pOptions->put_Flags(FLAGS_INTERLEAVE_NEVER);
pOptions->Release();
}
pCompressors->Release();
}
Run the project by pressing F5, or by selecting Debug -> Start Debugging.
If the steps are followed correctly, the application runs and converts all the JPEG images from a user-selected folder to a video file.
This tutorial showed how to produce a video file from still images using the ltmmConvert
Object.