这次要在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, 0, ref 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(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 ++;
}
[代码中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的头,导致本程序无法正确运行