/* This class has been written by * Corinna John (Hannover, Germany) * cj@binary-universe.net * * You may do with this code whatever you like, * except selling it or claiming any rights/ownership. * * Please send me a little feedback about what you're * using this code for and what changes you'd like to * see in later versions. (And please excuse my bad english.) * * WARNING: This is experimental code. * Please do not expect "Release Quality". * */ using System; using System.IO; using System.Drawing; using System.Drawing.Imaging; using System.Runtime.InteropServices; namespace Scurvy.Media.Avi { internal sealed class AviVideoStream : AviStream { /// handle for AVIStreamGetFrame private int getFrameObject; /// size of an imge in bytes, stride*height private int frameSize; public int FrameSize { get { return frameSize; } } private double frameRate; public double FrameRate { get{ return frameRate; } } private int width; public int Width{ get{ return width; } } private int height; public int Height{ get{ return height; } } private Int16 countBitsPerPixel; public Int16 CountBitsPerPixel { get{ return countBitsPerPixel; } } /// count of frames in the stream private int countFrames = 0; public int CountFrames{ get{ return countFrames; } } /// initial frame index /// Added by M. Covington private int firstFrame = 0; public int FirstFrame { get { return firstFrame; } } private Avi.AVICOMPRESSOPTIONS compressOptions; public Avi.AVICOMPRESSOPTIONS CompressOptions { get { return compressOptions; } } public Avi.AVISTREAMINFO StreamInfo { get { return GetStreamInfo(aviStream); } } /// Initialize an empty VideoStream /// The file that contains the stream /// true: Create a compressed stream before adding frames /// Frames per second /// Size of one frame in bytes /// Width of each image /// Height of each image /// PixelFormat of the images public AviVideoStream(int aviFile, bool writeCompressed, double frameRate, int frameSize, int width, int height, PixelFormat format) { this.aviFile = aviFile; this.writeCompressed = writeCompressed; this.frameRate = frameRate; this.frameSize = frameSize; this.width = width; this.height = height; this.countBitsPerPixel = ConvertPixelFormatToBitCount(format); this.firstFrame = 0; CreateStream(); } /// Initialize a new VideoStream and add the first frame /// The file that contains the stream /// true: create a compressed stream before adding frames /// Frames per second /// Image to write into the stream as the first frame public AviVideoStream(int aviFile, bool writeCompressed, double frameRate, Bitmap firstFrame) { Initialize(aviFile, writeCompressed, frameRate, firstFrame); CreateStream(); AddFrame(firstFrame); } /// Initialize a new VideoStream and add the first frame /// The file that contains the stream /// true: create a compressed stream before adding frames /// Frames per second /// Image to write into the stream as the first frame public AviVideoStream(int aviFile, Avi.AVICOMPRESSOPTIONS compressOptions, double frameRate, Bitmap firstFrame) { Initialize(aviFile, true, frameRate, firstFrame); CreateStream(compressOptions); AddFrame(firstFrame); } /// Initialize a VideoStream for an existing stream /// The file that contains the stream /// An IAVISTREAM from [aviFile] public AviVideoStream(int aviFile, IntPtr aviStream){ this.aviFile = aviFile; this.aviStream = aviStream; Avi.BITMAPINFOHEADER bih = new Avi.BITMAPINFOHEADER(); int size = Marshal.SizeOf(bih); Avi.AVIStreamReadFormat(aviStream, 0, ref bih, ref size); Avi.AVISTREAMINFO streamInfo = GetStreamInfo(aviStream); this.frameRate = (float)streamInfo.dwRate / (float)streamInfo.dwScale; this.width = (int)streamInfo.rcFrame.right; this.height = (int)streamInfo.rcFrame.bottom; this.frameSize = bih.biSizeImage; this.countBitsPerPixel = bih.biBitCount; this.firstFrame = Avi.AVIStreamStart(aviStream.ToInt32()); this.countFrames = Avi.AVIStreamLength(aviStream.ToInt32()); } /// Copy all properties from one VideoStream to another one /// Used by EditableVideoStream /// /// /// /// internal AviVideoStream(int frameSize, double frameRate, int width, int height, Int16 countBitsPerPixel, int countFrames, Avi.AVICOMPRESSOPTIONS compressOptions, bool writeCompressed) { this.frameSize = frameSize; this.frameRate = frameRate; this.width = width; this.height = height; this.countBitsPerPixel = countBitsPerPixel; this.countFrames = countFrames; this.compressOptions = compressOptions; this.writeCompressed = writeCompressed; this.firstFrame = 0; } /// Initialize a new VideoStream /// Used only by constructors /// The file that contains the stream /// true: create a compressed stream before adding frames /// Frames per second /// Image to write into the stream as the first frame private void Initialize(int aviFile, bool writeCompressed, double frameRate, Bitmap firstFrameBitmap) { this.aviFile = aviFile; this.writeCompressed = writeCompressed; this.frameRate = frameRate; this.firstFrame = 0; BitmapData bmpData = firstFrameBitmap.LockBits(new Rectangle( 0, 0, firstFrameBitmap.Width, firstFrameBitmap.Height), ImageLockMode.ReadOnly, firstFrameBitmap.PixelFormat); this.frameSize = bmpData.Stride * bmpData.Height; this.width = firstFrameBitmap.Width; this.height = firstFrameBitmap.Height; this.countBitsPerPixel = ConvertPixelFormatToBitCount(firstFrameBitmap.PixelFormat); firstFrameBitmap.UnlockBits(bmpData); } /// Get the count of bits per pixel from a PixelFormat value /// One of the PixelFormat members beginning with "Format..." - all others are not supported /// bit count private Int16 ConvertPixelFormatToBitCount(PixelFormat format){ String formatName = format.ToString(); if(formatName.Substring(0, 6) != "Format"){ throw new Exception("Unknown pixel format: "+formatName); } formatName = formatName.Substring(6, 2); Int16 bitCount = 0; if( Char.IsNumber(formatName[1]) ){ //16, 32, 48 bitCount = Int16.Parse(formatName); }else{ //4, 8 bitCount = Int16.Parse(formatName[0].ToString()); } return bitCount; } /// Returns a PixelFormat value for a specific bit count /// count of bits per pixel /// A PixelFormat value for [bitCount] private PixelFormat ConvertBitCountToPixelFormat(int bitCount){ String formatName; if(bitCount > 16){ formatName = String.Format("Format{0}bppRgb", bitCount); }else if(bitCount == 16){ formatName = "Format16bppRgb555"; }else{ // < 16 formatName = String.Format("Format{0}bppIndexed", bitCount); } return (PixelFormat)Enum.Parse(typeof(PixelFormat), formatName); } private Avi.AVISTREAMINFO GetStreamInfo(IntPtr aviStream){ Avi.AVISTREAMINFO streamInfo = new Avi.AVISTREAMINFO(); int result = Avi.AVIStreamInfo(StreamPointer, ref streamInfo, Marshal.SizeOf(streamInfo)); if(result != 0) { throw new Exception("Exception in VideoStreamInfo: "+result.ToString()); } return streamInfo; } private void GetRateAndScale(ref double frameRate, ref int scale) { scale = 1; while (frameRate != (long)frameRate) { frameRate = frameRate * 10; scale *= 10; } } /// Create a new stream private void CreateStreamWithoutFormat() { int scale = 1; double rate = frameRate; GetRateAndScale(ref rate, ref scale); Avi.AVISTREAMINFO strhdr = new Avi.AVISTREAMINFO(); strhdr.fccType = Avi.mmioStringToFOURCC("vids", 0); strhdr.fccHandler = Avi.mmioStringToFOURCC("CVID", 0); strhdr.dwFlags = 0; strhdr.dwCaps = 0; strhdr.wPriority = 0; strhdr.wLanguage = 0; strhdr.dwScale = (int)scale; strhdr.dwRate = (int)rate; // Frames per Second strhdr.dwStart = 0; strhdr.dwLength = 0; strhdr.dwInitialFrames = 0; strhdr.dwSuggestedBufferSize = frameSize; //height_ * stride_; strhdr.dwQuality = -1; //default strhdr.dwSampleSize = 0; strhdr.rcFrame.top = 0; strhdr.rcFrame.left = 0; strhdr.rcFrame.bottom = (uint)height; strhdr.rcFrame.right = (uint)width; strhdr.dwEditCount = 0; strhdr.dwFormatChangeCount = 0; strhdr.szName = new UInt16[64]; int result = Avi.AVIFileCreateStream(aviFile, out aviStream, ref strhdr); if (result != 0) { throw new Exception("Exception in AVIFileCreateStream: " + result.ToString()); } } /// Create a new stream private void CreateStream() { CreateStreamWithoutFormat(); if (writeCompressed) { CreateCompressedStream(); } else { SetFormat(aviStream); } } /// Create a new stream private void CreateStream(Avi.AVICOMPRESSOPTIONS options){ CreateStreamWithoutFormat(); CreateCompressedStream(options); } /// Create a compressed stream from an uncompressed stream private void CreateCompressedStream(){ //display the compression options dialog... Avi.AVICOMPRESSOPTIONS_CLASS options = new Avi.AVICOMPRESSOPTIONS_CLASS(); options.fccType = (uint)Avi.streamtypeVIDEO; options.lpParms = IntPtr.Zero; options.lpFormat = IntPtr.Zero; Avi.AVISaveOptions(IntPtr.Zero, Avi.ICMF_CHOOSE_KEYFRAME | Avi.ICMF_CHOOSE_DATARATE, 1, ref aviStream, ref options); Avi.AVISaveOptionsFree(1, ref options); //..or set static options /*Avi.AVICOMPRESSOPTIONS opts = new Avi.AVICOMPRESSOPTIONS(); opts.fccType = (UInt32)Avi.mmioStringToFOURCC("vids", 0); opts.fccHandler = (UInt32)Avi.mmioStringToFOURCC("CVID", 0); opts.dwKeyFrameEvery = 0; opts.dwQuality = 0; // 0 .. 10000 opts.dwFlags = 0; // AVICOMRPESSF_KEYFRAMES = 4 opts.dwBytesPerSecond= 0; opts.lpFormat = new IntPtr(0); opts.cbFormat = 0; opts.lpParms = new IntPtr(0); opts.cbParms = 0; opts.dwInterleaveEvery = 0;*/ //get the compressed stream this.compressOptions = options.ToStruct(); int result = Avi.AVIMakeCompressedStream(out compressedStream, aviStream, ref compressOptions, 0); if(result != 0) { throw new Exception("Exception in AVIMakeCompressedStream: "+result.ToString()); } SetFormat(compressedStream); } /// Create a compressed stream from an uncompressed stream private void CreateCompressedStream(Avi.AVICOMPRESSOPTIONS options) { int result = Avi.AVIMakeCompressedStream(out compressedStream, aviStream, ref options, 0); if (result != 0) { throw new Exception("Exception in AVIMakeCompressedStream: " + result.ToString()); } this.compressOptions = options; SetFormat(compressedStream); } /// Add one frame to a new stream /// /// /// This works only with uncompressed streams, /// and compressed streams that have not been saved yet. /// Use DecompressToNewFile to edit saved compressed streams. /// public void AddFrame(Bitmap bmp){ bmp.RotateFlip(RotateFlipType.RotateNoneFlipY); BitmapData bmpDat = bmp.LockBits( new Rectangle( 0,0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, bmp.PixelFormat); int result = Avi.AVIStreamWrite(writeCompressed ? compressedStream : StreamPointer, countFrames, 1, bmpDat.Scan0, (Int32)(bmpDat.Stride * bmpDat.Height), 0, 0, 0); if (result!= 0) { throw new Exception("Exception in VideoStreamWrite: "+result.ToString()); } bmp.UnlockBits(bmpDat); countFrames++; } /// Apply a format to a new stream /// The IAVISTREAM /// /// The format must be set before the first frame can be written, /// and it cannot be changed later. /// private void SetFormat(IntPtr aviStream){ Avi.BITMAPINFOHEADER bi = new Avi.BITMAPINFOHEADER(); bi.biSize = Marshal.SizeOf(bi); bi.biWidth = width; bi.biHeight = height; bi.biPlanes = 1; bi.biBitCount = countBitsPerPixel; bi.biSizeImage = frameSize; int result = Avi.AVIStreamSetFormat(aviStream, 0, ref bi, bi.biSize); if(result != 0){ throw new Exception("Error in VideoStreamSetFormat: "+result.ToString()); } } /// Prepare for decompressing frames /// /// This method has to be called before GetBitmap and ExportBitmap. /// Release ressources with GetFrameClose. /// public void GetFrameOpen(){ Avi.AVISTREAMINFO streamInfo = GetStreamInfo(StreamPointer); //Open frames Avi.BITMAPINFOHEADER bih = new Avi.BITMAPINFOHEADER(); bih.biBitCount = countBitsPerPixel; bih.biClrImportant = 0; bih.biClrUsed = 0; bih.biCompression = 0; bih.biPlanes = 1; bih.biSize = Marshal.SizeOf(bih); bih.biXPelsPerMeter = 0; bih.biYPelsPerMeter = 0; // Corrections by M. Covington: // If these are pre-set, interlaced video is not handled correctly. // Better to give zeroes and let Windows fill them in. bih.biHeight = 0; // was (Int32)streamInfo.rcFrame.bottom; bih.biWidth = 0; // was (Int32)streamInfo.rcFrame.right; // Corrections by M. Covington: // Validate the bit count, because some AVI files give a bit count // that is not one of the allowed values in a BitmapInfoHeader. // Here 0 means for Windows to figure it out from other information. if (bih.biBitCount > 24) { bih.biBitCount = 32; } else if (bih.biBitCount > 16) { bih.biBitCount = 24; } else if (bih.biBitCount > 8) { bih.biBitCount = 16; } else if (bih.biBitCount > 4) { bih.biBitCount = 8; } else if (bih.biBitCount > 0) { bih.biBitCount = 4; } getFrameObject = Avi.AVIStreamGetFrameOpen(StreamPointer, ref bih); if(getFrameObject == 0){ throw new Exception("Exception in VideoStreamGetFrameOpen!"); } } /// Export a frame into a bitmap file /// Position of the frame /// Name of the file to store the bitmap public void ExportBitmap(int position, String dstFileName) { ExportBitmap(position, dstFileName, ImageFormat.Bmp); } /// Export a frame into a bitmap file /// Position of the frame /// Name of the file to store the bitmap /// The format to save the image to public void ExportBitmap(int position, String dstFileName, ImageFormat format){ Bitmap bmp = GetBitmap(position); bmp.Save(dstFileName, format); bmp.Dispose(); } /// Export a frame into a bitmap /// Position of the frame public Bitmap GetBitmap(int position) { return GetBitmap(position, PixelFormat.Format32bppArgb); } /// Export a frame into a bitmap /// Position of the frame /// The pixel format of the returned bitmap public Bitmap GetBitmap(int position, PixelFormat pixelFormat){ if(position > countFrames){ throw new Exception("Invalid frame position: "+position); } Avi.AVISTREAMINFO streamInfo = GetStreamInfo(StreamPointer); //Decompress the frame and return a pointer to the DIB int dib = Avi.AVIStreamGetFrame(getFrameObject, firstFrame + position); //Copy the bitmap header into a managed struct Avi.BITMAPINFOHEADER bih = new Avi.BITMAPINFOHEADER(); bih = (Avi.BITMAPINFOHEADER)Marshal.PtrToStructure(new IntPtr(dib), bih.GetType()); if(bih.biSizeImage < 1){ throw new Exception("Exception in VideoStreamGetFrame"); } //copy the image byte[] bitmapData; int address = dib + Marshal.SizeOf(bih); if(bih.biBitCount < 16){ //copy palette and pixels bitmapData = new byte[bih.biSizeImage + Avi.PALETTE_SIZE]; }else{ //copy only pixels bitmapData = new byte[bih.biSizeImage]; } Marshal.Copy(new IntPtr(address), bitmapData, 0, bitmapData.Length); //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(); Marshal.Copy(new IntPtr(address), bitmapInfo, 0, bitmapInfo.Length); Marshal.FreeHGlobal(ptr); //create file header Avi.BITMAPFILEHEADER bfh = new Avi.BITMAPFILEHEADER(); bfh.bfType = Avi.BMP_MAGIC_COOKIE; bfh.bfSize = (Int32)(55 + bih.biSizeImage); //size of file as written to disk bfh.bfReserved1 = 0; bfh.bfReserved2 = 0; bfh.bfOffBits = Marshal.SizeOf(bih) + Marshal.SizeOf(bfh); if(bih.biBitCount < 16){ //There is a palette between header and pixel data bfh.bfOffBits += Avi.PALETTE_SIZE; } //write a bitmap stream BinaryWriter bw = new BinaryWriter( new MemoryStream() ); //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); Bitmap bmp = (Bitmap)Image.FromStream(bw.BaseStream); Bitmap saveableBitmap = new Bitmap(bmp.Width, bmp.Height, pixelFormat); Graphics g = Graphics.FromImage(saveableBitmap); g.DrawImage(bmp, 0,0); g.Dispose(); bmp.Dispose(); bw.Close(); return saveableBitmap; } /// Free ressources that have been used by GetFrameOpen public void GetFrameClose(){ if(getFrameObject != 0){ Avi.AVIStreamGetFrameClose(getFrameObject); getFrameObject = 0; } } /// Copy all frames into a new file /// Name of the new file /// true: Compress the new stream /// AviManager for the new file /// Use this method if you want to append frames to an existing, compressed stream public AviManager DecompressToNewFile(String fileName, bool recompress, out AviVideoStream newStream2){ AviManager newFile = new AviManager(fileName, false); this.GetFrameOpen(); Bitmap frame = GetBitmap(0); AviVideoStream newStream = newFile.AddVideoStream(recompress, frameRate, frame); frame.Dispose(); for(int n=1; nCopy the stream into a new file /// Name of the new file public override void ExportStream(String fileName){ Avi.AVICOMPRESSOPTIONS_CLASS opts = new Avi.AVICOMPRESSOPTIONS_CLASS(); opts.fccType = (uint)Avi.streamtypeVIDEO; opts.lpParms = IntPtr.Zero; opts.lpFormat = IntPtr.Zero; IntPtr streamPointer = StreamPointer; Avi.AVISaveOptions(IntPtr.Zero, Avi.ICMF_CHOOSE_KEYFRAME | Avi.ICMF_CHOOSE_DATARATE, 1, ref streamPointer, ref opts); Avi.AVISaveOptionsFree(1, ref opts); Avi.AVISaveV(fileName, 0, 0, 1, ref aviStream, ref opts); } } }