Как написать докладчика EVR

[Компонент, описанный на этой странице, Расширенный отрисовщик видео — это устаревшая функция. Он был заменен простой отрисовщик видео (SVR), предоставляемый через компоненты MediaPlayer и IMFMediaEngine . Чтобы воспроизвести видеоконтент, необходимо отправить данные в один из этих компонентов и разрешить им создать экземпляр нового отрисовщика видео. Эти компоненты оптимизированы для Windows 10 и Windows 11. Корпорация Майкрософт настоятельно рекомендует использовать новый код MediaPlayer или БОЛЕЕ низкий уровень API IMFMediaEngine для воспроизведения видеотрансляции в Windows вместо EVR, когда это возможно. Корпорация Майкрософт предлагает, что существующий код, использующий устаревшие API, будет перезаписан для использования новых API, если это возможно.]

В этой статье описывается, как написать пользовательский выступающий для расширенного отрисовщика видео (EVR). Настраиваемый выступающий может использоваться как с DirectShow, так и с Media Foundation; Интерфейсы и объектная модель одинаковы для обоих технологий, хотя точную последовательность операций может отличаться.

Пример кода в этом разделе адаптирован из примера EVRPresenter, который предоставляется в пакете SDK для Windows.

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

Необходимые компоненты

Перед написанием пользовательского докладчика необходимо ознакомиться со следующими технологиями:

  • Расширенный отрисовщик видео. См . расширенный отрисовщик видео.
  • Графика Direct3D. Вам не нужно понимать трехмерную графику для записи докладчика, но необходимо знать, как создать устройство Direct3D и управлять поверхностями Direct3D. Если вы не знакомы с Direct3D, ознакомьтесь с разделами "Устройства Direct3D" и "Ресурсы Direct3D" в документации по пакету SDK для графики DirectX.
  • Графы фильтров DirectShow или конвейер Media Foundation в зависимости от технологии, которую будет использовать приложение для отрисовки видео.
  • Преобразования Media Foundation. Средство смешивания EVR — это преобразование Media Foundation, а выступающий вызывает методы непосредственно на миксере.
  • Реализация COM-объектов. Выступающий — это объект COM с бесплатным потоком.

Объектная модель докладчика

В этом разделе содержится обзор объектной модели и интерфейсов докладчика.

Поток данных внутри EVR

EVR использует два компонента подключаемого модуля для отрисовки видео: миксер и выступающий. При необходимости миксер смешивает видеопотоки и отменяет видео. Выступающий рисует (или представляет) видео на отображение и расписания при рисовании каждого кадра. Приложения могут заменить любой из этих объектов пользовательской реализацией.

EVR имеет один или несколько входных потоков, а миксер имеет соответствующее количество входных потоков. Stream 0 всегда является эталонным потоком. Другие потоки — подпотоки, которые смешивает альфа-смешиватель в ссылочный поток. Ссылочный поток определяет главную частоту кадров для составного видео. Для каждого эталонного кадра миксер принимает последний кадр из каждой подпотоки, альфа-смешает их в эталонный кадр и выводит один составной кадр. Миксер также выполняет деинтерлакирование и преобразование цветов из YUV в RGB при необходимости. EVR всегда вставляет миксер в конвейер видео независимо от количества входных потоков или формата видео. На следующем рисунке показан этот процесс.

diagram showing the reference stream and substream pointing to the mixer, which points to the presenter, which points to the display

Выступающий выполняет следующие задачи:

  • Задает выходной формат в миксере. Перед началом потоковой передачи выступающий задает тип носителя в выходном потоке миксера. Этот тип носителя определяет формат составного изображения.
  • Создает устройство Direct3D.
  • Выделяет поверхности Direct3D. Смеситель щелкает составные кадры на этих поверхностях.
  • Возвращает выходные данные из миксера.
  • Расписания при представлении кадров. EVR предоставляет часы презентации, а выступающий планирует кадры в соответствии с данными часами.
  • Представляет каждый кадр с помощью Direct3D.
  • Выполняет шаг кадра и очистку.

Состояния докладчика

В любое время выступающий находится в одном из следующих состояний:

  • Запущено. Часы презентации EVR выполняются. Выступающий планирует видеокадры для презентации по мере их поступления.
  • Приостановлено. Часы презентации приостановлены. Выступающий не представляет никаких новых примеров, но сохраняет свою очередь запланированных примеров. Если получены новые примеры, выступающий добавляет их в очередь.
  • Остановлено. Часы презентации остановлены. Выступающий не карта все запланированные образцы.
  • Завершение работы. Выступающий освобождает все ресурсы, связанные с потоковой передачей, например поверхности Direct3D. Это начальное состояние докладчика, и окончательное состояние перед уничтожением докладчика.

В примере кода в этом разделе состояние докладчика представлено перечислением:

enum RENDER_STATE
{
    RENDER_STATE_STARTED = 1,
    RENDER_STATE_STOPPED,
    RENDER_STATE_PAUSED,
    RENDER_STATE_SHUTDOWN,  // Initial state.
};

Некоторые операции недопустимы, пока выступающий находится в состоянии завершения работы. Пример кода проверка для этого состояния путем вызова вспомогательного метода:

    HRESULT CheckShutdown() const
    {
        if (m_RenderState == RENDER_STATE_SHUTDOWN)
        {
            return MF_E_SHUTDOWN;
        }
        else
        {
            return S_OK;
        }
    }

Интерфейсы докладчика

Для реализации следующих интерфейсов требуется выступающий:

Интерфейс Description
МВФClockStateSink Уведомляет докладчика о изменении состояния часов EVR. См. статью о реализации IMFClockStateSink.
МВФGetService Предоставляет способ получения интерфейсов от докладчика приложения и других компонентов в конвейере.
IMFTopologyServiceLookupClient Позволяет выступающим получать интерфейсы из EVR или миксера. См. статью о реализации IMFTopologyServiceLookupClient.
МВФVideoDeviceID Гарантирует, что выступающий и миксер используют совместимые технологии. См. статью о реализации IMFVideoDeviceID.
МВФVideoPresenter Обрабатывает сообщения из EVR. См. статью о реализации МВФVideoPresenter.

 

Следующие интерфейсы являются необязательными:

Интерфейс Description
IEVRTrustedVideoPlugin Позволяет выступающим работать с защищенным носителем. Реализуйте этот интерфейс, если выступающий является доверенным компонентом, предназначенным для работы в защищенном пути носителя (PMP).
МВФRateSupport Сообщает диапазон частот воспроизведения, поддерживаемых выступающим. См. статью о реализации МВФRateSupport.
МВФVideoPositionMapper Карты координаты выходного видеокадров для координат входного видеокадры.
IQualProp Сообщает сведения о производительности. EVR использует эти сведения для управления качеством. Этот интерфейс описан в пакете SDK DirectShow.

 

Вы также можете предоставить интерфейсы для взаимодействия приложения с выступающим. Стандартный выступающий реализует интерфейс IMFVideoDisplayControl для этой цели. Вы можете реализовать этот интерфейс или определить свой собственный. Приложение получает интерфейсы от докладчика путем вызова IMFGetService::GetService в EVR. Если идентификатор GUID службы MR_VIDEO_RENDER_SERVICE, EVR передает запрос GetService выступающим.

Реализация МВФVideoDeviceID

Интерфейс IMFVideoDeviceID содержит один метод GetDeviceID, который возвращает GUID устройства. GUID устройства гарантирует, что выступающий и миксер используют совместимые технологии. Если идентификаторы guid устройства не соответствуют, EVR не может инициализировать.

Стандартный миксер и выступающий используют Direct3D 9 с GUID устройства, равным IID_IDirect3DDevice9. Если вы планируете использовать пользовательский выступающий со стандартным миксером, идентификатор GUID устройства докладчика должен быть IID_IDirect3DDevice9. При замене обоих компонентов можно определить новый GUID устройства. В оставшейся части этой статьи предполагается, что выступающий использует Direct3D 9. Ниже приведена стандартная реализация GetDeviceID:

HRESULT EVRCustomPresenter::GetDeviceID(IID* pDeviceID)
{
    if (pDeviceID == NULL)
    {
        return E_POINTER;
    }

    *pDeviceID = __uuidof(IDirect3DDevice9);
    return S_OK;
}

Метод должен завершиться успешно, даже если выступающий завершит работу.

Реализация IMFTopologyServiceLookupClient

Интерфейс IMFTopologyServiceLookupClient позволяет выступающим получать указатели интерфейса из EVR и из микшера следующим образом:

  1. Когда EVR инициализирует докладчика, он вызывает метод IMFTopologyServiceLookupClient::InitServicePointers. Аргумент — это указатель на интерфейс МВФTopologyServiceLookup EVR.
  2. Выступающий вызывает МВФTopologyServiceLookup::LookupService , чтобы получить указатели интерфейса из EVR или миксера.

Метод LookupService аналогичен методу IMFGetService::GetService. Оба метода принимают GUID службы и идентификатор интерфейса (IID) в качестве входных данных, но LookupService возвращает массив указателей интерфейса, а GetService возвращает один указатель. Однако на практике можно всегда задать размер массива 1. Запрос объекта зависит от GUID службы:

  • Если идентификатор GUID службы MR_VIDEO_RENDER_SERVICE, запрашивается EVR.
  • Если идентификатор GUID службы MR_VIDEO_MIXER_SERVICE, смешиватель запрашивается.

В реализации InitServicePointers получите следующие интерфейсы из EVR:

Интерфейс EVR Description
IMediaEventSink Предоставляет способ отправки сообщений в EVR выступающим. Этот интерфейс определен в пакете SDK DirectShow, поэтому сообщения следуют шаблону событий DirectShow, а не событий Media Foundation.
МВФКлок Представляет часы EVR. Выступающий использует этот интерфейс для планирования примеров презентации. EVR может работать без часов, поэтому этот интерфейс может быть недоступен. Если нет, пропустить код ошибки из LookupService.
Часы также реализуют интерфейс МВФTimer. В конвейере Media Foundation часы реализуют интерфейс МВФPresentationClock. Он не реализует этот интерфейс в DirectShow.

 

Получите следующие интерфейсы из миксера:

Интерфейс mixer Description
МВФTransform Позволяет выступающим взаимодействовать с миксером.
МВФVideoDeviceID Позволяет выступающим проверять GUID устройства миксера.

 

Следующий код реализует метод InitServicePointers:

HRESULT EVRCustomPresenter::InitServicePointers(
    IMFTopologyServiceLookup *pLookup
    )
{
    if (pLookup == NULL)
    {
        return E_POINTER;
    }

    HRESULT             hr = S_OK;
    DWORD               dwObjectCount = 0;

    EnterCriticalSection(&m_ObjectLock);

    // Do not allow initializing when playing or paused.
    if (IsActive())
    {
        hr = MF_E_INVALIDREQUEST;
        goto done;
    }

    SafeRelease(&m_pClock);
    SafeRelease(&m_pMixer);
    SafeRelease(&m_pMediaEventSink);

    // Ask for the clock. Optional, because the EVR might not have a clock.
    dwObjectCount = 1;

    (void)pLookup->LookupService(
        MF_SERVICE_LOOKUP_GLOBAL,   // Not used.
        0,                          // Reserved.
        MR_VIDEO_RENDER_SERVICE,    // Service to look up.
        IID_PPV_ARGS(&m_pClock),    // Interface to retrieve.
        &dwObjectCount              // Number of elements retrieved.
        );

    // Ask for the mixer. (Required.)
    dwObjectCount = 1;

    hr = pLookup->LookupService(
        MF_SERVICE_LOOKUP_GLOBAL, 0,
        MR_VIDEO_MIXER_SERVICE, IID_PPV_ARGS(&m_pMixer), &dwObjectCount
        );

    if (FAILED(hr))
    {
        goto done;
    }

    // Make sure that we can work with this mixer.
    hr = ConfigureMixer(m_pMixer);
    if (FAILED(hr))
    {
        goto done;
    }

    // Ask for the EVR's event-sink interface. (Required.)
    dwObjectCount = 1;

    hr = pLookup->LookupService(
        MF_SERVICE_LOOKUP_GLOBAL, 0,
        MR_VIDEO_RENDER_SERVICE, IID_PPV_ARGS(&m_pMediaEventSink),
        &dwObjectCount
        );

    if (FAILED(hr))
    {
        goto done;
    }

    // Successfully initialized. Set the state to "stopped."
    m_RenderState = RENDER_STATE_STOPPED;

done:
    LeaveCriticalSection(&m_ObjectLock);
    return hr;
}

Если указатели интерфейса, полученные из LookupService, больше не допустимы, EVR вызывает МВФTopologyServiceLookupClient::ReleaseServicePointers. В этом методе выпустите все указатели интерфейса и задайте состояние докладчика для завершения работы:

HRESULT EVRCustomPresenter::ReleaseServicePointers()
{
    // Enter the shut-down state.
    EnterCriticalSection(&m_ObjectLock);

    m_RenderState = RENDER_STATE_SHUTDOWN;

    LeaveCriticalSection(&m_ObjectLock);

    // Flush any samples that were scheduled.
    Flush();

    // Clear the media type and release related resources.
    SetMediaType(NULL);

    // Release all services that were acquired from InitServicePointers.
    SafeRelease(&m_pClock);
    SafeRelease(&m_pMixer);
    SafeRelease(&m_pMediaEventSink);

    return S_OK;
}

EVR вызывает ReleaseServicePointers по различным причинам, в том числе:

  • Отключение или повторное подключение закреплений (DirectShow) или добавление или удаление приемников потоков (Media Foundation).
  • Изменение формата.
  • Установка новых часов.
  • Окончательное завершение работы EVR.

Во время существования докладчика EVR может вызывать InitServicePointers и ReleaseServicePointers несколько раз.

Реализация МВФVideoPresenter

Интерфейс МВФVideoPresenter наследует МВФClockStateSink и добавляет два метода:

Метод Description
GetCurrentMediaType Возвращает тип мультимедиа составных видеокадров.
ProcessMessage Сигнализирует выступающим выполнять различные действия.

 

Метод GetCurrentMediaType возвращает тип носителя докладчика. (Дополнительные сведения о настройке типа носителя см. в разделе Переговорные форматы.) Тип носителя возвращается в виде указателя интерфейса IMFVideoMediaType. В следующем примере предполагается, что выступающий сохраняет тип мультимедиа в качестве указателя МВФMediaType. Чтобы получить интерфейс IMFVideoMediaType из типа мультимедиа, вызовите QueryInterface:

HRESULT EVRCustomPresenter::GetCurrentMediaType(
    IMFVideoMediaType** ppMediaType
    )
{
    HRESULT hr = S_OK;

    if (ppMediaType == NULL)
    {
        return E_POINTER;
    }

    *ppMediaType = NULL;

    EnterCriticalSection(&m_ObjectLock);

    hr = CheckShutdown();
    if (FAILED(hr))
    {
        goto done;
    }

    if (m_pMediaType == NULL)
    {
        hr = MF_E_NOT_INITIALIZED;
        goto done;
    }

    hr = m_pMediaType->QueryInterface(IID_PPV_ARGS(ppMediaType));

done:
    LeaveCriticalSection(&m_ObjectLock);
    return hr;
}

Метод ProcessMessage — это основной механизм взаимодействия EVR с выступающим. Определены следующие сообщения. Сведения о реализации каждого сообщения приведены в оставшейся части этого раздела.

Message Description
MFVP_MESSAGE_INVALIDATEMEDIATYPE Тип выходного носителя миксера недопустим. Выступающий должен договориться о новом типе носителя с миксером. См . форматы переговоров.
MFVP_MESSAGE_BEGINSTREAMING Потоковая передача началась. В этом сообщении не требуется определенное действие, но его можно использовать для выделения ресурсов.
MFVP_MESSAGE_ENDSTREAMING Потоковая передача закончилась. Выпустите все ресурсы, выделенные в ответ на сообщение MFVP_MESSAGE_BEGINSTREAMING .
MFVP_MESSAGE_PROCESSINPUTNOTIFY Миксер получил новый входной образец и может иметь возможность создать новый выходной кадр. Выступающий должен вызвать МВФTransform::P rocessOutput на миксере. См. раздел " Выходные данные обработки".
MFVP_MESSAGE_ENDOFSTREAM Презентация закончилась. См . раздел "Конец потока".
MFVP_MESSAGE_FLUSH EVR очищает данные в конвейере отрисовки. Выступающий должен не карта любые видеокадры, запланированные для презентации.
MFVP_MESSAGE_STEP Запрашивает выступающий шаг вперед N кадров. Выступающий должен вывести карта следующие кадры N-1 и отобразить Nth кадр. См . шаг кадра.
MFVP_MESSAGE_CANCELSTEP Отменяет шаг кадра.

 

Реализация IMFClockStateSink

Выступающий должен реализовать интерфейс МВФClockStateSink в рамках его реализации МВФVideoPresenter, который наследует МВФClockStateSink. EVR использует этот интерфейс для уведомления докладчика при изменении состояния часов EVR. Дополнительные сведения о состояниях часов см. в разделе "Часы презентации".

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

Метод Description
OnClockStart
  1. Задайте для запуска состояние докладчика.
  2. Если llClockStartOffset не PRESENTATION_CURRENT_POSITION, смыть очередь примеров докладчика. (Это эквивалентно получению MFVP_MESSAGE_FLUSH сообщение.)
  3. Если предыдущий запрос на шаг кадра по-прежнему ожидается, обработайте запрос (см . шаг кадра). В противном случае попробуйте обработать выходные данные из миксера (см. раздел "Выходные данные обработки".
OnClockStop
  1. Задайте состояние докладчика для остановки.
  2. Очистка очереди примеров докладчика.
  3. Отмена любой ожидающей операции шага кадра.
OnClockPause Задайте состояние докладчика для приостановки.
OnClockRestart Обработайте ту же, что и OnClockStart , но не очищайте очередь примеров.
OnClockSetRate
  1. Если скорость изменяется с нуля на ненулевое, отмена шага кадра.
  2. Сохраните новую частоту часов. Частота часов влияет на представление выборок. Дополнительные сведения см. в разделе "Примеры планирования".

 

Реализация МВФRateSupport

Для поддержки скорости воспроизведения, отличной от 1× скорости, выступающий должен реализовать интерфейс МВФRateSupport . Ниже приведены некоторые рекомендации по реализации методов в этом интерфейсе. Все методы должны завершиться сбоем после завершения работы докладчика. Дополнительные сведения об этом интерфейсе см. в разделе "Управление скоростью".

значение Описание
GetSlowestRate Верните ноль, чтобы указать минимальную скорость воспроизведения.
GetFastestRate Для нетоненого воспроизведения скорость воспроизведения не должна превышать частоту обновления монитора: максимальная = скорость обновления (Гц) / частота кадров видео (fps). Частота кадров видео указывается в типе носителя докладчика.
Для утонченного воспроизведения скорость воспроизведения не связана; возвращает значение FLT_MAX. На практике источник и декодатор будут ограничивающими факторами во время тонкого воспроизведения.
Для обратного воспроизведения возвращает отрицательный показатель максимальной скорости.
IsRateSupported Возвращает MF_E_UNSUPPORTED_RATE, если абсолютное значение flRate превышает максимальную скорость воспроизведения докладчика. Вычислите максимальную скорость воспроизведения, как описано для GetFastestRate.

 

В следующем примере показано, как реализовать метод GetFastestRate:

float EVRCustomPresenter::GetMaxRate(BOOL bThin)
{
    // Non-thinned:
    // If we have a valid frame rate and a monitor refresh rate, the maximum
    // playback rate is equal to the refresh rate. Otherwise, the maximum rate
    // is unbounded (FLT_MAX).

    // Thinned: The maximum rate is unbounded.

    float   fMaxRate = FLT_MAX;
    MFRatio fps = { 0, 0 };
    UINT    MonitorRateHz = 0;

    if (!bThin && (m_pMediaType != NULL))
    {
        GetFrameRate(m_pMediaType, &fps);
        MonitorRateHz = m_pD3DPresentEngine->RefreshRate();

        if (fps.Denominator && fps.Numerator && MonitorRateHz)
        {
            // Max Rate = Refresh Rate / Frame Rate
            fMaxRate = (float)MulDiv(
                MonitorRateHz, fps.Denominator, fps.Numerator);
        }
    }

    return fMaxRate;
}

В предыдущем примере вызывается вспомогательный метод GetMaxRate для вычисления максимальной скорости воспроизведения вперед:

В следующем примере показано, как реализовать метод IsRateSupported:

HRESULT EVRCustomPresenter::IsRateSupported(
    BOOL bThin,
    float fRate,
    float *pfNearestSupportedRate
    )
{
    EnterCriticalSection(&m_ObjectLock);

    float   fMaxRate = 0.0f;
    float   fNearestRate = fRate;  // If we support fRate, that is the nearest.

    HRESULT hr = CheckShutdown();
    if (FAILED(hr))
    {
        goto done;
    }

    // Find the maximum forward rate.
    // Note: We have no minimum rate (that is, we support anything down to 0).
    fMaxRate = GetMaxRate(bThin);

    if (fabsf(fRate) > fMaxRate)
    {
        // The (absolute) requested rate exceeds the maximum rate.
        hr = MF_E_UNSUPPORTED_RATE;

        // The nearest supported rate is fMaxRate.
        fNearestRate = fMaxRate;
        if (fRate < 0)
        {
            // Negative for reverse playback.
            fNearestRate = -fNearestRate;
        }
    }

    // Return the nearest supported rate.
    if (pfNearestSupportedRate != NULL)
    {
        *pfNearestSupportedRate = fNearestRate;
    }

done:
    LeaveCriticalSection(&m_ObjectLock);
    return hr;
}

Отправка событий в EVR

Выступающий должен уведомить EVR о различных событиях. Для этого он использует интерфейс IMediaEventSink EVR, полученный при вызове метода IMFTopologyServiceLookupClient::InitServicePointers в EVR. (The Интерфейс IMediaEventSink изначально является интерфейсом DirectShow, но используется как в DirectShow EVR, так и в Media Foundation.) В следующем коде показано, как отправить событие в EVR:

    // NotifyEvent: Send an event to the EVR through its IMediaEventSink interface.
    void NotifyEvent(long EventCode, LONG_PTR Param1, LONG_PTR Param2)
    {
        if (m_pMediaEventSink)
        {
            m_pMediaEventSink->Notify(EventCode, Param1, Param2);
        }
    }

В следующей таблице перечислены события, отправляемые выступающим, а также параметры события.

Мероприятие Description
EC_COMPLETE Выступающий завершил отрисовку всех кадров после сообщения MFVP_MESSAGE_ENDOFSTREAM.
  • Param1: HRESULT, указывающий состояние операции.
  • Param2: не используется.
Дополнительные сведения см. в разделе "Конец потока".
EC_DISPLAY_CHANGED Устройство Direct3D изменилось.
  • Param1: не используется.
  • Param2: не используется.
Дополнительные сведения см. в разделе "Управление устройством Direct3D".
EC_ERRORABORT Произошла ошибка, требующая остановки потоковой передачи.
  • Param1: HRESULT , указывающий на ошибку, которая произошла.
  • Param2: не используется.
EC_PROCESSING_LATENCY Указывает время, которое принимает выступающий для отрисовки каждого кадра. (Необязательно.)
  • Param1: указатель на константное значение LONGLONG , содержащее время обработки кадра в 100-наносекундах.
  • Param2: не используется.
Дополнительные сведения см. в разделе "Обработка выходных данных".
EC_SAMPLE_LATENCY Указывает текущее время задержки в примерах отрисовки. Если значение положительное, примеры находятся за расписанием. Если значение отрицательное, выборки опережают расписание. (Необязательно.)
  • Param1: указатель на константное значение LONGLONG , содержащее время задержки в 100-наносекундах.
  • Param2: не используется.
EC_SCRUB_TIME Отправлен сразу после EC_STEP_COMPLETE , если скорость воспроизведения равна нулю. Это событие содержит метку времени отображаемого кадра.
  • Param1: более низкие 32 бита метки времени.
  • Param2: верхние 32 бита метки времени.
Дополнительные сведения см. в разделе "Шаг кадра".
EC_STEP_COMPLETE Выступающий завершил или отменил шаг кадра.
- Param1: не используется.
- Param2: не используется.
Дополнительные сведения см. в разделе "Шаг кадра".
Примечание. Предыдущая версия документации, описанная в Param1 , неправильно описана. Этот параметр не используется для этого события.

 

Форматы переговоров

Каждый раз, когда выступающий получает сообщение MFVP_MESSAGE_INVALIDATEMEDIATYPE из EVR, он должен задать выходной формат в миксере следующим образом:

  1. Вызов МВФTransform::GetOutputAvailableType на миксере, чтобы получить возможный тип выходных данных. Этот тип описывает формат, который смешиватель может производить с учетом входных потоков и возможностей обработки видео на графическом устройстве.

  2. Проверьте, может ли выступающий использовать этот тип носителя в качестве формата отрисовки. Ниже приведены некоторые проверка, хотя реализация может иметь свои собственные требования:

    • Видео должно быть распаковлено.
    • Видео должно иметь только прогрессивные кадры. Убедитесь, что атрибут MF_MT_INTERLACE_MODE равен MFVideoInterlace_Progressive.
    • Формат должен быть совместим с устройством Direct3D.

    Если тип недопустим, вернитесь к шагу 1 и получите следующий предлагаемый тип миксера.

  3. Создайте новый тип носителя, который является клоном исходного типа, а затем измените следующие атрибуты:

    • Задайте атрибут MF_MT_FRAME_SIZE равным ширине и высоте, которую вы хотите выделить для поверхностей Direct3D.
    • Задайте для атрибута MF_MT_PAN_SCAN_ENABLED значение FALSE.
    • Задайте атрибут MF_MT_PIXEL_ASPECT_RATIO равным пару отображения (обычно 1:1).
    • Задайте геометрическую диафрагму (MF_MT_GEOMETRIC_APERTURE атрибут) равным прямоугольнику в поверхности Direct3D. Когда миксер создает выходной кадр, он щелкает исходное изображение на этот прямоугольник. Геометрическая диафрагма может быть такой же большой, как поверхность, или она может быть подрезкой внутри поверхности. Дополнительные сведения см. в разделе "Исходные и целевые прямоугольники".
  4. Чтобы проверить, будет ли миксер принимать измененный тип вывода, вызовите МВФTransform::SetOutputType с флагом MFT_SET_TYPE_TEST_ONLY . Если миксер отклоняет тип, вернитесь к шагу 1 и получите следующий тип.

  5. Выделите пул поверхностей Direct3D, как описано в разделе "Выделение поверхностей Direct3D". Миксер будет использовать эти поверхности при рисовании составных видеокадров.

  6. Задайте выходной тип в миксере, вызвав SetOutputType без флагов. Если первый вызов SetOutputType выполнен на шаге 4, метод должен выполниться повторно.

Если смешиватель выходит из типов, метод GetOutputAvailableType возвращает MF_E_NO_MORE_TYPES. Если выступающий не может найти подходящий тип вывода для миксера, поток не может быть отрисовывается. В этом случае DirectShow или Media Foundation могут попробовать другой формат потока. Поэтому выступающий может получать несколько MFVP_MESSAGE_INVALIDATEMEDIATYPE сообщений в строке до тех пор, пока не будет найден допустимый тип.

Миксер автоматически буквирует видео, учитывая пропорции пикселей (PAR) источника и назначения. Для наилучших результатов ширина поверхности и высота и геометрическая диафрагма должны быть равны фактическому размеру, который будет отображаться на дисплее. На следующем рисунке показан этот процесс.

diagram showing a composited fram leading to a direct3d surface, which leads to a window

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

HRESULT EVRCustomPresenter::RenegotiateMediaType()
{
    HRESULT hr = S_OK;
    BOOL bFoundMediaType = FALSE;

    IMFMediaType *pMixerType = NULL;
    IMFMediaType *pOptimalType = NULL;
    IMFVideoMediaType *pVideoType = NULL;

    if (!m_pMixer)
    {
        return MF_E_INVALIDREQUEST;
    }

    // Loop through all of the mixer's proposed output types.
    DWORD iTypeIndex = 0;
    while (!bFoundMediaType && (hr != MF_E_NO_MORE_TYPES))
    {
        SafeRelease(&pMixerType);
        SafeRelease(&pOptimalType);

        // Step 1. Get the next media type supported by mixer.
        hr = m_pMixer->GetOutputAvailableType(0, iTypeIndex++, &pMixerType);
        if (FAILED(hr))
        {
            break;
        }

        // From now on, if anything in this loop fails, try the next type,
        // until we succeed or the mixer runs out of types.

        // Step 2. Check if we support this media type.
        if (SUCCEEDED(hr))
        {
            // Note: None of the modifications that we make later in CreateOptimalVideoType
            // will affect the suitability of the type, at least for us. (Possibly for the mixer.)
            hr = IsMediaTypeSupported(pMixerType);
        }

        // Step 3. Adjust the mixer's type to match our requirements.
        if (SUCCEEDED(hr))
        {
            hr = CreateOptimalVideoType(pMixerType, &pOptimalType);
        }

        // Step 4. Check if the mixer will accept this media type.
        if (SUCCEEDED(hr))
        {
            hr = m_pMixer->SetOutputType(0, pOptimalType, MFT_SET_TYPE_TEST_ONLY);
        }

        // Step 5. Try to set the media type on ourselves.
        if (SUCCEEDED(hr))
        {
            hr = SetMediaType(pOptimalType);
        }

        // Step 6. Set output media type on mixer.
        if (SUCCEEDED(hr))
        {
            hr = m_pMixer->SetOutputType(0, pOptimalType, 0);

            assert(SUCCEEDED(hr)); // This should succeed unless the MFT lied in the previous call.

            // If something went wrong, clear the media type.
            if (FAILED(hr))
            {
                SetMediaType(NULL);
            }
        }

        if (SUCCEEDED(hr))
        {
            bFoundMediaType = TRUE;
        }
    }

    SafeRelease(&pMixerType);
    SafeRelease(&pOptimalType);
    SafeRelease(&pVideoType);

    return hr;
}

Дополнительные сведения о типах видеофайла см. в разделе "Типы мультимедиа видео".

Управление устройством Direct3D

Выступающий создает устройство Direct3D и обрабатывает любую потерю устройства во время потоковой передачи. Выступающий также размещает диспетчер устройств Direct3D, который предоставляет способ использования того же устройства другими компонентами. Например, средство смешивания использует устройство Direct3D для смешивания вложенных потоков, деинтерлакирования и изменения цвета. Декодеры могут использовать устройство Direct3D для ускорения декодирования видео. (Дополнительные сведения об ускорениях видео см. в разделе Ускорение видео DirectX 2.0.)

Чтобы настроить устройство Direct3D, выполните следующие действия.

  1. Создайте объект Direct3D, вызвав Direct3DCreate9 или Direct3DCreate9Ex.
  2. Создайте устройство, вызвав IDirect3D9::CreateDevice или IDirect3D9Ex::CreateDevice.
  3. Создайте диспетчер устройств, вызвав DXVA2CreateDirect3DDeviceManager9.
  4. Задайте устройство в диспетчере устройств, вызвав IDirect3DDeviceManager9::ResetDevice.

Если другому компоненту конвейера требуется диспетчер устройств, он вызывает МВФGetService::GetService в EVR, указав MR_VIDEO_ACCELERATION_SERVICE для GUID службы. EVR передает запрос выступающим. После получения указателя IDirect3DeviceManager9 он может получить дескриптор устройства, вызвав IDirect3DeviceManager9::OpenDeviceHandle. Когда объекту нужно использовать устройство, он передает дескриптор устройства методу IDirect3DeviceManager9::LockDevice, который возвращает указатель IDirect3Device9.

После создания устройства, если выступающий уничтожает устройство и создает новый, выступающий должен снова вызвать ResetDevice. Метод ResetDevice недействителен для всех существующих дескрипторов устройств, что приводит к возврату DXVA2_E_NEW_VIDEO_DEVICE LockDevice. Этот код ошибки сигнализирует другим объектам с помощью устройства, которое они должны открыть новый дескриптор устройства. Дополнительные сведения об использовании диспетчера устройств см. в диспетчер устройств Direct3D.

Выступающий может создать устройство в окне или в полноэкранном монопольном режиме. В режиме окна следует указать приложение, чтобы указать окно видео. Стандартный выступающий реализует метод IMFVideoDisplayControl::SetVideoWindow для этой цели. При первом создании докладчика необходимо создать устройство. Как правило, вы не будете знать все параметры устройства в настоящее время, например окно или формат обратного буфера. Вы можете создать временное устройство и заменить его позже>. просто не забудьте вызвать ResetDevice в диспетчере устройств.

Если вы создаете новое устройство или вызываете IDirect3Device9::Reset или IDirect3DDevice9Ex::ResetEx на существующем устройстве, отправьте событие EC_DISPLAY_CHANGED в EVR. Это событие уведомляет EVR о повторном определении типа носителя. EVR игнорирует параметры события для этого события.

Выделение поверхностей Direct3D

После того как выступающий задает тип носителя, он может выделить поверхности Direct3D, которые миксер будет использовать для записи видеокадров. Поверхность должна соответствовать типу носителя докладчика:

  • Формат поверхности должен соответствовать подтипу носителя. Например, если подтип MFVideoFormat_RGB24, формат поверхности должен быть D3DFMT_X8R8G8B8. Дополнительные сведения о подтипах и форматах Direct3D см. в разделе Идентификаторы GUID подтипа видео.
  • Ширина и высота поверхности должны соответствовать измерениям, заданным в атрибуте MF_MT_FRAME_SIZE типа носителя.

Рекомендуемый способ выделения поверхностей зависит от того, запускает ли выступающий окно или полноэкранный режим.

Если устройство Direct3D отображается в окне, можно создать несколько цепочек буферов, каждый из которых имеет один буфер назад. С помощью этого подхода можно независимо представить каждую поверхность, так как представление одной цепочки буферов не будет мешать другим цепочкам буферов. Миксер может записывать данные на поверхность, а другая поверхность запланирована на презентацию.

Сначала определите, сколько цепочек буферов нужно создать. Рекомендуется не менее трех. Для каждой цепочки буферов сделайте следующее:

  1. Вызовите IDirect3Device9::CreateAdditionalSwapChain , чтобы создать цепочку буферов.
  2. Вызовите IDirect3DSwapChain9::GetBackBuffer , чтобы получить указатель на обратную буферную область цепочки буферов.
  3. Вызовите MFCreateVideoSampleFromSurface и передайте указатель на поверхность. Эта функция возвращает указатель на образец видео. Образец видео реализует интерфейс IMFSample, и выступающий использует этот интерфейс для доставки поверхности в миксер, когда выступающий вызывает метод IMFTransform::P rocessOutput. Дополнительные сведения об объекте примера видео см. в разделе "Примеры видео".
  4. Сохраните указатель МВФSample в очереди. Выступающий извлекнет примеры из этой очереди во время обработки, как описано в разделе "Выходные данные обработки".
  5. Сохраните ссылку на указатель IDirect3DSwapChain9 , чтобы цепочка буферов не была выпущена.

В полноэкранном эксклюзивном режиме устройство не может иметь несколько цепочек буферов. Эта цепочка буферов создается неявно при создании полноэкранного устройства. Цепочка буферов может иметь несколько буферов назад. К сожалению, если во время записи в другой буфер обратной цепочки отсутствует простой способ координации двух операций. Это связано с тем, как Direct3D реализует перевернутую поверхность. При вызове Present драйвер графики обновляет указатели поверхности в графической памяти. Если при вызове Present вы держите любые указатели IDirect3DSurface9, они будут указывать на разные буферы после возврата вызова Present.

Самый простой вариант — создать один пример видео для цепочки буферов. Если этот параметр выбран, выполните те же действия, которые заданы для режима окна. Единственное различие заключается в том, что пример очереди содержит один пример видео. Другим вариантом является создание внеэкранных поверхностей, а затем перерезать их в задний буфер. Создаваемые поверхности должны поддерживать метод IDirectXVideoProcessor::VideoProcessBlt , который смешиватель использует для составных выходных кадров.

Примеры отслеживания

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

Чтобы упростить отслеживание состояния каждого примера, объект видео реализует интерфейс IMFTrackedSample. Этот интерфейс можно использовать следующим образом:

  1. Реализуйте интерфейс МВФAsyncCallback в выступающих.

  2. Перед размещением примера в запланированной очереди запросите объект видео для интерфейса IMFTrackedSample.

  3. Вызов МВФTrackedSample::SetAllocator с указателем на интерфейс обратного вызова.

  4. Когда образец готов к презентации, удалите его из запланированной очереди, представить его и освободить все ссылки на образец.

  5. В примере вызывается обратный вызов. (Пример объекта не удаляется в этом случае, так как он содержит количество ссылок на себя до вызова обратного вызова.)

  6. В обратном вызове верните образец в доступную очередь.

Выступающий не требуется использовать IMFTrackedSample для отслеживания примеров; вы можете реализовать любую технику, которая лучше всего подходит для вашего дизайна. Одним из преимуществ МВФTrackedSample является то, что вы можете переместить функции планирования и отрисовки докладчика в вспомогательные объекты, и эти объекты не нуждаются в каком-либо специальном механизме для обратного вызова выступающим при выпуске примеров видео, так как образец объекта предоставляет этот механизм.

В следующем коде показано, как задать обратный вызов:

HRESULT EVRCustomPresenter::TrackSample(IMFSample *pSample)
{
    IMFTrackedSample *pTracked = NULL;

    HRESULT hr = pSample->QueryInterface(IID_PPV_ARGS(&pTracked));

    if (SUCCEEDED(hr))
    {
        hr = pTracked->SetAllocator(&m_SampleFreeCB, NULL);
    }

    SafeRelease(&pTracked);
    return hr;
}

В обратном вызове вызовите МВФAsyncResult::GetObject в асинхронном объекте результата, чтобы получить указатель на пример:

HRESULT EVRCustomPresenter::OnSampleFree(IMFAsyncResult *pResult)
{
    IUnknown *pObject = NULL;
    IMFSample *pSample = NULL;
    IUnknown *pUnk = NULL;

    // Get the sample from the async result object.
    HRESULT hr = pResult->GetObject(&pObject);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pObject->QueryInterface(IID_PPV_ARGS(&pSample));
    if (FAILED(hr))
    {
        goto done;
    }

    // If this sample was submitted for a frame-step, the frame step operation
    // is complete.

    if (m_FrameStep.state == FRAMESTEP_SCHEDULED)
    {
        // Query the sample for IUnknown and compare it to our cached value.
        hr = pSample->QueryInterface(IID_PPV_ARGS(&pUnk));
        if (FAILED(hr))
        {
            goto done;
        }

        if (m_FrameStep.pSampleNoRef == (DWORD_PTR)pUnk)
        {
            // Notify the EVR.
            hr = CompleteFrameStep(pSample);
            if (FAILED(hr))
            {
                goto done;
            }
        }

        // Note: Although pObject is also an IUnknown pointer, it is not
        // guaranteed to be the exact pointer value returned through
        // QueryInterface. Therefore, the second QueryInterface call is
        // required.
    }

    /*** Begin lock ***/

    EnterCriticalSection(&m_ObjectLock);

    UINT32 token = MFGetAttributeUINT32(
        pSample, MFSamplePresenter_SampleCounter, (UINT32)-1);

    if (token == m_TokenCounter)
    {
        // Return the sample to the sample pool.
        hr = m_SamplePool.ReturnSample(pSample);
        if (SUCCEEDED(hr))
        {
            // A free sample is available. Process more data if possible.
            (void)ProcessOutputLoop();
        }
    }

    LeaveCriticalSection(&m_ObjectLock);

    /*** End lock ***/

done:
    if (FAILED(hr))
    {
        NotifyEvent(EC_ERRORABORT, hr, 0);
    }
    SafeRelease(&pObject);
    SafeRelease(&pSample);
    SafeRelease(&pUnk);
    return hr;
}

Обработка выходных данных

Каждый раз, когда миксер получает новый входной образец, EVR отправляет MFVP_MESSAGE_PROCESSINPUTNOTIFY сообщение выступающим. Это сообщение указывает на то, что смешиватель может иметь новый видеокадр для доставки. В ответ выступающий вызывает МВФTransform::P rocessOutput на миксере. Если метод выполнен успешно, выступающий планирует пример презентации.

Чтобы получить выходные данные из миксера, выполните следующие действия.

  1. Проверьте состояние часов. Если часы приостановлены, пропустите сообщение MFVP_MESSAGE_PROCESSINPUTNOTIFY , если это не первый видеокадр. Если часы выполняются, или если это первый видеокадр, продолжайте.

  2. Получите пример из очереди доступных примеров. Если очередь пуста, это означает, что все выделенные образцы в настоящее время запланированы для представления. В этом случае игнорируйте сообщение MFVP_MESSAGE_PROCESSINPUTNOTIFY в настоящее время. Когда будет доступен следующий пример, повторите описанные здесь действия.

  3. (Необязательно.) Если часы доступны, получите текущее время часов (T1), вызвав IMFClock::GetCorrelatedTime.

  4. Вызов МВФTransform::P rocessOutput на миксере. Если ProcessOutput выполнен успешно, пример содержит видеокадр. Если метод завершается ошибкой, проверка код возврата. Следующие коды ошибок из ProcessOutput не являются критическими сбоями:

    Код ошибки Description
    MF_E_TRANSFORM_NEED_MORE_INPUT Миксер требует больше входных данных, прежде чем он сможет создать новый выходной кадр.
    Если вы получите этот код ошибки, проверка, достигла ли EVR конца потока и отвечает соответствующим образом, как описано в конце потока. В противном случае пропустите это сообщение MF_E_TRANSFORM_NEED_MORE_INPUT . EVR отправит еще один, когда миксер получает больше входных данных.
    MF_E_TRANSFORM_STREAM_CHANGE Тип вывода миксера стал недопустимым, возможно, из-за изменения формата вышестоящий.
    Если вы получите этот код ошибки, задайте тип носителя докладчика значение NULL. EVR запросит новый формат.
    MF_E_TRANSFORM_TYPE_NOT_SET Для миксера требуется новый тип носителя.
    Если вы получите этот код ошибки, переназовите выходной тип миксера, как описано в форматах переговоров.

     

    Если ProcessOutput успешно выполнен, продолжайте работу.

  5. (Необязательно.) Если часы доступны, получите текущее время (T2). Объем задержки, введенной миксером, равен (T2 - T1). Отправьте событие EC_PROCESSING_LATENCY с этим значением в EVR. EVR использует это значение для контроля качества. Если нет часов, нет причин отправлять событие EC_PROCESSING_LATENCY .

  6. (Необязательно.) Запросите пример ДЛЯ МВФTrackedSample и вызов МВФTrackedSample::SetAllocator , как описано в примерах отслеживания.

  7. Запланируйте пример презентации.

Эта последовательность шагов может завершиться до того, как выступающий получает любые выходные данные из миксера. Чтобы убедиться, что запросы не удаляются, следует повторить те же действия, когда происходит следующее:

  • Вызывается метод МВФClockStateSink::OnClockStart или IMFClockStateSink::OnClockStart. Это обрабатывает ситуацию, когда миксер пропускает входные данные, так как часы приостановлены (шаг 1).
  • Вызывается обратный вызов МВФTrackedSample . Это обрабатывает случай, когда миксер получает входные данные, но все примеры видео докладчика используются (шаг 2).

В следующих нескольких примерах кода приведены более подробные сведения об этих шагах. Выступающий ProcessInputNotify вызывает метод (показанный в следующем примере) при получении сообщения MFVP_MESSAGE_PROCESSINPUTNOTIFY .

//-----------------------------------------------------------------------------
// ProcessInputNotify
//
// Attempts to get a new output sample from the mixer.
//
// This method is called when the EVR sends an MFVP_MESSAGE_PROCESSINPUTNOTIFY
// message, which indicates that the mixer has a new input sample.
//
// Note: If there are multiple input streams, the mixer might not deliver an
// output sample for every input sample.
//-----------------------------------------------------------------------------

HRESULT EVRCustomPresenter::ProcessInputNotify()
{
    HRESULT hr = S_OK;

    // Set the flag that says the mixer has a new sample.
    m_bSampleNotify = TRUE;

    if (m_pMediaType == NULL)
    {
        // We don't have a valid media type yet.
        hr = MF_E_TRANSFORM_TYPE_NOT_SET;
    }
    else
    {
        // Try to process an output sample.
        ProcessOutputLoop();
    }
    return hr;
}

Этот ProcessInputNotify метод задает логический флаг для записи того, что миксер имеет новые входные данные. Затем вызывает метод, показанный ProcessOutputLoop в следующем примере. Этот метод пытается извлечь как можно больше примеров из миксера:

void EVRCustomPresenter::ProcessOutputLoop()
{
    HRESULT hr = S_OK;

    // Process as many samples as possible.
    while (hr == S_OK)
    {
        // If the mixer doesn't have a new input sample, break from the loop.
        if (!m_bSampleNotify)
        {
            hr = MF_E_TRANSFORM_NEED_MORE_INPUT;
            break;
        }

        // Try to process a sample.
        hr = ProcessOutput();

        // NOTE: ProcessOutput can return S_FALSE to indicate it did not
        // process a sample. If so, break out of the loop.
    }

    if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT)
    {
        // The mixer has run out of input data. Check for end-of-stream.
        CheckEndOfStream();
    }
}

Метод, показанный ProcessOutput в следующем примере, пытается получить один видеокадр из миксера. Если видеокадр недоступен, ProcessSample возвращает S_FALSE или код ошибки, из которого прерывается цикл.ProcessOutputLoop Большая часть работы выполняется внутри ProcessOutput метода:

//-----------------------------------------------------------------------------
// ProcessOutput
//
// Attempts to get a new output sample from the mixer.
//
// Called in two situations:
// (1) ProcessOutputLoop, if the mixer has a new input sample.
// (2) Repainting the last frame.
//-----------------------------------------------------------------------------

HRESULT EVRCustomPresenter::ProcessOutput()
{
    assert(m_bSampleNotify || m_bRepaint);  // See note above.

    HRESULT     hr = S_OK;
    DWORD       dwStatus = 0;
    LONGLONG    mixerStartTime = 0, mixerEndTime = 0;
    MFTIME      systemTime = 0;
    BOOL        bRepaint = m_bRepaint; // Temporarily store this state flag.

    MFT_OUTPUT_DATA_BUFFER dataBuffer;
    ZeroMemory(&dataBuffer, sizeof(dataBuffer));

    IMFSample *pSample = NULL;

    // If the clock is not running, we present the first sample,
    // and then don't present any more until the clock starts.

    if ((m_RenderState != RENDER_STATE_STARTED) &&  // Not running.
         !m_bRepaint &&             // Not a repaint request.
         m_bPrerolled               // At least one sample has been presented.
         )
    {
        return S_FALSE;
    }

    // Make sure we have a pointer to the mixer.
    if (m_pMixer == NULL)
    {
        return MF_E_INVALIDREQUEST;
    }

    // Try to get a free sample from the video sample pool.
    hr = m_SamplePool.GetSample(&pSample);
    if (hr == MF_E_SAMPLEALLOCATOR_EMPTY)
    {
        // No free samples. Try again when a sample is released.
        return S_FALSE;
    }
    else if (FAILED(hr))
    {
        return hr;
    }

    // From now on, we have a valid video sample pointer, where the mixer will
    // write the video data.
    assert(pSample != NULL);

    // (If the following assertion fires, it means we are not managing the sample pool correctly.)
    assert(MFGetAttributeUINT32(pSample, MFSamplePresenter_SampleCounter, (UINT32)-1) == m_TokenCounter);

    if (m_bRepaint)
    {
        // Repaint request. Ask the mixer for the most recent sample.
        SetDesiredSampleTime(
            pSample,
            m_scheduler.LastSampleTime(),
            m_scheduler.FrameDuration()
            );

        m_bRepaint = FALSE; // OK to clear this flag now.
    }
    else
    {
        // Not a repaint request. Clear the desired sample time; the mixer will
        // give us the next frame in the stream.
        ClearDesiredSampleTime(pSample);

        if (m_pClock)
        {
            // Latency: Record the starting time for ProcessOutput.
            (void)m_pClock->GetCorrelatedTime(0, &mixerStartTime, &systemTime);
        }
    }

    // Now we are ready to get an output sample from the mixer.
    dataBuffer.dwStreamID = 0;
    dataBuffer.pSample = pSample;
    dataBuffer.dwStatus = 0;

    hr = m_pMixer->ProcessOutput(0, 1, &dataBuffer, &dwStatus);

    if (FAILED(hr))
    {
        // Return the sample to the pool.
        HRESULT hr2 = m_SamplePool.ReturnSample(pSample);
        if (FAILED(hr2))
        {
            hr = hr2;
            goto done;
        }
        // Handle some known error codes from ProcessOutput.
        if (hr == MF_E_TRANSFORM_TYPE_NOT_SET)
        {
            // The mixer's format is not set. Negotiate a new format.
            hr = RenegotiateMediaType();
        }
        else if (hr == MF_E_TRANSFORM_STREAM_CHANGE)
        {
            // There was a dynamic media type change. Clear our media type.
            SetMediaType(NULL);
        }
        else if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT)
        {
            // The mixer needs more input.
            // We have to wait for the mixer to get more input.
            m_bSampleNotify = FALSE;
        }
    }
    else
    {
        // We got an output sample from the mixer.

        if (m_pClock && !bRepaint)
        {
            // Latency: Record the ending time for the ProcessOutput operation,
            // and notify the EVR of the latency.

            (void)m_pClock->GetCorrelatedTime(0, &mixerEndTime, &systemTime);

            LONGLONG latencyTime = mixerEndTime - mixerStartTime;
            NotifyEvent(EC_PROCESSING_LATENCY, (LONG_PTR)&latencyTime, 0);
        }

        // Set up notification for when the sample is released.
        hr = TrackSample(pSample);
        if (FAILED(hr))
        {
            goto done;
        }

        // Schedule the sample.
        if ((m_FrameStep.state == FRAMESTEP_NONE) || bRepaint)
        {
            hr = DeliverSample(pSample, bRepaint);
            if (FAILED(hr))
            {
                goto done;
            }
        }
        else
        {
            // We are frame-stepping (and this is not a repaint request).
            hr = DeliverFrameStepSample(pSample);
            if (FAILED(hr))
            {
                goto done;
            }
        }

        m_bPrerolled = TRUE; // We have presented at least one sample now.
    }

done:
    SafeRelease(&pSample);

    // Important: Release any events returned from the ProcessOutput method.
    SafeRelease(&dataBuffer.pEvents);
    return hr;
}

Некоторые замечания об этом примере:

Переопределение кадров

Иногда выступающим может потребоваться перенаправить последний видеокадр. Например, стандартный выступающий перенаправляет кадр в следующих ситуациях:

Выполните следующие действия, чтобы запросить смешиватель для повторного создания последнего кадра:

  1. Получите пример видео из очереди.
  2. Запросите пример интерфейса IMFDesiredSample.
  3. Вызов IMFDesiredSample::SetDesiredSampleTimeAndDuration. Укажите метку времени последнего кадра видео. (Необходимо кэшировать это значение и обновить его для каждого кадра.)
  4. Call ProcessOutput на миксере.

При переопределении кадра можно игнорировать часы презентации и сразу представить кадр.

Примеры планирования

Видеокадры могут в любое время достичь EVR. Выступающий отвечает за представление каждого кадра в правильное время на основе метки времени кадра. Когда выступающий получает новый образец из миксера, он помещает образец в запланированную очередь. В отдельном потоке выступающий постоянно получает первый образец из головы очереди и определяет, следует ли:

  • Представить пример.
  • Сохраните образец в очереди, так как это происходит рано.
  • Dis карта пример, так как он поздно. Хотя вы должны избежать удаления кадров, если это возможно, вам может потребоваться, если выступающий постоянно отстает.

Чтобы получить метку времени для видеокадры, вызовите МВФSample::GetSampleTime в примере видео. Метка времени относительно часов представления EVR. Чтобы получить текущее время часов, вызовите IMFClock::GetCorrelatedTime. Если в EVR нет часов презентации или если у примера нет метки времени, вы можете представить пример сразу после его получения.

Чтобы получить длительность каждого примера, вызовите МВФSample::GetSampleDuration. Если в примере нет длительности, можно использовать функцию MFFrameRateToAverageTimePerFrame для вычисления длительности от частоты кадров.

При планировании примеров имейте в виду следующее:

  • Если скорость воспроизведения быстрее или медленнее обычной скорости, часы выполняются быстрее или медленнее. Это означает, что метка времени для примера всегда дает правильное целевое время относительно часов презентации. Однако при переводе времени презентации в другое время (например, счетчик производительности с высоким разрешением), необходимо масштабировать время на основе скорости часов. Если скорость часов изменяется, EVR вызывает метод IMFClockStateSink::OnClockSetRate.
  • Скорость воспроизведения может быть отрицательной для обратного воспроизведения. Если скорость воспроизведения отрицательная, часы презентации выполняются назад. Другими словами, время N + 1 происходит до времени N.

В следующем примере вычисляется, насколько ранним или поздним является пример, относительно часов презентации:

    LONGLONG hnsPresentationTime = 0;
    LONGLONG hnsTimeNow = 0;
    MFTIME   hnsSystemTime = 0;

    BOOL bPresentNow = TRUE;
    LONG lNextSleep = 0;

    if (m_pClock)
    {
        // Get the sample's time stamp. It is valid for a sample to
        // have no time stamp.
        hr = pSample->GetSampleTime(&hnsPresentationTime);

        // Get the clock time. (But if the sample does not have a time stamp,
        // we don't need the clock time.)
        if (SUCCEEDED(hr))
        {
            hr = m_pClock->GetCorrelatedTime(0, &hnsTimeNow, &hnsSystemTime);
        }

        // Calculate the time until the sample's presentation time.
        // A negative value means the sample is late.
        LONGLONG hnsDelta = hnsPresentationTime - hnsTimeNow;
        if (m_fRate < 0)
        {
            // For reverse playback, the clock runs backward. Therefore, the
            // delta is reversed.
            hnsDelta = - hnsDelta;
        }

Часы презентации обычно управляются системными часами или отрисовщиком звука. (Средство отрисовки звука получает время от скорости, по которой звук карта потребляет звук.) Как правило, часы презентации не синхронизируются с частотой обновления монитора.

Если параметры презентации Direct3D указывают D3DPRESENT_INTERVAL_DEFAULT или D3DPRESENT_INTERVAL_ONE для интервала представления, операция "Презентация" ожидает вертикального извлечения монитора. Это простой способ предотвратить слезы, но снижает точность алгоритма планирования. И наоборот, если интервал представления D3DPRESENT_INTERVAL_IMMEDIATE, метод Present выполняется немедленно, что приводит к слезотечению, если алгоритм планирования достаточно точный, что вызывается только в течение вертикального периода извлечения.

Следующие функции помогут получить точные сведения о времени.

  • IDirect3Device9::GetRasterStatus возвращает сведения о растре, включая текущую строку сканирования и то, находится ли растр в вертикальном пустом периоде.
  • DwmGetCompositionTimingInfo возвращает сведения о времени для диспетчера окон рабочего стола. Эта информация полезна, если включена композиция настольных компьютеров.

Демонстрация примеров

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

  1. Вызов IMFSample::GetBufferByIndex в примере видео, чтобы получить буфер.
  2. Запрос буфера для интерфейса IMFGetService .
  3. Вызовите МВФGetService::GetService , чтобы получить интерфейс IDirect3DSurface9 поверхности Direct3D. (Вы можете объединить этот шаг и предыдущий шаг в один, вызвав вызов MFGetService.)
  4. Вызовите IDirect3DSurface9::GetContainer на поверхности, чтобы получить указатель на цепочку буферов.
  5. Вызовите IDirect3DSwapChain9::P resent в цепочке буферов.

Следующий код показывает эти действия.

HRESULT D3DPresentEngine::PresentSample(IMFSample* pSample, LONGLONG llTarget)
{
    HRESULT hr = S_OK;

    IMFMediaBuffer* pBuffer = NULL;
    IDirect3DSurface9* pSurface = NULL;
    IDirect3DSwapChain9* pSwapChain = NULL;

    if (pSample)
    {
        // Get the buffer from the sample.
        hr = pSample->GetBufferByIndex(0, &pBuffer);
        if (FAILED(hr))
        {
            goto done;
        }

        // Get the surface from the buffer.
        hr = MFGetService(pBuffer, MR_BUFFER_SERVICE, IID_PPV_ARGS(&pSurface));
        if (FAILED(hr))
        {
            goto done;
        }
    }
    else if (m_pSurfaceRepaint)
    {
        // Redraw from the last surface.
        pSurface = m_pSurfaceRepaint;
        pSurface->AddRef();
    }

    if (pSurface)
    {
        // Get the swap chain from the surface.
        hr = pSurface->GetContainer(IID_PPV_ARGS(&pSwapChain));
        if (FAILED(hr))
        {
            goto done;
        }

        // Present the swap chain.
        hr = PresentSwapChain(pSwapChain, pSurface);
        if (FAILED(hr))
        {
            goto done;
        }

        // Store this pointer in case we need to repaint the surface.
        CopyComPointer(m_pSurfaceRepaint, pSurface);
    }
    else
    {
        // No surface. All we can do is paint a black rectangle.
        PaintFrameWithGDI();
    }

done:
    SafeRelease(&pSwapChain);
    SafeRelease(&pSurface);
    SafeRelease(&pBuffer);

    if (FAILED(hr))
    {
        if (hr == D3DERR_DEVICELOST || hr == D3DERR_DEVICENOTRESET || hr == D3DERR_DEVICEHUNG)
        {
            // We failed because the device was lost. Fill the destination rectangle.
            PaintFrameWithGDI();

            // Ignore. We need to reset or re-create the device, but this method
            // is probably being called from the scheduler thread, which is not the
            // same thread that created the device. The Reset(Ex) method must be
            // called from the thread that created the device.

            // The presenter will detect the state when it calls CheckDeviceState()
            // on the next sample.
            hr = S_OK;
        }
    }
    return hr;
}

Исходные и конечные прямоугольники

Исходный прямоугольник — это часть отображаемого видеокадры. Он определяется относительно нормализованной системы координат, в которой весь видеокадр занимает прямоугольник с координатами {0, 0, 1, 1}. Прямоугольник назначения — это область в области назначения, в которой рисуется видеокадр. Стандартный выступающий позволяет приложению задать эти прямоугольники путем вызова IMFVideoDisplayControl::SetVideoPosition.

Существует несколько вариантов применения исходных и целевых прямоугольников. Первый вариант заключается в том, чтобы позволить миксеру применить их:

  • Задайте исходный прямоугольник с помощью атрибута VIDEO_ZOOM_RECT . Миксер применит исходный прямоугольник, когда он щелкает видео к целевой поверхности. Исходный прямоугольник миксера по умолчанию — это весь кадр.
  • Задайте прямоугольник назначения в качестве геометрической диафрагмы в типе выходных данных миксера. Дополнительные сведения см. в разделе "Форматы переговоров".

Второй вариант — применить прямоугольники при использовании IDirect3DSwapChain9::P resent путем указания параметров pSourceRect и pDestRect в методе Present. Эти параметры можно объединить. Например, можно задать исходный прямоугольник на миксере, но применить целевой прямоугольник в методе Present .

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

Конец потока

Когда все входные потоки в EVR закончились, EVR отправляет MFVP_MESSAGE_ENDOFSTREAM сообщение выступающим. Однако при получении сообщения может потребоваться несколько видеокадров. Прежде чем отвечать на сообщение конца потока, необходимо слить все выходные данные из миксера и представить все оставшиеся кадры. После представления последнего кадра отправьте событие EC_COMPLETE в EVR.

В следующем примере показан метод, который отправляет событие EC_COMPLETE , если выполнены различные условия. В противном случае возвращается S_OK без отправки события:

HRESULT EVRCustomPresenter::CheckEndOfStream()
{
    if (!m_bEndStreaming)
    {
        // The EVR did not send the MFVP_MESSAGE_ENDOFSTREAM message.
        return S_OK;
    }

    if (m_bSampleNotify)
    {
        // The mixer still has input.
        return S_OK;
    }

    if (m_SamplePool.AreSamplesPending())
    {
        // Samples are still scheduled for rendering.
        return S_OK;
    }

    // Everything is complete. Now we can tell the EVR that we are done.
    NotifyEvent(EC_COMPLETE, (LONG_PTR)S_OK, 0);
    m_bEndStreaming = FALSE;
    return S_OK;
}

Этот метод проверка следующих состояний:

  • Если переменная m_fSampleNotify имеет значение TRUE, это означает, что миксер имеет один или несколько кадров, которые еще не обработаны. (Дополнительные сведения см. в разделе Обработка выходных данных.)
  • Переменная m_fEndStreaming является логическим флагом, начальным значением которого является FALSE. Выступающий задает для флага значение TRUE , когда EVR отправляет сообщение MFVP_MESSAGE_ENDOFSTREAM .
  • Предполагается AreSamplesPending , что метод возвращает значение TRUE , если в запланированной очереди ожидается один или несколько кадров.

В методе IMFVideoPresenter::P rocessMessage задайте m_fEndStreaming значение TRUE и вызовитеCheckEndOfStream, когда EVR отправляет сообщение MFVP_MESSAGE_ENDOFSTREAM:

HRESULT EVRCustomPresenter::ProcessMessage(
    MFVP_MESSAGE_TYPE eMessage,
    ULONG_PTR ulParam
    )
{
    HRESULT hr = S_OK;

    EnterCriticalSection(&m_ObjectLock);

    hr = CheckShutdown();
    if (FAILED(hr))
    {
        goto done;
    }

    switch (eMessage)
    {
    // Flush all pending samples.
    case MFVP_MESSAGE_FLUSH:
        hr = Flush();
        break;

    // Renegotiate the media type with the mixer.
    case MFVP_MESSAGE_INVALIDATEMEDIATYPE:
        hr = RenegotiateMediaType();
        break;

    // The mixer received a new input sample.
    case MFVP_MESSAGE_PROCESSINPUTNOTIFY:
        hr = ProcessInputNotify();
        break;

    // Streaming is about to start.
    case MFVP_MESSAGE_BEGINSTREAMING:
        hr = BeginStreaming();
        break;

    // Streaming has ended. (The EVR has stopped.)
    case MFVP_MESSAGE_ENDSTREAMING:
        hr = EndStreaming();
        break;

    // All input streams have ended.
    case MFVP_MESSAGE_ENDOFSTREAM:
        // Set the EOS flag.
        m_bEndStreaming = TRUE;
        // Check if it's time to send the EC_COMPLETE event to the EVR.
        hr = CheckEndOfStream();
        break;

    // Frame-stepping is starting.
    case MFVP_MESSAGE_STEP:
        hr = PrepareFrameStep(LODWORD(ulParam));
        break;

    // Cancels frame-stepping.
    case MFVP_MESSAGE_CANCELSTEP:
        hr = CancelFrameStep();
        break;

    default:
        hr = E_INVALIDARG; // Unknown message. This case should never occur.
        break;
    }

done:
    LeaveCriticalSection(&m_ObjectLock);
    return hr;
}

Кроме того, вызовитеCheckEndOfStream, если метод IMFTransform::P rocessOutput миксера возвращает MF_E_TRANSFORM_NEED_MORE_INPUT. Этот код ошибки указывает, что миксер не имеет больше входных примеров (см. раздел "Выходные данные обработки").

Шаг кадра

EVR предназначен для поддержки шага кадров в DirectShow и очистки в Media Foundation. Пошаговые шаги кадров и очистка концептуально похожи. В обоих случаях приложение запрашивает один видеокадр за раз. Во внутреннем режиме выступающий использует один и тот же механизм для реализации обоих функций.

Шаг кадра в DirectShow работает следующим образом:

  • Приложение вызывает IVideoFrameStep::Step. Число шагов задано в параметре dwSteps . EVR отправляет MFVP_MESSAGE_STEP сообщение докладчику, где параметр сообщения (ulParam) — это количество шагов.
  • Если приложение вызывает IVideoFrameStep::CancelStep или изменяет состояние графа (выполняется, приостановлено или остановлено), EVR отправляет сообщение MFVP_MESSAGE_CANCELSTEP.

Очистка в Media Foundation работает следующим образом:

  • Приложение устанавливает скорость воспроизведения равным нулю путем вызова IMFRateControl::SetRate.
  • Чтобы отобразить новый кадр, приложение вызывает МВФMediaSession::Начало с требуемой позиции. EVR отправляет сообщение MFVP_MESSAGE_STEP с ulParam равным 1.
  • Чтобы остановить очистку, приложение задает частоту воспроизведения ненулевому значению. EVR отправляет сообщение MFVP_MESSAGE_CANCELSTEP .

Получив сообщение MFVP_MESSAGE_STEP, выступающий ожидает прибытия целевого кадра. Если число шагов равно N, выступающий отс карта читывает следующие примеры (N - 1) и представляет N-й пример. Когда выступающий завершит шаг кадра, он отправляет событие EC_STEP_COMPLETE в EVR с значением lParam1 , которое имеет значение FALSE. Кроме того, если скорость воспроизведения равна нулю, выступающий отправляет событие EC_SCRUB_TIME . Если evR отменяет шаг кадра, пока операция шага кадра по-прежнему ожидается, выступающий отправляет событие EC_STEP_COMPLETE с значением lParam1 с значением TRUE.

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

Реализация шага кадра

В этом разделе описывается алгоритм реализации шага кадров. Алгоритм пошагового шага кадров использует следующие переменные:

  • step_count. Целое число без знака, указывающее количество шагов в текущей операции пошагового шага кадров.

  • step_queue. Очередь указателей МВФSample .

  • step_state. В любое время выступающий может находиться в одном из следующих состояний относительно шага кадров:

    State Description
    NOT_STEPPING Не шаг кадра.
    ОЖИДАНИЯ Ведущий получил сообщение MFVP_MESSAGE_STEP , но часы не начались.
    PENDING Выступающий получил сообщение MFVP_MESSAGE_STEP и часы начались, но выступающий ожидает получения целевого кадра.
    ЗАПЛАНИРОВАННЫЕ Выступающий получил целевой кадр и запланировал его для презентации, но кадр не был представлен.
    ЗАВЕРШЕНО Выступающий представил целевой кадр и отправил событие EC_STEP_COMPLETE и ожидает следующего сообщения MFVP_MESSAGE_STEP или MFVP_MESSAGE_CANCELSTEP .

     

    Эти состояния не зависят от состояний докладчика, перечисленных в разделе "Государства докладчика".

Для алгоритма пошагового выполнения кадров определены следующие процедуры:

Процедура PrepareFrameStep

  1. Добавочное step_count.
  2. Задайте для step_state значение WAITING.
  3. Если часы выполняются, вызовите StartFrameStep.

Процедура StartFrameStep

  1. Если step_state равно ОЖИДАНИю, задайте для step_state значение PENDING. Для каждого примера в step_queue вызовите DeliverFrameStepSample.
  2. Если step_state равно NOT_STEPPING, удалите все примеры из step_queue и запланируйте их для презентации.

Процедура CompleteFrameStep

  1. Задайте для step_state значение COMPLETE.
  2. Отправьте событие EC_STEP_COMPLETE с помощью lParam1 = FALSE.
  3. Если частота часов равна нулю, отправьте событие EC_SCRUB_TIME с примером времени.

Процедура DeliverFrameStepSample

  1. Если частота часов равна нулю и + время выборки времени длительности,< отсчитывается карта выборке. Выход.
  2. Если step_state равно SCHEDULED или COMPLETE, добавьте пример в step_queue. Выход.
  3. Декремент step_count.
  4. Если step_count> 0, откажитесь карта образце. Выход.
  5. Если step_state равно ОЖИДАНИю, добавьте пример в step_queue. Выход.
  6. Запланируйте пример презентации.
  7. Задайте для step_state значение SCHEDULED.

Процедура CancelFrameStep

  1. Задайте для NOT_STEPPING значение step_state
  2. Сброс step_count до нуля.
  3. Если предыдущее значение step_state было ОЖИДАНИЕ, ОЖИДАНИЕ или РАСПИСАНИЕ, отправьте EC_STEP_COMPLETE с помощью lParam1 = TRUE.

Вызовите следующие процедуры:

Сообщение докладчика или метод Процедура
сообщение MFVP_MESSAGE_STEP PrepareFrameStep
сообщение MFVP_MESSAGE_STEP CancelStep
IMFClockStateSink::OnClockStart StartFrameStep
МВФClockStateSink::OnClockRestart StartFrameStep
Обратный вызов МВФTrackedSample CompleteFrameStep
МВФClockStateSink::OnClockStop CancelFrameStep
IMFClockStateSink::OnClockSetRate CancelFrameStep

 

На следующей блок-диаграмме показаны процедуры пошагового шага кадров.

flow chart showing paths that start with mfvp-message-step and mfvp-message-processinputnotify and end at

Настройка докладчика в EVR

После реализации докладчика необходимо настроить EVR для его использования.

Настройка докладчика в DirectShow

В приложении DirectShow задайте выступающий в EVR следующим образом:

  1. Создайте фильтр EVR, вызвав CoCreateInstance. CLSID CLSID_EnhancedVideoRenderer.
  2. Добавьте EVR в граф фильтров.
  3. Создайте экземпляр докладчика. Выступающий может поддерживать стандартное создание COM-объектов через IClassFactory, но это не обязательно.
  4. Запрос фильтра EVR для интерфейса IMFVideoRenderer.
  5. Вызов МВФVideoRenderer::InitializeRenderer.

Настройка докладчика в Media Foundation

В Media Foundation есть несколько вариантов в зависимости от того, создаете ли приемник мультимедиа EVR или объект активации EVR. Дополнительные сведения об объектах активации см. в разделе "Объекты активации".

Для приемника мультимедиа EVR выполните следующие действия.

  1. Вызовите MFCreateVideoRenderer , чтобы создать приемник мультимедиа.
  2. Создайте экземпляр докладчика.
  3. Запрос приемника мультимедиа EVR для интерфейса МВФVideoRenderer .
  4. Вызов МВФVideoRenderer::InitializeRenderer.

Для объекта активации EVR выполните следующие действия.

  1. Вызовите MFCreateVideoRendererActivate , чтобы создать объект активации.

  2. Задайте один из следующих атрибутов объекта активации:

    Атрибут Description
    MF_ACTIVATE_CUSTOM_VIDEO_PRESENTER_ACTIVATE Указатель на объект активации для докладчика.
    С помощью этого флага необходимо предоставить объект активации для докладчика. Объект активации должен реализовать интерфейс IMFActivate.
    MF_ACTIVATE_CUSTOM_VIDEO_PRESENTER_CLSID CLSID докладчика.
    С помощью этого флага выступающий должен поддерживать стандартное создание COM-объекта с помощью IClassFactory.

     

  3. При необходимости задайте атрибут MF_ACTIVATE_CUSTOM_VIDEO_PRESENTER_FLAGS для объекта активации.

Расширенный отрисовщик видео

Пример EVRPresenter