![]() |
||||||||||||
|
|
|||||||||||
Worum geht es?Der Video Stream in einem AVI-Film ist nichts weiter als eine Folge von Bitmaps. In diesem Artikel geht es darum, diese Bitmaps zu extrahieren und den Stream anschließend wieder zusammen zu setzen, um auch im Video eine Nachricht verstecken zu können. Falls Du schon weißt, wie man AVI-Videos bearbeitet, solltest Du diese Seite überspringen, es ist reine Zeitverschwendung. Den Video Stream lesenDie Windows AVI Library ist ein Satz von Funktionen in avifil32.dll. Bevor der verwendet werden kann,
muss er mit
//AVI Library initialisieren
[DllImport("avifil32.dll")]
public static extern void AVIFileInit();
//Eine AVI Datei öffnen
[DllImport("avifil32.dll", PreserveSig=true)]
public static extern int AVIFileOpen(
ref int ppfile,
String szFile,
int uMode,
int pclsidHandler);
//einen Stream in einer offenen AVI Datei holen
[DllImport("avifil32.dll")]
public static extern int AVIFileGetStream(
int pfile,
out IntPtr ppavi,
int fccType,
int lParam);
//Einen offenen AVI Stream freigeben
[DllImport("avifil32.dll")]
public static extern int AVIStreamRelease(IntPtr aviStream);
//Eine offene AVI Datei freigeben
[DllImport("avifil32.dll")]
public static extern int AVIFileRelease(int pfile);
//AVI Library schließen
[DllImport("avifil32.dll")]
public static extern void AVIFileExit();
Jetzt können wir eine AVI Datei öffnen und den Video Stream finden. AVI Dateien enthalten mehrere Streams von vier verschiedenen Typen (Video, Audio, Midi und Text). Normalerweise existiert nur ein Stream von jedem Typ, und wir sind nur am Video Stream interessiert.
private int aviFile = 0;
private IntPtr aviStream;
public void Open(string fileName) {
AVIFileInit(); //Intitialize AVI library
//Datei öffnen
int result = AVIFileOpen(
ref aviFile,
fileName,
OF_SHARE_DENY_WRITE, 0);
//Video Stream holen
result = AVIFileGetStream(
aviFile,
out aviStream,
streamtypeVIDEO, 0);
}
Bevor wir die Frames auslesen können, müssen wir wissen, was genau wir lesen wollen:
//Startposition eines Streams ermitteln
[DllImport("avifil32.dll", PreserveSig=true)]
public static extern int AVIStreamStart(int pavi);
//Anzahl der Frames in einem Stream ermitteln
[DllImport("avifil32.dll", PreserveSig=true)]
public static extern int AVIStreamLength(int pavi);
//Header-Infos über einen offenen Stream abrufen
[DllImport("avifil32.dll")]
public static extern int AVIStreamInfo(
int pAVIStream,
ref AVISTREAMINFO psi,
int lSize);
Mit diesen Funktionen können wir eine
//Pointer auf ein GETFRAME Objekt holen (gibt bei Fehlern 0 zurück)
[DllImport("avifil32.dll")]
public static extern int AVIStreamGetFrameOpen(
IntPtr pAVIStream,
ref BITMAPINFOHEADER bih);
//Pointer auf ein DIB holen (gibt bei Fehlern 0 zurück)
[DllImport("avifil32.dll")]
public static extern int AVIStreamGetFrame(
int pGetFrameObj,
int lPos);
//GETFRAME Object freigeben
[DllImport("avifil32.dll")]
public static extern int AVIStreamGetFrameClose(int pGetFrameObj);
Endlich können wir die Frames entpacken...
//Startposition und Anzahl der Frames holen
int firstFrame = AVIStreamStart(aviStream.ToInt32());
int countFrames = AVIStreamLength(aviStream.ToInt32());
//Header-Inforamtionen holen
AVISTREAMINFO streamInfo = new AVISTREAMINFO();
result = AVIStreamInfo(aviStream.ToInt32(), ref streamInfo, Marshal.SizeOf(streamInfo));
//Header für die Bitmaps zusammensetzen
BITMAPINFOHEADER bih = new BITMAPINFOHEADER();
bih.biBitCount = 24;
bih.biCompression = 0;
bih.biHeight = (Int32)streamInfo.rcFrame.bottom;
bih.biWidth = (Int32)streamInfo.rcFrame.right;
bih.biPlanes = 1;
bih.biSize = (UInt32)Marshal.SizeOf(bih);
//Entpacken von DIBs (device independend bitmaps) vorbereiten
int getFrameObject = AVIStreamGetFrameOpen(aviStream, ref bih);
...
//Den Frame an einer bestimmten Position exportieren
public void ExportBitmap(int position, String dstFileName){
//Frame dekomprimieren und Pointer zum DIB zurückgeben
int pDib = Avi.AVIStreamGetFrame(getFrameObject, firstFrame + position);
//Bitmap-Header in eine verwaltete Struktur kopieren
BITMAPINFOHEADER bih = new BITMAPINFOHEADER();
bih = (BITMAPINFOHEADER)Marshal.PtrToStructure(new IntPtr(pDib), bih.GetType());
//Das Bild kopieren
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++;
}
//Bitmap-Details kopieren
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++;
}
...und in einer Bitmap-Datei speichern.
//Header aufbauen
Avi.BITMAPFILEHEADER bfh = new Avi.BITMAPFILEHEADER();
bfh.bfType = Avi.BMP_MAGIC_COOKIE;
bfh.bfSize = (Int32)(55 + bih.biSizeImage); //Größe der gespeicherten Datei
bfh.bfOffBits = Marshal.SizeOf(bih) + Marshal.SizeOf(bfh);
//Zieldatei erstellen oder überschreiben
FileStream fs = new FileStream(dstFileName, System.IO.FileMode.Create);
BinaryWriter bw = new BinaryWriter(fs);
//Header schreiben
bw.Write(bfh.bfType);
bw.Write(bfh.bfSize);
bw.Write(bfh.bfReserved1);
bw.Write(bfh.bfReserved2);
bw.Write(bfh.bfOffBits);
//Details schreiben
bw.Write(bitmapInfo);
//Write bitmap data
bw.Write(bitmapData);
bw.Close();
fs.Close();
} //end of ExportBitmap
Die Anwendung kann die extrahierten Bitmaps genauso verwenden wie jedes andere Bild. Wenn eine Träger-Datei ein AVI Video ist, wird der erste Frame in eine temporäre Datei extrahiert, geöffnet und ein Teil der Nachricht darin versteckt. Anschließend wird die geänderte Bitmap in einen neuen Stream geschrieben und mit dem nächsten Frame weiter gearbeitet. Nach dem letzten Frame schließt die Anwendung beide Video Dateien, löscht die temporären Bitmap Dateien, und macht mit der nächsten Träger-Datei weiter. Einen Video Stream schreibenWenn die Anwendung eine AVI Träger-Datei öffnet, erstellt sie auch eine weitere AVI Datei
für die resultierenden Bitmaps.
Der neue Video Stream muss die gleiche Höhe/Breite und Frame Rate haben wie das Original,
darum können wir ihn nicht gleich in der
//Neuen Strean in einer vorhandenen AVI Datei erstellen
[DllImport("avifil32.dll")]
public static extern int AVIFileCreateStream(
int pfile,
out IntPtr ppavi,
ref AVISTREAMINFO ptr_streaminfo);
//Format eines neuen Stram festlegen
[DllImport("avifil32.dll")]
public static extern int AVIStreamSetFormat(
IntPtr aviStream, Int32 lPos,
ref BITMAPINFOHEADER lpFormat, Int32 cbFormat);
//Einen Frame in einen Stream schreiben
[DllImport("avifil32.dll")]
public static extern int AVIStreamWrite(
IntPtr aviStream, Int32 lStart, Int32 lSamples,
IntPtr lpBuffer, Int32 cbBuffer, Int32 dwFlags,
Int32 dummy1, Int32 dummy2);
Jetzt können wir einen Stream erstellen...
//Neuen Viedo Stream anlegen
private void CreateStream() {
//Eigenschaften des Streams festlegen
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);
//Höhste Qualität verwenden! Kompression zerstört die versteckte Nachricht.
strhdr.dwQuality = 10000;
strhdr.rcFrame.bottom = (UInt32)height;
strhdr.rcFrame.right = (UInt32)width;
strhdr.szName = new UInt16[64];
//Den Stream erstellen
int result = AVIFileCreateStream(aviFile, out aviStream, ref strhdr);
//Format festlegen
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 zuweisen
result = Avi.AVIStreamSetFormat(aviStream, 0, ref bi, Marshal.SizeOf(bi));
}
...und Video Frames schreiben.
//Leere AVI Datei anlegen
public void Open(string fileName, UInt32 frameRate) {
this.frameRate = frameRate;
Avi.AVIFileInit();
int hr = Avi.AVIFileOpen(
ref aviFile, fileName,
OF_WRITE | OF_CREATE, 0);
}
//Ein Bild zum Stream hinzufügen - wenn erstes Bild: Stream erstellen
public void AddFrame(Bitmap bmp) {
BitmapData bmpDat = bmp.LockBits(
new Rectangle(0, 0, bmp.Width, bmp.Height),
ImageLockMode.ReadOnly,PixelFormat.Format24bppRgb);
//Es ist der erste Frame - Größe festlegen und Stream erstellen
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
}
//Bitmap hinzufügen
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 ++;
}
Mehr brauchen wir nicht, um Video Stream zu lesen und zu schreiben. Non-Video Streams und Kompression
sind erstmal uninteressant, weil Kompression die versteckte Nachricht durch Farbveränderungen
zerstören würde, und Sound die Dateien noch grösser machen würde - unkomprimierte
AVI Dateien sind gross genug ;-)
Änderungen in
Die Methode |