/**************************************************************************** While the underlying libraries are covered by LGPL, this sample is released as public domain. It 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. *****************************************************************************/ using System; using System.Drawing; using System.Drawing.Imaging; using System.Collections; using System.Runtime.InteropServices; using System.Threading; using System.Diagnostics; using System.Collections.Generic; using DirectShowLib; namespace WebCamService { public delegate void CamImage(Image camimage); /// Summary description for MainForm. public class Capture : ISampleGrabberCB, IDisposable { #region Member variables /// graph builder interface. private IFilterGraph2 m_FilterGraph = null; private IMediaControl m_mediaCtrl = null; /// so we can wait for the async job to finish private ManualResetEvent m_PictureReady = null; /// Set by async routine when it captures an image private volatile bool m_bGotOne = false; /// Indicates the status of the graph private bool m_bRunning = false; /// Dimensions of the image, calculated once in constructor. private IntPtr m_handle = IntPtr.Zero; private int m_videoWidth; private int m_videoHeight; private int m_stride; public int m_Dropped = 0; public Image image = null; IntPtr ip = IntPtr.Zero; public event CamImage camimage; System.Windows.Forms.Timer timer1 = new System.Windows.Forms.Timer(); #endregion #region API [DllImport("Kernel32.dll", EntryPoint="RtlMoveMemory")] private static extern void CopyMemory(IntPtr Destination, IntPtr Source, int Length); #endregion public Capture() { } /// Use capture with selected media caps public Capture(int iDeviceNum, AMMediaType media) { DsDevice[] capDevices; // Get the collection of video devices capDevices = DsDevice.GetDevicesOfCat(FilterCategory.VideoInputDevice); if (iDeviceNum + 1 > capDevices.Length) { throw new Exception("No video capture devices found at that index!"); } try { // Set up the capture graph SetupGraph(capDevices[iDeviceNum], media); // tell the callback to ignore new images m_PictureReady = new ManualResetEvent(false); m_bGotOne = true; m_bRunning = false; timer1.Interval = 1000 / 15; // 15 fps timer1.Tick += new EventHandler(timer1_Tick); timer1.Start(); } catch { Dispose(); throw; } } /// release everything. public void Dispose() { timer1.Stop(); if (camimage != null) { camimage(null); // clear last pic } CloseInterfaces(); if (m_PictureReady != null) { m_PictureReady.Close(); m_PictureReady = null; } } // Destructor ~Capture() { Dispose(); } public int Width { get { return m_videoWidth; } } public int Height { get { return m_videoHeight; } } public int Stride { get { return m_stride; } } /// capture the next image public IntPtr GetBitMap() { if (m_handle == IntPtr.Zero) m_handle = Marshal.AllocCoTaskMem(m_stride * m_videoHeight); try { // get ready to wait for new image m_PictureReady.Reset(); m_bGotOne = false; // If the graph hasn't been started, start it. Start(); // Start waiting if ( ! m_PictureReady.WaitOne(5000, false) ) { throw new Exception("Timeout waiting to get picture"); } //Pause(); //- we are effectivly pulling at 15 fps, so no need to pause } catch { Marshal.FreeCoTaskMem(m_handle); throw; } // Got one return m_handle; } // Start the capture graph public void Start() { if (!m_bRunning) { int hr = m_mediaCtrl.Run(); DsError.ThrowExceptionForHR( hr ); m_bRunning = true; } } // Pause the capture graph. // Running the graph takes up a lot of resources. Pause it when it // isn't needed. public void Pause() { if (m_bRunning) { int hr = m_mediaCtrl.Pause(); DsError.ThrowExceptionForHR( hr ); m_bRunning = false; } } public static List getDevices() { List list = new List(); DsDevice[] capDevices; // Get the collection of video devices capDevices = DsDevice.GetDevicesOfCat(FilterCategory.VideoInputDevice); foreach (DsDevice dev in capDevices) { list.Add(dev.Name); } return list; } public bool showhud = true; void timer1_Tick(object sender, EventArgs e) { try { ip = this.GetBitMap(); image = new Bitmap(this.Width, this.Height, this.Stride, PixelFormat.Format24bppRgb, ip); image.RotateFlip(RotateFlipType.RotateNoneFlipY); if (camimage != null) { camimage(image); } } catch { Console.WriteLine("Grab bmp failed"); timer1.Enabled = false; this.CloseInterfaces(); System.Windows.Forms.MessageBox.Show("Problem with capture device, grabbing frame took longer than 5 sec"); } } /// build the capture graph for grabber. private void SetupGraph(DsDevice dev, AMMediaType media) { int hr; ISampleGrabber sampGrabber = null; IBaseFilter capFilter = null; ICaptureGraphBuilder2 capGraph = null; // Get the graphbuilder object m_FilterGraph = (IFilterGraph2) new FilterGraph(); m_mediaCtrl = m_FilterGraph as IMediaControl; try { // Get the ICaptureGraphBuilder2 capGraph = (ICaptureGraphBuilder2) new CaptureGraphBuilder2(); // Get the SampleGrabber interface sampGrabber = (ISampleGrabber) new SampleGrabber(); // Start building the graph hr = capGraph.SetFiltergraph( m_FilterGraph ); DsError.ThrowExceptionForHR( hr ); // Add the video device hr = m_FilterGraph.AddSourceFilterForMoniker(dev.Mon, null, "Video input", out capFilter); DsError.ThrowExceptionForHR( hr ); // add video crossbar // thanks to Andrew Fernie - this is to get tv tuner cards working IAMCrossbar crossbar = null; object o; hr = capGraph.FindInterface(PinCategory.Capture, MediaType.Video, capFilter, typeof(IAMCrossbar).GUID, out o); if (hr >= 0) { crossbar = (IAMCrossbar)o; int oPin, iPin; int ovLink, ivLink; ovLink = ivLink = 0; crossbar.get_PinCounts(out oPin, out iPin); int pIdxRel; PhysicalConnectorType tp; for (int i = 0; i < iPin; i++) { crossbar.get_CrossbarPinInfo(true, i, out pIdxRel, out tp); if (tp == PhysicalConnectorType.Video_Composite) ivLink = i; } for (int i = 0; i < oPin; i++) { crossbar.get_CrossbarPinInfo(false, i, out pIdxRel, out tp); if (tp == PhysicalConnectorType.Video_VideoDecoder) ovLink = i; } try { crossbar.Route(ovLink, ivLink); o = null; } catch { throw new Exception("Failed to get IAMCrossbar"); } } //add AVI Decompressor IBaseFilter pAVIDecompressor = (IBaseFilter)new AVIDec(); hr = m_FilterGraph.AddFilter(pAVIDecompressor, "AVI Decompressor"); DsError.ThrowExceptionForHR(hr); // IBaseFilter baseGrabFlt = (IBaseFilter) sampGrabber; ConfigureSampleGrabber(sampGrabber); // Add the frame grabber to the graph hr = m_FilterGraph.AddFilter( baseGrabFlt, "Ds.NET Grabber" ); DsError.ThrowExceptionForHR( hr ); SetConfigParms(capGraph, capFilter, media); hr = capGraph.RenderStream(PinCategory.Capture, MediaType.Video, capFilter, pAVIDecompressor, baseGrabFlt); if (hr < 0) { hr = capGraph.RenderStream(PinCategory.Capture, MediaType.Video, capFilter, null, baseGrabFlt); } DsError.ThrowExceptionForHR( hr ); SaveSizeInfo(sampGrabber); } finally { if (capFilter != null) { Marshal.ReleaseComObject(capFilter); capFilter = null; } if (sampGrabber != null) { Marshal.ReleaseComObject(sampGrabber); sampGrabber = null; } if (capGraph != null) { Marshal.ReleaseComObject(capGraph); capGraph = null; } } } private void SaveSizeInfo(ISampleGrabber sampGrabber) { int hr; // Get the media type from the SampleGrabber AMMediaType media = new AMMediaType(); hr = sampGrabber.GetConnectedMediaType( media ); DsError.ThrowExceptionForHR( hr ); if( (media.formatType != FormatType.VideoInfo) || (media.formatPtr == IntPtr.Zero) ) { throw new NotSupportedException( "Unknown Grabber Media Format" ); } // Grab the size info VideoInfoHeader videoInfoHeader = (VideoInfoHeader) Marshal.PtrToStructure( media.formatPtr, typeof(VideoInfoHeader) ); m_videoWidth = videoInfoHeader.BmiHeader.Width; m_videoHeight = videoInfoHeader.BmiHeader.Height; m_stride = m_videoWidth * (videoInfoHeader.BmiHeader.BitCount / 8); DsUtils.FreeAMMediaType(media); media = null; } private void ConfigureSampleGrabber(ISampleGrabber sampGrabber) { AMMediaType media; int hr; // Set the media type to Video/RBG24 media = new AMMediaType(); media.majorType = MediaType.Video; media.subType = MediaSubType.RGB24; media.formatType = FormatType.VideoInfo; hr = sampGrabber.SetMediaType( media ); DsError.ThrowExceptionForHR( hr ); DsUtils.FreeAMMediaType(media); media = null; // Configure the samplegrabber hr = sampGrabber.SetCallback( this, 1 ); DsError.ThrowExceptionForHR( hr ); } // Set the Framerate, and video size private void SetConfigParms(ICaptureGraphBuilder2 capGraph, IBaseFilter capFilter, AMMediaType media) { int hr; object o; // Find the stream config interface hr = capGraph.FindInterface( PinCategory.Capture, MediaType.Video, capFilter, typeof(IAMStreamConfig).GUID, out o ); IAMStreamConfig videoStreamConfig = o as IAMStreamConfig; if (videoStreamConfig == null) { throw new Exception("Failed to get IAMStreamConfig"); } // Set the new format hr = videoStreamConfig.SetFormat( media ); DsError.ThrowExceptionForHR( hr ); DsUtils.FreeAMMediaType(media); media = null; } /// Shut down capture private void CloseInterfaces() { int hr; try { if( m_mediaCtrl != null ) { // Stop the graph hr = m_mediaCtrl.Stop(); m_bRunning = false; } } catch (Exception ex) { Debug.WriteLine(ex); } if (m_FilterGraph != null) { Marshal.ReleaseComObject(m_FilterGraph); m_FilterGraph = null; } } /// sample callback, NOT USED. int ISampleGrabberCB.SampleCB( double SampleTime, IMediaSample pSample ) { if (!m_bGotOne) { // Set bGotOne to prevent further calls until we // request a new bitmap. m_bGotOne = true; IntPtr pBuffer; pSample.GetPointer(out pBuffer); int iBufferLen = pSample.GetSize(); if (pSample.GetSize() > m_stride * m_videoHeight) { throw new Exception("Buffer is wrong size"); } CopyMemory(m_handle, pBuffer, m_stride * m_videoHeight); // Picture is ready. m_PictureReady.Set(); } Marshal.ReleaseComObject(pSample); return 0; } /// buffer callback, COULD BE FROM FOREIGN THREAD. int ISampleGrabberCB.BufferCB( double SampleTime, IntPtr pBuffer, int BufferLen ) { if (!m_bGotOne) { // The buffer should be long enought if(BufferLen <= m_stride * m_videoHeight) { // Copy the frame to the buffer CopyMemory(m_handle, pBuffer, m_stride * m_videoHeight); } else { throw new Exception("Buffer is wrong size"); } // Set bGotOne to prevent further calls until we // request a new bitmap. m_bGotOne = true; // Picture is ready. m_PictureReady.Set(); } else { m_Dropped++; } return 0; } } }