Create Movie From HBitmap Images

Introduction

As every one knows a video is a sequence of images or bitmaps. And also it is known that HBitmap is the basic ingredient of Bitmap. And we have lots of HBitmaps with us in all our windows applications whether they are animations or just static interfaces. And it is high time for all of us to save all those beautiful sequence of HBitmaps into a file and call it as movie or animation or demo, you name it.

The following presents a way of creating a Movie (avi/ wmv/ mov) from a sequence of HBitmaps. The required functionality has been wrapped in appropriate classes like CAviFile, CwmvFile and CQTMovieFile. Using these classes is fairly simple and involves a single function call AppendNewFrame(); All the necessary initialization (like frame rate settings etc..) would be taken care by the class itself when the AppendNewFrame() is called for the first time (except for the QuickTime class. It has its own Graphics world that need to be initialized explicitly through a call to InitGraphicsWorld()).

As one can easily expect, this approach is two fold. For those who want to create a movie from a set of image files, say *.jpg or *.bmp, all that is needed is---load all the images into an array of HBitmaps and call AppendNewFrame() on each of them in the order of presentation. On the otherhand, for those who want to create a video from a program generated animation sequence, just render the drawing to an HBitmap and call AppendNewFrame() on it for each update (perhaps in WM_PAINT or OnPaint() handler).

Before we move on further, I would like to mention one point worth noting. These classes that we are about to discuss are primarily aimed at providing an add-on support for otherwise complete applications (though they could be used perfectly well in your not yet designed application also). To be exact, these classes have been designed with especial care so as to not to interfere with the design or functionality of the application that is using them. If any error occurs within these classes, they would rather turn off themselves than causing the entire application to halt. Hence the user is freed from unnecessary initializations and error checkings. If every thing goes well, you would have a fine movie at the end, but if any thing goes wrong (with in these modules), still you could have your application running perfectly.

In the following sections each of these classes has been explained separately.

Creating Avi Movie

The class CAviFile creates an avi movie from HBitmaps. Using this class is a two step process. The First step involves creating a CAviFile object. The constructor of CAviFile has been declared in AviFile.h as:

class CAviFile{
public:
    CAviFile(LPCSTR lpszFileName=_T("Output.avi"), 
             DWORD dwCodec = mmioFOURCC('M','P','G','4'), DWORD dwFrameRate = 1);
    ~CAviFile(void);
    HRESULT AppendNewFrame(HBITMAP hBitmap);
    HRESULT AppendNewFrame(int nWidth, int nHeight, LPVOID pBits, int nBitsPerPixel);
};

The constructor accepts three arguments - the output file name, which by default is set to Output.avi, the Video codec to be used for compression, which by default is set to MPG4, and the Frame rate (FPS), which by default is set to 1. While creating the CAviFile object, you can either use the default parameter values, which should work fine for most of the cases, or you can pass your own choice of values. More on this is explained later.

Once a CAviFile object has been created with appropriate codec and frame rate values, the second step involved in using it is the actual call to the method AppendNewFrame(). From the above it is clear that the method has two overloaded alternatives. One version is,

 HRESULT AppendNewFrame(HBITMAP hBitmap); 
which is useful when all our drawing has been done on to a HBitmap and is ready to be stuffed to the end of the current movie as a new frame. The other form accepts raw bitmap bits instead of HBitmap, as shown below.
 HRESULT AppendNewFrame(int nWidth, int nHeight, LPVOID pBits, int nBitsPerPixel);
If you have your rendered drawing in the form of raw bits rather than HBitmap, you might prefer this second verson. However, it should be noted that once we start with one form we can not switch to other form in between during the movie creation.

The following illustrates the typical code sequence involved in using the CAviFile class:

  #include "avifile.h" 
  CAviFile aviFile; // Create the CAviFile object  
  OnPaint()
  {
      hdc = BeginPaint(hWnd, &ps);
      //...
      //... draw something onto hBitmap
      //...
      EndPaint(hWnd, &ps);      
      aviFile.AppendNewFrame(hBackBitmap); // Append the bitmap as a new frame to movie
  }

The method CAviFile::AppendNewFrame() returns S_OK on success and E_FAIL on failure. In case of errors, we can use the CAviFile's GetLastErrorMessage() method to retrieve the error message description in string format.

  LPCTSTR CAviFile::GetLastErrorMessage() const {  return m_szErrMsg;  }
The typical usage is as shown below.
  if(FAILED(avi.AppendNewFrame(hBackBitmap)))
  {
    MessageBox(hWnd, avi.GetLastErrorMessage(), _T("Error Occured"), MB_OK | MB_ICONERROR);
  }

Implementation

This section briefly covers the behind scene mechanisms involved in the operation of CAviFile class. One can find all these details from the implementation file AviFile.cpp itself.

Avi movie creation has been one of the most oldest forms of movie creation and has lot of support through AVIFile set of functions. Before calling any AVIFile function we should call AVIFileInit() and before exiting the application we should call AVIFileExit(). The constructor and destructor are the best places for both of them. Hence you can find them in the constructor and destructor of CAviFile class respectively.

Among all the set of AVIFile functions, the ones that we are interested in are : AVIFileOpen(), AVIFileRelease(), AVIFileCreateStream(), AVIMakeCompressedStream(), AVIStreamSetFormat(), AVIStreamWrite().

Among the above, AVIStreamWrite() is the main operation that actually writes the compressed image bits to the movie file. All the others are used for setting the file, stream and compression options. After creating/opening the avi file with AVIFileOpen(), the compression options and video codec options can be chosen by settings appropriate values for the AVISTREAMINFO structure members and passing it to the function AVICreateStream(). For example, The fccHandler member of AVISTREAMINFO represents the four character code for the video codec. Typically the four character code for the video codec is a string such as "divx" "mpg4" etc.. that would be unique for each video codec installed in the system. The function mmioFOURCC() can be used to convert these characters into the dword format acceptable by the fccHandler member. By default, in the CAviFile implementation we use "mpg4" codec. To choose a different codec for your application, pass the codec's fourcc as part of the constructor as shown below.

  CAviFile  avi("Output.Avi", mmioFOURCC('D','I','V','X'), 1);	// Use DivX codec with 1 FPS

A list of Fourcc codes and other related information can be found at: FourCC Codecs Reference List;

Note that you can pass 0 for the fourcc value to avoid using the codecs altogether, where by your bitmaps would be inserted into the movie as they are without being processed by any codec.

  CAviFile  avi("Output.Avi", 0, 1);	// Doesn't use any Codec !!

The member dwRate of AVISTREAMINFO controls the Frame rate of the movie. Values between 5 to 15 are common and should be good for most animations. (dwRate = 15 typically means 15 frames per second). You can change this frame rate setting by passing your own choice of value to the third paramter (dwFrameRate) of the CAviFile constructor.

Once all these settings has been done succesfully, we can create a new video stream in the movie file by using the function AVICreateStream() function, after which we can call the method AVIMakeCompressedStream() to setup the compression filter for the created stream. The success of AVIMakeCompressedStream() depends on the codec you are using being avaiable on the system. If you have used an invalid fourcc value or if the codec is not available on the machine, the call to AVIMakeCompressedStream() would fail.

Finally, after setting the compression settings but before starting to write the actual image sequence, we need to set the format of our video stream, which is done by using AVIStreamSetFormat. The success of AVIStreamSetFormat() depends on the input bitmap data being suitable to the requirements of the compressor (codec). Note that each codec has different requirements for processing their input data (such as the bits per pixel value being multiple of 4 or the width and height of frames being powers of 2 etc...), and passing a bitmap (or bits) that does not meet those requirements may cause AviStreamSetFormat() to fail.

Once the stream format has been set, we can start writing the HBitmap data using the function AVIStreamWrite(). This function automatically compresses the data (using the options we have set before) and writes the data to the video stream that would be saved to the output movie file. Upon completion, the movie file should be closed to flush all the buffers using the function AVIFileRelease().

The set of AVIFile functions discussed above are declared in the standard header file vfw.h, and the corresponding library that should be linked is vfw32.lib.

Download the Source code: CAviFile.zip

Creating WMV Movie

The class CwmvFile creates a wmv movie from HBitmaps. This class is based on the Windows Media Format SDK. The library file wmvcore.lib is part of the SDK and can be downloaded from: Windows Media Format SDK Website.

By default, the implementation of CwmvFile uses the Windows Media Format SDK Version 9.0. It has been defined in the file wmvfile.h as:

#define WMFORMAT_SDK_VERSION WMT_VER_9_0

However, You can change this to other versions by using the appropriate version number. Adding the following line in your main application causes the implementation to use the Media Format SDK Version 8.0 instead of the default 9.0 version

#define WMFORMAT_SDK_VERSION WMT_VER_8_0

Use the above #define before including the wmvfile.h so that the default #define in the wmvfile.h can be ignored.

Using the class CwmvFile is a two step process. The First step involves creating a CwmvFile object. The constructor of CwmvFile has been declared in wmvFile.h as:

class CwmvFile{
public:
    CwmvFile(LPCTSTR lpszFileName = _T("Output.wmv"), 
            const GUID& guidProfileID = WMProfile_V80_384Video, DWORD dwFrameRate = 1);
    ~CwmvFile(void);
    HRESULT AppendNewFrame(HBITMAP hBitmap);
    HRESULT AppendNewFrame(int nWidth, int nHeight, LPVOID pBits, int nBitsPerPixel);
};

The constructor accepts three arguments---the output file name, which by default is set to Output.wmv, and a profile Id, followed by the frame rate option. A profile is a set of media parameters used to create the wmv movie file. The Profile Id is unique GUID given to a profile. Ids for most common system profiles have been listed at: http://msdn2.microsoft.com/en-gb/library/aa390939.aspx

All these profiles may not be available in your system. Using a profile Id which is not present in your system would fail the creation of CwmvFile object. List of all available profiles in your system can be enumerated by using the EnumProfiles() function provided in the source file: EnumProfiles.cpp in EnumProfiles.zip. The function EnumProfiles() outputs the names of all the profiles available on your machine. You can lookup the profile ids for the profile names at the afore mentioned system profiles webpage.

The second step in using the CwmvFileclass involves the actual call to AppendNewFrame(). From the above it is clear that it has two overloaded alternatives. One comes with HBitmap style - where all our drawing has been done on to a HBitmap and is ready to stuffed to the end of the current movie. The other form accepts raw bitmap bits instead of HBitmap. If we have our rendered drawing in the form of bits than HBitmap - we might as well use this option. However, it should be noted that once we start with one of them we can not switch to other form in between during the movie creation. The following illustrates the code sequence:

  #define WMFORMAT_SDK_VERSION WMT_VER_8_0
  #include "wmvfile.h"  
  CwmvFile wmvFile("wmvFile.wmv", WMProfile_V80_384PALVideo, 1); // Create the Movie object  
  OnPaint()
  {
      hdc = BeginPaint(hWnd, &ps);
      //...
      //... draw something onto hBitmap
      //...
      EndPaint(hWnd, &ps);    
      wmvFile.AppendNewFrame(hBackBitmap); // Append the newly drawn bitmap as a frame to the Movie object
  }

It should be noted that some codec's may require that height and width of the frame should be multiples of some integer like 3 or 4 etc.. That is, you can not have any size video height or width. There are limits on them like some codec's can handle only 320*240 or 640*480. Hence you should either create the HBitmap with those supported sizes or you should blit it to a new bitmap with the supporting sizes and use that instead. Using a HBitmap with unsupported sizes results in error.

Implementation

This section briefly covers the behind scene mechanisms involved in the operation of CwmvFile class. One can find all these from the implementation file wmvfile.cpp itself.

CwmvFile class maintains three private functions by name AppendFrameFirstTime(), AppendFrameUsual() and AppendDummy(). The first function AppendFrameFirstTime() contains all the initialization code to setup the movie properties like width and height of frames etc.. and is called only once - only for the first frame. For the rest of the frames AppendFrameUsual() takes care of them. It contains actual code for writing the samples of data to movie file and so is called for every frame. AppendDummy() is just place holder function and as its name implies does nothing. Along with these a Function pointer pAppendFrame has been defined. When the application starts when no frame have been added to movie file - the function pointer points to the AppendFrameFirstTime() function. Once the first frame has been added and all the movie file properties have been initialized properly, the pointer is updated to point to the AppendFrameUsual() function. Now, when the application calls the AppendNewFrame() on the CwmvFile object -all that the function does is just calling the function pointed to by the pAppendFrame pointer. Hence, the for the frist time it would be the AppendFrameFirstTime() function and from then on it would be AppendFrameUsual() function.

However, when an error occurs, the pointer pAppendFrame would be made to point to AppendDummy() so that from that point on wards no frames are added to the movie and just dummy code is executed and failure values are returned instead. This facilitates for you to continue animation even if an error occurs in the part of movie creation. However you can check for the return values in the code to determine the success state and can terminate the application on an error if you wish.

The actual process of creating the movie is done like this. Windows Media Format SDK supports COM interfaces IWMProfile, IWMWriter, IWMInputMediaProps, IWMProfileManager etc. for handling the movie operations. The IWMWriter is the actual interface that supports writing the images streams to the movie file. To create an object for this interface, we need to use the function WMCreateWriter(). This would give us the pointer to the IWMWriter interface. However, before we can actullay use the IWMWriter object for the writing we need to set the profile for the writer. For that we need to Load the profile specified by the Profile Id. We can do this by using the IWMProfileManager object. We create an object of IWMProfileMnager using the WMCreateProfileManager() function and then on the created profile manager object call the method IWMProfileManager::LoadProfileById(). This gives us the pointer to the loaded profile interface, which we can use with the writer object by using IWMWriter::SetProfile().

The Movie output file name can be set using the method IWMWriter::SetOutputFileName(). The actual input video properties are set by using the method IWMWriter::SetInputProps(). The interface IWMInputMediaProps supports settings for various input properties like frame rate, source frame rectangle size etc. (Note that as mentioned earlier, these frame sizes should be in accordance with the requirements of the Video Codec and Profile). Before we can actually start writing the image data into the movie file we should call the method IWMWriter::BeginWriting(). The method IWMWriter::WriteSample() actually writes the image data stream into the movie file. However, it expects data in the form of INSSBuffer. Hence for every frame we need to create an INSSBuffer object using the method IWMWriter::AllocateSample() and use the returned buffer to hold the image data and pass it to the IWMWriter::WriteSample(). We should free the buffer using the INSSBuffer::Release() method. Note that the buffer should be created afresh for each input frame. Once we are finished with writing the image stream we should call the IWMWriter::EndWriter() method to complete the writing. Finally before terminating the application we should free all our objects by using the Release() method on each of them.

Note that before we can actually perform any operation on COM objects we should enable COM using the function CoInitialize() and upon freeing all the COM objects we should close COM using the CoUninitialize() function. These functions have been placed in the CwmvFile constructor and destructor respectively.

Download the Source code: CwmvFile.zip, EnumProfiles.zip

Creating QuickTime Movie

Our class CQTMovieFile creates a QuickTime movie from HBitmaps. This class is based on the QuickTime 6 SDK for Windows. The library file QtmlClient.lib is part of the SDK and can be downloaded from: http://developer.apple.com/quicktime/

Using the CQTMovieFile is a three step process. The first step involves creating the CQTMovieFile Object. The constructor for the class has been declared as:

class CQTMovieFile
{
public:
    CQTMovieFile();
    HRESULT InitGraphicsWorld(HDC , HBITMAP , LPCTSTR lpszFileName=_T("Output.mov"));
    ~CQTMovieFile(void);
    HRESULT AppendNewFrame();
};

QuickTime uses its own Graphics world for its operations much the same way the windows uses the GDI. So, it is required to establish a connection between our GDI object HBitmap and the internal Graphics World of QuickTime. This is done through the function InitGraphicsWorld(). The method involved in creating the Quick time movie using the HBitmap is as follows: QuickTime movie is nothing but a sequence of frames of its Graphics World (much the same way as the Avi File is a sequence of Bitmaps). Hence, if we can draw our animation into the Graphics world of QuickTime then all of that drawing can be directly converted into QuickTime movie easily. This is facilitated by our function InitGrpahicsWorld(). All that it does is telling the Graphics World not to use its own memory but to use the memory provided by the HBitmap that we supply to it.

Once we successfully finish the call to InitGraphicsWorld() a relation would be established between the Graphics World of the QuickTime and GDI of the Windows. The HBitmap that we supplied acts as the connection between these two worlds. Whatever we draw on the HBitmap would affect the Graphics World of QuickTime (since it is using the bits of HBitmap as its Graphics World Memory) and whatever Quick Time does to its Graphics World would be reflected on to our HBitmap.

Using this newly established connection between QuickTime and GDI for our QuickTime movie creation is fairly simple. Render each frame of your animation onto the HBitmap and call the AppendNewFrame() function to append this frame to the Movie file. Note that AppendNewFrame() has no parameters. It is due to the fact that Quick Time has got all the data it needs readily available with itself in the form of Graphics World. And also note that the CQTMovieFile, unlike CAviFile and CwmvFile, does not have the overloaded raw bits version of the AppendNewFrame() function. Hence you can use only HBitmaps with the CQTMovieFile class. The following illustrates the code sequence:

  #include "QTMovieFile.h"  
  CQTMovieFile movFile; // Create the Movie Object  
  OnCreate()
  {
      InitGraphicsWorld(hBackDC,hBackBitmap); // Initialize the Movie object
  }
  OnPaint()
  {
      hdc = BeginPaint(hWnd, &ps);
      //...
      //... draw something onto hBackBitmap
      //...
      EndPaint(hWnd, &ps);      
      movFile.AppendNewFrame(); // Append the newly drawn bitmap as a frame to the Movie
  }

The HBitmap that is to be used as the connection between the Graphics World of the QuickTime and the GDI of the Windows should be created using the function CreateDIBSection(). And also note that the HBitmap that is to be used with the Graphics World should be a top-down bitmap, so the height value of the BITMAPINFOHEADER of the BITMAPINFO structure should be set to negative when calling the CreateDIBSecion. Using a positive height value when creating the bitmap would cause a bottom up bitmap to be generated and hence would result in an up side down QuickTime movie.

Implementation

This section briefly covers the behind scene mechanisms involved in the operation of CQTMovieFile class. One can find all these from the implementation file QTMoviefile.cpp itself.

According to the QuickTime SDK - before any of the QuickTime Functions can be accessed, the function InitializeQTML() should be called and before closing the application TerminateQTML() should be called. Hence the CQTMovileFile class calls them in the constructor and destructor respectively. Another issue is - before calling any Movie Operations we should call EnterMovies() and upon termination we should call ExitMovies(). These too have been placed in the constructor and destructor as well.

QuickTime provides functions like CreateMovieFile(), NewMovieTrack(), NewTrackMedia(), AddMediaSample(), InsertMediaIntoTrack(), AddMovieResource() etc.. to manipulate and control the behavior of the movie data and files along with compression functions like CompressImage() etc. that make the movie creation an easy job. The detailed documentation for these can be found at: Quicktime Movie Internals Guide.

For the QuickTime Movies Video Codecs can be chosen from a variety of types. By default the implementation uses kJPEGCodecType. You can override this with #defining VIDEO_CODEC_TYPE in your application like,

#define VIDEO_CODEC_TYPE kAnimationCodecType 

This should be #defined before #including the QTMovieFile.h

For a list of all the available codecs visit: QuickTime Constants Reference.

Download the Source code: CQTMovieFile.zip

Conclusion

All the methods discussed above are aimed at a single goal - creating a movie from a HBitmap. However, the results may vary depending on a variety of settings ranging from the video frame rate settings to the codec being used. For example, for some screen capture applications, choosing a particular codec known as Windows Media Video 9 Screen codec should give optimal results in both quality and size - though the same may not be the case for high bit rate animation applications (Refer to the article Capturing the Screen for more details about a C++ Library for capturing the screen programmatically, and to the article Recording the Animations for details on creating movie from DirectX and OpenGL animations). Also the particular format (avi /wmv/mov) to be chosen for your content depends on many factors such as the size of the movie produced by each format, the quality of the movie produced and the issues of platform independence etc. While QuickTime offers wide platform coverage, the Windows Media format is quickly replacing the Avi format and is promising better quality. Whatever it is, next time when you see a HBitmap - don't forget to check if you can make a nice movie out of it.

By   

P.GopalaKrishna

Index.html    Other Articles