Partilhar via


Apoio à busca num filtro de origem

[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

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:

  1. 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.
  2. O filtro chama IPin::NewSegment para informar os 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 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:

  1. Chame IPin::BeginFlush para iniciar a liberação de dados.
  2. Pare o thread de streaming.
  3. Chame IPin::EndFlush.
  4. Reinicie o thread de streaming.
  5. Chamada IPin::NewSegment.
  6. 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.

Escrevendo filtros de código-fonte