Если вы хотите сделать это в .NET, необходимо выполнить следующие шаги:
Используйте общий фильтр исходного кода DirectShow.NET (GSSF.AX) из каталога Misc / GSSF в образце пакета . Исходный фильтр всегда является COM-модулем, поэтому вам также необходимо зарегистрировать его, используя «RegSvr32 GSSF.ax».
Реализация поставщика растровых изображений в .NET
Настройте график и подключите вывод из GSSF к реализации поставщика растровых изображений.
Молись.
Я использую следующее в проекте и сделал его многоразовым для использования в будущем.
Код (не самый лучший и не законченный, но рабочий старт) (для этого требуется IVideoSource, который приведен ниже):
public class VideoSourceToVideo : IDisposable
{
object locker = new object();
public event EventHandler<EventArgs> Starting;
public event EventHandler<EventArgs> Stopping;
public event EventHandler<EventArgs> Completed;
/// <summary> graph builder interface. </summary>
private DirectShowLib.ICaptureGraphBuilder2 captureGraphBuilder = null;
DirectShowLib.IMediaControl mediaCtrl = null;
IMediaEvent mediaEvent = null;
bool stopMediaEventLoop = false;
Thread mediaEventThread;
/// <summary> Dimensions of the image, calculated once in constructor. </summary>
private readonly VideoInfoHeader videoInfoHeader;
IVideoSource source;
public VideoSourceToVideo(IVideoSource source, string destFilename, string encoderName)
{
try
{
this.source = source;
// Set up the capture graph
SetupGraph(destFilename, encoderName);
}
catch
{
Dispose();
throw;
}
}
/// <summary> release everything. </summary>
public void Dispose()
{
StopMediaEventLoop();
CloseInterfaces();
}
/// <summary> build the capture graph for grabber. </summary>
private void SetupGraph(string destFilename, string encoderName)
{
int hr;
// Get the graphbuilder object
captureGraphBuilder = new DirectShowLib.CaptureGraphBuilder2() as DirectShowLib.ICaptureGraphBuilder2;
IFilterGraph2 filterGraph = new DirectShowLib.FilterGraph() as DirectShowLib.IFilterGraph2;
mediaCtrl = filterGraph as DirectShowLib.IMediaControl;
IMediaFilter mediaFilt = filterGraph as IMediaFilter;
mediaEvent = filterGraph as IMediaEvent;
captureGraphBuilder.SetFiltergraph(filterGraph);
IBaseFilter aviMux;
IFileSinkFilter fileSink = null;
hr = captureGraphBuilder.SetOutputFileName(MediaSubType.Avi, destFilename, out aviMux, out fileSink);
DsError.ThrowExceptionForHR(hr);
DirectShowLib.IBaseFilter compressor = DirectShowUtils.GetVideoCompressor(encoderName);
if (compressor == null)
{
throw new InvalidCodecException(encoderName);
}
hr = filterGraph.AddFilter(compressor, "compressor");
DsError.ThrowExceptionForHR(hr);
// Our data source
IBaseFilter source = (IBaseFilter)new GenericSampleSourceFilter();
// Get the pin from the filter so we can configure it
IPin ipin = DsFindPin.ByDirection(source, PinDirection.Output, 0);
try
{
// Configure the pin using the provided BitmapInfo
ConfigurePusher((IGenericSampleConfig)ipin);
}
finally
{
Marshal.ReleaseComObject(ipin);
}
// Add the filter to the graph
hr = filterGraph.AddFilter(source, "GenericSampleSourceFilter");
Marshal.ThrowExceptionForHR(hr);
hr = filterGraph.AddFilter(source, "source");
DsError.ThrowExceptionForHR(hr);
hr = captureGraphBuilder.RenderStream(null, null, source, compressor, aviMux);
DsError.ThrowExceptionForHR(hr);
IMediaPosition mediaPos = filterGraph as IMediaPosition;
hr = mediaCtrl.Run();
DsError.ThrowExceptionForHR(hr);
}
private void ConfigurePusher(IGenericSampleConfig ips)
{
int hr;
source.SetMediaType(ips);
// Specify the callback routine to call with each sample
hr = ips.SetBitmapCB(source);
DsError.ThrowExceptionForHR(hr);
}
private void StartMediaEventLoop()
{
mediaEventThread = new Thread(MediaEventLoop)
{
Name = "Offscreen Vid Player Medialoop",
IsBackground = false
};
mediaEventThread.Start();
}
private void StopMediaEventLoop()
{
stopMediaEventLoop = true;
if (mediaEventThread != null)
{
mediaEventThread.Join();
}
}
public void MediaEventLoop()
{
MediaEventLoop(x => PercentageCompleted = x);
}
public double PercentageCompleted
{
get;
private set;
}
// FIXME this needs some work, to be completely in-tune with needs.
public void MediaEventLoop(Action<double> UpdateProgress)
{
mediaEvent.CancelDefaultHandling(EventCode.StateChange);
//mediaEvent.CancelDefaultHandling(EventCode.Starvation);
while (stopMediaEventLoop == false)
{
try
{
EventCode ev;
IntPtr p1, p2;
if (mediaEvent.GetEvent(out ev, out p1, out p2, 0) == 0)
{
switch (ev)
{
case EventCode.Complete:
Stopping.Fire(this, null);
if (UpdateProgress != null)
{
UpdateProgress(source.PercentageCompleted);
}
return;
case EventCode.StateChange:
FilterState state = (FilterState)p1.ToInt32();
if (state == FilterState.Stopped || state == FilterState.Paused)
{
Stopping.Fire(this, null);
}
else if (state == FilterState.Running)
{
Starting.Fire(this, null);
}
break;
// FIXME add abort and stuff, and propagate this.
}
// Trace.WriteLine(ev.ToString() + " " + p1.ToInt32());
mediaEvent.FreeEventParams(ev, p1, p2);
}
else
{
if (UpdateProgress != null)
{
UpdateProgress(source.PercentageCompleted);
}
// FiXME use AutoResetEvent
Thread.Sleep(100);
}
}
catch (Exception e)
{
Trace.WriteLine("MediaEventLoop: " + e);
}
}
}
/// <summary> Shut down capture </summary>
private void CloseInterfaces()
{
int hr;
try
{
if (mediaCtrl != null)
{
// Stop the graph
hr = mediaCtrl.Stop();
mediaCtrl = null;
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
if (captureGraphBuilder != null)
{
Marshal.ReleaseComObject(captureGraphBuilder);
captureGraphBuilder = null;
}
GC.Collect();
}
public void Start()
{
StartMediaEventLoop();
}
}
IVideoSource:
public interface IVideoSource : IGenericSampleCB
{
double PercentageCompleted { get; }
int GetImage(int iFrameNumber, IntPtr ip, int iSize, out int iRead);
void SetMediaType(global::IPerform.Video.Conversion.Interops.IGenericSampleConfig psc);
int SetTimeStamps(global::DirectShowLib.IMediaSample pSample, int iFrameNumber);
}
ImageVideoSource (в основном из примеров DirectShow.NET):
// A generic class to support easily changing between my different sources of data.
// Note: You DON'T have to use this class, or anything like it. The key is the SampleCallback
// routine. How/where you get your bitmaps is ENTIRELY up to you. Having SampleCallback call
// members of this class was just the approach I used to isolate the data handling.
public abstract class ImageVideoSource : IDisposable, IVideoSource
{
#region Definitions
/// <summary>
/// 100 ns - used by a number of DS methods
/// </summary>
private const long UNIT = 10000000;
#endregion
/// <summary>
/// Number of callbacks that returned a positive result
/// </summary>
private int m_iFrameNumber = 0;
virtual public void Dispose()
{
}
public abstract double PercentageCompleted { get; protected set; }
abstract public void SetMediaType(IGenericSampleConfig psc);
abstract public int GetImage(int iFrameNumber, IntPtr ip, int iSize, out int iRead);
virtual public int SetTimeStamps(IMediaSample pSample, int iFrameNumber)
{
return 0;
}
/// <summary>
/// Called by the GenericSampleSourceFilter. This routine populates the MediaSample.
/// </summary>
/// <param name="pSample">Pointer to a sample</param>
/// <returns>0 = success, 1 = end of stream, negative values for errors</returns>
virtual public int SampleCallback(IMediaSample pSample)
{
int hr;
IntPtr pData;
try
{
// Get the buffer into which we will copy the data
hr = pSample.GetPointer(out pData);
if (hr >= 0)
{
// Set TRUE on every sample for uncompressed frames
hr = pSample.SetSyncPoint(true);
if (hr >= 0)
{
// Find out the amount of space in the buffer
int cbData = pSample.GetSize();
hr = SetTimeStamps(pSample, m_iFrameNumber);
if (hr >= 0)
{
int iRead;
// Get copy the data into the sample
hr = GetImage(m_iFrameNumber, pData, cbData, out iRead);
if (hr == 0) // 1 == End of stream
{
pSample.SetActualDataLength(iRead);
// increment the frame number for next time
m_iFrameNumber++;
}
}
}
}
}
finally
{
// Release our pointer the the media sample. THIS IS ESSENTIAL! If
// you don't do this, the graph will stop after about 2 samples.
Marshal.ReleaseComObject(pSample);
}
return hr;
}
}
RawVideoSource (пример конкретного генератора управляемого источника для конвейера DirectShow):
internal class RawVideoSource : ImageVideoSource
{
private byte[] buffer;
private byte[] demosaicBuffer;
private RawVideoReader reader;
public override double PercentageCompleted
{
get;
protected set;
}
public RawVideoSource(string sourceFile)
{
reader = new RawVideoReader(sourceFile);
}
override public void SetMediaType(IGenericSampleConfig psc)
{
BitmapInfoHeader bmi = new BitmapInfoHeader();
bmi.Size = Marshal.SizeOf(typeof(BitmapInfoHeader));
bmi.Width = reader.Header.VideoSize.Width;
bmi.Height = reader.Header.VideoSize.Height;
bmi.Planes = 1;
bmi.BitCount = 24;
bmi.Compression = 0;
bmi.ImageSize = (bmi.BitCount / 8) * bmi.Width * bmi.Height;
bmi.XPelsPerMeter = 0;
bmi.YPelsPerMeter = 0;
bmi.ClrUsed = 0;
bmi.ClrImportant = 0;
int hr = psc.SetMediaTypeFromBitmap(bmi, 0);
buffer = new byte[reader.Header.FrameSize];
demosaicBuffer = new byte[reader.Header.FrameSize * 3];
DsError.ThrowExceptionForHR(hr);
}
long startFrameTime;
long endFrameTime;
unsafe override public int GetImage(int iFrameNumber, IntPtr ip, int iSize, out int iRead)
{
int hr = 0;
if (iFrameNumber < reader.Header.NumberOfFrames)
{
reader.ReadFrame(buffer, iFrameNumber, out startFrameTime, out endFrameTime);
Demosaic.DemosaicGBGR24Bilinear(buffer, demosaicBuffer, reader.Header.VideoSize);
Marshal.Copy(demosaicBuffer, 0, ip, reader.Header.FrameSize * 3);
PercentageCompleted = ((double)iFrameNumber / reader.Header.NumberOfFrames) * 100.0;
}
else
{
PercentageCompleted = 100;
hr = 1; // End of stream
}
iRead = iSize;
return hr;
}
override public int SetTimeStamps(IMediaSample pSample, int iFrameNumber)
{
reader.ReadTimeStamps(iFrameNumber, out startFrameTime, out endFrameTime);
DsLong rtStart = new DsLong(startFrameTime);
DsLong rtStop = new DsLong(endFrameTime);
int hr = pSample.SetTime(rtStart, rtStop);
return hr;
}
}
И взаимодействия с GSSF.AX COM:
namespace IPerform.Video.Conversion.Interops
{
[ComImport, Guid("6F7BCF72-D0C2-4449-BE0E-B12F580D056D")]
public class GenericSampleSourceFilter
{
}
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
Guid("33B9EE57-1067-45fa-B12D-C37517F09FC0")]
public interface IGenericSampleCB
{
[PreserveSig]
int SampleCallback(IMediaSample pSample);
}
[Guid("CE50FFF9-1BA8-4788-8131-BDE7D4FFC27F"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IGenericSampleConfig
{
[PreserveSig]
int SetMediaTypeFromBitmap(BitmapInfoHeader bmi, long lFPS);
[PreserveSig]
int SetMediaType([MarshalAs(UnmanagedType.LPStruct)] AMMediaType amt);
[PreserveSig]
int SetMediaTypeEx([MarshalAs(UnmanagedType.LPStruct)] AMMediaType amt, int lBufferSize);
[PreserveSig]
int SetBitmapCB(IGenericSampleCB pfn);
}
}
Удачи, попытайтесь заставить это работать, используя это. Или прокомментируйте дополнительные вопросы, чтобы мы могли решить другие вопросы.