Bitmaps to videos
Written by Harry Fairhead   
Tuesday, 21 July 2009
Article Index
Bitmaps to videos
Bitmap Class
Making the video
Compression
Action

Getting the compression

Now we have a stream we could start storing bitmaps in it – but few video players, and certainly not Windows Media Player, actually know how to handle an uncompressed stream.

We therefore have to create a compressed stream from the uncompressed stream and the simplest way of doing this is to let the user select from a dialog box that the API provides. This is the reason we needed the hwnd parameter in the constructor to allow the dialog box to have a reference to its parent window.

dialog

The compression dialog

 

First we need a structure to define the compression options:

[StructLayout(LayoutKind.Sequential, 
Pack = 1)]
public struct AVICOMPRESSOPTIONS
{
public Int32 fccType;
public Int32 fccHandler;
public Int32 dwKeyFrameEvery;
public Int32 dwQuality;
public Int32 dwBytesPerSecond;
public Int32 dwFlags;
public Int32 lpFormat;
public Int32 cbFormat;
public Int32 lpParms;
public Int32 cbParms;
public Int32 dwInterleaveEvery;
} ;

 

The API function that displays the dialog box is defined as:

 

[DllImport("avifil32.dll")]
extern static int AVISaveOptions(
IntPtr hWnd,
int uiFlags,
int noStreams,
IntPtr ppavi,
ref IntPtr ppOptions);

The last two parameters are different from anything used so far and likely to cause trouble if you don’t treat them correctly. Both ppavi and ppOptions are pointers to pointers. As a result we have to pin the memory areas to which they refer to stop them being moved by the Garbage Collector. We also need some unmanaged memory for the API function to use to store the user’s selections. This can be provided using the AllocHGlobal method of the Marshal object:

IntPtr buf = Marshal.AllocHGlobal(
Marshal.SizeOf(
typeof(AVICOMPRESSOPTIONS)));

Next we need to get a handle, basically a pointer, to this area of memory and pin it:

GCHandle handle1 = GCHandle.Alloc(
pStream,
GCHandleType.Pinned);

We can now call the API function:

result = AVISaveOptions(
m_hWnd,
ICMF_CHOOSE_KEYFRAME |
ICMF_CHOOSE_DATARATE,
1,
handle1.AddrOfPinnedObject(),
ref buf);

The constants used are:

private const int 
ICMF_CHOOSE_KEYFRAME = 0x00000001;
private const int
ICMF_CHOOSE_DATARATE = 0x00000002;

Finally we transfer the selection made by the user to the structure and free the memory and the handle to the memory:

 

AVICOMPRESSOPTIONS opts = 
(AVICOMPRESSOPTIONS)
Marshal.PtrToStructure(buf,
typeof(AVICOMPRESSOPTIONS));
Marshal.FreeHGlobal(buf);
handle1.Free();

If the user presses the OK button the result of the function is one.

Now we have to make the compressed stream using the options selected by the user and then set its format. The two functions needed for this are:

[DllImport("avifil32.dll")]
extern static
int AVIMakeCompressedStream(
ref IntPtr ppsComp,
IntPtr ppavi,
ref AVICOMPRESSOPTIONS opts,
int pclisidHandler);
[DllImport("avifil32.dll")]
extern static int AVIStreamSetFormat(
IntPtr ppavi,
int lpos,
IntPtr lpFormat,
int cbFormat);

To make the compressed stream we simply use:

result = AVIMakeCompressedStream(
ref psComp,
pStream,
ref opts,
0);
handle = GCHandle.Alloc(bm.bmIH,
GCHandleType.Pinned);

which returns a pointer, pStream, to the compressed stream. To set the format of the compressed stream we need to use the bitmap information header and as before this needs to be pinned before being passed to the API:

handle = GCHandle.Alloc(bm.bmIH,
GCHandleType.Pinned);
result = AVIStreamSetFormat(psComp,
0,
handle.AddrOfPinnedObject(),
bm.bmIH.biSize);
handle.Free();

The final API call writes the first bitmap’s data to the compressed stream and this has to be pinned before use:

handle = GCHandle.Alloc(bm.bmIH,
GCHandleType.Pinned);
result = AVIStreamWrite(
psComp,
1,
1,
handle.AddrOfPinnedObject(),
bm2.bits.GetLength(0),
AVIIF_KEYFRAME, 0, 0);
handle.Free();

We also need a global variable to keep track of the number of frames written:

private int FrameCount = 0;

and a definition for the constant:

private const int AVIIF_KEYFRAME = 0x00000010;

And we also need to remember to set it to one after successfully writing one frame:

FrameCount = 1;

The definition of the API function is:

[DllImport("avifil32.dll")]
extern static int AVIStreamWrite(
IntPtr ppavi,
int lStart,
int lSamples,
IntPtr buffer,
int cbBuffer,
int dwFlags,
int sampwritten,
int bytes);

Additional frames and clean up

The FirstFrame method is quite long because as well as storing the first frame it also has to set everything up. The NextFrame method is much simpler because all we have to do is read the next bitmap and then write it to the stream:

public void NextFrame(string BMPFile)
{
RawBitmap bm = new RawBitmap();
bm.LoadFromFile(BMPFile);
GCHandle handle = GCHandle.Alloc(
bm.bits,
GCHandleType.Pinned);
int result = AVIStreamWrite(psComp,
++FrameCount,
1,
handle.AddrOfPinnedObject(),
bm.bits.GetLength(0),
AVIIF_KEYFRAME, 0, 0);
}

Before the program ends we have to close the streams and the file:

public void closeAll()
{
int result = AVIStreamRelease(psComp);
result = AVIStreamRelease(pStream);
result = AVIFileRelease(pFile);
}

The API functions are defined as:

[DllImport("avifil32.dll")]
extern static int
AVIStreamRelease(IntPtr ppavi);

[DllImport("avifil32.dll")]
extern static int
AVIFileRelease(IntPtr pfile);

<ASIN:0735618216>

<ASIN:0735611807>

<ASIN:0672329638>

 



Last Updated ( Tuesday, 21 July 2009 )