#region License Information (GPL v3) /* ShareX - A program that allows you to take screenshots and share any file type Copyright (C) 2008-2014 ShareX Developers This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Optionally you can also view the license at . */ #endregion License Information (GPL v3) // AForge Video for Windows Library // AForge.NET framework // http://www.aforgenet.com/framework/ // // Copyright © AForge.NET, 2007-2011 // contacts@aforgenet.com using System; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Runtime.InteropServices; namespace HelpersLib { /// /// AVI files writing using Video for Windows interface. /// /// /// The class allows to write AVI files using Video for Windows API. /// /// Sample usage: /// /// // instantiate AVI writer, use WMV3 codec /// AVIWriter writer = new AVIWriter( "wmv3" ); /// // create new AVI file and open it /// writer.Open( "test.avi", 320, 240 ); /// // create frame image /// Bitmap image = new Bitmap( 320, 240 ); /// /// for ( int i = 0; i < 240; i++ ) /// { /// // update image /// image.SetPixel( i, i, Color.Red ); /// // add the image as a new frame of video file /// writer.AddFrame( image ); /// } /// writer.Close( ); /// /// public class AVIWriter : IDisposable { // AVI file private IntPtr file; // video stream private IntPtr stream; // compressed stream private IntPtr streamCompressed; // buffer private IntPtr buffer = IntPtr.Zero; // width of video frames private int width; // height of vide frames private int height; // length of one line private int stride; // quality private int quality; // frame rate private int rate = 25; // current position private int position; // codec used for video compression private string codec = "DIB "; // dummy object to lock for synchronization private readonly object sync = new object(); /// /// Width of video frames. /// /// /// The property specifies the width of video frames, which are acceptable /// by method for saving, which is set in /// method. public int Width { get { return (buffer != IntPtr.Zero) ? width : 0; } } /// /// Height of video frames. /// /// /// The property specifies the height of video frames, which are acceptable /// by method for saving, which is set in /// method. public int Height { get { return (buffer != IntPtr.Zero) ? height : 0; } } /// /// Current position in video stream. /// /// /// The property tell current position in video stream, which actually equals /// to the amount of frames added using method. public int Position { get { return position; } } /// /// Desired playing frame rate. /// /// /// The property sets the video frame rate, which should be use during playing /// of the video to be saved. /// /// The property should be set befor opening new file to take effect. /// /// Default frame rate is set to 25. public int FrameRate { get { return rate; } set { rate = value; } } /// /// Codec used for video compression. /// /// /// The property sets the FOURCC code of video compression codec, which needs to /// be used for video encoding. /// /// The property should be set befor opening new file to take effect. /// /// Default video codec is set "DIB ", which means no compression. public string Codec { get { return codec; } set { codec = value; } } /// /// Compression video quality. /// /// /// The property sets video quality used by codec in order to balance compression rate /// and image quality. The quality is measured usually in the [0, 100] range. /// /// The property should be set befor opening new file to take effect. /// /// Default value is set to -1 - default compression quality of the codec. public int Quality { get { return quality; } set { quality = value; } } /// /// Initializes a new instance of the class. /// /// /// Initializes Video for Windows library. public AVIWriter() { NativeMethods.AVIFileInit(); } public AVIWriter(string output, int fps, int width, int height, bool showOptions = false) : this() { FrameRate = fps; Open(output, width, height, showOptions); } /// /// Initializes a new instance of the class. /// /// /// Codec to use for compression. /// /// Initializes Video for Windows library. public AVIWriter(string codec) : this() { this.codec = codec; } /// /// Destroys the instance of the class. /// ~AVIWriter() { Dispose(false); } /// /// Dispose the object. /// /// /// Frees unmanaged resources used by the object. The object becomes unusable /// after that. public void Dispose() { Dispose(true); // remove me from the Finalization queue GC.SuppressFinalize(this); } /// /// Dispose the object. /// /// /// Indicates if disposing was initiated manually. protected virtual void Dispose(bool disposing) { if (disposing) { // dispose managed resources } // close current AVI file if any opened and uninitialize AVI library Close(); NativeMethods.AVIFileExit(); } /// /// Create new AVI file and open it for writing. /// /// /// AVI file name to create. /// Video width. /// Video height. /// /// The method opens (creates) a video files, configure video codec and prepares /// the stream for saving video frames with a help of method. /// /// Failed opening the specified file. /// A error occurred while creating new video file. See exception message. /// Insufficient memory for internal buffer. /// Video file resolution must be a multiple of two. public void Open(string fileName, int width, int height, bool showOptions = false) { // close previous file Close(); // check width and height if (((width & 1) != 0) || ((height & 1) != 0)) { throw new ArgumentException("Video file resolution must be a multiple of two."); } bool success = false; try { lock (sync) { // calculate stride stride = width * 3; if ((stride % 4) != 0) stride += (4 - stride % 4); // create new file if (NativeMethods.AVIFileOpen(out file, fileName, OpenFileMode.Create | OpenFileMode.Write, IntPtr.Zero) != 0) throw new IOException("Failed opening the specified file."); this.width = width; this.height = height; // describe new stream AVISTREAMINFO info = new AVISTREAMINFO(); info.type = NativeMethods.mmioFOURCC("vids"); info.handler = NativeMethods.mmioFOURCC(codec); info.scale = 1; info.rate = rate; info.suggestedBufferSize = stride * height; // create stream if (NativeMethods.AVIFileCreateStream(file, out stream, ref info) != 0) throw new Exception("Failed creating stream."); // describe compression options AVICOMPRESSOPTIONS options = new AVICOMPRESSOPTIONS(); options.handler = NativeMethods.mmioFOURCC(codec); options.quality = quality; if (showOptions) { NativeMethods.AVISaveOptions(stream, ref options); } // create compressed stream if (NativeMethods.AVIMakeCompressedStream(out streamCompressed, stream, ref options, IntPtr.Zero) != 0) throw new Exception("Failed creating compressed stream."); // describe frame format BITMAPINFOHEADER bitmapInfoHeader = new BITMAPINFOHEADER(); bitmapInfoHeader.size = Marshal.SizeOf(bitmapInfoHeader.GetType()); bitmapInfoHeader.width = width; bitmapInfoHeader.height = height; bitmapInfoHeader.planes = 1; bitmapInfoHeader.bitCount = 24; bitmapInfoHeader.sizeImage = 0; bitmapInfoHeader.compression = 0; // BI_RGB // set frame format if (NativeMethods.AVIStreamSetFormat(streamCompressed, 0, ref bitmapInfoHeader, Marshal.SizeOf(bitmapInfoHeader.GetType())) != 0) throw new Exception("Failed setting format of the compressed stream."); // alloc unmanaged memory for frame buffer = Marshal.AllocHGlobal(stride * height); if (buffer == IntPtr.Zero) { throw new OutOfMemoryException("Insufficient memory for internal buffer."); } position = 0; success = true; } } finally { if (!success) { Close(); } } } /// /// Close video file. /// public void Close() { lock (sync) { // free unmanaged memory if (buffer != IntPtr.Zero) { Marshal.FreeHGlobal(buffer); buffer = IntPtr.Zero; } // release compressed stream if (streamCompressed != IntPtr.Zero) { NativeMethods.AVIStreamRelease(streamCompressed); streamCompressed = IntPtr.Zero; } // release stream if (stream != IntPtr.Zero) { NativeMethods.AVIStreamRelease(stream); stream = IntPtr.Zero; } // release file if (file != IntPtr.Zero) { NativeMethods.AVIFileRelease(file); file = IntPtr.Zero; } } } /// /// Add new frame to the AVI file. /// /// /// New frame image. /// /// The method adds new video frame to an opened video file. The width and heights /// of the frame should be the same as it was specified in method /// (see and properties). /// /// Thrown if no video file was open. /// Bitmap size must be of the same as video size, which was specified on opening video file. /// A error occurred while writing new video frame. See exception message. public void AddFrame(Bitmap frameImage) { lock (sync) { // check if AVI file was properly opened if (buffer == IntPtr.Zero) throw new IOException("AVI file should be successfully opened before writing."); // check image dimension if ((frameImage.Width != width) || (frameImage.Height != height)) throw new ArgumentException("Bitmap size must be of the same as video size, which was specified on opening video file."); // lock bitmap data BitmapData imageData = frameImage.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); // copy image data int srcStride = imageData.Stride; int dstStride = stride; int src = imageData.Scan0.ToInt32() + srcStride * (height - 1); int dst = buffer.ToInt32(); for (int y = 0; y < height; y++) { NativeMethods.memcpy(dst, src, dstStride); dst += dstStride; src -= srcStride; } // unlock bitmap data frameImage.UnlockBits(imageData); // write to stream if (NativeMethods.AVIStreamWrite(streamCompressed, position, 1, buffer, stride * height, 0, IntPtr.Zero, IntPtr.Zero) != 0) { throw new Exception("Failed adding frame."); } position++; } } } }