1

I've been working on an application that uses Directshow.NET, and decided to start tinkering with GraphEd.exe to figure out the exact route I need to take.

GraphEd Graph

The simplest version of what I want is in the image above (where "test.mpeg" is a "File writer" filter).

Sure enough, what looks easy in GraphEd is not as simple as coding it. After a few seconds, I realized that directshow is something I just don't understand. Here is my C# code attempt:

int hr;

IBaseFilter cameraStream = null;
IBaseFilter mpegEncoder = null;
IBaseFilter fileWriter = null;
ICaptureGraphBuilder2 capGraph = null;

filterGraph = (IFilterGraph2)new FilterGraph();
try
{
    capGraph = (ICaptureGraphBuilder2)new CaptureGraphBuilder2();
    hr = capGraph.SetFiltergraph(filterGraph);
    Marshal.ThrowExceptionForHR(hr);

    hr = filterGraph.AddSourceFilterForMoniker(dev.Mon, null, dev.Name, out cameraStream);
    Marshal.ThrowExceptionForHR(hr);

    IPin cameraData = null;
    hr = capGraph.FindPin(cameraStream, PinDirection.Output, PinCategory.Capture, MediaType.Video, true, 0, out cameraData );
    //hr = cameraStream.FindPin("Capture", out cameraData);
    Marshal.ThrowExceptionForHR(hr);

    mpegEncoder = (IBaseFilter)new MJPGEnc();
    hr = filterGraph.AddFilter(mpegEncoder, "mpeg encoder");
    //hr = filterGraph.FindFilterByName("Microsoft MPEG-2 Video Encoder", out mpegEncoder);
    Marshal.ThrowExceptionForHR(hr);

    IPin mpegInput = null;
    //hr = mpegEncoder.FindPin("Input0", out mpegInput);
    hr = capGraph.FindPin(mpegEncoder, PinDirection.Input, null, null, true, 0, out mpegInput);
    Marshal.ThrowExceptionForHR(hr);

    filterGraph.Connect(cameraData, mpegInput);

    IPin mpegOutput = null;
    //hr = mpegEncoder.FindPin("Output", out mpegOutput);
    hr = capGraph.FindPin(mpegEncoder, PinDirection.Output, null, null, true, 0, out mpegOutput);
    Marshal.ThrowExceptionForHR(hr);

    //hr = filterGraph.FindFilterByName("File writer", out fileWriter);
    fileWriter = (IBaseFilter)new FileWriter();
    IFileSinkFilter test = fileWriter as IFileSinkFilter2;
    AMMediaType mtype = new AMMediaType();
    mtype.majorType = MediaType.Video;
    mtype.subType = MediaSubType.RGB24;
    mtype.formatPtr = IntPtr.Zero;
    test.SetFileName(videoPath, mtype );

    IPin fwriterIn = null;
    //hr = fileWriter.FindPin("in", out fwriterIn);
    hr = capGraph.FindPin(fileWriter, PinDirection.Input, null, null, true, 0, out fwriterIn);
    Marshal.ThrowExceptionForHR(hr);

    filterGraph.Connect(mpegOutput, fwriterIn);

    hr = capGraph.RenderStream(null, null, cameraStream, null, fileWriter); // *** Breaks on this line! ***
    Marshal.ThrowExceptionForHR(hr);

    mediaControl = filterGraph as IMediaControl;

}
finally
{
    if (cameraStream != null)
    {
        Marshal.ReleaseComObject(cameraStream);
        cameraStream = null;
    }
    if (mpegEncoder != null)
    {
        Marshal.ReleaseComObject(mpegEncoder);
        mpegEncoder = null;
    }
    if (fileWriter != null)
    {
        Marshal.ReleaseComObject(fileWriter);
        fileWriter = null;
    }
    if (capGraph != null)
    {
        Marshal.ReleaseComObject(capGraph);
        capGraph = null;
    }
}

After writing and looking at how ridiculous my code was (as far as programming logic is concerned), I realized I needed some help.

Could anyone please direct me to some sort of structured directshow tutorial that explains things to me as if I'm a huge newbie, or possibly link me to some sort of C# library that handles webcam to file recording? I'm not generally a win32 programmer, so anything that eases the pain should be good :)

Also, I do have camera device enumeration code that seems to work:

private void rescan_camera()
{
    comboBox1.Items.Clear();

    DsDevice[] cameraDevices = DsDevice.GetDevicesOfCat(FilterCategory.VideoInputDevice);

    for(int i=0; i < cameraDevices.Length; i++) {
        comboBox1.Items.Add(cameraDevices[i].Name);
    }
    if (comboBox1.Items.Count > 0) comboBox1.SelectedIndex = 0;
}

I can get the moniker data from a DsDevice, which I know is a key part to telling the directshow filter graph where I want to record from, but that's about the extent of my knowledge.

Edit: Updated code, shows exact place of line break.

OzBarry
  • 1,098
  • 1
  • 17
  • 44
  • Your code is not that bad, what kind of help you need with it? You should rather not use pin names, and instead look pins up as "first unconnected output pin" etc. – Roman R. Oct 17 '12 at 21:39
  • Well, for one, I have no idea how to setup the File writer to direct itself to a file. Also, I have no idea how to look pins up as first unconnected output pin. – OzBarry Oct 17 '12 at 22:48
  • 1
    `1` Your last line has `IFileSinkFilter test` and `test.SetFileName` will set up writer, see http://stackoverflow.com/questions/6416957/question-about-setting-up-file-writer-in-c-sharp – Roman R. Oct 17 '12 at 22:52
  • `2` There are helpers on the DirectShow.NET library, e.g. note use of `ByConnectionStatus` here `pin =DsFindPin.ByConnectionStatus(Source,DirectShowLib.PinConnectedStatus.Un­­­connected, 0);` http://www.tech-archive.net/Archive/Development/microsoft.public.win32.programmer.directx.video/2007-07/msg00390.html – Roman R. Oct 17 '12 at 22:56
  • I updated my code; when I run in debug mode, it breaks on the capGraph.RenderStream line - any advice on that? Also, if you answer it rather than add a comment, I can upvote you/select your answer, you've been extremely helpful and I greatly appreciate it! – OzBarry Oct 18 '12 at 15:16
  • 1
    Breaks with `HRESULT` error, or exception? Not quite clear. Basically if you have it working in GraphEdit, you can do it in code as well. – Roman R. Oct 18 '12 at 18:42
  • "An unhandled exception of type 'System.Runtime.InteropServices.COMException' occurred in mscorlib.dll Additional information: Error HRESULT E_FAIL has been returned from a call to a COM component." Technically, a HRESULT error. I don't really understand the idea behind capGraph.RenderStream(). I tried playing around with it, and decided this might work (but didn't): ```hr = capGraph.RenderStream(null, null, cameraStream, cameraEncoder, fileWriter);```. – OzBarry Oct 18 '12 at 18:49
  • 1
    I walked throguh your code once again... you have all pins connected, you don't need `RenderStream` at all. The whole purpose of it is to be a helper "smartly" connecting fitlers and pins. You however already did all that job. Comment it out and `IMediaControl.Run` the graph. – Roman R. Oct 18 '12 at 18:52
  • When I comment out RenderStream, the program does not throw any exceptions, however, the file I specify for video output does not get created. – OzBarry Oct 18 '12 at 19:02
  • I don't see you are doing `Run`. You will also need `WaitForCompletion` to wait until processing is finished (or, `Thread.Sleep(10000)` to record 10 seconds if you are capturing from live source, as it seems to be the case). – Roman R. Oct 18 '12 at 19:04
  • This is part of one class from a much larger program. [Here is the full class](https://gist.github.com/3914141), and the larger part of the application calls ```MyCapture recording(device, "c:\Users\\Roaming\Recording\\camera.mjpeg"); recording.StartRecording(); /* .. waits for user to click stop recording button .. */ recording.StopRecording();``` – OzBarry Oct 18 '12 at 19:10
  • So, I ended up changing the file output: rather than setting the media type for setFileName, I changed it to null, and now everything is fine :) – OzBarry Oct 19 '12 at 19:22

1 Answers1

0

To repeat a comment: So, I ended up changing the file output: rather than setting the media type for setFileName, I changed it to null, and now everything is fine :)

OzBarry
  • 1,098
  • 1
  • 17
  • 44