[CodeProject每日一荐] 藏东西系列:在AVI文件中隐藏信息

这次要在AVI文件中隐藏信息了,AVI文件是一系列的位图.

Steganography IV - Reading and Writing AVI files By Corinna John 
[读视频流]
Windows AVI 库是avifil32.dll中函数的集合. 使用之前先得用 AVIFileInit初始化. AVIFileOpen 打开文件, AVIFileGetStream 取得视频流. 这些函数申请的内存最后都必须释放. AVI文件可以包含四种不同类型的多个流,通常每种类型只有一个流,我们这里只关心视频流.

// 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();

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);
}

读帧之前,我们需要明了我们要读的东西: 第一个帧从哪里开始的?一共有多少帧?图像的高度宽度是多少?AVI库包含以下函数回答我们的上述问题

// 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);

调用以上函数我们可以填充位图信息头BITMAPINFOHEADER 结构,然后我们用以下函数来提取帧

// 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);

准备工作就绪,现在解压帧

// 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[bih.biSizeImage];
    
int address = pDib + Marshal.SizeOf(bih);
    
for(int offset=0; offset<bitmapData.Length; offset++){
        bitmapData[offset] 
= Marshal.ReadByte(new IntPtr(address));
        address
++;
    }


    
//Copy bitmap info
    byte[] bitmapInfo = new byte[Marshal.SizeOf(bih)];
    IntPtr ptr;
    ptr 
= Marshal.AllocHGlobal(bitmapInfo.Length);
    Marshal.StructureToPtr(bih, ptr, 
false);
    address 
= ptr.ToInt32();
    
for(int offset=0; offset<bitmapInfo.Length; offset++){
        bitmapInfo[offset] 
= Marshal.ReadByte(new IntPtr(address));
        address
++;
    }

然后保存为位图

     // 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

应用程序会把解出来的位图当作普通位图来隐藏信息,如果载体文件是AVI文件,则提取第一帧到一个临时位图文件,放入一些信息,接下来是第二帧...最后一帧之后则关闭AVI文件,删除临时位图文件,接下来处理下一个载体文件.

[写视频流]
应用程序块打开AVI 载体文件时, 它创建另一个AVI文件来保存新的位图,新的文件大小和帧频率都与原来的一样, 我们不能用 Open() 来创建,而使用AVIFileCreateStream, AVIStreamSetFormat and AVIStreamWrite:

// 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);

现在创建视频流:

// 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, 0ref bi, Marshal.SizeOf(bi));
}

 写入帧:

// 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(00, 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), 
        
000); 

    bmp.UnlockBits(bmpDat);
    
this.countFrames ++;
}
[代码中CryptUtility类的改变]
HideOrExtract() 在前面的版本中一次读入所有载体位图,但现在必须改进了,每次加在一个位图,在加载下一个位图前先释放本位图.当前使用的位图( 一个简单的位图或者AVI的一帧)保存在BitmapInfo结构中,通过by ref方式传递
public   struct  BitmapInfo {
    
//uncompressed image
    public Bitmap bitmap;
    
    
//position of the frame in the AVI stream, -1 for non-avi bitmaps
    public int aviPosition;
    
//count of frames in the AVI stream, or 0 for non-avi bitmaps
    public int aviCountFrames;
    
    
//path and name of the bitmap file
    
//this file will be deleted later, if aviPosition is 0 or greater
    public String sourceFileName;
    
//how many bytes will be hidden in this image
    public long messageBytesToHide;

    
public void LoadBitmap(String fileName){
        bitmap 
= new Bitmap(fileName);
        sourceFileName 
= fileName;
    }

}
MovePixelPosition需移到下一个载体位图时, 首先检查aviPosition. 如果aviPosition < 0, ,则保存位图且释放资源, 如果aviPosition >= 0, 则是AVI的一帧. 不保存为文件而是加到打开的AVI流中. 如果位图是简单位图或AVI的最后一帧, 该方法会移到下一个载体文件. 如果AVI中还有帧,则移到下一帧.

[使用代码]
工程中多了三个类
  • AviReader打开已有的AVI 文件复制帧到位图
  • AviWriter创建新的AVI 文件,组合帧到视频流中.
  • Avi包含函数声明和结构定义.
  • [注意]
    如果安装了VirtualDub,有可能设置为不允许其它程序写AVI的头,导致本程序无法正确运行

    你可能感兴趣的:(project)