Creating an AVI File from Bitmaps Using ltmmSampleSource for C++

The following code utilizes ltmmSampleSource and ltmmConvert to generate an AVI file from generated 24-bit device independent bitmaps.

#include "stdafx.h"

// include the LEAD Multimedia TOOLKIT header
#include "ltmm.h" 
// include amvideo.h for VIDEOINFOHEADER, available in the Visual Studio 6.0 or the DirectX SDK
#include <amvideo.h>
// includes for string handling
#include <tchar.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>

/////////////////////////////////////////////////////////////////
// CreateCountingAvi
// create an AVI file composed of frames of counting numbers
// pszAviFile - output file name
// nFrames - number of frames to generate
//
HRESULT CreateCountingAvi(LPCWSTR pszAviFile, int nFrames)
{
   HRESULT hr;
   IltmmConvert* pConvert = NULL;
   IltmmSampleSource* pSampleSource = NULL;
   IltmmMediaTypeDisp* pMediaType = NULL;
   IltmmMediaSampleDisp* pMediaSample;
   BSTR bstr;
   VIDEOINFOHEADER vih;
   SAFEARRAY sa;
   VARIANT var;
   int n;
   HBITMAP hbmFrame;
   HBITMAP hbmSave;
   HDC hDC;
   RECT rc;
   TCHAR sz[16];
   LARGE_INTEGER starttime;
   LARGE_INTEGER stoptime;
   LARGE_INTEGER mstarttime;
   LARGE_INTEGER mstoptime;
   LOGFONT logfont;
   HFONT hfntSave;
   HFONT hfntFrame;
   int len;
   SIZE size;
   
   // initialize COM library
   hr = CoInitialize(NULL);
   if(FAILED(hr))
      goto error;
   
   // create the convert object
   hr = CoCreateInstance(CLSID_ltmmConvert, NULL, CLSCTX_INPROC_SERVER, IID_IltmmConvert, (void**) &pConvert);
   if(FAILED(hr))
      goto error;
   
   // create the source object
   hr = CoCreateInstance(CLSID_ltmmSampleSource, NULL, CLSCTX_INPROC_SERVER, IID_IltmmSampleSource, (void**) &pSampleSource);
   if(FAILED(hr))
      goto error;
   
   // create the media type object
   hr = CoCreateInstance(CLSID_ltmmMediaType, NULL, CLSCTX_INPROC_SERVER, IID_IltmmMediaTypeDisp, (void**) &pMediaType);
   if(FAILED(hr))
      goto error;
   
   // set type to MEDIATYPE_Video
   bstr = SysAllocString(L"{73646976-0000-0010-8000-00AA00389B71}");
   hr = pMediaType->put_Type (bstr);
   SysFreeString(bstr);
   if(FAILED(hr))
      goto error;
   
   // set subtype to MEDIASUBTYPE_RGB24
   bstr = SysAllocString(L"{e436eb7d-524f-11ce-9f53-0020af0ba770}");
   hr = pMediaType->put_Subtype (bstr);
   SysFreeString(bstr);
   if(FAILED(hr))
      goto error;
   
   // set format to FORMAT_VideoInfo
   bstr = SysAllocString(L"{05589f80-c356-11ce-bf01-00aa0055595a}");
   hr = pMediaType->put_FormatType (bstr);
   SysFreeString(bstr);
   if(FAILED(hr))
      goto error;
   
   // set VIDEOINFOHEADER to 320x240 24-bit RGB image at 15 frames per second
   memset(&vih, 0, sizeof(vih));
   vih.bmiHeader.biCompression = BI_RGB;
   vih.bmiHeader.biBitCount = 24;
   vih.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
   vih.bmiHeader.biWidth = 320;
   vih.bmiHeader.biHeight = 240;
   vih.bmiHeader.biPlanes = 1;
   vih.bmiHeader.biSizeImage = (((vih.bmiHeader.biWidth * 3) + 3) & ~3) * vih.bmiHeader.biHeight;
   vih.bmiHeader.biClrImportant = 0;
   vih.AvgTimePerFrame = ((__int64) 10000000 / 15);
   vih.dwBitRate = vih.bmiHeader.biSizeImage * 8 * 15;
   
   // pass VIDEOINFOHEADER to ltmmMediaType via SAFEARRAY
   memset(&sa, 0, sizeof(sa));
   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;
   
   hr = pMediaType->SetFormatData (sizeof(vih), var);
   if(FAILED(hr))
      goto error;
   
   // fixed image size samples
   hr = pMediaType->put_FixedSizeSamples (VARIANT_TRUE);
   if(FAILED(hr))
      goto error;
   
   hr = pMediaType->put_SampleSize (vih.bmiHeader.biSizeImage);
   if(FAILED(hr))
      goto error;
   
   //   set the source media type
   hr = pSampleSource->SetMediaType (pMediaType);
   if(FAILED(hr))
      goto error;
   
   //   set the convert object source
   hr = pConvert->put_SourceObject (pSampleSource);
   if(FAILED(hr))
      goto error;
#ifdef _DEBUG
   {
      IUnknown* punk;
      pConvert->get_SourceObject(&punk);
      assert(punk != NULL);
      if(punk)
         punk->Release();
   }
#endif
   
   //   set the convert output file name
   bstr = SysAllocString(pszAviFile);
   hr = pConvert->put_TargetFile (bstr);
   SysFreeString(bstr);
   if(FAILED(hr))
      goto error;
   
   //   need a dc to draw to
   hDC = CreateCompatibleDC(NULL);
   if(!hDC)
   {
      hr = E_OUTOFMEMORY;
      goto error;
   }
   
   //   create a SAFEARRAY that points to a DIB sections bits
   memset(&sa, 0, sizeof(sa));
   sa.cbElements = sizeof(unsigned char);
   sa.cDims = 1;
   sa.fFeatures = (FADF_AUTO | FADF_FIXEDSIZE);
   sa.pvData = &vih;
   sa.rgsabound[0].cElements = vih.bmiHeader.biSizeImage;
   V_VT(&var) = (VT_ARRAY | VT_UI1);
   V_ARRAY(&var) = &sa;
   
   hbmFrame = CreateDIBSection(NULL, (BITMAPINFO*) &vih.bmiHeader, DIB_RGB_COLORS, &sa.pvData, NULL, 0);
   if(!hbmFrame)
   {
      DeleteDC(hDC);
      hr = E_OUTOFMEMORY;
      goto error;
   }
   
   //   create a font big enough to fit the height of each frame
   memset(&logfont, 0, sizeof(logfont));
   _tcscpy(logfont.lfFaceName, _T("Arial"));
   logfont.lfHeight = vih.bmiHeader.biHeight;
   hfntFrame = CreateFontIndirect(&logfont);
   if(!hfntFrame)
   {
      DeleteDC(hDC);
      DeleteObject(hbmFrame);
      hr = E_OUTOFMEMORY;
      goto error;
   }
   
   //   select the font and the bitmap into the device context
   hbmSave = (HBITMAP) SelectObject(hDC, hbmFrame);
   hfntSave = (HFONT) SelectObject(hDC, hfntFrame);
   
   // need a rect for the ExtTextOut
   SetRect(&rc, 0, 0, vih.bmiHeader.biWidth, vih.bmiHeader.biHeight);
   
   // start the conversion
   hr = pConvert->StartConvert ();
   if(FAILED(hr))
      goto converterror;
   
   starttime.QuadPart = 0;
   mstarttime.QuadPart = 0;
   for(n = 0; n < nFrames; n++)
   {
      // convert the frame number to text and draw it to the bitmap
      
      len = _stprintf(sz, _T("%u"), n + 1);
      
      GetTextExtentPoint32(hDC, sz, len, &size);
      ExtTextOut(hDC, (rc.right - rc.left - size.cx) / 2, (rc.bottom - rc.top - size.cy) / 2, ETO_CLIPPED | ETO_OPAQUE, &rc, sz, len, NULL);
      GdiFlush();
      
      // get a free sample buffer
      hr = pSampleSource->GetSampleBuffer (1000, &pMediaSample);
      if(FAILED(hr))
         goto converterror;
      
      // set the time in 100 nanoseconds (based on frame rate)
      stoptime.QuadPart = starttime.QuadPart + vih.AvgTimePerFrame;
      hr = pMediaSample->SetTime (starttime.HighPart, starttime.LowPart, stoptime.HighPart, stoptime.LowPart);
      if(FAILED(hr))
      {
         pMediaSample->Release();
         goto converterror;
      }
      // media time is equal to the frame number
      mstoptime.QuadPart = mstarttime.QuadPart + 1;
      hr = pMediaSample->SetMediaTime (mstarttime.HighPart, mstarttime.LowPart, mstoptime.HighPart, mstoptime.LowPart);
      if(FAILED(hr))
      {
         pMediaSample->Release();
         goto converterror;
      }
      // this is a sync point
      hr = pMediaSample->put_SyncPoint (VARIANT_TRUE);
      if(FAILED(hr))
      {
         pMediaSample->Release();
         goto converterror;
      }
      // set the sample data
      hr = pMediaSample->SetData (vih.bmiHeader.biSizeImage, var);
      if(FAILED(hr))
      {
         pMediaSample->Release();
         goto converterror;
      }
      
      // send the sample downstream
      hr = pSampleSource->DeliverSample (1000, pMediaSample);
      if(FAILED(hr))
      {
         pMediaSample->Release();
         goto converterror;
      }
      
      // adjust the next time values
      starttime = stoptime;
      mstarttime = mstoptime;
      
      // release the sample buffer
      pMediaSample->Release();
   }
   // all done, inform the downstream filters
   hr = pSampleSource->DeliverEndOfStream(1000);
   if(FAILED(hr))
      goto converterror;
   // stop
   hr = pConvert->StopConvert ();
   if(FAILED(hr))
      goto converterror;
   
   // cleanup and exit
   hr = S_OK;
   
converterror:
   SelectObject(hDC, hfntSave);
   DeleteObject(hfntFrame);
   SelectObject(hDC, hbmSave);
   DeleteObject(hbmFrame);
   DeleteDC(hDC);
error:
   
   if(pConvert)
      pConvert->Release();
   if(pSampleSource)
      pSampleSource->Release();
   if(pMediaType)
      pMediaType->Release();
   CoUninitialize();
   
   return hr;
}

int main(int argc, char* argv[])
{
   CreateCountingAvi(L"c:\\count.avi", 10);   
   return 0;
}