-2

I've seen a couple of solutions for this but they don't seem to use eventargs. So, in Windows Forms, a working code:

private void StartButton_Click(object sender, EventArgs e)
    {
        FinalFrame = new VideoCaptureDevice(CaptureDevice[cboDevices.SelectedIndex].MonikerString);
        FinalFrame.NewFrame += FinalFrame_NewFrame;
        FinalFrame.Start();
    }

        private void FinalFrame_NewFrame(object sender, NewFrameEventArgs eventArgs)
    {
        pboLive.Image = (Bitmap)eventArgs.Frame.Clone();
    }

But in WPF:

private void StartButton_Click(object sender, RoutedEventArgs e){ int capturedeviceindex = cboDevices.SelectedIndex; FilterInfo cd = CaptureDevice[cboDevices.SelectedIndex]; string cdms = cd.MonikerString; FinalFrame = new VideoCaptureDevice(cdms); FinalFrame.NewFrame += FinalFrame_NewFrame; FinalFrame.Start(); }



private void FinalFrame_NewFrame(object sender, NewFrameEventArgs eventArgs){ pboLive.Source = ImageSourceForBitmap((Bitmap)eventArgs.Frame.Clone());}

not working due to

"The calling thread cannot access this object because a different thread owns it."

I'm almost 100% sure I am somehow supposed to use

    this.Dispatcher.Invoke(() => {
...// your code here.});

but how does that go with eventargs?

OK, so the current answer I marked works once for some reason, then it requires restarting the program.

Is there some need to explicitly destroy some threads that are left alive after the WPF window is closed (while the Word application from where the program is called is still alive)?

On subsequent times it gets in the code of:

    private void FinalFrame_NewFrame(object sender, NewFrameEventArgs eventArgs) { var imageSource = ImageSourceForBitmap(eventArgs.Frame); imageSource.Freeze(); pboLive.Dispatcher.Invoke(() => pboLive.Source = imageSource); }

to that last row of "Dispatcher.Invoke..." and seems to be in infinite loop without displaying anything on pboLive

edit. Sorry for the code formatting. Line changes doesn't seem to be allowed here, don't ask me why.

  • _"I'm almost 100% sure I am somehow supposed to use..."_ -- yes, that's likely to be required. _"how does that go with eventargs?"_ -- meaning, what? If you're asking how you can use the `eventArgs` parameter in your anonymous method, you just do. It works fine. C# will "capture" the variable into a persistent object that is used for the anonymous method. – Peter Duniho Aug 22 '17 at 05:08
  • While it's true what Peter says, it's not necessary to pass the NewFrameEventArgs to a Dispatcher Action. Just do as much work as possible on the background thread by doing the converson from Bitmap to ImageSource there. Then freeze the ImageSource to make it accessible in the UI thread. – Clemens Aug 22 '17 at 05:20
  • To be clear: I agree 100% with @Clemens. My comment was simply intended to address the specific question regarding use of the `eventArgs` parameter. It's not meant to be any sort of tacit recommendation to actually _do_ that. – Peter Duniho Aug 22 '17 at 07:05
  • @Arto You've understood that it might be more efficient to call ImageSourceForBitmap in the background thread, before calling Invoke? – Clemens Aug 22 '17 at 07:50
  • @Clemens No, I don't have a slightest clue what invoke is even supposed to be. Seems to work for me anyway. – Arto Kilponen Os Kilponen Aug 22 '17 at 10:12
  • If you compare the solutions given by Colin and by me, there is a significant difference. In Colin's approach, the ImageSourceForBitmap runs in the UI thread (because it's inside the method passed to Invoke), while in my approach it runs in the background thread that calls FinalFrame_NewFrame. As you should usually try to put as less load as possible on the UI thread, and you already have the background thread available, my approach is more efficient. You should give it a try, because it improves your code. – Clemens Aug 22 '17 at 10:22

3 Answers3

0

The FinalFrame_NewFrame event is raised on a worker thread by your "video device"...thus as you have figured out you need to use Invoke to get access to your pboLive element on the UI thread...but you need to pass across the "bitmap".

I believe you just need this:

    this.Dispatcher.Invoke(
        new Action<Bitmap>(
            (bitmap) =>
            {
                pboLive.Source = ImageSourceForBitmap(bitmap);
            }
        ),
        (Bitmap)eventArgs.Frame.Clone()
        );

I think the Clone is unnecessary, if you are creating a BitmapImage in that ImageSourceForBitmap function.

If you are using CreateBitmapSourceFromHBitmap then I can see why you are doing your own copy of the Bitmap (whose lifetime is owned by the Video device)...but you might as well create a BitmapImage or something equivalent.

Colin Smith
  • 12,375
  • 4
  • 39
  • 47
  • This has the same problem than the code by @Clemens - it works exactly once. On subsequent uses, it goes into an infinite loop without displaying anything on pboLive. With subsequent uses I mean that the window is closed by `this.Close();`, but the Word application stays open. Closing and re-opening Word solves it - for another single use. Does this leave some threads open that I'm supposed to explicitly close? – Arto Kilponen Os Kilponen Aug 29 '17 at 06:21
  • At a guess I would say you need to "stop" your FinalFrame object before you close the window to which it is posting frame events. When you say "infinite loop"....do you mean a lock-up...that's different - you could be hitting a deadlock situation due to using .Invoke, which can occur in certain circumstances. Also please describe the structure of this window creation...are you creating a main window, and then creating, and closing a window from that to show your "frames"?....and then it's the 2nd time you create the window, that you have the problem?...some code of that would help. – Colin Smith Aug 29 '17 at 07:51
  • If the problem is in-fact somewhere in the .Invoke then you can do a Break-All to see what is going on....look at the call stack.... https://blogs.msmvps.com/duniho/2008/09/12/form-closing-race-condition/ It might not be safe to call your FinalFrame "stop" from the UI (during your window close)...and might have to be done in a background thread. Also please describe what you are doing with the Word application...it's not clear how you are using that at the moment. – Colin Smith Aug 29 '17 at 07:57
  • @Arto Your new problem hasn't got anything to do with the code in your question, or with what you are asking here in general. Please stop posting comments here, and ask a new StackOverflow question with the *relevant parts* of your code. – Clemens Aug 29 '17 at 10:23
  • @Clemens will do, I also think that it gets off-topic here. – Arto Kilponen Os Kilponen Aug 29 '17 at 12:44
  • Note: using .Stop() on the AForge VideoCaptureDevice object...is a rather abrupt way to end your video capturing task....you should really use .SignalToStop(), and then "wait" in another thread...for the videocapturedevice to tell you it has shutdown everything...it can be tricky to achieve though...look for various other examples in stackoverflow etc, on the best way. http://www.aforgenet.com/forum/viewtopic.php?f=2&t=2160 , https://en.code-bude.net/2013/01/02/how-to-easily-record-from-a-webcam-in-c/ ,,,,, http://hintdesk.com/c-aforge-net-examples-for-average-color-and-motion-detection/ ..... – Colin Smith Aug 29 '17 at 21:43
0

A pattern I've seen in production code, using CheckAccess to prevent unnecessary Invoke calls;

void DoTheThing()
{
    if(Dispatcher.CheckAccess())
    {
        // Do the thing, ie set up the video capture
    }
    else
    {
        Dispatcher.Invoke(DoTheThing);
    }
}

This, however, is simply a pattern to clarify how you should be calling Dispatcher.Invoke and I stringly encourage perusing other answers, as they pertain to the issue at hand more directly.

David
  • 10,458
  • 1
  • 28
  • 40
  • So is the idea to call recursively this method from that else-branch? If I do so, it says I can't convert from void to System.Action. – Arto Kilponen Os Kilponen Aug 29 '17 at 06:24
  • If you're using the above code, make sure to use `DoTheThing`, thus passing the method, instead of `DoTheThing()`, thus invoking the method and attempting to pass the result, a `void`, to `Invoke` – David May 09 '18 at 13:45
0

Since your NewFrame event handler is called on a background thread, you should freeze the ImageSource returned from ImageSourceForBitmap in order to make it cross-thread accessible.

Then you would assign it to the Image's Source property in a Dispatcher Action:

private void FinalFrame_NewFrame(object sender, NewFrameEventArgs eventArgs)
{
    var imageSource = ImageSourceForBitmap(eventArgs.Frame);
    imageSource.Freeze();

    pboLive.Dispatcher.Invoke(() => pboLive.Source = imageSource);
}
Clemens
  • 123,504
  • 12
  • 155
  • 268