Я использую библиотеку DirectShow. NET в C#, чтобы написать программу, которая захватывает кадры с видеокарты ATI AllInWonder для преобразования старых лент VHS в цифровые. Видеокарта старая и использует порт AGP. У меня только два компьютера с этим портом, и процессор слишком медленный, чтобы захватить видеосигнал и закодировать его в видеофайл. Я взял пример DxLo go из примеров DirectShow. NET и изменил его, чтобы просто выгружать необработанные данные кадра в файл, который будет проанализирован позже.
Мой граф графа состоит из подключенного исходного фильтра в смарт-тройку, которую captureGraphBuilder добавил автоматически. Далее идет sampleGrabber, подключенный к нулевому фильтру. Обратный вызов sampleGrabber просто сбрасывает данные кадра RGB24 в выходной файл.
Я могу записать видеосигнал и преобразовать его в файл AVI. Однако я получаю только частоту около 15 кадров в секунду, хотя у меня установлен фильтр источника на 30 кадров в секунду. Я знаю, что моя карта поддерживает 30 кадров в секунду, так как в исходном программном обеспечении есть такая возможность. Мой процессор загружен примерно на 60%, поэтому я не думаю, что это вызывает проблему.
Я хотел бы использовать интерфейс IAMDroppedFrames в своем фильтре захвата, чтобы узнать, пропускаю ли я кадры или проблема в в другом месте. Я могу получить интерфейс отброшенных кадров и данные из него в моей функции построения графиков, однако, поскольку ссылки на фильтры освобождаются после построения графика, я теряю доступ к интерфейсу отброшенных кадров.
I пытались использовать интерфейсы EnumFilters и EnumPins с разочаровывающими результатами. Я могу успешно получить объекты IBaseFilter для всех моих фильтров, и я могу получить имя моего исходного фильтра «Веб-камера Logitech» (это то, что я использую для тестирования своего кода, чтобы я мог написать код на своей основной машине вместо визуального express 2010 на Windows XP). Когда я запускаю метод EnumPins, а затем вызываю pins.Next в результирующем интерфейсе, я получаю сообщение об ошибке «System.InvalidCastException: 'Невозможно преобразовать объект типа' System .__ ComObject 'в тип' DirectShowLib.IPin '.'« Для некоторых причина, по которой метод pins.Next возвращает ComObject вместо интерфейса IPin. Если я использую тот же самый код в фильтре smart-tee, я могу получить объекты IPin и получить доступ к интерфейсам на входных и выходных контактах.
Итак, в основном я ищу способ получить IAMDroppedFrames считать, пока выполняется мой захват, и единственный известный мне способ сделать это - использовать методы EnumFilters и EnumPins. Однако один фильтр, из которого мне нужно получить контакты, я не могу, но я могу получить доступ к контактам любого из других фильтров.
Как я могу получить текущий счет сброшенных кадры, отображаемые в моей форме во время захвата?
Код для получения ссылок IPin:
IEnumFilters filters = null;
m_FilterGraph.EnumFilters(out filters);
IntPtr numFilters = new IntPtr();
IBaseFilter[] filtersFound = new IBaseFilter[1];
filters.Skip(3);
filters.Next(1, filtersFound, numFilters);
FilterInfo info;
filtersFound[0].QueryFilterInfo(out info);
System.Diagnostics.Debug.WriteLine(info.achName);
IEnumPins pins = null;
filtersFound[0].EnumPins(out pins);
IntPtr numPins = new IntPtr();
IPin[] pinsFound = new IPin[1];
pins.Next(1, pinsFound, numPins);
Полный код:
/****************************************************************************
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.Runtime.InteropServices;
using System.Diagnostics;
using System.IO;
using DirectShowLib;
using System.ComponentModel;
namespace DxLogo
{
/// <summary> Summary description for MainForm. </summary>
internal class Capture : ISampleGrabberCB, IDisposable
{
//static long milliseconds = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
//static bool written = false;
#region Member variables
/// <summary> graph builder interface. </summary>
private IFilterGraph2 m_FilterGraph = null;
IMediaControl m_mediaCtrl = null;
/// <summary> Set by async routine when it captures an image </summary>
private bool m_bRunning = false;
/// <summary> Dimensions of the image, calculated once in constructor. </summary>
private int m_videoWidth;
private int m_videoHeight;
private int m_stride;
FileStream file = File.Open("C:\\Users\\Admin\\Desktop\\CapturedFrames\\output", FileMode.OpenOrCreate, FileAccess.Write);
BitmapData m_bmdLogo = null;
Bitmap m_Bitmap = null;
#if DEBUG
// Allow you to "Connect to remote graph" from GraphEdit
DsROTEntry m_rot = null;
#endif
#endregion
#region API
[DllImport("Kernel32.dll", EntryPoint="RtlMoveMemory")]
private static extern void CopyMemory(IntPtr Destination, IntPtr Source, [MarshalAs(UnmanagedType.U4)] uint Length);
#endregion
/// zero based device index, and some device parms, plus the file name to save to
public Capture(int iDeviceNum, int iFrameRate, int iWidth, int iHeight, string FileName)
{
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], iFrameRate, iWidth, iHeight, FileName);
}
catch
{
Dispose();
throw;
}
}
/// <summary> release everything. </summary>
public void Dispose()
{
CloseInterfaces();
if (m_Bitmap != null)
{
m_Bitmap.UnlockBits(m_bmdLogo);
m_Bitmap = null;
m_bmdLogo = null;
}
}
// Destructor
~Capture()
{
CloseInterfaces();
}
/// <summary> capture the next image </summary>
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;
}
}
/// <summary> build the capture graph for grabber. </summary>
private void SetupGraph(DsDevice dev, int iFrameRate, int iWidth, int iHeight, string FileName)
{
int hr;
ISampleGrabber sampGrabber = null;
IBaseFilter baseGrabFlt = null;
IBaseFilter capFilter = null;
//IBaseFilter muxFilter = null;
IBaseFilter nullFilter = null;
IFileSinkFilter fileWriterFilter = null;
ICaptureGraphBuilder2 capGraph = null;
// Get the graphbuilder object
m_FilterGraph = new FilterGraph() as IFilterGraph2;
m_mediaCtrl = m_FilterGraph as IMediaControl;
#if DEBUG
m_rot = new DsROTEntry(m_FilterGraph);
#endif
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, dev.Name, out capFilter);
DsError.ThrowExceptionForHR( hr );
baseGrabFlt = (IBaseFilter) sampGrabber;
ConfigureSampleGrabber(sampGrabber);
// Add the frame grabber to the graph
hr = m_FilterGraph.AddFilter( baseGrabFlt, "Ds.NET Grabber" );
DsError.ThrowExceptionForHR( hr );
Type type = Type.GetTypeFromCLSID(new Guid("C1F400A4-3F08-11d3-9F0B-006008039E37"));
nullFilter = (IBaseFilter)Activator.CreateInstance(type);
hr = m_FilterGraph.AddFilter(nullFilter, "Null Filter");
DsError.ThrowExceptionForHR(hr);
// If any of the default config items are set
if (iFrameRate + iHeight + iWidth > 0)
{
SetConfigParms(capGraph, capFilter, iFrameRate, iWidth, iHeight);
}
/*
// Create a filter for the output avi file
hr = capGraph.SetOutputFileName(MediaSubType.Avi, FileName, out muxFilter, out fileWriterFilter);
DsError.ThrowExceptionForHR( hr );
*/
// Connect everything together
hr = capGraph.RenderStream( PinCategory.Capture, MediaType.Video, capFilter, baseGrabFlt, nullFilter);
DsError.ThrowExceptionForHR( hr );
object droppedFramesObject;
capGraph.FindInterface(FindDirection.DownstreamOnly, null, capFilter, typeof(IAMDroppedFrames).GUID, out droppedFramesObject);
IAMDroppedFrames droppedFrames = droppedFramesObject as IAMDroppedFrames;
int numDropped, numNotDropped;
droppedFrames.GetNumDropped(out numDropped);
droppedFrames.GetNumNotDropped(out numNotDropped);
System.Diagnostics.Debug.WriteLine("Dropped Frames");
System.Diagnostics.Debug.WriteLine(numDropped);
System.Diagnostics.Debug.WriteLine(numNotDropped);
// Now that sizes are fixed, store the sizes
SaveSizeInfo(sampGrabber);
}
finally
{
if (fileWriterFilter != null)
{
Marshal.ReleaseComObject(fileWriterFilter);
fileWriterFilter = null;
}
if (nullFilter != null)
{
Marshal.ReleaseComObject(nullFilter);
nullFilter = null;
}
if (capFilter != null)
{
Marshal.ReleaseComObject(capFilter);
capFilter = null;
}
if (sampGrabber != null)
{
Marshal.ReleaseComObject(sampGrabber);
sampGrabber = null;
}
}
}
/// <summary> Read and store the properties </summary>
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;
}
/// <summary> Set the options on the sample grabber </summary>
private void ConfigureSampleGrabber(ISampleGrabber sampGrabber)
{
int hr;
AMMediaType media = new AMMediaType();
// Set the media type to Video/RBG24
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 callback
hr = sampGrabber.SetCallback( this, 1 );
DsError.ThrowExceptionForHR( hr );
}
// Set the Framerate, and video size
private void SetConfigParms(ICaptureGraphBuilder2 capGraph, IBaseFilter capFilter, int iFrameRate, int iWidth, int iHeight)
{
int hr;
object o;
AMMediaType media;
IAMStreamConfig videoStreamConfig;
IAMVideoControl videoControl = capFilter as IAMVideoControl;
// Find the stream config interface
hr = capGraph.FindInterface(
PinCategory.Capture, MediaType.Video, capFilter, typeof(IAMStreamConfig).GUID, out o );
videoStreamConfig = o as IAMStreamConfig;
try
{
if (videoStreamConfig == null)
{
throw new Exception("Failed to get IAMStreamConfig");
}
hr = videoStreamConfig.GetFormat(out media);
DsError.ThrowExceptionForHR( hr );
// copy out the videoinfoheader
VideoInfoHeader v = new VideoInfoHeader();
Marshal.PtrToStructure( media.formatPtr, v );
// if overriding the framerate, set the frame rate
if (iFrameRate > 0)
{
v.AvgTimePerFrame = 10000000 / iFrameRate;
}
// if overriding the width, set the width
if (iWidth > 0)
{
v.BmiHeader.Width = iWidth;
}
// if overriding the Height, set the Height
if (iHeight > 0)
{
v.BmiHeader.Height = iHeight;
}
// Copy the media structure back
Marshal.StructureToPtr( v, media.formatPtr, false );
// Set the new format
hr = videoStreamConfig.SetFormat( media );
DsError.ThrowExceptionForHR( hr );
DsUtils.FreeAMMediaType(media);
media = null;
// Fix upsidedown video
if (videoControl != null)
{
VideoControlFlags pCapsFlags;
IPin pPin = DsFindPin.ByCategory(capFilter, PinCategory.Capture, 0);
hr = videoControl.GetCaps(pPin, out pCapsFlags);
DsError.ThrowExceptionForHR( hr );
if ((pCapsFlags & VideoControlFlags.FlipVertical) > 0)
{
hr = videoControl.GetMode(pPin, out pCapsFlags);
DsError.ThrowExceptionForHR( hr );
hr = videoControl.SetMode(pPin, 0);
}
}
}
finally
{
Marshal.ReleaseComObject(videoStreamConfig);
}
}
/// <summary> Shut down capture </summary>
private void CloseInterfaces()
{
int hr;
try
{
if( m_mediaCtrl != null )
{
// Stop the graph
hr = m_mediaCtrl.Stop();
m_mediaCtrl = null;
m_bRunning = false;
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
#if DEBUG
if (m_rot != null)
{
m_rot.Dispose();
}
#endif
if (m_FilterGraph != null)
{
Marshal.ReleaseComObject(m_FilterGraph);
m_FilterGraph = null;
}
GC.Collect();
}
/// <summary> sample callback, NOT USED. </summary>
int ISampleGrabberCB.SampleCB( double SampleTime, IMediaSample pSample )
{
Marshal.ReleaseComObject(pSample);
return 0;
}
/// <summary> buffer callback, COULD BE FROM FOREIGN THREAD. </summary>
int ISampleGrabberCB.BufferCB( double SampleTime, IntPtr pBuffer, int BufferLen )
{
//System.Diagnostics.Debug.WriteLine(m_videoWidth);
//System.Diagnostics.Debug.WriteLine(m_videoHeight);
Byte[] byteArray = new byte[BufferLen];
Marshal.Copy(pBuffer, byteArray, 0, BufferLen);
file.Write(byteArray, 0, BufferLen);
IEnumFilters filters = null;
m_FilterGraph.EnumFilters(out filters);
IntPtr numFilters = new IntPtr();
IBaseFilter[] filtersFound = new IBaseFilter[1];
filters.Skip(3);
filters.Next(1, filtersFound, numFilters);
FilterInfo info;
filtersFound[0].QueryFilterInfo(out info);
System.Diagnostics.Debug.WriteLine(info.achName);
IEnumPins pins = null;
filtersFound[0].EnumPins(out pins);
IntPtr numPins = new IntPtr();
IPin[] pinsFound = new IPin[1];
pins.Next(1, pinsFound, numPins);
return 0;
}
}
}