I haven’t had much time the last few months, but in my spare minutes I’ve been doing some work with the MediaKit. Mostly to solve some very outstanding issues and annoyances. The biggest one was a reported memory leak and another was a problem with reentrancy of the message pump. Both were somewhat related. Both had to be fixed. Why would you want to dump the leak MediaElement for something that leaked just as bad or worse?
Reentrancy? What?
In the WPF MediaKit, I was using a new WPF Dispatcher instance and thread. The reason for this was:
- WPF’s UI thread runs STA. We might want to run MTA COM apartments for our COM objects
- DirectShow has blocking calls. We never want to block the UI thread. Maybe that was ok in Winforms. Sure is not kosher in WPF any more.
- I need a Win32 message pump, and WPF’s Dispatcher supplied that.
- It’s very convenient. The API is familiar. It’s well engineered.
So what’s the issue with the WPF Dispatcher? Reentrancy! See, DirectShow uses the Win32 message pump to relay some messages, maybe handle automatically created render windows, fire events and what not. So lets say we tell our DShow’s Dispatcher to BeginInvoke(OpenFile) then BeginInvoke(Play). We’d expect (given equal dispatcher priorities) that OpenFile will execute, then finish. Then the dispatcher will execute Play, then finish. Imagine my surprise when I saw OpenFile() execute 1/2 of the method, then switch over to the Play method! Same thread in two places at the same time? Debugging DShow can be tough, but it shouldn’t be like quantum physics! What was happening is a call to DShow was dispatching a Win32 message, which was picked up by the WPF Dispatcher and executed, then the Dispatcher would execute the next delegate in the queue, which was something like Play(). Jaime Rodriguez and Dwayne Need were kind enough to explain this to me in gory detail. Apparently, the WPF Dispatcher interleaves the Win32 message pump and .NET delegates.
Fixing this was going to require creating my own simple dispatcher/dispatcher-object that does what the WPF Dispatcher does, just it doesn’t interleave Win32/CLR delegates. This fixed many issues off the bat. Specially that damned RaceOnRCWCleanup MDA! Also, you no longer need some stupid static factory class to create your WPF Dispatcher and initialize your COM player.
Memory Leaks! @#$%!
Running a simple loop of: Create MediaUriElement, assign a Uri, Play(), Close(). This would eventually show over 16,000 open handles after about half a day and well over a gig working set. Writing a new Dispatcher fixed quite a bit of my issues in this area, but spending some time with Windbg was really what revealed where the problem areas were. This still was not an easy process, considering the complexity of a managed COM API, running in .NET, using 3rd party filters, rendering in a D3D environment, interoping to WPF. It’s a jungle in there. I still want to monitor the leak area, but so far I’m pretty very pleased with it.
Not really a memory leak, but I wanted to cover a change in the MediaKit’s base classes. I have now modified it so the default “Unloaded” behavior is Close. As long as the Close is called by something, either automatic w/ the unloaded behavior, or explicit in your code. This needs to be called when you are done with any of the players. This will property shut down the dshow player thread. What was extremely tricky was the scenario of: What if you call Close, but then you set a property (ie the Uri “Source”) and tell it to Play(). The control won’t do shit anymore. I added a method on to my WorkerDispatcher called EnsureThread. This ensures the WorkerDispatcher has a thread. This is automatically called in the base classes’ Play() and Pause() protected virtuals.
Optimization
Turns out that creating too many IDirect3DDevice9s on lower end cards makes your app die. I now have one static shared IDirect3DDevice9 in the VMR9 allocator. This seems to work out great, but this limits us to MTA ONLY at the moment. In the future I will update the EVR to do the same, but also leave in the ability to support STA (which may take more work on my custom Dispatcher, WorkerDispatcher). This may be done in the future by setting a “UseSharedResources” flag.
If you are using WPF MediaKit now, there are breaking changes…but I believe them to be very minimal. Please test the newest code and let me know if you find any issues. I have not deeply tested any of the sample controls, like the DVD or Webcam controls, extensively but they will be next on my list.