Os threads de streaming e de aplicativo
[O recurso associado a esta página, DirectShow, é um recurso herdado. Foi substituído por MediaPlayer, IMFMediaEngine e Audio/Video Capture in Media Foundation. Esses recursos foram otimizados para Windows 10 e Windows 11. A Microsoft recomenda fortemente que o novo código use MediaPlayer, IMFMediaEngine e Audio/Video Capture in Media Foundation em vez de DirectShow, quando possível. A Microsoft sugere que o código existente que usa as APIs herdadas seja reescrito para usar as novas APIs, se possível.]
Qualquer aplicativo DirectShow contém pelo menos dois threads importantes: o thread do aplicativo e um ou mais threads de streaming. Os exemplos são entregues nos threads de streaming e as alterações de estado ocorrem no thread do aplicativo. O thread de streaming main é criado por um filtro de origem ou analisador. Outros filtros podem criar threads de trabalho que fornecem exemplos e eles também são considerados threads de streaming.
Alguns métodos são chamados no thread do aplicativo, enquanto outros são chamados em um thread de streaming. Por exemplo:
- Threads de streaming: IMemInputPin::Receive, IMemInputPin::ReceiveMultiple, IPin::EndOfStream, IMemAllocator::GetBuffer.
- Thread de aplicativo: IMediaFilter::P ause, IMediaFilter::Run, IMediaFilter::Stop, IMediaSeeking::SetPositions, IPin::BeginFlush, IPin::EndFlush.
- Ou: IPin::NewSegment.
Ter um thread de streaming separado permite que os dados fluam pelo grafo enquanto o thread do aplicativo aguarda a entrada do usuário. O perigo de vários threads, no entanto, é que um filtro pode criar recursos quando pausa (no thread do aplicativo), usá-los dentro de um método de streaming e destruí-los quando ele for interrompido (também no thread do aplicativo). Se você não tiver cuidado, o thread de streaming poderá tentar usar os recursos depois que eles forem destruídos. A solução é proteger recursos usando seções críticas e sincronizar métodos de streaming com alterações de estado.
Um filtro precisa de uma seção crítica para proteger o estado do filtro. A classe CBaseFilter tem uma variável de membro para esta seção crítica, CBaseFilter::m_pLock. Esta seção crítica é chamada de bloqueio de filtro. Além disso, cada pin de entrada precisa de uma seção crítica para proteger os recursos usados pelo thread de streaming. Essas seções críticas são chamadas de bloqueios de streaming; você deve declará-los em sua classe de pino derivada. É mais fácil usar a classe CCritSec , que encapsula um objeto CRITICAL_SECTION do Windows e pode ser bloqueada usando a classe CAutoLock . A classe CCritSec também fornece algumas funções úteis de depuração. Para obter mais informações, consulte Funções críticas de depuração de seção.
Quando um filtro é interrompido ou liberado, ele deve sincronizar o thread do aplicativo com o thread de streaming. Para evitar deadlocks, ele deve primeiro desbloquear o thread de streaming, que pode ser bloqueado por vários motivos:
- Ele está aguardando para obter uma amostra dentro do método IMemAllocator::GetBuffer , pois todos os exemplos do alocador estão em uso.
- Ele está aguardando outro filtro retornar de um método de streaming, como Receive.
- Ele está aguardando dentro de um de seus próprios métodos de streaming, para que algum recurso se torne disponível.
- É um filtro de renderizador aguardando a hora da apresentação do próximo exemplo
- É um filtro de renderizador aguardando dentro do método Receive enquanto está em pausa.
Portanto, quando o filtro é interrompido ou liberado, ele deve fazer o seguinte:
- Libere qualquer exemplo que ele esteja mantendo por qualquer motivo. Isso desbloqueia o método GetBuffer .
- Retorne de qualquer método de streaming o mais rápido possível. Se um método de streaming estiver aguardando um recurso, ele deverá parar de esperar imediatamente.
- Comece rejeitando exemplos em Receber, para que o thread de streaming não acesse mais recursos. (A classe CBaseInputPin lida com isso automaticamente.)
- O método Stop deve descompromissar todos os alocadores do filtro. (A classe CBaseInputPin lida com isso automaticamente.)
A liberação e a interrupção ocorrem no thread do aplicativo. Um filtro é interrompido em resposta ao método IMediaControl::Stop . O Gerenciador de Grafo de Filtro emite o comando stop em upstream ordem, começando pelos renderizadores e trabalhando para trás até os filtros de origem. O comando stop ocorre completamente dentro do método CBaseFilter::Stop do filtro. Quando o método retorna, o filtro deve estar em um estado parado.
A liberação normalmente ocorre devido a um comando seek. Um comando de liberação começa a partir do filtro de origem ou analisador e viaja downstream. A liberação ocorre em dois estágios: o método IPin::BeginFlush informa um filtro para descartar todos os dados pendentes e de entrada; o método IPin::EndFlush sinaliza o filtro para aceitar dados novamente. A liberação requer dois estágios porque a chamada BeginFlush está no thread do aplicativo, durante o qual o thread de streaming continua a fornecer dados. Portanto, alguns exemplos podem chegar após a chamada beginflush . O filtro deve descartá-los. Todos os exemplos que chegam após a chamada de EndFlush têm a garantia de serem novos e devem ser entregues.
As seções a seguir contêm exemplos de código mostrando como implementar os métodos de filtro mais importantes, como Pausar, Receber e assim por diante, de maneiras que evitam deadlocks e condições de corrida. No entanto, cada filtro tem requisitos diferentes, portanto, você precisará adaptar esses exemplos ao filtro específico.
Observação
As classes base CTransformFilter e CTransInPlaceFilter lidam com muitos dos problemas descritos neste artigo. Se você estiver escrevendo um filtro de transformação e o filtro não aguardar eventos dentro de um método de streaming ou manter amostras fora de Receive, essas classes base deverão ser suficientes.