Renderizadores de vídeo alternativos
[O recurso associado a esta página, DirectShow, é um recurso herdado. Ele foi substituído por MediaPlayer, IMFMediaEngine e Captura de Áudio/Vídeo na 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 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.]
Este tópico descreve como escrever um renderizador de vídeo personalizado para o DirectShow.
Observação
Em vez de escrever um renderizador de vídeo personalizado, é recomendável que você escreva um alocador-apresentador de plug-in para o Renderizador de Mixagem de Vídeo (VMR) ou Renderizador de Vídeo Avançado (EVR). Essa abordagem fornecerá todos os benefícios da VMR/EVR, incluindo suporte para DXVA (Aceleração de Vídeo) DirectX, desinterlacagem de hardware e etapa de quadro e provavelmente será mais robusto do que um renderizador de vídeo personalizado. Para obter mais informações, consulte estes tópicos:
Escrevendo um renderizador alternativo
O Microsoft DirectShow fornece um renderizador de vídeo baseado em janela; ele também fornece um renderizador de tela inteira na instalação em tempo de execução. Você pode usar as classes base do DirectShow para escrever renderizadores de vídeo alternativos. Para que os renderizadores alternativos interajam corretamente com aplicativos baseados em DirectShow, os renderizadores devem seguir as diretrizes descritas neste artigo. Você pode usar as classes CBaseRenderer e CBaseVideoRenderer para ajudar a seguir essas diretrizes ao implementar uma renderização de vídeo alternativa. Devido ao desenvolvimento contínuo do DirectShow, examine sua implementação periodicamente para garantir que os renderizadores sejam compatíveis com a versão mais recente do DirectShow.
Este tópico discute muitas notificações de que um renderizador é responsável por lidar. Uma breve revisão das notificações do DirectShow pode ajudar a definir o estágio. Há essencialmente três tipos de notificações que ocorrem no DirectShow:
- Notificações de fluxo, que são eventos que ocorrem no fluxo de mídia e são passadas de um filtro para o outro. Elas podem ser notificações de inicialização, liberação de fim ou fim de fluxo e são enviadas chamando o método apropriado no pin de entrada do filtro downstream (por exemplo , IPin::BeginFlush).
- Notificações de grafo de filtro, que são eventos enviados de um filtro para o Gerenciador de Grafo de Filtro, como EC_COMPLETE. Isso é feito chamando o método IMediaEventSink::Notify no Gerenciador de Grafo de Filtro.
- Notificações de aplicativo, que são recuperadas do Gerenciador de Grafo de Filtro pelo aplicativo controlador. Um aplicativo chama o método IMediaEvent::GetEvent no Gerenciador de Grafo de Filtro para recuperar esses eventos. Muitas vezes, o Gerenciador de Grafo de Filtro passa pelos eventos que recebe para o aplicativo.
Este tópico discute a responsabilidade do filtro do renderizador no tratamento de notificações de fluxo recebidas e no envio de notificações de grafo de filtro apropriadas.
Tratamento de notificações de fim de fluxo e liberação
Uma notificação de fim de fluxo começa em um filtro de upstream (como o filtro de origem) quando esse filtro detecta que ele não pode enviar mais dados. Ele é passado por todos os filtros do grafo e, eventualmente, termina no renderizador, que é responsável por enviar posteriormente uma notificação de EC_COMPLETE para o Gerenciador de Grafo de Filtro. Os renderizadores têm responsabilidades especiais quando se trata de lidar com essas notificações.
Um renderizador recebe uma notificação de fim do fluxo quando o método IPin::EndOfStream do pino de entrada é chamado pelo filtro upstream. Um renderizador deve observar essa notificação e continuar a renderizar todos os dados que já recebeu. Depois que todos os dados restantes forem recebidos, o renderizador deverá enviar uma notificação EC_COMPLETE para o Gerenciador de Grafo de Filtro. A notificação EC_COMPLETE deve ser enviada apenas uma vez por um renderizador sempre que atingir o final de um fluxo. Além disso, EC_COMPLETE notificações nunca devem ser enviadas, exceto quando o grafo de filtro está em execução. Portanto, se o grafo de filtro for pausado quando um filtro de origem enviar uma notificação de fim do fluxo, EC_COMPLETE não deverá ser enviado até que o grafo de filtro seja finalmente executado.
Todas as chamadas para os métodos IMemInputPin::Receive ou IMemInputPin::ReceiveMultiple depois que uma notificação de fim de fluxo é sinalizada devem ser rejeitadas. E_UNEXPECTED é a mensagem de erro mais apropriada a ser retornada nesse caso.
Quando um grafo de filtro é interrompido, qualquer notificação de fim de fluxo armazenada em cache deve ser desmarcada e não ressentida quando for iniciada. Isso ocorre porque o Gerenciador de Grafo de Filtro sempre pausa todos os filtros antes de executá-los para que ocorra a liberação adequada. Portanto, por exemplo, se o grafo de filtro estiver pausado e uma notificação de fim do fluxo for recebida e o grafo de filtro for interrompido, o renderizador não deverá enviar uma notificação de EC_COMPLETE quando for executado posteriormente. Se nenhuma busca tiver ocorrido, o filtro de origem enviará automaticamente outra notificação de fim do fluxo durante o estado de pausa que precede um estado de execução. Se, por outro lado, uma busca tiver ocorrido enquanto o grafo de filtro for interrompido, o filtro de origem poderá ter dados a serem enviados, portanto, ele não enviará uma notificação de fim de fluxo.
Os renderizadores de vídeo geralmente dependem de notificações de fim de fluxo para mais do que o envio de EC_COMPLETE notificações. Por exemplo, se um fluxo tiver terminado de ser reproduzido (ou seja, uma notificação de fim de fluxo será enviada) e outra janela for arrastada por uma janela do renderizador de vídeo, várias mensagens de janela WM_PAINT serão geradas. A prática típica para executar renderizadores de vídeo é evitar a repintação do quadro atual após o recebimento de mensagens de WM_PAINT (com base na suposição de que outro quadro a ser desenhado será recebido). No entanto, quando a notificação de fim do fluxo tiver sido enviada, o renderizador estará em um estado de espera; ele ainda está em execução, mas está ciente de que não receberá nenhum dado adicional. Nessas circunstâncias, o renderizador normalmente desenha a área de reprodução preta.
Lidar com a liberação é uma complicação adicional para renderizadores. A liberação é realizada por meio de um par de métodos IPinchamados BeginFlush e EndFlush. A liberação é essencialmente um estado adicional que o renderizador deve manipular. É ilegal para um filtro de origem chamar BeginFlush sem chamar EndFlush, portanto, espero que o estado seja curto e discreto; no entanto, o renderizador deve lidar corretamente com dados ou notificações que recebe durante a transição de liberação.
Todos os dados recebidos após chamar BeginFlush devem ser rejeitados imediatamente retornando S_FALSE. Além disso, qualquer notificação de fim de fluxo armazenada em cache também deve ser desmarcada quando um renderizador é liberado. Normalmente, um renderizador será liberado em resposta a uma busca. A liberação garante que os dados antigos sejam limpos do grafo de filtro antes que novas amostras sejam enviadas. (Normalmente, a reprodução de duas seções de um fluxo, uma após a outra, é melhor tratada por meio de comandos adiados em vez de esperar que uma seção seja concluída e, em seguida, emitindo um comando seek.)
Manipulando alterações de estado e pausando a conclusão
Um filtro de renderizador se comporta da mesma forma que qualquer outro filtro no grafo de filtro quando seu estado é alterado, com a seguinte exceção. Depois de pausado, o renderizador terá alguns dados enfileirados, prontos para serem renderizados quando forem executados posteriormente. Quando o renderizador de vídeo é interrompido, ele mantém esses dados enfileirados. Essa é uma exceção à regra directShow de que nenhum recurso deve ser mantido por filtros enquanto o grafo de filtro é interrompido.
O motivo dessa exceção é que, mantendo recursos, o renderizador sempre terá uma imagem com a qual repintar a janela se receber uma mensagem WM_PAINT . Ele também tem uma imagem para satisfazer métodos, como CBaseControlVideo::GetStaticImage, que solicitam uma cópia da imagem atual. Outro efeito da retenção de recursos é que manter a imagem impede que o alocador seja descompactado, o que, por sua vez, faz com que a próxima alteração de estado ocorra muito mais rapidamente porque os buffers de imagem já estão alocados.
Um renderizador de vídeo deve renderizar e liberar amostras somente durante a execução. Enquanto pausado, o filtro pode renderizá-los (por exemplo, ao desenhar uma imagem de pôster estático em uma janela), mas não deve liberá-los. Os renderizadores de áudio não farão nenhuma renderização enquanto estiverem em pausa (embora possam executar outras atividades, como preparar o dispositivo de onda, por exemplo). A hora em que os exemplos devem ser renderizados é obtida combinando o tempo de fluxo no exemplo com o tempo de referência passado como um parâmetro para o método IMediaControl::Run . Os renderizadores devem rejeitar amostras com horários de início menores ou iguais aos horários de término.
Quando um aplicativo pausa um grafo de filtro, o grafo de filtro não retorna de seu método IMediaControl::P ause até que haja dados enfileirados nos renderizadores. Para garantir isso, quando um renderizador estiver em pausa, ele deverá retornar S_FALSE se não houver dados esperando para serem renderizados. Se tiver dados enfileirados, ele poderá retornar S_OK.
O Gerenciador de Grafo de Filtro verifica todos os valores retornados ao pausar um grafo de filtro para garantir que os renderizadores tenham dados enfileirados. Se um ou mais filtros não estiverem prontos, o Gerenciador de Grafo de Filtros sondará os filtros no grafo chamando IMediaFilter::GetState. O método GetState usa um parâmetro de tempo limite. Um filtro (normalmente um renderizador) que ainda está aguardando a chegada dos dados antes de concluir a alteração de estado retornará VFW_S_STATE_INTERMEDIATE se o método GetState expirar. Depois que os dados chegarem ao renderizador, GetState deverá ser retornado imediatamente com S_OK.
No estado intermediário e concluído, o estado de filtro relatado será State_Paused. Somente o valor retornado indica se o filtro está realmente pronto ou não. Se, enquanto um renderizador estiver aguardando a chegada dos dados, seu filtro de origem enviará uma notificação de fim do fluxo, isso também deverá concluir a alteração de estado.
Depois que todos os filtros realmente tiverem dados aguardando para serem renderizados, o grafo de filtro concluirá sua alteração de estado de pausa.
Manipulando terminação
Os renderizadores de vídeo devem lidar corretamente com eventos de encerramento do usuário. Isso implica ocultar corretamente a janela e saber o que fazer se uma janela for posteriormente forçada a ser exibida. Além disso, os renderizadores de vídeo devem notificar o Gerenciador de Grafo de Filtro quando sua janela é destruída (ou com mais precisão, quando o renderizador é removido do grafo de filtro) para liberar recursos.
Se o usuário fechar a janela de vídeo (por exemplo, pressionando ALT+F4), a convenção será ocultar a janela imediatamente e enviar uma notificação EC_USERABORT para o Gerenciador de Grafo de Filtro. Essa notificação é passada para o aplicativo, o que interromperá a reprodução do grafo. Depois de enviar EC_USERABORT, um renderizador de vídeo deve rejeitar quaisquer exemplos adicionais entregues a ele.
O sinalizador interrompido pelo grafo deve ser deixado ligado pelo renderizador até que seja interrompido posteriormente, momento em que ele deve ser redefinido para que um aplicativo possa substituir a ação do usuário e continuar reproduzindo o grafo, se desejar. Se ALT+F4 for pressionado enquanto o vídeo estiver em execução, a janela ficará oculta e todas as amostras adicionais entregues serão rejeitadas. Se a janela for mostrada posteriormente (talvez por meio de IVideoWindow::p ut_Visible), nenhuma EC_REPAINT notificações deverá ser gerada.
O renderizador de vídeo também deve enviar a notificação de EC_WINDOW_DESTROYED para o grafo de filtro quando o renderizador de vídeo estiver terminando. Na verdade, é melhor lidar com isso quando o método IBaseFilter::JoinFilterGraph do renderizador é chamado com um parâmetro nulo (indicando que o renderizador está prestes a ser removido do grafo de filtro), em vez de esperar até que a janela de vídeo real seja destruída. O envio dessa notificação permite que o distribuidor de plug-in no Gerenciador de Grafo de Filtro passe recursos que dependem do foco da janela para outros filtros, como dispositivos de áudio.
Manipulando alterações de formato dinâmico
Em alguns casos, o filtro de upstream do renderizador pode tentar alterar o formato de vídeo enquanto o vídeo está sendo reproduzido. Na maioria das vezes, é o descompactador de vídeo que inicia uma alteração de formato dinâmico.
Um filtro upstream tentando alterar formatos dinamicamente deve sempre chamar o método IPin::QueryAccept no pin de entrada do renderizador. Um renderizador de vídeo tem alguma margem de manobra quanto a quais tipos de alterações de formato dinâmico ele deve dar suporte. No mínimo, ele deve permitir que o filtro upstream altere as paletas. Quando um filtro de upstream altera os tipos de mídia, ele anexa o tipo de mídia ao primeiro exemplo entregue no novo formato. Se o renderizador mantiver amostras em uma fila para renderização, ele não deverá alterar o formato até renderizar o exemplo com a alteração de tipo.
Um renderizador de vídeo também pode solicitar uma alteração de formato do decodificador. Por exemplo, ele pode pedir ao decodificador para fornecer um formato compatível com DirectDraw com um biHeight negativo. Quando o renderizador estiver em pausa, ele deverá chamar QueryAccept no pino upstream para ver quais formatos o decodificador pode fornecer. No entanto, o decodificador pode não enumerar todos os tipos que ele pode aceitar, portanto, o renderizador deve oferecer alguns tipos mesmo que o decodificador não os anuncie.
Se o decodificador puder alternar para o formato solicitado, ele retornará S_OK de QueryAccept. Em seguida, o renderizador anexa o novo tipo de mídia ao próximo exemplo de mídia no alocador upstream. Para que isso funcione, o renderizador deve fornecer um alocador personalizado que implemente um método privado para anexar o tipo de mídia ao próximo exemplo. (Nesse método privado, chame IMediaSample::SetMediaType para definir o tipo.)
O pin de entrada do renderizador deve retornar o alocador personalizado do renderizador no método IMemInputPin::GetAllocator . Substitua IMemInputPin::NotifyAllocator para que ele falhe se o filtro upstream não usar o alocador do renderizador.
Com alguns decodificadores, definir biHeight como um número positivo em tipos YUV faz com que o decodificador desenhe a imagem de cabeça para baixo. (Isso está incorreto e deve ser considerado um bug no decodificador.)
Sempre que uma alteração de formato for detectada pelo renderizador de vídeo, ela deverá enviar uma notificação de EC_DISPLAY_CHANGED . A maioria dos renderizadores de vídeo escolhe um formato durante a conexão para que o formato possa ser desenhado com eficiência por meio da GDI. Se o usuário alterar o modo de exibição atual sem reiniciar o computador, um renderizador poderá encontrar-se com uma conexão de formato de imagem incorreta e deve enviar essa notificação. O primeiro parâmetro deve ser o pino que precisa ser reconectado. O Gerenciador de Grafo de Filtro organizará para que o grafo de filtro seja interrompido e o pino seja reconectado. Durante a reconexão subsequente, o renderizador pode aceitar um formato mais apropriado.
Sempre que um renderizador de vídeo detecta uma alteração de paleta no fluxo, ele deve enviar a notificação EC_PALETTE_CHANGED para o Gerenciador de Grafo de Filtro. Os renderizadores de vídeo do DirectShow detectam se uma paleta realmente mudou no formato dinâmico ou não. Os renderizadores de vídeo fazem isso não apenas para filtrar o número de EC_PALETTE_CHANGED notificações enviadas, mas também para reduzir a quantidade de criação, instalação e exclusão necessárias da paleta.
Por fim, o renderizador de vídeo também pode detectar que o tamanho do vídeo foi alterado, nesse caso, ele deve enviar a notificação EC_VIDEO_SIZE_CHANGED . Um aplicativo pode usar essa notificação para negociar espaço em um documento composto. As dimensões de vídeo reais estão disponíveis por meio da interface de controle IBasicVideo . Os renderizadores do DirectShow detectam se o vídeo realmente mudou de tamanho ou não antes de enviar esses eventos.
Manipulando propriedades persistentes
Todas as propriedades definidas por meio das interfaces IBasicVideo e IVideoWindow devem ser persistentes entre conexões. Portanto, desconectar e reconectar um renderizador não deve mostrar efeitos no tamanho, posição ou estilos da janela. No entanto, se as dimensões de vídeo forem alteradas entre conexões, o renderizador deverá redefinir os retângulos de origem e de destino para seus padrões. As posições de origem e destino são definidas por meio da interface IBasicVideo .
IBasicVideo e IVideoWindow fornecem acesso suficiente às propriedades para permitir que um aplicativo salve e restaure todos os dados na interface em um formato persistente. Isso será útil para aplicativos que devem salvar a configuração exata e as propriedades dos grafos de filtro durante uma sessão de edição e restaurá-los posteriormente.
Tratamento de notificações de EC_REPAINT
A notificação EC_REPAINT é enviada somente quando o renderizador é pausado ou interrompido. Essa notificação sinaliza para o Gerenciador de Grafo de Filtro que o renderizador precisa de dados. Se o grafo de filtro for interrompido quando receber uma dessas notificações, ele pausará o grafo de filtro, aguardará todos os filtros receberem dados (chamando GetState) e, em seguida, o interromperá novamente. Quando interrompido, um renderizador de vídeo deve manter a imagem para que as mensagens WM_PAINT subsequentes possam ser tratadas.
Portanto, se um renderizador de vídeo receber uma mensagem WM_PAINT quando parado ou pausado e não tiver nada com o qual pintar sua janela, ele deverá enviar EC_REPAINT para o Gerenciador de Grafo de Filtro. Se uma notificação de EC_REPAINT for recebida enquanto estiver pausada, o Gerenciador de Grafo de Filtro chamará IMediaPosition::p ut_CurrentPosition com a posição atual (ou seja, busca a posição atual). Isso faz com que os filtros de origem liberem o grafo de filtro e faz com que novos dados sejam enviados por meio do grafo de filtro.
Um renderizador deve enviar apenas uma dessas notificações por vez. Portanto, depois que o renderizador enviar uma notificação, ele não deverá mais ser enviado até que alguns exemplos sejam entregues. A maneira convencional de fazer isso é ter um sinalizador para significar que uma repinta pode ser enviada, que é desativada após um EC_REPAINT notificação ser enviada. Esse sinalizador deve ser redefinido depois que os dados forem entregues ou quando o pino de entrada for liberado, mas não se o fim do fluxo for sinalizado no pino de entrada.
Se o renderizador não monitorar suas notificações de EC_REPAINT , ele inundará o Gerenciador de Grafo de Filtro com EC_REPAINT solicitações (que são relativamente caras de processar). Por exemplo, se um renderizador não tiver nenhuma imagem para desenhar e outra janela for arrastada pela janela do renderizador em uma operação de arrastar completamente, o renderizador receberá várias mensagens WM_PAINT . Somente o primeiro deles deve gerar uma notificação de evento EC_REPAINT do renderizador para o Gerenciador de Grafo de Filtro.
Um renderizador deve enviar seu pin de entrada como o primeiro parâmetro para a notificação de EC_REPAINT . Ao fazer isso, o pin de saída anexado será consultado para IMediaEventSink e, se houver suporte, a notificação de EC_REPAINT será enviada primeiro para lá. Isso permite que os pinos de saída manipulem as repintações antes que o grafo de filtro precise ser tocado. Isso não será feito se o grafo de filtro for interrompido, pois nenhum buffer estará disponível no alocador de renderizador descompactado.
Se o pino de saída não puder manipular a solicitação e o grafo de filtro estiver em execução, a notificação EC_REPAINT será ignorada. Um pino de saída deve retornar S_OK de IMediaEventSink::Notify para sinalizar que ele processou a solicitação de repinta com êxito. O pino de saída será chamado no thread de trabalho do Gerenciador de Grafo de Filtro, o que evita que o renderizador chame o pino de saída diretamente e, portanto, evita problemas de deadlock. Se o grafo de filtro for interrompido ou pausado e a saída não tratar a solicitação, o processamento padrão será feito.
Manipulando notificações no modo Full-Screen
O PID (distribuidor de plug-in) IVideoWindow no grafo de filtro gerencia a reprodução em tela inteira. Ele trocará um renderizador de vídeo por um renderizador de tela inteira especializado, esticará uma janela de um renderizador para tela inteira ou fará com que o renderizador implemente a reprodução em tela inteira diretamente. Para interagir em protocolos de tela inteira, um renderizador de vídeo deve enviar uma notificação EC_ACTIVATE sempre que sua janela for ativada ou desativada. Em outras palavras, uma notificação EC_ACTIVATE deve ser enviada para cada mensagem WM_ACTIVATEAPP que um renderizador recebe.
Quando um renderizador está sendo usado no modo de tela inteira, essas notificações gerenciam a alternância para dentro e fora desse modo de tela inteira. A desativação da janela normalmente ocorre quando um usuário pressiona ALT+TAB para alternar para outra janela, que o renderizador de tela inteira do DirectShow usa como uma indicação para retornar ao modo de renderização típico.
Quando a notificação de EC_ACTIVATE é enviada ao Gerenciador de Grafo de Filtro ao sair do modo de tela inteira, o Gerenciador de Grafo de Filtro envia uma notificação de EC_FULLSCREEN_LOST para o aplicativo de controle. O aplicativo pode usar essa notificação para restaurar o estado de um botão de tela inteira, por exemplo. As notificações EC_ACTIVATE são usadas internamente pelo DirectShow para gerenciar a alternância de tela inteira em indicações dos renderizadores de vídeo.
Resumo das notificações
Esta seção lista as notificações de grafo de filtro que um renderizador pode enviar.
Notificação de eventos | Descrição |
---|---|
EC_ACTIVATE | Enviado por renderizadores de vídeo no modo de renderização de tela inteira para cada WM_ACTIVATEAPP mensagem recebida. |
EC_COMPLETE | Enviado por renderizadores depois que todos os dados tiverem sido renderizados. |
EC_DISPLAY_CHANGED | Enviado por renderizadores de vídeo quando um formato de exibição é alterado. |
EC_PALETTE_CHANGED | Enviado sempre que um renderizador de vídeo detecta uma alteração de paleta no fluxo. |
EC_REPAINT | Enviado por renderizadores de vídeo interrompidos ou pausados quando uma mensagem WM_PAINT é recebida e não há dados a serem exibidos. Isso faz com que o Gerenciador de Grafo de Filtro gere um quadro para pintar na tela. |
EC_USERABORT | Enviado por renderizadores de vídeo para sinalizar um fechamento que o usuário solicitou (por exemplo, um usuário fechando a janela de vídeo). |
EC_VIDEO_SIZE_CHANGED | Enviado por renderizadores de vídeo sempre que uma alteração no tamanho do vídeo nativo é detectada. |
EC_WINDOW_DESTROYED | Enviado por renderizadores de vídeo quando o filtro é removido ou destruído para que os recursos que dependem do foco da janela possam ser passados para outros filtros. |
Tópicos relacionados