Russian Qt Forum
Ноябрь 23, 2024, 18:11 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1]   Вниз
  Печать  
Автор Тема: Концепция фильтров из DirectShow  (Прочитано 4803 раз)
alexis031182
Гость
« : Февраль 06, 2013, 17:48 »

Здравствуйте.

Небольшое вступление... У меня давным давно была мечта реализовать концепцию фильтров DirectShow на C++ самостоятельно. Ещё в бытность того времени, когда мне приходилось иметь дело с этой, не спорю, замечательной библиотекой, мне очень нравилось то, каким образом построено управление подключением модулей друг с другом. В DirectShow они назывались фильтрами (пишу в прошедшем времени, т.к. давно уже не имею дела с виндовс, а потому не знаю, каким образом обстоят дела в текущий момент времени). Фильтром может быть и драйвер устройства, и некий программный модуль, предназначенный для обработки данных, и даже просто окном, которое, например, просто рисует видеокадры на экране. Безусловно, фильтры могут работать не только с видеопотоком, но с потоком любых медиаданных. Даже класс, принимающий данные по сети, может быть фильтром. В общем-то, концепция фильтров предполагает, что есть некий поток данных. У этого потока имеется фильтр источник и фильтры обработчики.

Однако фильтры могут работать одновременно с произвольным количеством потоков. Чтобы было понятно, о чём речь, я схематично представил идею в pdf-файле (во вложении к посту).

Как не трудно заметить, фильтры подключаются через некие точки входа/выхода данных (в DirectShow они назывались Pin, что переводится не очень благозвучно на русский язык). Каждый из этих "пинов" несёт в себе информацию, а что собственно за медиаданные будут через них проходить. Это необходимо для того, чтобы например по ошибке нельзя было подключить аудиофильтр к видеофильтру.

Сложность реализации в том, что проверка возможности подключения фильтров между собой должна выполняться ещё на этапе компиляции. При этом необходимо, чтобы в рантайме непосредственно само подключение/отключение производить было возможно. Необходимо, чтобы фильтры при подключении поддерживали произвольные параметры и произвольное их количество (единственное условие, что на отдающей и приёмной стороне двух фильтров параметры как по составу, так и по количеству должны совпадать). Обязательна возможность использования произвольного возвращаемого значения, а не только void, дабы фильтр источник мог соответствующим образом реагировать на изменение ситуации после отправки им данных. Вот, вроде, и все условия.

К сожалению, полностью сам я так и не смог решить эту задачу. Понятно, что вся реализация этой концепции может быть выполнена исключительно на шаблонах (см. правило об этапе компиляции). Впрочем, если кто представит другие варианты, то своё "исключительно" снимаю. И шляпу, как дань уважения. Реализованная мною библиотека основывается на концепции функторов Александреску.

Теперь, пару-тройку слов об использовании.

Любой класс (в том числе и наследник любого Qt класса) можно превратить в фильтр через наследование. Для этого подключаем в заголовке заголовочный файл (масло масляное):
Код
C++ (Qt)
#include "aloki/alokifilter.h"
Ну а объявление нашего класса может быть, например, таким:
Код
C++ (Qt)
class AOpenCVVideoDevice : public QObject, public ALoki::Filter {
   Q_OBJECT
 
   public:
       ...
};
Я взял OpenCV в пример не случайно, т.к. эта кроссплатформенная библиотека предоставляет функционал не только для обработки видео, но и для его захвата. Впрочем, какой поток медиаданных будет использоваться, вопрос не принципиальный.

Следующим этапом будет реализация "пина", например, но вовсе и необязательно, в конструкторе:
Код
C++ (Qt)
using namespace ALoki;
Filter::createOutputPin<void, TypeSequence<cv::Mat&, bool> >(0);
FilterPinInterface *pin_i = Filter::outputPin(0);
if(pin_i) _output_pin = static_cast<OutputPin*>(pin_i);
Первая строчка понятна всем. Вторая говорит о том, что вновь создаваемый "пин" не требует возврата значения (void), но будет отсылать видеокадр (cv::Mat&) с неким флагом (bool). Заметьте, что в качестве параметров можно использовать указатели и ссылки, в т.ч. и константные.

В данном примере мы используем такие параметры, но никто не мешает нам создать, например, такой "пин":
Код
C++ (Qt)
Filter::createOutputPin<RESULT, TypeSequence<NetworkDatagram, int, qint64, и_какой_нибудь_функтор_до_кучи> >(0);
В скобочках указывается индекс "пина". Как я уже говорил, их может быть произвольное количество, поэтому в последствии будет удобно находить желаемый, просто указав его индекс (вторая и третья строки примера). Ну и конечно мы можем указатель на наш "пин" сохранить непосредственно в объекте класса, дабы, например, не тратить ресурсы на его ("пина") поиск среди других в списке (последняя строка примера).

Теперь нам предстоит отправка данных. Тут всё тривиально и по сути сводится к вызову переопределённого оператора вызова (и снова масло) нашего "пина":
Код
C++ (Qt)
if(_output_pin) (*_output_pin)(video_frame, is_first_frame);

Далее, можно создать фильтр, который будет принимать видеокадры и каким-то образом их обрабатывать. А можно сразу взять, например, виджет, и унаследовав его в дополнение от "ALoki::Filter", приговорить кадры к выводу на экран. Но это самостоятельно, если конечно будет интерес. А сейчас мы просто возьмём какого-нибудь наследника QObject. Естественно через наследование и тем же макаром зададим ему предка "ALoki::Filter", ну и создадим входной "пин":
Код
C++ (Qt)
AClassifierOpenCVFilter::AClassifierOpenCVFilter(QObject *parent) : AOpenCVFilter(parent) {
   using namespace ALoki;
   typedef void(AClassifierOpenCVFilter::*present)(cv::Mat&, bool);
   Filter::createInputPin<void, TypeSequence<cv::Mat&, bool> >(
       0, this, &AClassifierOpenCVFilter::present);
}
В данном примере мы создаём входной "пин", который способен принимать видеокадры (плюс, левый bool-флаг в дополнение) от фильтра источника, созданного нами в предыдущем листинге. Отличительной особенностью входных пинов является необходимость в указании указателя (мать) на метод класса фильтра. В данном примере, класс "AClassifierOpenCVFilter" обязан содержать метод "void present(cv::Mat&, bool)", т.к. именно в него будут приходить видеокадры от фильтра-источника.

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

Код
C++ (Qt)
_opencv_video_device->connectFilter(0, _classifier_opencv_filter, 0);
Полагаю, что эта строчка примера не вызовет недоумения. Для подключения фильтров, мы берём фильтр-источник и вызываем его функцию "connectFilter()", где первым аргументом идёт индекс выходного "пина" у источника, вторым - принимающий данные фильтр, а третьим - этого фильтра входной "пин".

Что-то "пара-тройка" слов подзатянулась...
Записан
ритт
Гость
« Ответ #1 : Июль 20, 2013, 05:42 »

любопытно...
Записан
Страниц: [1]   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.479 секунд. Запросов: 21.