Поделиться через


Поддержка поиска в исходном фильтре

[Функция, связанная с этой страницей DirectShow, является устаревшей функцией. Он был заменен MediaPlayer, IMFMediaEngine, и аудио/ видео захвата в Media Foundation. Эти функции оптимизированы для Windows 10 и Windows 11. Корпорация Майкрософт настоятельно рекомендует использовать в новом коде MediaPlayer, IMFMediaEngine и аудио/видеозахват в Media Foundation вместо DirectShow, когда это возможно. Корпорация Майкрософт предлагает переписать существующий код, в котором используются устаревшие API, чтобы по возможности использовать новые API.]

В этом разделе описывается, как реализовать поиск в фильтре источника Microsoft DirectShow. В нем в качестве отправной точки используется пример фильтра мяча и описывается дополнительный код, необходимый для поддержки поиска в этом фильтре.

Пример фильтра мяча — это фильтр источника, который создает анимированный мяч с подпрыгиванием. В этой статье описывается, как добавить функцию поиска в этот фильтр. После добавления этой функции вы можете отобразить фильтр в GraphEdit и управлять мячом, перетащив ползунок GraphEdit.

Этот раздел состоит из следующих подразделов.

Обзор поиска в DirectShow

Приложение ищет граф фильтра, вызывая метод IMediaSeeking в диспетчере фильтров графов. Затем диспетчер фильтров графа распределяет вызов на каждый отрисовщик в графе. Каждый отрисовщик отправляет вызов вышестоящий через выходной контакт следующего фильтра вышестоящий. Вызов перемещается вышестоящий до тех пор, пока не достигнет фильтра, который может выполнить команду seek, обычно это фильтр источника или фильтр средства синтаксического анализа. Как правило, фильтр, который является источником меток времени, также обрабатывает поиск.

Фильтр реагирует на команду seek следующим образом:

  1. Фильтр очищает граф. Это очищает все устаревшие данные из графа, что повышает скорость реагирования. В противном случае могут быть доставлены примеры, которые были помещены в буфер до выполнения команды seek.
  2. Фильтр вызывает IPin::NewSegment , чтобы сообщить подчиненным фильтрам о новом времени остановки, времени начала и скорости воспроизведения.
  3. Затем фильтр устанавливает флаг непрерывности в первом образце после команды seek.

Метки времени начинаются с нуля после любой команды seek (включая изменения скорости).

Краткий обзор фильтра мяча

Фильтр мяча является источником отправки. Это означает, что он использует рабочий поток для доставки нисходящих образцов, в отличие от источника извлечения, который пассивно ожидает, пока подчиненный фильтр запросит примеры. Фильтр Ball построен из класса CSource , а его выходной контакт — из класса CSourceStream . Класс CSourceStream создает рабочий поток, который управляет потоком данных. Этот поток входит в цикл, который получает примеры из распределителя, заполняет их данными и доставляет их ниже.

Большая часть действия в CSourceStream выполняется в методе CSourceStream::FillBuffer , который реализует производный класс. Аргумент этого метода является указателем на доставляемый образец. Реализация фильтра мяча FillBuffer извлекает адрес буфера образца и обращается непосредственно к буферу, задавая отдельные значения пикселей. (Рисование выполняется вспомогательным классом CBall, поэтому вы можете игнорировать сведения о глубине битов, палитрах и т. д.)

Изменение фильтра мяча для поиска

Чтобы сделать фильтр мяча доступным для поиска, используйте класс CSourceSeeking , который предназначен для реализации поиска в фильтрах с одним выходным закреплением. Добавьте класс CSourceSeeking в список наследования для класса CBallStream:

class CBallStream :  // Defines the output pin.
    public CSourceStream, public CSourceSeeking

Кроме того, необходимо добавить инициализатор для CSourceSeeking в конструктор CBallStream:

    CSourceSeeking(NAME("SeekBall"), (IPin*)this, phr, &m_cSharedState),

Эта инструкция вызывает базовый конструктор для CSourceSeeking. Параметры — это имя, указатель на маркер-владение, значение HRESULT и адрес объекта критического раздела. Имя используется только для отладки, а макрос NAME компилируется в пустую строку в розничных сборках. Закрепление непосредственно наследует CSourceSeeking, поэтому второй параметр является указателем на себя, приведение к указателю IPin . Значение HRESULT игнорируется в текущей версии базовых классов; он остается для совместимости с предыдущими версиями. Критический раздел защищает общие данные, такие как текущее время начала, время остановки и скорость воспроизведения.

Добавьте следующий оператор в конструктор CSourceSeeking :

m_rtStop = 60 * UNITS;

Переменная m_rtStop указывает время остановки. По умолчанию значение равно _I64_MAX / 2, что составляет примерно 14 600 лет. Предыдущая инструкция задает более консервативные 60 секунд.

В CBallStream необходимо добавить две дополнительные переменные-члены:

BOOL            m_bDiscontinuity; // If true, set the discontinuity flag.
REFERENCE_TIME  m_rtBallPosition; // Position of the ball. 

После каждой команды seek фильтр должен установить флаг несогласованности в следующем примере, вызвав метод IMediaSample::SetDiscontinuity. Переменная m_bDiscontinuity будет отслеживать это. Переменная m_rtBallPosition определяет положение мяча в видеокадре. Исходный фильтр ball вычисляет позицию из времени потока, но время потока сбрасывается до нуля после каждой команды seek. В ищущемся потоке абсолютная позиция не зависит от времени потока.

QueryInterface

Класс CSourceSeeking реализует интерфейс IMediaSeeking . Чтобы предоставить этот интерфейс клиентам, переопределите метод NonDelegatingQueryInterface :

STDMETHODIMP CBallStream::NonDelegatingQueryInterface
    (REFIID riid, void **ppv)
{
    if( riid == IID_IMediaSeeking ) 
    {
        return CSourceSeeking::NonDelegatingQueryInterface( riid, ppv );
    }
    return CSourceStream::NonDelegatingQueryInterface(riid, ppv);
}

Метод называется "NonDelegating" из-за того, как базовые классы DirectShow поддерживают агрегирование модели COM. Дополнительные сведения см. в разделе "Реализация IUnknown" в пакете SDK DirectShow.

Методы поиска

Класс CSourceSeeking поддерживает несколько переменных-членов, связанных с поиском.

Переменная Описание Значение по умолчанию
m_rtStart Время начала Ноль
m_rtStop Время остановки _I64_MAX / 2
m_dRateSeeking Скорость воспроизведения 1,0

 

Реализация CSourceSeekingIMediaSeeking::SetPositions обновляет время начала и остановки, а затем вызывает два чистых виртуальных метода в производном классе , CSourceSeeking::ChangeStart и CSourceSeeking::ChangeStop. Реализация IMediaSeeking::SetRate аналогична: она обновляет частоту воспроизведения, а затем вызывает чистый виртуальный метод CSourceSeeking::ChangeRate. В каждом из этих виртуальных методов закрепление должно выполнять следующие действия.

  1. Вызовите IPin::BeginFlush , чтобы начать очистку данных.
  2. Остановите поток потоковой передачи.
  3. Вызовите IPin::EndFlush.
  4. Перезапустите поток потоковой передачи.
  5. Вызовите IPin::NewSegment.
  6. Установите флаг непрерывности в следующем примере.

Порядок этих действий имеет решающее значение, так как поток потоковой передачи может блокироваться, пока он ожидает доставки примера или получения нового примера. Метод BeginFlush гарантирует, что поток потоковой передачи не будет заблокирован и, следовательно, не будет взаимоблокирован на шаге 2. Вызов EndFlush сообщает подчиненным фильтрам о том, что они ожидают новые примеры, поэтому они не отклоняют их при повторном запуске потока на шаге 4.

Следующий закрытый метод выполняет шаги 1–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();
    }
}

Когда поток потоковой передачи запускается снова, он вызывает метод CSourceStream::OnThreadStartPlay . Переопределите этот метод, чтобы выполнить шаги 5 и 6:

HRESULT CBallStream::OnThreadStartPlay()
{
    m_bDiscontinuity = TRUE;
    return DeliverNewSegment(m_rtStart, m_rtStop, m_dRateSeeking);
}

В методе ChangeStart задайте нулевое время потока, а для положения мяча — новое время начала. Затем вызовите CBallStream::UpdateFromSeek:

HRESULT CBallStream::ChangeStart( )
{
    {
        CAutoLock lock(CSourceSeeking::m_pLock);
        m_rtSampleTime = 0;
        m_rtBallPosition = m_rtStart;
    }
    UpdateFromSeek();
    return S_OK;
}

В методе ChangeStop вызовите CBallStream::UpdateFromSeek, если новое время остановки меньше текущей позиции. В противном случае время остановки останется в будущем, поэтому нет необходимости сбрасывать граф.

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;
}

Для изменения скорости метод CSourceSeeking::SetRate устанавливает m_dRateSeeking на новую (отменяя старое значение) перед вызовом ChangeRate. К сожалению, если вызывающий объект дал недопустимую частоту( например, меньше нуля), это слишком поздно к моменту вызова ChangeRate . Одним из решений является переопределение SetRate и проверка для допустимых тарифов:

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; }

Рисование в буфере

Ниже приведена измененная версия CSourceStream::FillBuffer, подпрограммы, которая рисует мяч на каждом кадре:

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;
}

Основные различия между этой и исходной версией:

  • Как упоминалось ранее, переменная m_rtBallPosition используется для задания положения мяча, а не времени потока.
  • После удержания критического раздела метод проверяет, превышает ли текущее положение время остановки. Если это так, он возвращает S_FALSE, который сигнализирует базовому классу о прекращении отправки данных и доставке уведомления об окончании потока.
  • Метки времени делятся на текущую ставку.
  • Если m_bDiscontinuity имеет значение TRUE, метод устанавливает флаг непрерывности для примера.

Есть еще одно незначительное различие. Так как исходная версия полагается на наличие ровно одного буфера, она заполняет весь буфер нулями один раз, когда начинается потоковая передача. После этого он просто стирает мяч из предыдущей позиции. Однако эта оптимизация показывает небольшую ошибку в фильтре Ball. Когда метод CBaseOutputPin::D ecideAllocator вызывает метод IMemInputPin::NotifyAllocator, он устанавливает флаг только для чтения значение FALSE. В результате подчиненный фильтр может свободно записывать данные в буфер. Одним из решений является переопределение DecideAllocator , чтобы установить для флага только для чтения значение TRUE. Однако для простоты новая версия просто полностью удаляет оптимизацию. Вместо этого эта версия каждый раз заполняет буфер нулями.

Прочие изменения

В новой версии эти две строки удаляются из конструктора CBall:

    m_iRandX = rand();
    m_iRandY = rand();

Исходный фильтр мяча использует эти значения, чтобы добавить некоторую случайность в начальную позицию мяча. Для наших целей мы хотим, чтобы мяч был детерминированным. Кроме того, некоторые переменные были изменены с объектов CRefTime на переменные REFERENCE_TIME . (Класс CRefTime — это тонкая оболочка для REFERENCE_TIME значения.) Наконец, реализация IQualityControl::Notify была немного изменена; Дополнительные сведения см. в исходном коде.

Ограничения класса CSourceSeeking

Класс CSourceSeeking не предназначен для фильтров с несколькими выходными контактами из-за проблем с обменом данными между контактами. Например, представьте себе фильтр синтаксического анализа, который получает чередующийся аудио-видеопоток, разделяет поток на аудио- и видеокомпоненты и доставляет видео из одного выходного пина и аудио из другого. Оба выходных контакта будут получать каждую команду seek, но фильтр должен искать только один раз для каждой команды поиска. Решение заключается в том, чтобы назначить один из контактов для управления поиском и игнорировать команды поиска, полученные другим закреплением.

Однако после выполнения команды seek оба контакта должны очистить данные. Чтобы еще больше усложнить ситуацию, команда seek выполняется в потоке приложения, а не потоке потоковой передачи. Поэтому необходимо убедиться, что ни тот, ни другой пин-код не заблокирован и ожидает возврата вызова IMemInputPin::Receive , иначе это может привести к взаимоблокировке. Дополнительные сведения о потокобезопасной очистке в контактах см. в разделе Потоки и критические разделы.

Написание исходных фильтров