I’m working on a DirectShow filter which takes input samples and turns them into modified output samples but where there isn’t a one-to-one correspondence between input and output samples so CTransformFilter doesn’t seem to be the way to go.
The best way of writing this appears to be writing a filter using CBaseFilter, CBaseInputPin and CBaseOutputPin where samples are received on an input pin and processed by a worker thread which creates and delivers new samples from the output pin. The worker thread copies the input sample data before starting work so that my filter doesn’t have to maintain a reference to the input samples outside the input CBaseInputPin::Receive call.
What’s the best practice for maintaining thread safety and avoiding deadlocks in this case? Should the input and output pin share the same streaming lock or should they have a streaming lock each for their streaming operations? Do buffer allocation, sample delivery and other output pin operations need to hold the streaming lock(s) and/or the filter lock? Any sample code that does something similar? Any other gotchas to watch out for in this situation?
The DirectShow bases classes contain scary comments for CBaseOutputPin::Deliver and CBaseOutputPin::GetDeliveryBuffer which I don’t fully understand (pasted below).
/* Deliver a filled-in sample to the connected input pin. NOTE the object must
have locked itself before calling us otherwise we may get halfway through
executing this method only to find the filter graph has got in and
disconnected us from the input pin. If the filter has no worker threads
then the lock is best applied on Receive(), otherwise it should be done
when the worker thread is ready to deliver. There is a wee snag to worker
threads that this shows up. The worker thread must lock the object when
it is ready to deliver a sample, but it may have to wait until a state
change has completed, but that may never complete because the state change
is waiting for the worker thread to complete. The way to handle this is for
the state change code to grab the critical section, then set an abort event
for the worker thread, then release the critical section and wait for the
worker thread to see the event we set and then signal that it has finished
(with another event). At which point the state change code can complete */
You have a few samples in Windows SDK in
\Samples\multimedia\directshow\filtersand earlier version of SDK had even more. This would be perhaps the best sample code you can check for locking practices.The filter and pins normally use shared critical sections to ensure thread safety. For instance, CTransformFilter::m_csFilter protects the state data of the filter and not only section, but pins also use the section. The additional section is also used to serialize streaming requests (pushing through samples, sending EOS notifications).
Your filter might be using state critical section, or you can alternatively use additional synchronization object (section, reader-writer lock, or a mutex) in order to avoid deadlocks with critical section being possibly locked by base classes.
Regular suggestions apply: to avoid deadlocks you should make sure your locking order is designed in a way that if section A can be locked on a thread which already has section B locked, you should only lock B [on other threads] when without existing lock on A, so that no deadlock is possible.
So typically, you have two scenarios which most use cases fall into: