Suporte à busca em um filtro de origem
[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 implementar a busca em um filtro de origem do Microsoft DirectShow. Ele usa o exemplo de Filtro de Bola como ponto de partida e descreve o código adicional necessário para dar suporte à busca neste filtro.
O exemplo de Filtro de Bola é um filtro de origem que cria uma bola saltitante animada. Este artigo descreve como adicionar funcionalidade de busca a esse filtro. Depois de adicionar essa funcionalidade, você pode renderizar o filtro no GraphEdit e controlar a bola arrastando o controle deslizante GraphEdit.
Este tópico contém as seguintes seções:
- Visão geral da busca no DirectShow
- Visão geral rápida do filtro de bola
- Modificando o filtro de bola para busca
- Limitações da classe CSourceSeeking
Visão geral da busca no DirectShow
Um aplicativo busca o grafo de filtro chamando um método IMediaSeeking no Gerenciador de Grafo de Filtro. Em seguida, o Gerenciador de Grafo de Filtro distribui a chamada para cada renderizador no grafo. Cada renderizador envia a chamada upstream, por meio do pino de saída do próximo filtro upstream. A chamada viaja upstream até atingir um filtro que pode executar o comando seek, normalmente um filtro de origem ou um filtro de analisador. Em geral, o filtro que origina os carimbos de data/hora também manipula a busca.
Um filtro responde a um comando seek da seguinte maneira:
- O filtro libera o grafo. Isso limpa todos os dados obsoletos do grafo, o que melhora a capacidade de resposta. Caso contrário, exemplos que foram armazenados em buffer antes do comando seek poderão ser entregues.
- O filtro chama IPin::NewSegment para informar filtros downstream da nova hora de parada, hora de início e taxa de reprodução.
- Em seguida, o filtro define o sinalizador de descontinuidade no primeiro exemplo após o comando seek.
Os carimbos de data/hora começam de zero após qualquer comando seek (incluindo alterações de taxa).
Visão geral rápida do filtro de bola
O filtro Ball é uma fonte de push, o que significa que ele usa um thread de trabalho para fornecer amostras downstream, em vez de uma fonte de pull, que espera passivamente por um filtro downstream para solicitar amostras. O filtro Ball é criado a partir da classe CSource e seu pino de saída é criado a partir da classe CSourceStream . A classe CSourceStream cria o thread de trabalho que impulsiona o fluxo de dados. Esse thread insere um loop que obtém exemplos do alocador, preenche-os com dados e entrega-os downstream.
A maior parte da ação em CSourceStream ocorre no método CSourceStream::FillBuffer , que a classe derivada implementa. O argumento para esse método é um ponteiro para o exemplo a ser entregue. A implementação do Filtro de bola de FillBuffer recupera o endereço do buffer de exemplo e desenha diretamente para o buffer definindo valores de pixel individuais. (O desenho é feito por uma classe auxiliar, CBall, para que você possa ignorar os detalhes sobre profundidades de bits, paletas e assim por diante.)
Modificando o filtro de bola para busca
Para tornar o filtro Ball pesquisável, use a classe CSourceSeeking , que foi projetada para implementar a busca em filtros com um pino de saída. Adicione a classe CSourceSeeking à lista de herança da classe CBallStream:
class CBallStream : // Defines the output pin.
public CSourceStream, public CSourceSeeking
Além disso, você precisará adicionar um inicializador para CSourceSeeking ao construtor CBallStream:
CSourceSeeking(NAME("SeekBall"), (IPin*)this, phr, &m_cSharedState),
Essa instrução chama o construtor base para CSourceSeeking. Os parâmetros são um nome, um ponteiro para o pino proprietário, um valor HRESULT e o endereço de um objeto de seção crítico. O nome é usado apenas para depuração e a macro NAME é compilada em uma cadeia de caracteres vazia em builds de varejo. O pino herda diretamente CSourceSeeking, portanto, o segundo parâmetro é um ponteiro para si mesmo, convertido em um ponteiro IPin . O valor HRESULT é ignorado na versão atual das classes base; ele permanece para compatibilidade com versões anteriores. A seção crítica protege os dados compartilhados, como a hora de início atual, a hora de parada e a taxa de reprodução.
Adicione a seguinte instrução dentro do construtor CSourceSeeking :
m_rtStop = 60 * UNITS;
A variável m_rtStop especifica o tempo de parada. Por padrão, o valor é _I64_MAX/2, que é de aproximadamente 14.600 anos. A declaração anterior o define como um mais conservador de 60 segundos.
Duas variáveis de membro adicionais devem ser adicionadas ao CBallStream:
BOOL m_bDiscontinuity; // If true, set the discontinuity flag.
REFERENCE_TIME m_rtBallPosition; // Position of the ball.
Após cada comando seek, o filtro deve definir o sinalizador de descontinuidade no próximo exemplo chamando IMediaSample::SetDiscontinuity. A variável m_bDiscontinuity acompanhará isso. A variável m_rtBallPosition especificará a posição da bola dentro do quadro de vídeo. O filtro Ball original calcula a posição do tempo de fluxo, mas o tempo de fluxo é redefinido para zero após cada comando seek. Em um fluxo que pode ser buscado, a posição absoluta é independente do tempo de fluxo.
QueryInterface
A classe CSourceSeeking implementa a interface IMediaSeeking . Para expor essa interface aos clientes, substitua o método NonDelegatingQueryInterface :
STDMETHODIMP CBallStream::NonDelegatingQueryInterface
(REFIID riid, void **ppv)
{
if( riid == IID_IMediaSeeking )
{
return CSourceSeeking::NonDelegatingQueryInterface( riid, ppv );
}
return CSourceStream::NonDelegatingQueryInterface(riid, ppv);
}
O método é chamado de "NonDelegating" devido à maneira como as classes base do DirectShow dão suporte à agregação COM (Component Object Model). Para obter mais informações, consulte o tópico "Como implementar o IUnknown" no SDK do DirectShow.
Buscando métodos
A classe CSourceSeeking mantém várias variáveis de membro relacionadas à busca.
Variável | Descrição | Valor padrão |
---|---|---|
m_rtStart | Hora de início | Zero |
m_rtStop | Hora de término | _I64_MAX/2 |
m_dRateSeeking | Taxa de reprodução | 1.0 |
A implementação CSourceSeeking de IMediaSeeking::SetPositions atualiza os horários de início e parada e chama dois métodos virtuais puros na classe derivada, CSourceSeeking::ChangeStart e CSourceSeeking::ChangeStop. A implementação de IMediaSeeking::SetRate é semelhante: atualiza a taxa de reprodução e chama o método virtual puro CSourceSeeking::ChangeRate. Em cada um desses métodos virtuais, o pin deve fazer o seguinte:
- Chame IPin::BeginFlush para começar a liberar dados.
- Interrompa o thread de streaming.
- Chame IPin::EndFlush.
- Reinicie o thread de streaming.
- Chame IPin::NewSegment.
- Defina o sinalizador de descontinuidade no próximo exemplo.
A ordem dessas etapas é crucial, pois o thread de streaming pode ser bloqueado enquanto aguarda para entregar um exemplo ou obter um novo exemplo. O método BeginFlush garante que o thread de streaming não esteja bloqueado e, portanto, não será deadlock na etapa 2. A chamada EndFlush informa os filtros downstream para esperar novos exemplos, para que eles não os rejeitem quando o thread for iniciado novamente na etapa 4.
O seguinte método privado executa as etapas 1 a 4:
void CBallStream::UpdateFromSeek()
{
if (ThreadExists())
{
DeliverBeginFlush();
// Shut down the thread and stop pushing data.
Stop();
DeliverEndFlush();
// Restart the thread and start pushing data again.
Pause();
}
}
Quando o thread de streaming é iniciado novamente, ele chama o método CSourceStream::OnThreadStartPlay . Substitua esse método para executar as etapas 5 e 6:
HRESULT CBallStream::OnThreadStartPlay()
{
m_bDiscontinuity = TRUE;
return DeliverNewSegment(m_rtStart, m_rtStop, m_dRateSeeking);
}
No método ChangeStart , defina o tempo de fluxo como zero e a posição da bola como a nova hora de início. Em seguida, chame CBallStream::UpdateFromSeek:
HRESULT CBallStream::ChangeStart( )
{
{
CAutoLock lock(CSourceSeeking::m_pLock);
m_rtSampleTime = 0;
m_rtBallPosition = m_rtStart;
}
UpdateFromSeek();
return S_OK;
}
No método ChangeStop , chame CBallStream::UpdateFromSeek se o novo tempo de parada for menor que a posição atual. Caso contrário, o tempo de parada ainda será no futuro, portanto, não há necessidade de liberar o grafo.
HRESULT CBallStream::ChangeStop( )
{
{
CAutoLock lock(CSourceSeeking::m_pLock);
if (m_rtBallPosition < m_rtStop)
{
return S_OK;
}
}
// We're already past the new stop time. Flush the graph.
UpdateFromSeek();
return S_OK;
}
Para alterações de taxa, o método CSourceSeeking::SetRate define m_dRateSeeking à nova taxa (descartando o valor antigo) antes de chamar ChangeRate. Infelizmente, se o chamador deu uma taxa inválida, por exemplo, menor que zero, é tarde demais quando ChangeRate é chamado. Uma solução é substituir SetRate e marcar por taxas válidas:
HRESULT CBallStream::SetRate(double dRate)
{
if (dRate <= 1.0)
{
return E_INVALIDARG;
}
{
CAutoLock lock(CSourceSeeking::m_pLock);
m_dRateSeeking = dRate;
}
UpdateFromSeek();
return S_OK;
}
// Now ChangeRate won't ever be called, but it's pure virtual, so it needs
// a dummy implementation.
HRESULT CBallStream::ChangeRate() { return S_OK; }
Desenho no buffer
Aqui está a versão modificada de CSourceStream::FillBuffer, a rotina que desenha a bola em cada quadro:
HRESULT CBallStream::FillBuffer(IMediaSample *pMediaSample)
{
BYTE *pData;
long lDataLen;
pMediaSample->GetPointer(&pData);
lDataLen = pMediaSample->GetSize();
{
CAutoLock cAutoLockShared(&m_cSharedState);
if (m_rtBallPosition >= m_rtStop)
{
// End of the stream.
return S_FALSE;
}
// Draw the ball in its current position.
ZeroMemory( pData, lDataLen );
m_Ball->MoveBall(m_rtBallPosition);
m_Ball->PlotBall(pData, m_BallPixel, m_iPixelSize);
// The sample times are modified by the current rate.
REFERENCE_TIME rtStart, rtStop;
rtStart = static_cast<REFERENCE_TIME>(
m_rtSampleTime / m_dRateSeeking);
rtStop = rtStart + static_cast<int>(
m_iRepeatTime / m_dRateSeeking);
pMediaSample->SetTime(&rtStart, &rtStop);
// Increment for the next loop.
m_rtSampleTime += m_iRepeatTime;
m_rtBallPosition += m_iRepeatTime;
}
pMediaSample->SetSyncPoint(TRUE);
if (m_bDiscontinuity)
{
pMediaSample->SetDiscontinuity(TRUE);
m_bDiscontinuity = FALSE;
}
return NOERROR;
}
As principais diferenças entre essa versão e o original são as seguintes:
- Conforme mencionado anteriormente, a variável m_rtBallPosition é usada para definir a posição da bola, em vez do tempo de fluxo.
- Depois de manter a seção crítica, o método verifica se a posição atual excede o tempo de parada. Nesse caso, ele retorna S_FALSE, o que sinaliza a classe base para parar de enviar dados e fornecer uma notificação de fim do fluxo.
- Os carimbos de data/hora são divididos pela taxa atual.
- Se m_bDiscontinuity for TRUE, o método definirá o sinalizador de descontinuidade no exemplo.
Há outra pequena diferença. Como a versão original depende de ter exatamente um buffer, ela preenche todo o buffer com zeros uma vez, quando o streaming começa. Depois disso, ele apenas apaga a bola de sua posição anterior. No entanto, essa otimização revela um pequeno bug no filtro Bola. Quando o método CBaseOutputPin::D ecideAllocator chama IMemInputPin::NotifyAllocator, ele define o sinalizador somente leitura como FALSE. Como resultado, o filtro downstream é livre para gravação no buffer. Uma solução é substituir DecideAllocator para que ele defina o sinalizador somente leitura como TRUE. Para simplificar, no entanto, a nova versão simplesmente remove a otimização completamente. Em vez disso, essa versão preenche o buffer com zeros sempre.
Alterações diversas
Na nova versão, essas duas linhas são removidas do construtor CBall:
m_iRandX = rand();
m_iRandY = rand();
O filtro Ball original usa esses valores para adicionar alguma aleatoriedade à posição inicial da bola. Para nossos propósitos, queremos que a bola seja determinística. Além disso, algumas variáveis foram alteradas de objetos CRefTime para variáveis REFERENCE_TIME . (A classe CRefTime é um wrapper fino para um valor REFERENCE_TIME .) Por fim, a implementação de IQualityControl::Notify foi modificada ligeiramente; você pode consultar o código-fonte para obter detalhes.
Limitações da classe CSourceSeeking
A classe CSourceSeeking não se destina a filtros com vários pinos de saída, devido a problemas com a comunicação entre pinos. Por exemplo, imagine um filtro de analisador que recebe um fluxo de áudio-vídeo intercalado, divide o fluxo em seus componentes de áudio e vídeo e entrega vídeo de um pino de saída e áudio de outro. Ambos os pinos de saída receberão todos os comandos seek, mas o filtro deve procurar apenas uma vez por comando seek. A solução é designar um dos pinos para controlar a busca e ignorar os comandos de busca recebidos pelo outro pino.
No entanto, após o comando seek, ambos os pinos devem liberar dados. Para complicar ainda mais as coisas, o comando seek acontece no thread do aplicativo, não no thread de streaming. Portanto, você deve ter certeza de que nenhum dos pinos está bloqueado e aguardando o retorno de uma chamada IMemInputPin::Receive ou pode causar um deadlock. Para obter mais informações sobre a liberação thread-safe em pinos, consulte Threads e Seções Críticas.
Tópicos relacionados