Thursday, October 25, 2007

BMP to AVI

The whole point of this blog and all of the work of the previous sections was to take individual pictures on old, deteriorating strip of film, and preserve them in a digital movie format. The simplest way to do this that I found is simply to create an uncompressed AVI including all of the *.bmp frames.

First, it is useful to review the AVI format. There are several good sites full of useful information, but I haven't kept track of any of them and cannot recommend any of them. At the most basic level, an AVI file is an empty box. You can put anything you want into it so long as you specify the method for retrieving the items within the box. Computers use codecs to determine how things are put into and removed from these "video boxes." My method uses the original AVI codec that comes with Windows. It is limited to a 2 GB files size (the codec was created long before 2 GB was ever dreamed of as a potential file size - back when the biggest hard drives were measured in MB). The codec is easy to use - you simply tell the computer (through the AVI Header) information about how many frames there will be in the video, their dimension (number of bytes each), and how fast to display them on the screen, then which order to play them in (pack the bytes of data into the file.

The AVI Class used in my code was provided from somewhere else, so I will happily share it with anyone. Simply request the AVI Class in an email to digireel [at] gmail [dot] com directed to Kyle. The AVI Class is called in the routines below.

The essential code follows. I will give it in pieces and describe what happens in each step. First dimension the variables to be used.

Dim i, j, k, l, fps, result, maxframes, navi, pw, ph As Integer
Dim bmp(), fbmp() As Byte
Dim avistream, pfile As IntPtr
Dim bm As Bitmap
Dim bmd As BitmapData
Dim bi As New Avi.BITMAPINFOHEADER
Dim opts As New Avi.AVI_COMPRESS_OPTIONS
Dim strhdr As New Avi.AVISTREAM_INFO

Some of these variables are part of the AVI class I mentioned above. There are other variables used, but they are straight forward. Next populate the AVI header with the correct information. The Height and Width are the height and width of your individual *.bmp frames in pixels. The strhdr.dwRate property is the frame rate in frames per second (either 16 or 18 for 8mm films).

'Populate the AVI Info Header
strhdr.fccType = 1935960438 'vids
strhdr.fccHandler = 541215044
strhdr.dwFlags = 0
strhdr.wPriority = 0
strhdr.wLanguage = 0
strhdr.dwInitialFrames = 0
strhdr.dwScale = 1
strhdr.dwRate = fps
strhdr.dwStart = 0
strhdr.dwSuggestedBufferSize = 3 * Height * Width
strhdr.dwQuality = 0
strhdr.dwSampleSize = 0
strhdr.rcFrame.top = Height
strhdr.rcFrame.left = Width

Next populate a BMP header with the appropriate information. This header will be referenced by the AVI file header to interpret the image data for each frame. The Width and Height variables below are the width and height of your *.bmp frames in pixels. The bi.biBitCount refers to the bits per pixes (24 bit color) of the images.

'Populate the BMP Info Header
bi.biSize = Int(Marshal.SizeOf(bi))
bi.biWidth = Width
bi.biHeight = Height
bi.biPlanes = 1
bi.biBitCount = 24
bi.biCompression = 0
bi.biSizeImage = Width * Height
bi.biXPelsPerMeter = bm.HorizontalResolution * 1 / 2.54 * 100
bi.biYPelsPerMeter = bm.VerticalResolution * 1 / 2.54 * 100
bi.biClrUsed = 0
bi.biClrImportant = 0

Resize 2 arrays to hold the byte information of the individual frames to be added to the AVI.

ReDim bmp(bm.Height * bm.Width * 3 - 1)
ReDim fbmp(bm.Height * bm.Width * 3 - 1)

Determine the maximum number of frames that can be put into a single AVI file of 2 GB. In this case, I am not exactly sure of the file size of each frame used, so I give myself a 10 MB margin of error. Then determine how many AVI files will be needed to encode all of the frames.

maxframes = Floor((2147483648 - 1048576 * 10) / (bm.Height * bm.Width * 3 + 40))
navi = Ceiling(nframe / maxframes)

With the headers populated and the number of frames in each AVI known, we are ready to initialize the AVI class and begin filling the video. All of the following code snippets must be repeated for each AVI to be created.

First create the final AVI file and prepare it to the data you will put into it.

Avi.AVIFileInit()
strhdr.dwLength = nframe
'Create the AVI file and get its pointer (pfile)
result = Avi.AVIFileOpen(pfile, "c:\YourDirectory\YourVideoName.AVI", &H1 Or &H1000, 0&)
'Create the AVI File Holder to fill the AVI file
result = Avi.AVIFileCreateStream(pfile, avistream, strhdr)
'Format the AVI File Holder
result = Avi.AVIStreamSetFormat(avistream, 0, bi, Marshal.SizeOf(bi))

Then fill the AVI file with the frames. This is done in a loop where each frame is read byte by byte into the AVI file. The following code is done for each frame.

bm = New Bitmap("c:\YourDir\YourFrame.bmp")
bmd = bm.LockBits(New Rectangle(0, 0, bm.Width, bm.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb)
Marshal.Copy(bmd.Scan0, fbmp, 0, bmd.Stride * bmd.Height)
For j = 0 To bmd.Stride - 1
For k = 0 To bmd.Height - 1
bmp((bmd.Height - k - 1) * bmd.Stride + j) = fbmp(j + k * bmd.Stride)
Next k
Next j
Marshal.Copy(bmp, 0, bmd.Scan0, bmd.Height * bmd.Stride)
Avi.AVIStreamWrite(avistream, i, 1, bmd.Scan0, bmd.Stride * bmd.Height, 0, 0&, 0&)
bm.UnlockBits(bmd)
bm = Nothing
bmd = Nothing

Be sure to release the memory you allocated for the image and AVI files and close the files.

Avi.AVIStreamRelease(avistream)
Avi.AVIFileRelease(pfile)

After creating the uncompressed files, I convert them to DV AVI format using Ulead Video Studio. I do this because the original AVI files are limited to 2 GB in size and a typical 50 foot reel may require 13 GB of space while the DV format AVI file can be any size (I have some files up to 10 GB or 40 minutes of film) and a typical 50 foot reel requires only 1 GB of space.

To watch the films on a DVD player, it is a simple matter to use any software available to the user (I use Ulead) to convert the DV AVI file to mpeg2 format and burn it to a DVD (DVD video files are *.vob format).

For my personal film archive, I keep only the *.jpg images, the DV AVI files, and the vob files.

Some examples of my personal projects are found in the next section.

Results

No comments: