Автоматизация записи эфира

AutoRecorder заголовок

Предисловие

Отвлекся я от публикаций на тему АСК. Как всегда, это бывает, родилась проблема на ровном месте. А началось всё с новой сотрудницы на нашей телекомпании, которая пришла к нам, чтобы сайт наш и соц. сети не зачахли в конец, а развивались, как и положено в этих наших интернетах. И вот эта девочка как-то подходит и спрашивает: «Вот мы вроде всё создаём и вещаем в формате HD? А чего же у нас для сайта и т.д. видео пишется в старом формате SD?» Хотел было отмахнуться от неё, мол «чего ты, дева молодая, да не опытная, лезешь в дела взрослые, мужитские да технические»… Но призадумался. Дело в том, что новый комплекс (черти его знает почему) не предусматривал записи эфиров для таких целей. И когда, при вводе в эксплуатацию, этот вопрос встал, решать его, как водится, нужно было быстро и безболезненно. Вот и прикрутили машину от старого комплекса с её платами видеозахвата и старым ПО, да и забыли про это дело, закружившись в танце с бубном с косяками особенностями нового АСК. Но нет ничего вечного и этот вопрос всё же всплыл. А значит нужно решать. Ну решили то мы его быстро, всего то подобрать новые платы захвата и программу, которая сможет писать в нужном нам формате (про особенности и форматы чуть позже), но, как говориться, «тут Остапа понесло!».

Глава 1 «постановка задачи»

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

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

Ни хрена себе цепочка людей и действий только для того, чтобы обеспечить «побочный» продукт нашей жизнедеятельности… Ведь в теории всё можно сделать проще путём автоматизации. Как? И вот, поковырявшись в носу, в своей голове и в схемах комплекса, я вспоминаю про интереснейший блок «управление». Подробно будем про него говорить отдельно, а сейчас расскажу в общих чертах:

АСК – это ничто иное как совокупность разностных устройств и машин. И они друг про друга практически не знают, а управлять ими нужно. Да и самим устройствам нужно управлять другими устройствами. Это и есть автоматизация в какой-то степени. Так, например, есть у нас видеомикшерный пульт, на котором режиссер переключается между эфирными серверами, камерами и т.д. В то же время, о всех действиях пульта должны знать другие устройства. Например, видеосервера должны подчиняться пульту и запускать видеосюжеты, когда пульт выдает их в эфир. Генераторы полиэкранных изображений должны подсвечивать источник, который пульт отдал в эфир и т.д. Большинство этих сигналов проходит по отдельным линиям связи GPI, но есть и те, что общаются по отдельной инженерной сети LAN. И вот тут меня осенило: «Если перехватить сигналы от пульта, можно резать видео налету в автоматическом режиме!» Ну что же, вижу цель, не вижу…

Глава 2 «… Препятствия!»

«Ну ё#$@ мать!» выкрикнул я, после дня поиска документации в интернетах, и грязно выругался. Дело в том, что протокол TSL UMD V3.1, по которому общается наш видеомикшер с контроллером Tally и мнемоник, в интернете описан скудно и то на английском. Учитывая тот факт, что английский у меня разговорный, а программист из меня «хуановый», чтение этих крупиц информации доставляло неимоверное жжение в области, где спина заканчивает свое благородное название. И вот, после нескольких дней чтения, подгорания, проб и ошибок, я смог принять первые пакеты. Данный протокол работает по UDP порту (в нашем случае 5000), по принципу «выплюнул в указанный IP адрес пакет и забыл». Со стороны приема же оказалось вообще всё просто: слушаем порт UDP 5000, и как только пакет пришел, плевать кто его прислал, обрабатываем. На C# приём пакета в текстовом режиме в кодировке ASCII выглядит вот так:

В этих пакетах пульт шлёт нам 18 байт информации:

  • 1 байт – заголовок. Как правило, там должен быть адрес дисплея, но я так и не смог правильно понять его зависимости и т.д. Да он нам и не нужен.
  • 2 байт – контроль. Этот байт сообщает нам об индикаторах Tally.
  • 16 байт – текстовая информация. Тут передаются названия источников (мнемоники).

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

И так, что мы приняли от нашего пульта данным кодом:

За точку среза записи мы берем переход на любую из трех наших камер. Почему? Тут нужно пояснить про построение новостного эфира. Дело в том, что каждый новый сюжет начинается с диктора, который зачитывает так называемую подводку к сюжету. Далее уже идёт сам сюжет с какого-либо сервера, но нам эта часть уже не интересна. Мы продолжаем писать в файл до следующего выхода на камеру. Значит наша программа должна реагировать на три пакета «?1CAM1», «?1CAM2» и «?1CAM3». Остальные пропускаем мимо. Подумалось мне что всё, основная проблема решена. Но нет! Дальше начинается цирк с конями…

Глава 3 «С#. Ну что с тобой не так?»

Дальше нужно было написать или собрать ту часть кода, что непосредственно подключалась бы к плате видеозахвата и кодировала бы видео. Но, изнасиловав гугл так, что Сергей Михайлович Брин на пару с Ларри Пейджем уже, наверное, начали креститься от очередных запросов с моего адреса, я так и не смог найти пример подобного кода. Отчаянию не было предела. Тупик! В одиночку я конечно же не смогу такое написать с нуля. «Ну что ж, попробуем подключить тяжелую артиллерию» подумал я и решил написать своему старому другу – программисту. Я могу только представить какое было у него выражение лица, когда он прочитал мой вопрос на тему «как кодировать видео при помощи C#?». Наверняка он сначала проржался, потом побился головой об стол с криками «нахрена я вообще дружу с таким идиотом», и только потом… сделав глубокий вдох… он объяснил мне, как ребёнку, который сует пальцы в розетку. Оказалось, что C# слишком тяжел и не поворотлив для того, чтобы пытаться обработать им видео HD. Именно по этой причине, нормальные люди используют С++ и подобные языки… Ну что же, снова тупик. На этот раз я подумал, что окончательный. Но тут мне на глаза попался проект ffmpeg. Это консольная программа как раз предназначенная для захвата, кодирования, копирования, подрезания и чёрт знает для чего еще. Открыв документацию, я офигел от возможных вариантов применения и вновь принялся ковырять гугл, с новой силой.

И так выработалась концепция: наша программа будет некой обёрткой для консольной ffmpeg, со свистелками, перделками и прочим непотребством. Несколько дней чтения английской документации (да. да. в асбестовых трусах) я смог выстрадать строку запуска ffmpeg с которой мы можем работать:

ffmpeg.exe f decklinkiDeckLink Studio 2@9″ –loglevel errorf mp4 –movflags frag_keyframec:v mpeg2videopix_fmt yuv422pb:v 10Mvf fps=30 –s 1280x720 –g 30 –g 12 –mbd rdtrellis 1 –flags +mv0 –cmp 0 –subcmp 2 –c:a aacb:a 128Kar 48000 –yfilename.mp4”

Да, выглядит как жесть жестяная, но во всём этом есть логика. Давайте разберемся:

  • f decklinkiDeckLink Studio 2@9″ – тут всё более или менее понятно. Так мы говорим, что у нас плата decklink studio 2, а @9 объявляет, что мы подключаемся к ней в режиме 1080i50. (что бы проще было сформировать командную строку, можно обратиться к проекту FFmpegGUI. Удобная оболочка.)
  • -loglevel error – указываем уровень передачи логов нашему приложению. В данном случае говорим сообщать только об ошибках. Это нужно, что бы не засорять логи в нашей программе.
  • f mp4 – задаём контейнер формата mp4
  • movflags frag_keyframe – вот тут поподробнее. Эта часть говорит, что ключевые кадры и всю служебную фигню нужно складывать вначале файла. Зачем? Да очень просто. Так мы сможем читать еще не законченный файл. Т.е. даже если файл всё еще пишется, мы всегда сможем взять от него кусок, не прерывая записи.
  • keyframec:v mpeg2videopix_fmt yuv422pb:v 10Mvf fps=30 –s 1280x720 –g 30 –g 12 –mbd rdtrellis 1 –flags +mv0 –cmp 0 –subcmp 2 –c:a aacb:a 128Kar 48000 – так задаём формат видео и аудио. Кодек, разрешение, битрейт, кадры в секунду и т.д. В нашем случае это mpeg2, 4:2:2, 10Mbit/s, 30fps, 1280×720. Честно сказать, сам не все ключи понимаю, а по сему эта часть почти вся сформирована FFmpegGUI + немного гугла.
  • yfilename.mp4” – это понятно, имя выходного файла. Это нужно только для теста в консоли. В дальнейшем наша программа сама будет формировать имя файла.

Убедившись, что всё работает из консоли и пишется, можно переходить обратно, к написанию нашей обёртки.

Глава 4 «А вот и Джонни»

И так, логика программы выстрадана:

  • Программа в ручном режиме или по времени должна запускать / останавливать процесс ffmpeg с нужными параметрами.
  • По команде от видеомикшера по UDP программа должна запускать еще один процесс ffmpeg с иными параметрами. Этот процесс открывает записываемый файл на лету и делает его копию с указанным временем начала и временем окончания в другую папку.
  • Программа должна вести журнал логов и ошибок, а также контролировать подчиненный процесс.

Ну поехали кодить:

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

Туда же впиливаем планировщик задач, что бы основной процесс записи можно было так же автоматизировать:

Грузить статью исходным кодом сильно не буду. Кривой он, если честно (но работает), да и заскучает читатель. Однако по настройкам немного пробежимся:

  • cmd: – тут всё понятно. Суда пишем нашу строку параметров запуска ffmpeg. Запускать эти пироги будем примерно вот так:
  • Путь для записи / Путь для срезов – указываем папки куда писать целый файл и куда копировать срезы сюжетов.
  • Δ начала / Δ конца – этот параметр нужно подбирать исходя из задержек сети и задержек выбранного нами кодека. Т.е. прибавляем или отнимаем от начального и конечного времени среза столько, что бы срезанный сюжет не ловил лишние кадры в начале и в конце файла. Однако тут пришлось подключать еще одну утилиту от ffmpeg. Что бы правильно отрезать, нужно узнать текущую длительность файла. На помощь приходит ffprobe. Делаем такую функцию, которая возвращает нам длительность файла:

    После чего можем крамсать и резать:
  • UDP порт – порт, который мы слушаем и принимаем пакеты.
  • TSL UMD – тут через запятую перечисляем управление + мнемоники, на которые нам нужно реагировать. В нашем случае это 1CAM1,1CAM2,1CAM3 (как говорилось выше).
  • Мин. Срез – это небольшая защита, на случай, если режиссёр слишком часто переключает камеры. Что бы не резать секундные куски. Поверьте, эта фича тут нужна. Её я реализовал путём таймера:
  • Размер лога – тут всё понятно. Как только файл логов достигнет указанной отметки, он сам очистится.
    Сам класс логирования выглядит банально:

По планировщику всё вообще понятно и без слов:

Единственный момент – окно-предохранитель. Я нашел на просторах сети библиотеку AutoClosingMessageBox которая позволяет выводить диалоговые окна с автозакрытием:

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

И еще небольшой “кастыль” debounceSched(); Реализован аналогично debounceCut(); А существует он потому что мой радиус кривизны рук в области программирования не смог толком разобраться с разными потоками выполнения некоторых функций, отчего в if (d1 == d2) программа сваливалась по 9-10 раз за каждое срабатывание. Да простят меня все программисты, которые это читают, за их кровоточащие глаза.

Ну и главная форма:

Тут всё просто:

  • В чёрном квадрате бежит обратный отсчет до ближайшего события планировщика (если он включен), либо время записи.
  • Индикатор ID – с id основного процесса записи. Если он зелёный, значит всё хорошо. Если нет – всё плохо, или запись просто остановлена.
  • Индикатор CUT – отображает количество срезов. Если зелёный, значит ждёт команды. Если красный – в процессе среза.
  • Индикатор WAIT – отображает мнемоники которые пришли от пульта и на которые он отреагировал. Становится красным при обновлении данных.
  • Ну и кнопки старта/стопа записи и перехода на ручное управление. На случай если нужно сделать срез вручную.

Эпилог

Вот так, нехитрыми способами, можно найти себе приключений на пятую точку. Но, потратив на это изыскание два месяца работы, получаешь не кислую порцию новых знаний, и «плюсик в карму». Проект сейчас перешел из бета – теста в стадию внедрения. В случае удачного внедрения я смогу длинную цепочку производства, описанную вначале, сократить вдвое. Амбициозно, правда?

Исходники класть сюда не стану, положу саму программу. Но если кому-то захочется поковыряться, можете написать мне. Я не жадный!

И в конце о системных требованиях:

  • Core i5 или выше
  • SSD или RAID0 (stripe)
  • Windows 7 x64 или выше
  • .Net framework 4.0
  • источник пакетов TSL UMD (микшер)

архив проекта

2 комментария для “Автоматизация записи эфира

  1. Мужик, это супер! Я думаю проекты такого уровня стоят много денег, а тут взять и сделать с нуля… Огонь!

  2. Может и стоит денег, я не нашел аналогов в продаже. А на счет “сделать с нуля” – уж ты то должен понимать мою мотивацию, радиостанцию с нуля построить – эт тебе тоже не в лужу пописать 🙂

Добавить комментарий

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