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

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:

  1. 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.
  2. O filtro chama IPin::NewSegment para informar filtros downstream da nova hora de parada, hora de início e taxa de reprodução.
  3. 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:

  1. Chame IPin::BeginFlush para começar a liberar dados.
  2. Interrompa o thread de streaming.
  3. Chame IPin::EndFlush.
  4. Reinicie o thread de streaming.
  5. Chame IPin::NewSegment.
  6. 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.

Gravando filtros de origem