Статьи‎ > ‎Фильтры‎ > ‎

Передача данных в графе с точки зрения разработчика

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

В этом разделе будет более подробно описан проход данных через граф фильтров. Внимание будет сфокусировано на транспорте локальной памяти (local memory transport) с использованием интерфейсов IMemInputPin и IAsyncReader. Это рассмотрение предназначено для программистов, разрабатывающих свои собственные фильтры.

Через граф фильтров проходит множество данных. Их, грубо, можно отнести к двум категориям: медиаданные и контрольные данные. Вообще говоря, медиаданные движутся вниз по графу, а управляющие данные - вверх по потоку. Медиаданные включают видеофреймы, аудиофреймы, MPEG-пакеты и т.д., то, что делается вверху потока, но они включают также команды сброса (flush), уведомления об окончании потока и другие данные, которые двигаются вместе с потоком. Управляющие данные не являются частью медиапотока. Примерами управляющих данных есть запросы контроля качества и команды поиска.

Передача сэмплов

Рассмотрим же, каким образом фильтр передает сэмплы. Будут описаны обе модели - push, использующая методы интерфейса IMemInputPin, и pull, использующая IAsyncReader.

Начнем с пересылки сэмплов в push-модели.

Исходящий контакт пересылает сэмпл, используя для этого вызов метода IMemInputPin::Receive или метода IMemInputPin::ReceiveMultiple, который есть эквивалентным, но пересылает массив сэмплов. Входящий контакт может блокировать вызов внутри Receive (или ReceiveMultiple). Если контакт может блокировать, его метод IMemInputPin::ReceiveCanBlock вернет S_OK. Если контакт гарантированно никогда не будет блокировать, ReceiveCanBlock вернет S_FALSE. Возвращенное значение S_OK не значит, что вызов Receive будет блокироваться всегда, а только то, что такое может быть.

Хотя Receive может быть заблокирован до ожидания доступности ресурса, он не будет блокировать ожидания данных от другого фильтра. Так сделано по причине взаимной блокировки, которая происходила бы, если бы вышестоящий фильтр ожидал, пока нижестоящий фильтр освободит сэмпл, но это ожидание никогда бы не завершилось, т.к. нижележащий фильтр ожидал бы данных от вышележащего фильтра. Если фильтр имеет несколько входных контактов, однако, один контакт может ожидать, пока другой контакт получит данные. Например, фильтр AVI Mux делает так для чередования аудио и видеоданных.

Фильтр может отказать в приеме сэмпла по следующим причинам:

  • Контакт сбрасывается (см. Сброс информации (flushing))
  • Контакт не соединен
  • Фильтр остановлен
  • Произошла какая-то другая ошибка

В первом случае метод Receive вернет S_FALSE и код ошибки - в других случаях. Вышестоящий фильтр останавливает передачу сэмплов, если код возврата отличен от S_OK.

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

В базовом классе DirectShow метод CBaseInputPin::CheckStreaming выполняет проверку общего сбоя в случаях сброса (flushing), остановки и т.д. Производным классам необходимо выполнять специфические для фильтра проверки. В случае ошибки метод CBaseInputPin::Receive посылает уведомление об окончании потока и событие EC_ERRORABORT.

Перейдем теперь к рассмотрению запроса сэмплов в pull-модели.

Входящий контакт посредством нтерфейса IAsyncReader запрашивает данные у исходящего контакта, вызывая один из его (интерфейса) методов:

  • IAsyncReader::Request
  • IAsyncReader::SyncReader
  • IAsyncReader::SyncReadAligned

Метод Request асинхронен; входящий контакт вызывает метод IAsyncReader::WaitForNext для ожидания запроса на завершение. Два других метода - синхронные.

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

Рассмотрим разбор медиаданных, ошибки при работе и кратко скажем об изменении форматов.

Если фильтр занимается разбором медиаданных, нельзя доверять заголовкам или другим описательным данным в контексте. Например, нельзя доверять величинам значений, находящихся в чанках AVI RIFF или в MPEG-пакетах. Наиболее общие примеры такого типа ошибок включают в себя:

  • Чтение N байт данных, где значение N получено из контекста, без проверки соответсвия значения N нашему буферу.
  • Перемещение по байтовому смещению внутри буфера без проверки, находится ли это смещение внутри нашего буфера.

Другой общий класс ошибок включает отсутствие проверки описания формата, встречаемого в контексте. Например:

  • Структура BITMAPINFOHEADER может быть следовать за таблицей цветов. Структура BITMAPINFO определяется как структура BITMAPINFOHEADER, идущая за массивом RGBQUAD значений, дающих таблицу цветов. Размер массива определяется значением biClrUsed. Никогда не копируйте таблицу цветов в BITMAPINFO без того, чтобы сначала проверить размер буфера, вделенного под структуру BITMAPINFO.
  • Структура WAVEFORMATEX может содержать дополнительную информацию, вставленную в эту структуру. Член cbSize указывает размер доплнительной информации.

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

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

Когда граф запущен, если ваш фильтр принимает ни в какие ворота не лкзущий контекст, он должен прекратить пересылку. Нужно сделать следующее:

  • Вернуть код ошибки из Receive.
  • Вызвать IPin::EndOfStream на нижележащем фильтре.
  • Вызвать CBaseFilter::NotifyEvent для посылки сообщения EC_ERRORABORT.

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

Уведомления об окончании потока (End of Stream Notifications)

Когда фильтр источника заканчивает передавать данные, он вызывает метод IPin::EndOfStream входящего контакта нижележащего фильтра. Нижележащий фильтр распространяет вызов на следующий фильтр и т.д. Когда EndOfStream достигает рендерера, тот отправляет сообщение EC_COMPLETE менеджеру графа фильтров. Если рендерер имеет несколько входящих контактов, то собщения EC_COMPLETE будут посланы соответствующее число раз.

Фильтр должен сериализировать EndOfStream с другими потоковыми вызовами, такими как IMemInputPin::Receive. (Другими словами, нижележащий фильтр всегда должен получать вызовы в правильном порядке.)

В некоторых случаях нижележащий фильтр может определить, что поток закончился, раньше фильтра источника. (Например, нижележащий фильтр может разбирать поток.) В этом случае нижележащий поток может отправить уведомление об окончании потока, в случае чего метод IMemInputPin::Receive будет возвращать S_FALSE, пока граф не остановится или не сбросится. Возвращаемое значение S_FALSE информирует фильтр источника о том, что нужно остановить отправку данных.

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

Для определения количества потоков менеджер графа фильтров подсчитывает количество фильтров, поддерживающих поиск (через IMediaSeeking и IMediaPosition) и имеют воспроизводящий (renderer) входящий контакт, который определяется как входящий контакт, не имеющий соответствующих выходов. Менеджер графа фильтров определяет, что контакт есть воспроизводящим, одним из двух способов:

  • Метод контакта IPin::QueryInternalConnections возвращает ноль в параметре nPin.
  • Фильтр предоставляет интерфейс IAMFilterMiscFlags и возвращает флаг AM_FILTER_MISC_FLAGS_IS_RENDERER.

Рассмотрим теперь уведомление об окончании потока в pull-модели. В соединении IAsyncReader фильтр источника не посылает уведомления о получении потока. Вместо этого оно производится нижележащим фильтром, который обычно является фильтром разбора. Анализатор отправляет сообщение EndOfStream вниз по потоку. Он не отправляет такого сообщения наверх, фильтру источника.

Новые сегменты

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

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

В push-модели фильтр источника инициирует вызов NewSegment. В pull-модели его делает фильтр разбора. В любом случае фильтр вызывает NewSegment на входящем контакте нижележащего фильтра, который распространяет вызов на следующий фильтр, пока этот вызов не достигнет рендерера. Фильтр должен сериализировать вызов NewSegment с другими потоковыми вызовами, такими, как IMemInputPin::Receive.

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

Сброс (flushing)

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

Сбрасывание немного отличается для push- и pull- моделей. Опишем сначала push-модель, а затем - отличия для pull-модели.

Сбрасывание происходит в два этапа.

  • Сначала фильтр источника вызывает IPin::BeginFlush на входящем контакте нижележащего фильтра. Нижележащий фильтр стартует отказ от принятия сэмпла от вышележащего фильтра. Это также отменяет действительность всех удерживаемых сэмплов и ведет к посылке вызова BeginFlush следующему фильтру.
  • Когда фильтр источника становится готовым отправлять новые данные, он вызывает IPin::EndFlush на входящем контакте. Этим самым сообщается нижележащему фильтру, что тот может принимать новые сэмплы. Нижележащий фильтр переправляет вызов EndFlush следующему фильтру.

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

  1. Вызывает BeginFlush на входящем контакте нижележащего фильтра.
  2. Отказывает при вызове в будущем любым потоковым данным (новым входящим сэмплам), включая Receive и EndOfStream.
  3. Разблокирует все вышележащие фильтры, блокированные ожиданием сэмплов от аллокатора фильтра. Некоторые фильтры декоммитят свои аллокаторы для этой цели.
  4. Выходит из всех ожиданий, блокирующих поток. Например, фильтр рендеринга блокирует поток в состоянии паузы. Блокирует его он и в том случае, когда ожидает необходимое для корректного показа сэмпла время. Фильтр должен быть разблокирован для того, чтобы сэплы, находящиеся в очереди вышележащих фильтров, могла быть переданными и отказанными. Этот шаг гарантирует, что все вышележащие фильтры будут, в конце концов, разблокированы.

В методе EndFlush входящий контакт делает следующее:

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

Начиная с этого места, фильтр снова сможет принимать сэмплы. Все сэмплы будут гарантированно новее сброшенных.

В pull-модели сброс инициирует фильтр разбора вместо фильтра источника. Он не только вызывает IPin::BeginFlush и IPin::EndFlush на нижележащем фильтре, но и, также, вызывает IAsyncReader::BeginFlush и IAsyncReader::EndFlush на исходящем контакте фильтра источника. Если фильтр источника имеет отложенные запросы на чтение, он отменяет их.

Поиск

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

Когда фильтр выполноет операцию поиска, он сбрасывает (flushes) все более ранние данные. Результатом есть минимизация задержки выполнения команды поиска, т.к. существующие данные выбрасываются из графа. После команды поиска потоковое время устанавливается в ноль.

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

Рис. 11. Использование интерфейса IMediaSeeking

Если фильтр разбора имеет более одного исходящего контакта, обычно один из них разрабатывается для поддержки поиска. Другие контакты отказывают либо игнорируют любые полученные команды поиска. Таким образом парсер удерживает все потоки в синхронизированном состоянии.

Интерфейс IMediaPosition для фильтров крайне неподходящ. Автоматизация клиентов необходима для использования этого интерфейса на менеджере графа фильтров, т.к. IMediaSeeking несовместим с автоматизацией (?), но менеджер графа фильтров транслирует все вызовы IMediaPosition в вызовы IMediaSeeking.

Динамическое изменение формата

Рассмотрим вопрос об изменении медиаформатов в фильтрах DirectShow после их (фильтров) соединения. Рассмотрение будет включать в себя вопрос о пересоединении контактов в том случае, когда граф останавливается, и переключение форматов "на ходу", при передаче данных между фильтрами.

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

Но DirectShow не ограничивает фильтры в их возможности изменять медиатип. Изменение фильтрами медиатипов называется динамическим изменением формата. Если вы пишетеDirectShow-фильтр, вы должны понимать механизмы динамических изменений формата. Даже если ваш фильтр и не поддерживает таких изменений, он должен корректно обрабатывать ситуацию, когда другой фильтр требует нового формата.

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

Изменение формата в случае остановленного графа

Когда граф остановлен, фильтр может изменить тип медиа путем пересоединения следующим образом:

  1. Фильтр вызывает IPin.QueryAccept для остальных контактов фильтра и специфициркет новый медиатип (рис. 1A).
  2. Если QueryAccept возвращает S_OK, фильтр вызывает IFilterGraph2.ReconnectEx для пересоединения контактов (рис. 1B).

Рис. 12. Изменение формата при остановленном графе

Примеры использования:

  • Tee filters. Tee-фильтр - это фильтр, расщепляющий входящий поток на множество исходящих без изменения данных в нем. Этот фильтр может работать с разными типа медиа, но они должны совпадать для всех контактов. Следовательно, когда входной контакт соединен, фильтр должен переустановить все существующие соединения для исходящих контактов и обратно. Как пример можно взять пример фильтра InfTee из примеров DirectShow SDK.
  • Trans-in-place filters. Trans_in-place-фильтр - это трансформационный фильтр, модифицирующий входящие данные в исходном буфере вместо копирования данных в отдельный исходящий буфер. Trans-in-place-фильтр должен использовать один и тот же распределитель памяти (allocator) и для входящего, и для исходящего соединения. Первый контакт соединения (входящий или исходящий) получает распределитель памяти обычным образом. Но когда соединяется второй контакт, первый контакт может ему и не подойти. В этом случае второй контакт выбирает иной распределитель, и первый контакт пересоединяется с использованием нового распределителя (рис. 2). В качестве примера реализации можно посмотреть класс CTransInPlaceFilter.

Рис. 13. Пересоединение контактов для trns-in-place фильтра

Замечания по реализации

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

В методе ReconnectEx менеджер графа фильтров асинхронно рассоединяет и соединяет заново все контакты. Фильтр не должен пытаться пересоединиться, предварительно не получив S_OK на вызов QueryAccept. В противном случае контакт может остаться несоединенным, порождая ожибки графа. Фильтр также должен запрашивать пересоединения внутри метода IPin::Connect той же нити. Если метод коннект вернет результат на одной нити, пока другая нить не сделает запрос на пересоединение, менеджер графа фильтров сможет запустить граф перед тем, как сделать пересоедининение, порождая ошибки графа.

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

Изменение формата в случае активного графа

В противном случае (если фильтры не поддерживают динамического пересоединения), при активном графе (запущеном или приостановленном), сменить формат труднее. Существует три основных механизма с ограниченями по применимости:

  • QueryAccept (Downstream) [Принятие запроса нижележащего потока] используется, когда исходящий контакт предлагает изменение формата для этой нисходящей ветви, но только если новый формат не требует большего буфера.
  • QueryAccept (Upstream) [Принятие запроса вышележащего потока] используется, когда входящий контакт предлагает изменение формата для входящей нити. Новый формат может требовать того же размера буфера или большего.
  • ReceiveConnection [Пересылка соединения] используется, когда исходящий контакт предлагает изменение формата для нисходящей ветви, и новый формат требует большего буфера.

Отдельно будет рассмотрен важный случай поддержки изменения формата, затребованного видеорендерером.

QueryAccept (DownStream)

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

  1. Вызвать IPin.QueryAccept или IPinConnection.DynamicQueryAccept для нисходящего контакта для проверки, может ли другой контакт принять новый медиатип (рис. 3A).
  2. Если результатом выполнения п.1 есть S_OK , контакт принимает медиатип для следующей порции данных сэмпла. Для этого сначала вызывается методIMemAllocator.GetBuffer для получения порции (рис. 3B). Потом вызывается IMediaSample.SetMediaType для применения медиатипа к текущей порции (рис. 3C). Принимая медиатип к порции данных, фильтр показывает, что, начиная с этой порции, формат изменен.
  3. Контакт передает данные (рис. 3D).
  4. Когда нисходящий фильтр пересылает данные, он вызывает IMediaSample.GetMediaType для исправления нового медиатипа.

Рис. 14. Прием запроса на соединение нижележащим фильтром

Все контакты поддерживают метод IPin.QuertyAccept. Но этот метод, однако, немного отличается, т.к. возвращенное значение S_OK не всегда гарантирует, что вы можете изменить формат, когда граф активен. Некоторые фильтры могут вернуть S_OK, но отвергнут изменение, если граф активен. Поэтому в DirectX 8 был введен метод IPinConnection.DynamicQueryAccept для входящих контактов. Этот метод ясно определяет, что возвращаемое значение S_OK означает, что контакт может изменить формат, когда граф активен. Если входящий контакт поддерживает интерфейс IPinConnection, вызов DynamicQueryAccept предпочтительнее вызова QueryAccept.

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

Замечания по использованию

В базовых классах DirectShow CBasePin.QueryAccept вызывает метод CheckMediaType, который вызывается, также, и на протяжении инициализации соединения контакта. В случае трансформационного фильтра метод входящих контактов CheckMediaType всегда проверяет, когда подключен исходящий контакт и, таким образом, совместим ли входящий медиатип с исходящим медиатипом. Поэтому такая реализация похожа на QueryAccept. Если нет, вы должны будете переписать QueryAccept для обеспечения, в случае необходимости, дополнительных проверок. Заметьте также, что класс CTransformFilter инкапсулирует эту логику без использования методов CheckInputType и CheckTransform. С другой стороны, класс CTransInPlaceFilter, всегда вызывает QueryAccept для следующего нисходящего или исходящего фильтра.

Метод CBaseInputPin.Receive проверяет медиатип входящей порции данных и, если он тот же, вызывает CheckMediaType. Но в этом случае, однако, не устанавливается в новое значение член контакта m_mt, в котором хранится текущий медиатип. Когда ваш фильтр обрабатывет порцию данных, проверяйте ее медиатип. Если это новый тип, вам, возможно, нужно будет сохранить его либо вызовом метода SetMediaType вашего контакта, либо непосредственной установкой значения m_mt. С другой стороны, классCVideoTransformFilter, который разрабатывался для фильтров преобразования видео, сохраняет медиатип, когда он изменяется. Для выяснения нюансов смотрите исходные коды метода CVideoTransformFilter.Receive в базовой библиотеке DirectShow.

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

QueryAccept (Upstream)

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

Происходит следующее:

  1. Нижележащий фильтр проверяет, какой из контактов в соединении использует пользовательский распределитель. Если собственник распределителя - вышележащий фильтр, то нижележащий фильтр не изменяет формата.
  2. Нижележащий фильтр вызывает IPin::QueryAccept на исходящем контакте вышележащего фильтра (см. Рис.4, А).
  3. Если QueryAccept возвращает S_OK, то нижележащий фильтр вызывает приватный метод на этом распределителе для установления медиатипа. Внутри этого приватного метода распределитель вызывает IMediaSample::SetMediaType на следующем доступном сэмпле (В).
  4. Вышележащий фильтр вызывает GetBuffer для получения нового сэмпла (С) и IMediaSample::GetMediaType для получения нового медиатипа (D).
  5. Когда вышележащий фильтр перешлет сэмпл, он завершит применение медиатипа к этому сэмплу. Таким образом нижележащий фильтр сможет подтвердить, что медиатип изменен (E).

Рис. 15. Прием запроса на соединение вышележащим фильтром

Главные примеры такого рода изменения видеоформата содержатся в видеорендерерах DirectShow.

  • Исходный фильтр Video Renderer может переключаться между RGB и YUV - типами видео "на ходу". Соединение фильтров требует RGB-формата, совпадающего с текущими экранными установками. Это гарантирует вывод на GDI. После начала воспроизведение, в случае доступности DirectDraw, видеорендерер запрашивает изменение формата на тип YUV. Позже он снова может переключиться на GDI в случае потери DirectDrwa-поверхности (неважно по какой причине).
  • Новый Video Mixing Renderer (VMR) - фильтр может соединяться с любыми форматами, поддерживаемыми видеооборудованием, включая тип YUV. Однако графическое оборудование может менять шаг в широких пределах под управлением DirectDraw-поверхности для оптимизации производительности. VMR-фильтр использует QueryAccept для получения нового шага, специфицируемого в члене biWidth структуры BITMAPINFOHEADER. Прямоугольники источника и местоназначения структур VIDEOINFOHEADER и VIDEOINFOHEADER2 указывают область декодирования видео.

Замечания о реализации

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

Фильтры-преобразователи "по месту", находящиеся между видеорендерером и декодером, должны пропускать все вызовы QueryAccept вверх по течению. Информация о новом формате должна сохраняться, когда она поступает.

Фильтры-преобразователи "копировальщики" (т.е. не фильтры-преобразователи "по месту") должны реализовать один из следующих вариантов поведения:

  • Пропускать изменения форматов вверх по течению и сохранять, при поступлению, новую информацию о формате. Ваш фильтр должен использовать пользовательский распределитель, чтобы можно было присоединить формат к сэмлу, который пришел сверху.
  • Выполнить преобразование форматов внутри фильтра. Эт, возможно, будет сделать легче, чем пропустить изменение формата вверх по потоку. Но это, однако, может быть менее эффективным из-за того, что это может послужить помехой фильтру-декодеру декодировать в верном формате.
  • И последнее, можно просто отказать изменению формата. (Для получения большего количества сведений следует обратиться к методу CTransInPlaceOutputPin::CheckMediaType в библиотеке базовых классов DirectShow.) Отказ от изменения видеоформата может упростить вашу работу, но это, однако, не дает видеорендереру использовать более подходящий формат.

Следующий псевдокод показывает, как можно реализовать фильтр-преобразователь с копированием (происходящий от CTransformFilter), который может переключаться между YUV и RGB - исходящими типами. Здесь подразумевается, что фильтр не может конвертировать форматы, а только пропускать вызовы изменения форматов вверх по потоку.

HRESULT CMyTransform::CheckInputType(const CMediaType *pmt)

{

if (pmt is a YUV type that you support) {

return S_OK;

}

else {

return VFW_E_TYPE_NOT_ACCEPTED;

}

}

HRESULT CMyTransform::CheckTransform(const CMediaType *mtIn,

const CMediaType *mtOut)

{

if (mtOut is a YUV or RGB type that you support)

{

if ((mtIn has the same video dimensions as mtOut) &&

(you support the mtIn-to-mtOut transform))

{

return S_OK;

}

}

// otherwisereturn VFW_E_TYPE_NOT_ACCEPTED;

}

// GetMediaType: Return a preferred output type.

HRESULT CMyTransform::GetMediaType(int iPosition, CMediaType *pMediaType)

{

if (iPosition < 0) {

return E_INVALIDARG;

}

switch (iPosition)

{

case 0:

Copy the input type (YUV) to pMediaType

return S_OK;

case 1:

Construct an RGB type that matches the input type.

return S_OK;

default:

return VFW_S_NO_MORE_ITEMS;

}

}

// SetMediaType: Override from CTransformFilter.

HRESULT CMyTransform::SetMediaType(

PIN_DIRECTION direction, const CMediaType *pmt)

{

// Capture this information...if (direction == PINDIR_OUTPUT)

{

m_bYuv = (pmt->subtype == MEDIASUBTYPE_UYVY);

}

return S_OK;

}

HRESULT CMyTransform::Transform(

IMediaSample *pSource, IMediaSample *pDest)

{

// Look for format changes from downstream.

CMediaType *pMT = NULL;

HRESULT hr = pDest->GetMediaType((AM_MEDIA_TYPE**)&pMT);

if (hr == S_OK)

{

hr = m_pOutput->CheckMediaType(pMT);

if(FAILED(hr))

{

DeleteMediaType(pMT);

return E_FAIL;

}

// Notify our own output pin about the new type.

m_pOutput->SetMediaType(pMT);

DeleteMediaType(pMT);

}

// Process the buffersif (m_bYuv) {

return ProcessFrameYUV(pSource, pDest);

}

else {

return ProcessFrameRGB(pSource, pDest);

}

}

ReceiveConnection

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

  1. Вызывает IPin::ReceiveConnection на нижележащем входящем контакте.
  2. Если вызов ReceiveConnection успешен, вызывается IMemInputPin::NotifyAllocator на входящем контакте.

Вдобавок, исходящему контакту может понадобиться вызов IMemAllocator::SetProperties и затем отмена (decommit) и переподтверждение (recommit) распределителя для изменения размера буфера. Перед изменением размера буфера нужно убедиться, что все сэмплы, что стоят в очереди, переданы.

Некоторые MPEG-2-декодеры используют этот механизм для переключения между выходом MPEG-1 и MPEG-2 или в случае изменения видеоразмера.

Изменение формата фильтром видеотображения

Посмотрим, как фильтр-декодер или фильтр-преобразователь должны себя вести при изменении формата видеорендерером.

Видеорендерер (Video Renderer Filter).

Когда соединяется старый фильтр видеорендеринга, он требует RGB-формат, который ожидает формат показа первичного монитора. Это позволяет ему использовать GDI для воспроизведения, если DirectDraw недоступен. Когда начинается проигрывание, видеорендерер может переключиться в DirectDraw-совместимый формат. Для проверки, поддерживает ли вышележащий фильтр новый формат, видеорендерер вызывает IPin::QueryAccept на исходящем контакте вышележащего фильтра. Если вышележащий фильтр поддерживает новый формат, метод QueryAccept возвращает S_OK. Видеорендерер переключает форматы, присоединяя медиатип с новым форматом к новому медиасэмплу, возвращаемому распределителем. Вышележащий фильтр проверяет изменение форматов вызовом IMediaSample::GetMediaType для каждого сэмпла. Видеорендерер может переключаться назад и вперед исходным форматом и новым форматом когда ему заблагорассудится. Он не вызовет QueryAccept после первого изменения формата. Как только вышележащий фильтр примет новый формат, он должен будет переключиться назад и вперед.

Вышележащий фильтр может отказать изменению формата, возвратив S_FALSE из QueryAccept. В этом случае видеорендерер продолжит использование GDI в исходном формате.

Фильтр видеомикширования (Video Mixing Renderer Filter).

VMR-фильтр (VMR-7 или VMR-9) соединяется с любыми форматами, которые поддерживаются видеооборудованием системы. Он всегда использует DirectDraw для воспроизведения и выделяет поверхность DirectDraw, когда соединяется с вышележащим фильтром.

Графическое видеооборудование может требовать поверхность с бОльшим шагом, чем ширина картинки. В этом случае VMR запрашивает новый формат вызовом QueryAccept. Он выдает шаг поверхности в члене biWidth структуры BITMAPINFOHEADER видеоформата. Если вышележащий фильтр не возвращает S_OK из QueryAccept, то VMR отказывается от формата и пытается соединииться, используя следующий формат, заявляя о нем вышележащему фильтру. VMR присоединяет медиатип с новым форматом к первому медиасэмплу. После первого сэмпла формат остается постоянным; VMR не переключает форматы, пока граф запущен.

Продолжение: Фильтры DirectShow. Часть 5

Comments