Os threads de streaming e aplicativos
[O recurso associado a esta página, DirectShow, é um recurso herdado. Foi substituído por MediaPlayer, IMFMediaEnginee Audio/Video Capture in Media Foundation. Esses recursos foram otimizados para Windows 10 e Windows 11. A Microsoft recomenda vivamente que o novo código utilize MediaPlayer, IMFMediaEngine e Captura de Áudio/Vídeo no 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. As amostras são entregues nos threads de streaming e as alterações de estado acontecem no thread do aplicativo. O thread de streaming principal é criado por um filtro de fonte ou analisador. Outros filtros podem criar threads de trabalho que entregam amostras, e estas também são consideradas 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:
- Thread(s) de streaming: IMemInputPin::Receive, IMemInputPin::ReceiveMultiple, IPin::EndOfStream, IMemAllocator::GetBuffer.
- Thread da aplicação: IMediaFilter::Pause, IMediaFilter::Run, IMediaFilter::Stop, IMediaSeeking::SetPositions, IPin::BeginFlush, IPin::EndFlush.
- Ou: IPin::NewSegment.
Ter um thread de streaming separado permite que os dados fluam através do gráfico 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 ele pausa (no thread do aplicativo), usá-los dentro de um método de streaming e destruí-los quando ele para (também no thread do aplicativo). Se não tiveres cuidado, o thread de transmissão pode tentar usar os recursos após serem 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 classeCBaseFilter tem uma variável membro para esta seção crítica, CBaseFilter::m_pLock. Esta seção crítica é chamada de bloqueio de filtro. Além disso, cada pino 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 PIN 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 úteis funções de depuração. Para obter mais informações, consulte Critical Section Debugging Functions.
Quando um filtro para ou limpa, deve sincronizar o thread do aplicativo com o thread de streaming. Para evitar o impasse, 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 IMemAllocator::GetBuffer método, porque todas as amostras do alocador estão em uso.
- Ele está aguardando que outro filtro retorne de um método de streaming, como Receber.
- Está à espera dentro de um dos seus próprios métodos de transmissão, para que algum recurso fique disponível.
- É um filtro de renderizador aguardando o tempo de apresentação do próximo exemplo
- É um filtro do renderizador aguardando dentro do método Receive quando está em pausa.
Portanto, quando o filtro para ou enxagua, ele deve fazer o seguinte procedimento:
- Solte qualquer amostra que esteja segurando por qualquer motivo. Isso desbloqueia o GetBuffer método.
- 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 aguardar imediatamente.
- Comece a rejeitar amostras em Receber, para que o thread de streaming não acesse mais recursos. (A classeCBaseInputPinlida com isso automaticamente.)
- O método Stop deve libertar todos os alocadores do filtro. (A classe CBaseInputPin lida com isso automaticamente.)
Flushing e stop ambos acontecem no thread do aplicativo. Um filtro para em resposta ao IMediaControl::Stop método. O Filter Graph Manager emite o comando parar pela ordem ascendente, começando pelos renderizadores e trabalhando para trás até os filtros de origem. O comando stop acontece completamente dentro do método CBaseFilter::Stop do filtro. Quando o método retorna, o filtro deve estar em um estado interrompido.
O flushing normalmente ocorre por causa de um comando seek. Um comando flush começa a partir do filtro de origem ou do analisador e viaja para jusante. O flushing acontece em duas etapas: O método IPin::BeginFlush informa um filtro para descartar todos os dados pendentes e recebidos; o método IPin::EndFlush sinaliza ao 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, algumas amostras podem chegar após a chamada BeginFlush. O filtro deve eliminá-los. Quaisquer amostras que cheguem após a chamada EndFlush têm a garantia de serem novas 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 Pause, Receivee assim por diante, de forma a evitar impasses e condições de corrida. No entanto, cada filtro tem requisitos diferentes, pelo que terá de adaptar estes exemplos ao seu filtro específico.
Nota
O CTransformFilter e CTransInPlaceFilter classes base lidam com muitos dos problemas descritos neste artigo. Se estiveres a escrever um filtro de transformação e o teu filtro não esperar por eventos dentro de um método de streaming, nem reter amostras fora de Receive, então estas classes base devem ser suficientes.