Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
[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.]
Este tópico descreve como implementar a busca em um filtro de origem do Microsoft DirectShow. Ele usa o exemplo Ball Filter como ponto de partida e descreve o código adicional necessário para dar suporte à procura nesse filtro.
A amostra do filtro de fonte Ball Filter é um filtro 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 esferas
- Modificando o filtro de bola para buscar
- Limitações da classe CSourceSeeking
Visão geral da busca no DirectShow
Um aplicativo procura o gráfico de filtro chamando um método IMediaSeeking no Filter Graph Manager. Em seguida, o Filter Graph Manager distribui a chamada para cada renderizador no gráfico. Cada renderizador envia a chamada a montante, através do pino de saída do próximo filtro a montante. A chamada segue a montante até chegar a um filtro que pode executar o comando procurar, normalmente um filtro de fonte ou um filtro de análise. Em geral, o filtro que origina os carimbos de data/hora também lida com a busca.
Um filtro responde a um comando seek da seguinte maneira:
- O filtro libera o gráfico. Isso limpa todos os dados obsoletos do gráfico, o que melhora a capacidade de resposta. Caso contrário, amostras que foram armazenadas em buffer antes do comando seek podem ser entregues.
- O filtro chama IPin::NewSegment para informar os 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 na primeira amostra após o comando seek.
Os carimbos de data/hora começam a partir do zero após qualquer comando de busca (incluindo alterações de taxa).
Visão geral rápida do filtro de esferas
O filtro Ball é uma fonte de push, o que significa que utiliza um fio de execução para entregar amostras a jusante, em contraste com uma fonte pull, que espera passivamente que um filtro a jusante solicite amostras. O filtro Ball é construído a partir da classe CSource, e o seu pino de saída é construído a partir da classe CSourceStream. O classe CSourceStream cria o thread de trabalho que orienta o fluxo de dados. Esse thread entra em um loop que obtém amostras do alocador, preenche-as com dados e as entrega a jusante.
A maior parte da ação em CSourceStream acontece no métodoCSourceStream::FillBuffer, que a classe derivada implementa. O argumento para este método é um ponteiro para a amostra a ser entregue. A implementação do filtro Ball de FillBuffer recupera o endereço do buffer de amostra 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 a busca
Para tornar o filtro Ball passível de busca, use a classe CSourceSeeking , que foi projetada para implementar a funcionalidade de busca em filtros com um pino de saída. Adicione a classe CSourceSeeking à lista de herança para a classe CBallStream:
class CBallStream : // Defines the output pin.
public CSourceStream, public CSourceSeeking
Além disso, será necessário adicionar um inicializador para CSourceSeeking ao construtor CBallStream:
CSourceSeeking(NAME("SeekBall"), (IPin*)this, phr, &m_cSharedState),
Esta instrução chama o construtor base para CSourceSeeking. Os parâmetros são um nome, um ponteiro para o pino proprietário, um valor de HRESULT e o endereço de um objeto de seção crítica. O nome é usado apenas para depuração, e a macro NAME compila para uma string vazia em compilações de produção. O pino herda diretamente de CSourceSeeking , por isso o segundo parâmetro é um ponteiro para o próprio pino, convertido para um ponteiro IPin . O valor HRESULT é ignorado na versão atual das classes base; mantém-se para compatibilidade com versões anteriores. A seção crítica protege 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 CSourceSeeking construtor:
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 define um valor 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 a partir do tempo de transmissão, mas o tempo de transmissão é redefinido para zero após cada comando de procura. Em um fluxo pesquisável, a posição absoluta é independente do tempo de fluxo.
QueryInterface
A classe CSourceSeeking implementa a interfaceIMediaSeeking. Para expor esta 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 é denominado "NonDelegating" devido à forma como as classes base do DirectShow suportam a agregação do Modelo de Objetos de Componente (COM). Para obter mais informações, consulte o tópico "Como implementar IUnknown" no SDK do DirectShow.
Buscando métodos
A classe CSourceSeeking mantém várias variáveis de membros relacionadas à busca.
| Variável | Descrição | Valor padrão |
|---|---|---|
| m_rtStart | Hora de início | Zero |
| m_rtStop | Parar o tempo | _I64_MAX / 2 |
| m_dRateSeeking | Velocidade de reprodução | 1.0 |
A CSourceSeeking implementação de IMediaSeeking::SetPositions atualiza os tempos de início e paragem e, em seguida, chama dois métodos virtuais puros da classe derivada, CSourceSeeking::ChangeStart e CSourceSeeking::ChangeStop. A implementação do IMediaSeeking::SetRate é semelhante: atualiza a taxa de reprodução e, em seguida, chama o método virtual puro CSourceSeeking::ChangeRate. Em cada um desses métodos virtuais, o pino deve fazer o seguinte:
- Chame IPin::BeginFlush para iniciar a liberação de dados.
- Pare o thread de streaming.
- Chame IPin::EndFlush.
- Reinicie o thread de streaming.
- Chamada IPin::NewSegment.
- Defina o sinalizador de descontinuidade no próximo exemplo.
A ordem dessas etapas é crucial, porque o thread de streaming pode bloquear enquanto espera para entregar uma amostra ou obter uma nova amostra. O método BeginFlush garante que o thread de streaming não seja bloqueado e, portanto, não bloqueará a etapa 2. A chamada EndFlush informa os filtros downstream para esperarem novas amostras, para que eles não as rejeitem quando a tarefa for reiniciada 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 CSourceStream::OnThreadStartPlay método. 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 stream para zero e ajuste a posição da bola para o novo tempo de início. Então 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 a nova hora de paragem for menor que a posição atual. Caso contrário, o tempo de parada ainda está no futuro, então não há necessidade de limpar o gráfico.
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étodoCSourceSeeking::SetRate define m_dRateSeeking para a nova taxa (descartando o valor antigo) antes de chamar ChangeRate. Infelizmente, se o chamador deu uma taxa inválida — por exemplo, inferior a zero — já é tarde demais no momento em que ChangeRate é chamado. Uma solução é substituir SetRate e verificar as 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 do CSourceStream::FillBuffer, a rotina que desenha a bola em cada fotograma:
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 esta versão e a original são as seguintes:
- Como mencionado anteriormente, a variável m_rtBallPosition é usada para definir a posição da bola, em vez do tempo de fluxo.
- Depois de segurar a seção crítica, o método verifica se a posição atual excede o tempo de parada. Em caso afirmativo, ele retorna S_FALSE, que sinaliza a classe base para parar de enviar dados e entregar uma notificação de fim de fluxo.
- Os carimbos de data/hora são divididos pela taxa atual.
- Se m_bDiscontinuity é TRUE, o método define o indicador de descontinuidade na amostra.
Há outra pequena diferença. Como a versão original depende de ter exatamente um buffer, ele preenche todo o buffer com zeros uma vez, quando o streaming começa. Depois disso, ele simplesmente apaga a bola da sua posição anterior. No entanto, esta otimização revela um ligeiro bug no filtro Ball. Quando o métodoCBaseOutputPin::DecideAllocator chama IMemInputPin::NotifyAllocator, ele define o sinalizador somente leitura como FALSE. Como resultado, o filtro a jusante fica livre para escrever na memória intermédia. 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, esta versão preenche o buffer com zeros cada vez.
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 os 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 invólucro fino para um valor REFERENCE_TIME.) Por fim, a implementação do IQualityControl::Notify foi ligeiramente modificada; 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 analisador que recebe um fluxo de áudio-vídeo intercalado, divide o fluxo em seus componentes de áudio e vídeo e fornece vídeo de um pino de saída e áudio de outro pino. Ambos os pinos de saída receberão todos os comandos de pesquisa, mas o filtro deve procurar apenas uma vez por cada comando de pesquisa. A solução é designar um dos pinos para controlar a busca e ignorar os comandos de busca recebidos pelo outro pino.
Após o comando seek, no entanto, ambos os pinos devem liberar os dados. Para complicar ainda mais as coisas, o comando seek acontece no thread do aplicativo, não no thread de streaming. Portanto, certifique-se de que nenhum pino está bloqueado e aguardando uma chamada IMemInputPin::Receive para retornar, do contrário, poderá causar um impasse. Para obter mais informações sobre a liberação segura de roscas em pinos, consulte Threads and Critical Sections.
Tópicos relacionados