|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionThe video stream in an AVI file is nothing more than a sequence of bitmaps. This article is about extracting these bitmaps and re-building the stream, in order to hide a message in the video. BackgroundBefore reading this article, you should have read at least part one, Steganography - Hiding messages in the Noise of a Picture. This one uses the application described in parts 1-3, but you don’t need the extended features to understand it. Reading the Video StreamThe Windows AVI library is a set of functions in avifil32.dll. Before it is ready to use, it has to be initialized with //Initialize the AVI library
[DllImport("avifil32.dll")]
public static extern void AVIFileInit();
//Open an AVI file
[DllImport("avifil32.dll", PreserveSig=true)]
public static extern int AVIFileOpen(
ref int ppfile,
String szFile,
int uMode,
int pclsidHandler);
//Get a stream from an open AVI file
[DllImport("avifil32.dll")]
public static extern int AVIFileGetStream(
int pfile,
out IntPtr ppavi,
int fccType,
int lParam);
//Release an open AVI stream
[DllImport("avifil32.dll")]
public static extern int AVIStreamRelease(IntPtr aviStream);
//Release an ope AVI file
[DllImport("avifil32.dll")]
public static extern int AVIFileRelease(int pfile);
//Close the AVI library
[DllImport("avifil32.dll")]
public static extern void AVIFileExit();
Now we are able to open an AVI file and get the video stream. AVI files can contain many streams of four different types (Video, Audio, Midi and Text). Usually there is only one stream of each type, and we are only interested in the video stream. private int aviFile = 0;
private IntPtr aviStream;
public void Open(string fileName) {
AVIFileInit(); //Intitialize AVI library
//Open the file
int result = AVIFileOpen(
ref aviFile,
fileName,
OF_SHARE_DENY_WRITE, 0);
//Get the video stream
result = AVIFileGetStream(
aviFile,
out aviStream,
streamtypeVIDEO, 0);
}
Before we start reading the frames, we must know what exactly we want to read:
The AVI library contains a function for every question. //Get the start position of a stream
[DllImport("avifil32.dll", PreserveSig=true)]
public static extern int AVIStreamStart(int pavi);
//Get the length of a stream in frames
[DllImport("avifil32.dll", PreserveSig=true)]
public static extern int AVIStreamLength(int pavi);
//Get header information about an open stream
[DllImport("avifil32.dll")]
public static extern int AVIStreamInfo(
int pAVIStream,
ref AVISTREAMINFO psi,
int lSize);
With these functions we can fill a //Get a pointer to a GETFRAME object (returns 0 on error)
[DllImport("avifil32.dll")]
public static extern int AVIStreamGetFrameOpen(
IntPtr pAVIStream,
ref BITMAPINFOHEADER bih);
//Get a pointer to a packed DIB (returns 0 on error)
[DllImport("avifil32.dll")]
public static extern int AVIStreamGetFrame(
int pGetFrameObj,
int lPos);
//Release the GETFRAME object
[DllImport("avifil32.dll")]
public static extern int AVIStreamGetFrameClose(int pGetFrameObj);
Finally we are ready to decompress the frames... //get start position and count of frames
int firstFrame = AVIStreamStart(aviStream.ToInt32());
int countFrames = AVIStreamLength(aviStream.ToInt32());
//get header information
AVISTREAMINFO streamInfo = new AVISTREAMINFO();
result = AVIStreamInfo(aviStream.ToInt32(), ref streamInfo,
Marshal.SizeOf(streamInfo));
//construct the expected bitmap header
BITMAPINFOHEADER bih = new BITMAPINFOHEADER();
bih.biBitCount = 24;
bih.biCompression = 0; //BI_RGB;
bih.biHeight = (Int32)streamInfo.rcFrame.bottom;
bih.biWidth = (Int32)streamInfo.rcFrame.right;
bih.biPlanes = 1;
bih.biSize = (UInt32)Marshal.SizeOf(bih);
//prepare to decompress DIBs (device independend bitmaps)
int getFrameObject = AVIStreamGetFrameOpen(aviStream, ref bih);
...
//Export the frame at the specified position
public void ExportBitmap(int position, String dstFileName){
//Decompress the frame and return a pointer to the DIB
int pDib = Avi.AVIStreamGetFrame(getFrameObject, firstFrame + position);
//Copy the bitmap header into a managed struct
BITMAPINFOHEADER bih = new BITMAPINFOHEADER();
bih = (BITMAPINFOHEADER)Marshal.PtrToStructure(new IntPtr(pDib),
bih.GetType());
//Copy the image
byte[] bitmapData = new byte
...and store them in bitmap files. //Create file header
Avi.BITMAPFILEHEADER bfh = new Avi.BITMAPFILEHEADER();
bfh.bfType = Avi.BMP_MAGIC_COOKIE;
//size of file as written to disk
bfh.bfSize = (Int32)(55 + bih.biSizeImage);
bfh.bfOffBits = Marshal.SizeOf(bih) + Marshal.SizeOf(bfh);
//Create or overwrite the destination file
FileStream fs = new FileStream(dstFileName, System.IO.FileMode.Create);
BinaryWriter bw = new BinaryWriter(fs);
//Write header
bw.Write(bfh.bfType);
bw.Write(bfh.bfSize);
bw.Write(bfh.bfReserved1);
bw.Write(bfh.bfReserved2);
bw.Write(bfh.bfOffBits);
//Write bitmap info
bw.Write(bitmapInfo);
//Write bitmap data
bw.Write(bitmapData);
bw.Close();
fs.Close();
} //end of ExportBitmap
The application can use the extracted bitmaps just like any other image file. If one carrier file is an AVI video, it extracts the first frame to a temporary file, opens it and hides a part of the message. Then it writes the resulting bitmap to a new video stream, and continues with the next frame. After the last frame the application closes both video files, deletes the temporary bitmap file, and continues with the next carrier file. Writing to a Video StreamWhen the application opens an AVI carrier file, it creates another AVI file for the resulting bitmaps. The new video stream must have the same size and frame rate as the original stream, so we cannot create it in the //Create a new stream in an open AVI file
[DllImport("avifil32.dll")]
public static extern int AVIFileCreateStream(
int pfile,
out IntPtr ppavi,
ref AVISTREAMINFO ptr_streaminfo);
//Set the format for a new stream
[DllImport("avifil32.dll")]
public static extern int AVIStreamSetFormat(
IntPtr aviStream, Int32 lPos,
ref BITMAPINFOHEADER lpFormat, Int32 cbFormat);
//Write a sample to a stream
[DllImport("avifil32.dll")]
public static extern int AVIStreamWrite(
IntPtr aviStream, Int32 lStart, Int32 lSamples,
IntPtr lpBuffer, Int32 cbBuffer, Int32 dwFlags,
Int32 dummy1, Int32 dummy2);
Now we can create a stream... //Create a new video stream private void CreateStream() { //describe the stream to create AVISTREAMINFO strhdr = new AVISTREAMINFO(); strhdr.fccType = this.fccType; //mmioStringToFOURCC("vids", 0) strhdr.fccHandler = this.fccHandler; //"Microsoft Video 1" strhdr.dwScale = 1; strhdr.dwRate = frameRate; strhdr.dwSuggestedBufferSize = (UInt32)(height * stride); //use highest quality! Compression destroys the hidden message. strhdr.dwQuality = 10000; strhdr.rcFrame.bottom = (UInt32)height; strhdr.rcFrame.right = (UInt32)width; strhdr.szName = new UInt16[64]; //create the stream int result = AVIFileCreateStream(aviFile, out aviStream, ref strhdr); //define the image format BITMAPINFOHEADER bi = new BITMAPINFOHEADER(); bi.biSize = (UInt32)Marshal.SizeOf(bi); bi.biWidth = (Int32)width; bi.biHeight = (Int32)height; bi.biPlanes = 1; bi.biBitCount = 24; bi.biSizeImage = (UInt32)(this.stride * this.height); //format the stream result = Avi.AVIStreamSetFormat(aviStream, 0, ref bi, Marshal.SizeOf(bi)); } ...and write video frames. //Create an empty AVI file
public void Open(string fileName, UInt32 frameRate) {
this.frameRate = frameRate;
Avi.AVIFileInit();
int hr = Avi.AVIFileOpen(
ref aviFile, fileName,
OF_WRITE | OF_CREATE, 0);
}
//Add a sample to the stream - for first sample: create the stream
public void AddFrame(Bitmap bmp) {
BitmapData bmpDat = bmp.LockBits(
new Rectangle(0, 0, bmp.Width, bmp.Height),
ImageLockMode.ReadOnly,PixelFormat.Format24bppRgb);
//this is the first frame - get size and create a new stream
if (this.countFrames == 0) {
this.stride = (UInt32)bmpDat.Stride;
this.width = bmp.Width;
this.height = bmp.Height;
CreateStream(); //a method to create a new video stream
}
//add the bitmap to the stream
int result = AVIStreamWrite(aviStream,
countFrames, 1,
bmpDat.Scan0, //pointer to the beginning of the image data
(Int32) (stride * height),
0, 0, 0);
bmp.UnlockBits(bmpDat);
this.countFrames ++;
}
That's all we need to read and write video streams. Non-video streams and compression are not interesting at the moment, because compression destroys the hidden message by changing colours, and sound would make the files even larger - uncompressed AVI files are big enough! ;-) Changes in The | ||||||||||||||||||||