Первый фильтр под эту замечательную утилиту я написал лет пять тому назад. Это был простейщий фильтр, вставляющий в углу кадра реальное время и календарь. Так как я часто записываю семейные события, мне такой примитивный "эффект" был очень нужен. И я им пользуюсь по сей день.
На данный момент веду разработку нового, более продвинутого, фильтра.
Что он сейчас умеет:
Сейчас у меня имеются некоторые планы.
В основном, это касается разделением прослушивания на дополнительные потоки:
2014.04.27
Встроить поддержку nut-скриптов оказалось совсем просто и потребовало всего несколько часов.
К сожалению, некогда я уже занимался AngelScript, но на первых же этапах забросил, так как применить было не к чему. Теперь следует навёрствовать и изучать, встраивать разные функции управления кадрами...
2014.04.30
Что-то не совсем ладится работа...
Во-первых, зеркало файла для совместного использования самим VirtualDub фильтром и скриптовой оболочкой хотел оформить как .rdi-файл. Однако, всюду описание скудное и этот заголовок из 16 байтов я не уверен, что представил верно, так как под рукой нет никакого примера rdi-файла, а гугл совсем не то выдаёт. Оформил тупо как bmp.
Во-вторых, что-то не могу понять некоторые места Squirrel. Например, как из оболочки в процедуру скрипта передать структуру кадра, которая хранит свойства (ширина, высота, номер кадра и т.д.). И как в моей print-функции работать со входным параметром с типом OT_TABLE? Пришлось пока обойтись тупостью: getWidth(), getHeight() и т.д.
2014.05.01
Написал несколько надстроек и функций для фильтра.
2014.05.13
// Squirrel Script function render(st) { Quad86(0,0, 256,0, 0,239, st.x-3, st.y-3, 0.0, 0, 0, 720, 400, 1.0); Quad86(256,0, 319,0, st.x+3, st.y-3, 319,160, 0.0, 0, 0, 720, 400, 2.0); Quad86(st.x+3,st.y+3, 319,160, 0,239, 319,239, 0.0, 0, 0, 720, 400, 3.0); return; }
Через тернии к пикселам...
В силу того, что я начал работать с компьютерной техникой с пятого класса, став владельцем РЛК "КР-03" первый из моих знакомых и друзей, в то время, как в журналах публиковались схемы "Специалиста" и "Орион-128", у меня, параллельно с навыками программирования машинного кода, рос интерес к компьютерной графике. Пусть даже псевдографика КР580ВГ75 давала графическое разрешение 128x50, но мне удавалось с относительным успехом адаптировать программы под IBM-PC на Бейсике из справочником под свою персоналку.
При переходе на ZX-Spectrum хоть графика и стала лучше в восемь раз и обрела цвет, особенности программирования в машинном коду были просто чудовищными, так как без загрузки соответствующих прикладных программ, которые нужно было ещё сыскать среди сотен игр на рынке, заставили меня вернуться к РЛК. Вплоть до машины "Поиск"...
Пентиум-I
Естественно, когда у меня появился PC под управлением Windows'98, я начал искать те программы, про которые читал в журналах, залистывая до дыр. Это и Adobe Primiere, и Adobe Photoshop, и 3D-Studio MAX, и Visual Basic, и Corel Draw, и многое другое, что так или иначе касается графики.
Видимо, отсутствие возможности работать с графикой с самого начала ещё в школьные годы оставило свой отпечаток и появился комплекс. Друзья и знакомые иногда предлагали заняться чем-то иным, СУБД например или сетью. Но, так как я понимаю, что FAT/NTFS и другие файловые системы - есть СУБД в каком-то роде, а дефрагментация диска - один из сценариев сортировки всех записей в этой БД, подобные архисложные задачи я пытаюсь обходить стороной.
Графика имеет основной большой плюс: Если ты где-то ошибся, прорисовка сцены это наглядно выявит. От ошибок алгоритмизации графики ничто не рухнет, в отличие от БД. Ну, за редким исключением...
Вступление
Мне, как ламеру, знающему основы программирования, некогда надоело дёргать монотонно мышью в графическом редакторе, редактируя кадры одного видео. Я задался вопросом, нельзя ли алгоритмизировать процесс?
Долгое время я искал подходящий фильтр к VirtualDub с подобными фишками. Оказалось, легче не дожидаться, а начать писать самому...
Фильтр
При активации фильтра в VirtualDub создаётся большой BMP-файл, глобальное зеркало на него в памяти, а также и глобальное событие.
Принцип работы фильтра довольно прост: Он помещает кадр из BMP-файла на выход потока, а со входа потока копирует ккадр в тот же BMP-файл. Это вызывает опаздывание видео ровно на один кадр. Причём, с каждым кадром фильтр активирует событие.
У любого стороннего процесса, следящего за событием, есть возможно внести свои изменения в кадр. Времени на это достаточно...
Сценарии
В качестве языка сценариев я выбрал Squirrel.
В примитивной оболочке имеется текстовое поле редактируемого сценария, окошко предварительного просмотра потока и несколько ползунков со списками режимов.
Работа сценария начинается разовым вызовом функции main() и периодическим вызовом с каждым новым событием функции render(...).
Для обработки кадров имеется некоторый набор функций, принимающих в качестве аргументов индексы потоков, индексы буферов потоков и все другие необходимые данные...
Выбор потока и буфера
Вы можете запустить фильтр в четырёх резных окнах VirtualDub и назначить им 4 разных потока. Все потоки хранятся в единном BMP-файле.
Под один поток выделяется до 4Мб файла. Это примерно по одному кадру с размешением до 1024x768 с глубиной цвета 32 бита. Если же кадр имеет несколько меньшее разрешение вдвое или втрое, соответвенно, в одном сегменте 4Мб файла можно поместить несколько таких кадров...
Тем самым, в операциях над кадрами функциям мы всегда передаём индексы приёмника или источника как вещественные числа.
Цифра целой части указывает на индекс буфера потока. Цифра дробной части указывает на индекс промежуточного буфера в этом потоке.
Вот практически весь этот год я занимаюсь разработкой своей утилиты. В час по чайной ложке, как говорится...
Сейчас можно подвести некоторые итоги:
1. Как программист, я в себе очень разочаровался. Причины некоторых глюков до того банальны, что я только вздыхаю. Так, на буфер строки статуса выделил 256 байт, а потом долго выяснял, отчего подробный статус валит всё в крах;
2. Так как год назад купил не очень дешёвую DVR-карточку PCI-версии, в новом компьютере её некуда пристроить. Пришлось использовать ТВ-тюнер и VirtualDub. Если в официальной поставке DVR-софт был до неприличия Китайским (грузился несколько минут; вис в неожиданных местах; вешал систему намертво), попытался его заменить своей этой утилитой;
3. В собственной данной утилите с помощью Squirrel-скрипта написал DVR-сценарий: Следит за активностью в кадре. Когда активности нет, экономит дисковое пространство синим фоном (VirtualDub с кодеком XviD снижает битрейт до 2кб/сек.). А когда кто ходит, начинает бипать, останавливает проигрыватель (VideoLan и подобные) и на весь экран отображает кадр камеры (битрейт достигает 700кб/сек.). Таким образом, могу часами тупо лежать на диване, смотреть клипы и фильмы. А когда нужно, автоматически вижу зону наблюдения...
local fps = 25, Sleep = 0, start_hh = 23, start_mm = 59, show_hh = 0, show_mm = 0; local Refresh_max = fps * 70; // Задержка до общего обновления буфера фона local Refresh_cnt = Refresh_max; // Счётчик задержки до общего обновления буфера фона local Useful_max = fps * 12; // Задержка до экономии видео трафика local Useful_cnt = 0; // Счётчик задержки до экономии трафика local Detect_max = fps * 60; // Задержка до обновления буфера фона при обнаружении local Detect_cnt = Detect_max; // Счётчик "спокойствия" кадра local Detect_rep = fps * 45; // Повторная задержка счётчика "спокойствия" local Motion = 0; // Направление движения local Statistic_out = 0; // Сколько выходило local Statistic_in = 0; // Сколько входило local Statistic_noise = 0; local Statistic_detect = 0; local Statistic_motion = 0; local Statistic_refresh = 0; function reset(st) { Statistic_out = 0; Statistic_in = 0; Statistic_noise = 0; Statistic_detect = 0; Statistic_motion = 0; Statistic_refresh = 0; Beep(); } function main(st) { Status(st.ver + "; Resolution: " + st.w + "x" + st.h); Sleep = (st.ss + 5) % 60; if(State("Option", 1)) start_hh = st.hh, start_mm = 59, show_hh = (start_hh + 1) % 24, show_mm = (start_mm + 1) % 60; Statistic_out = State("swExtra", 20); // Сколько выходило Statistic_in = State("swExtra", 21); // Сколько входило Statistic_noise = State("swExtra", 22); Statistic_detect = State("swExtra", 23); Statistic_motion = State("swExtra", 24); Statistic_refresh = State("swExtra", 25); } function render(st) { // if(st.y > 40 && st.y < 480 - 40) Banner(1.0, 5, 0.0, st.x - 60, st.y - 40, -120, 80); if(st.mm == start_mm && st.hh == start_hh) { if(st.ss == 0) { State("sbExtra", 7, 0x80), State("sbExtra", 19, 0); } Beep(); return 1; } else if(st.mm == show_mm && st.hh == show_hh) { if(st.ss >= 15) { State("sbExtra", 7, 0xF0), State("sbExtra", 19, 1); start_hh = 23, start_mm = 59, show_hh = 0, show_mm = 0; } else { Beep(); return 1; } } local n = 0, perc=0, summ=0, subj=0, dirt=0, start=false; local gritty = State("sbExtra", 20); // Чувствительность local amount = State("sbExtra", 21); // Масштабность if(!State("Option", 0)) return Chroma(0.1, 0.0), Refresh_cnt = Refresh_max; Chroma(0.3, 0.0); // Копируем исходный кадр в "запас" Chroma(0.0, gritty + 0.1); // Строим дифференционную Альфа-маску if(Refresh_cnt < 1) // Если буфер фона безнадёжно устарел Chroma(0.1, 0.0), Refresh_cnt = Refresh_max; // * Обновить его и восстановить свежесть фона summ = Chroma(0.0, amount, // Индексируем все "годные" субъекты в кадре Useful_cnt > 0 ? 0.3 : 0x3355, 0.3); subj = (summ >> 24) & 255; // Число субъектов в кадре dirt = (State("Breaks", 255) >> 24) & 255; // Число объектов вне зоны полезности perc = (0.0 + ((summ & 0xFFFFFF) * 1000 / // Процентность масштабности общей (st.w * st.h))) / 10.0; // площади, занимаемой объектами в кадре if(subj > 0) // Обнаружены субъекты в "полезной" зоне? start = ((Detect_cnt --) == Detect_max), // * Уменьшить счётчик "терпения" детектора Useful_cnt = Useful_max, Spots(1, 255), // * Выставить счётчик полезности Refresh_cnt --, Beep(); // * Уменьшить уровень "свежести" буфера фона else // Объекты отсутствуют Detect_cnt += Detect_cnt < Detect_max ? 1 : 0, // * Восполнить уровень "терпения" детектора Useful_cnt --; // * Уменьшить уровень полезности потока if(subj == 0 && dirt > 0) // Обнаруживаются объекты вне полезной зоны? Refresh_cnt --; // * Уменьшить уровень свежести буфера фона if(Detect_cnt < 1) // Уровень "терпения" детектора исчерпан? Detect_cnt = Detect_rep, // * Восполнить "терпение" Refresh_cnt = 0, // * Принудить буфер фона обновиться Statistic_detect ++; // * Считать число "восполнений терпения" if(Refresh_cnt == 0) // Уровень свежести буфера фона исчерпан? Statistic_noise += !subj && dirt > 0 ? 1 : 0, // * Возможно, это простой шум Statistic_refresh ++; // * Считать число обновлений фона if(Useful_cnt < 1) // Полезность кадра исчерпана? Useful_cnt = 0; // * Полностью State("sbExtra", 7, !!Useful_cnt ? 0xF0 : 0xE0); // Скрыть/показать строку бинарного тайм-кода if(start) { start = Spots(-3, 1); if(start == -15) Statistic_in ++, Motion = start; if(start == -12 || start == -8 || start == -4) Statistic_out ++, Motion = start; } else if(Motion) { start = Spots(-3, 1); if(Motion == -15 && (start == -12 || start == -8 || start == -4)) Statistic_motion ++; else if(start == -15 && (Motion == -12 || Motion == -8 || Motion == -4)) Statistic_motion ++; Motion = 0; } if(Sleep > 0) -- Sleep; else Status( Str("%2d:%02d:%02d.%02d Таймер %2d:%-07.2d", st.hh, st.mm, st.ss, st.SS % 100, start_hh, start_mm), Str("Чувствительность:%3d%%", (100 - gritty)), Str("Масштаб :%3s%% при %5s%%", "" + perc, "" + ((0.0 + amount) / 10.0)), Str("Объектов:%3d #3:%02X", subj, Spots(-3, 1) & 0xFF), Str("Вне зоны:%3d", dirt), Str(" Свежесть плана :%3d%%", (Refresh_cnt * 100 / Refresh_max)), Str(" Полезность кадра:%3d%%", (Useful_cnt * 100 / Useful_max)), Str(" Статичность :%3d%%", (Detect_cnt * 100 / Detect_max)), "Статистика сеанса:", " Снимков :" + Statistic_detect, " Шума :" + Statistic_refresh, " Помех :" + Statistic_noise, " Ходьбы :" + Statistic_motion + " " + Statistic_in+">" + Statistic_out, (State("Option", 1) ? "Прозрачность" : "Перекрытие ") + " X:Y=" + st.x + ":" + st.y ); State("swExtra", 20, Statistic_out); // Сколько выходило State("swExtra", 21, Statistic_in); // Сколько входило State("swExtra", 22, Statistic_noise); State("swExtra", 23, Statistic_detect); State("swExtra", 24, Statistic_motion); State("swExtra", 25, Statistic_refresh); return 1; }
Снимок окна наблюдения
Снимок окна проекта
Сбылась мечта идиота!
На прошлой неделе реализовал давнюю задумку.
Теперь, Preview-область VirtualDub стала активной и чувствительной к кликам указателя мыши.
Реализовано просто и, как уверен, по всем правилам: При инициализации фильтра регистрирую персональный класс окна. А при активации - отыскиваю дочерное окно просмотра и открываю в нём своё прозрачное (WS_EX_TRANSPARENT) окошко. Оно определяет и свой указатель мыши, и ограничивает перемешение мыши в пределах своей области при нажатиях кнопок, и передаёт скриптовой среде сообщения разных событий (клики/перемещения, новый кадр и т.д.).
Доработал и исправил несколько грубых ошибок.
Ранее оболочка иногда валилась при запуске: Баг в преждевременной генерации события готовности прежде, чем проекция на файл.
Ранее оболочка иногда зависала при выходе: Баг в неверной последовательности дезинициализации.
Ранее оболочка в режиме полного экрана мерцала данными: Теперь используя DirectDraw весь текст в кадре выводится чётко.
Тема в архиве.