DirectShow/WPF 线程问题

发布于 2024-10-15 18:44:06 字数 732 浏览 5 评论 0原文

我正在使用 WPF 和 DirectShow 编写一个应用程序,但遇到了一个棘手的问题。我的应用程序通过使用 DirectShowNet(DS 的 C# 包装类)编写的静态类中的静态方法 Start() 和 Stop() 来利用 DS。我的 WPF 窗口中有一个 Windows 窗体面板(通过 WindowsFormsHost 对象),我需要将图形渲染到该面板。以下是应用程序的一般流程: Start() 方法构建图表并启动它;我传递了 Windows 窗体面板的句柄并使用 IVideoWindow 接口对其进行渲染。 Start() 返回并且图表在后台运行。在某个时刻,Stop() 被调用;此方法停止图表并销毁它。

只要我从同一线程调用 Start() 和 Stop() ,一切都会正常。但是,我需要从应用程序中的不同线程调用它们。在这种情况下,我会在破坏图形的代码部分中遇到异常(特别是当我尝试枚举过滤器时)。我发现在使用 DirectShow 时需要使用多线程单元。使用 Windows 窗体应用程序可以轻松实现这一点;我只需在我的主要方法上添加一个 [MTAThread] 即可,一切正常。

对于我的 WPF 应用程序,这显然不是一个选项。我的解决方法是在需要调用 Start() 和 Stop() 时启动新的 MTA 线程。这消除了异常,但引入了另一个问题。当 Start() 方法返回时,视频从渲染面板中消失。如果我将 Sleep 放在 Start() 方法的末尾,则视频将可见,直到 Sleep 结束。此外,我还验证了视频消失后图表继续运行。有人对如何进行有任何建议吗?谢谢。

凯文

I am writing an app using WPF and DirectShow and have run into a sticky issue. My application utilizes DS through static methods Start() and Stop() in a static class written using DirectShowNet (a C# wrapper class for DS). I have a Windows Forms panel in my WPF window (via a WindowsFormsHost object) that I need the graph to render to. Here is the general flow of the app: The Start() method builds the graph and starts it; I pass the handle of my windows form panel and render to it using the IVideoWindow interface. Start() returns and the graph runs in the background. At some point, Stop() is called; this method stops the graph 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 app. When this is the case, I get an exception in the part of code that destroys the graph (specifically, when I am attempting to enumerate the filters). I discovered that I need to use a Multithreaded Apartment when working with DirectShow. This is easy with a Windows Forms app; I just throw a [MTAThread] on my main method and everything works.

For my WPF app, this is apparently not an option. My workaround has been to launch new MTA threads when I need to call Start() and Stop(). This gets rid of the exception, but has introduced another problem. When the Start() method returns, the video disappears from the render panel. If I put a Sleep at the end of the Start() method, the video will be visible until the Sleep ends. In addition, I have verified that the graph continues to run after the video disappears. Does anyone have any advice as to how to proceed? Thanks.

Kevin

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

毅然前行 2024-10-22 18:44:06

抛出哪个异常?我猜测类似这样的事情:“调用线程无法访问该对象,因为另一个线程拥有它。”

在这种情况下,请使用正确的调度程序来拨打电话,如下所述 在这里

Which exception is thrown? I'm guessing something along the likes of: "The calling thread cannot access this object because a different thread owns it."

When this is the case, use a correct dispatcher to do your calls, as explained here.

甜味超标? 2024-10-22 18:44:06

仅供参考,Windows 窗体不支持 MTAThread 主线程。如果它有效,那么你很幸运。

我相信您应该能够从 STA 线程调用 DS 对象就好了 - 尽管我对 DS 不太熟悉,但听起来您正在使用 无窗口模式 在我看来,它最适合 STA。

在这种情况下,为什么不总是从主线程调用 Start/Stop 呢?如果另一个线程需要告诉主线程停止或启动,则只需将其任务放入 TaskScheduler.FromCurrentSynchronizationContext 中即可在主线程上运行。

FYI, Windows Forms doesn't support a MTAThread main thread. If it worked, then you just got lucky.

I believe you should be able to invoke DS objects from STA threads just fine - though I'm not that familiar with DS, it sounds like you're using windowless mode and it seems to me that it would work best with STA.

In that case, why not always call Start/Stop from your main thread? If another thread needs to tell the main thread to stop or start, then just have it queue a task to a TaskScheduler.FromCurrentSynchronizationContext to run it on the main thread.

浅黛梨妆こ 2024-10-22 18:44:06

好的,我以前遇到过一个不太相似的问题,但不是 WPF,所以请对以下(非常老套的)建议持保留态度。

以下方法基本上创建一个完全独立的应用程序线程来运行 directshow 命令,但告诉 direct show 使用 WPF 应用程序中托管的 Windows 窗体控件的句柄。

因此,首先我们需要一个虚拟的 WinForms 表单,我们可以用它来调用调用,但它永远不会被渲染:

/// <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);
    }
}

下一步是创建一个可以在其上放置消息循环的线程:

//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);
    }

}

因此,一旦虚拟表单(及其线程)已经创建,您可以在 MTA 上调用
像这样关闭应用程序线程:

/// <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();
}));

然后,当您完成 Directshow 的工作后,像这样关闭单独的应用程序线程:

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

这种方法的优点是您可以将 directshow 巧妙地沙箱到一个单独的线程中。缺点是 Invoke 调用的上下文切换,以及拥有另一个应用程序线程的开销。将其硬塞到当前的架构中可能会很有趣,但这应该会有所帮助。

让我知道你的进展如何,我很好奇它的效果如何。

Ok, so I've encountered a problem not too dissimilar before, but not with WPF, so take the following (very hacky) suggestion with a pinch of salt.

The following method basically creates an entirely separate application thread to run directshow commands in, but tells direct show to use the handle of the windows forms control hosted in your WPF application.

So, first we need a dummy WinForms form that we can use to invoke calls on, but that is never going to get rendered:

/// <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);
    }
}

Next step is to create a thread that we can put a message loop on:

//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 thread) have been created, you can invoke calls on the MTA
application thread like so:

/// <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 all done with the Directshow stuff, shutdown your separate application thread like so:

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

Advantage of this approach is that you neatly sandbox directshow into a separate thread. Disadvantage is the context switch of the Invoke calls, plus the overhead of having another application thread. You may have some fun shoehorning this into your current architecture, but it should help.

Let me know how you get on, I am intrigued as to how well this works.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文