Problems with DirectShow / WPF Threading

I am writing an application using WPF and DirectShow and have run into a sticky problem. My application uses DS through the static Start () and Stop () methods in a static class written using DirectShowNet (C # shell class for DS). I have a Windows Forms panel in my WPF window (through the WindowsFormsHost object) that I need to display a graph. Here is the general flow of the application: the Start () method builds a graph and starts it; I pass the window pane handle and render it using the IVideoWindow interface. Start () returns and the graph runs in the background. At some point Stop () is called; this method stops the chart and destroys it.

Everything works fine as long as I call Start () and Stop () from the same thread. However, I will need to call them from different threads in my application. When this is the case, I get an exception in the part of the code that destroys the graph (in particular, when I try to list the filters). I found that I need to use Multithreaded Apartment when working with DirectShow. This is easy with the Windows Forms application; I just throw [MTAThread] on my main method and everything works.

For my WPF application this is apparently not an option. My solution was to start new MTA threads when I need to call Start () and Stop (). This eliminates the exception, but introduces another problem. When the Start () method returns, the video disappears from the render panel. If I put Sleep at the end of the Start () method, the video will be displayed until the Sleep ends. In addition, I checked that the chart continues to work after the video disappears. Does anyone have any tips on how to proceed? Thanks.

Kevin

+4
source share
3 answers

Which exception is thrown? I guess something like: "The calling thread cannot access this object because another thread belongs to it."

If so, use the correct dispatcher to make your calls, as described here .

+1
source

FYI, Windows Forms does not support the main MTAThread thread. If that worked, you're just lucky.

I believe that you should be able to call DS objects from STA streams just fine - although I am not familiar with DS, it looks like you are using windowless mode , and it seems to me that it will work better with STA.

In this case, why not always call Start / Stop from the main thread? If another thread should inform the main thread of the termination or start, just ask him to queue the task on TaskScheduler.FromCurrentSynchronizationContext to start it in the main thread.

0
source

Ok, so I ran into a problem that wasn't too varied before, but not with WPF, so make the following (very hacky) sentence with a pinch of salt.

The following method basically creates a completely separate application stream for running directshow commands, but tells direct show to use the Windows control handle located in your WPF application.

So, first we need a dummy form of WinForms, which we can use to make calls, but this will never be displayed:

 /// <summary> /// Just a dummy invisible form. /// </summary> private class DummyForm : Form { protected override void SetVisibleCore(bool value) { //just override here, make sure that the form will never become visible if (!IsHandleCreated) { CreateHandle(); } value = false; base.SetVisibleCore(value); } } 

The next step is to create a thread on which we can put a message loop:

 //this will need to be a class level variable, since all the directshow //calls will get invoked on this form DummyForm dumbForm; Thread separateThread; private void CreateDummyForm() { ManualResetEvent reset = new ManualResetEvent(false); //create our thread separateThread = new Thread((ThreadStart) delegate { //we need a dummy form to invoke on dumbForm = new DummyForm(); //signal the calling method that it can continue reset.Set(); //now kick off the message loop Application.Run(dumbForm); }); //set the apartment state of this new thread to MTA separateThread.SetApartmentState(ApartmentState.MTA); separateThread.IsBackground = true; separateThread.Start(); //we need to wait for the windowing thread to have initialised before we can //say that initialisation is finished reset.WaitOne(); //wait for the form handle to be created, since this won't happen until the form //loads inside Application.Run while (!dumbForm.IsHandleCreated) { Thread.Sleep(0); } } 

So, once the dummy form (and its stream) has been created, you can call the MTA calls in the application stream, as shown below:

 /// <summary> /// Blank delegate, used to represent any Invoke or BeginInvoke target method. /// </summary> public delegate void InvokeHandler(); //i'm assuming here that DSComponent is a class that all your directshow //code is in, and externalControl is the WinForms control you have embedded in //your application. dumbForm.Invoke(new InvokeHandler(delegate { //maybe something like this? DSComponent.Start(externalControl); })); //and to stop it... dumbForm.Invoke(new InvokeHandler(delegate { DSComponent.Stop(); })); 

Then, when you're done with Directshow stuff, disable a separate application stream like this:

 //to end the separate thread and application loop, //just close your invisible form dumbForm.Close(); 

The advantage of this approach is that you carefully create a sandbox in a separate thread. The disadvantage is the Invoke call context switch, as well as the overhead of having another application flow. You may have been lucky with this in your current architecture, but this should help.

Let me know how you are doing, I am intrigued by how well this works.

0
source

Source: https://habr.com/ru/post/1338186/


All Articles