Синхронизация звука и видео. Взгляд дилетанта
В наш век все привыкли к скорости. Автомобили могут ездить со скоростью свыше 500 км\ч, китайский поезд на магнитной подушке развивает скорость до 600 км/ч, а самый безопасный вид транспорта, самолет, летает со средней скоростью 850 км/ч.
Но до сих пор, когда эта стальная птица летит в воздухе, все поднимают голову и следят за его полетом. Дети, обычно, ищут его по звуку, а взрослые уже знают, что его гудение значительно отстает от самого воздушного судна, и самолет давно уже находится в другой части неба.
Звук отстает от объекта
В телеиндустрии ситуация прямо противоположная: звук часто опережает изображение. Это является большой проблемой при ведении прямых трансляций из разных точек мира, будь то спортивные мероприятия или концерты. Никто не будет смотреть футбол, когда радость болельщиков от гола слышна раньше, чем будет видно, как этот гол забьют. Целые команды работают в поте лица над тем, чтобы вовремя добиться синхронности видео и звука. Раньше для этого использовали хлопушку, как при съёмке кино, а затем методом проб и ошибок подгоняли звук под видео в предельно короткие сроки до выхода трансляции в эфир.
Что произойдет, если в трансляции не будет синхронизации
Прогресс идет вперед, и сейчас появились специальные приложения и программы, которые работают быстро (несколько секунд) и позволяют измерить эту задержку с точностью до миллисекунды и даже точнее. Эти программы разработаны, как для смартфонов, так и для стационарных компьютеров.
Летом мне пришлось повторить подобное приложение, и я тогда с головой погрузилась во все тонкости синхронизации. А сейчас решила подвести итог своим исследованиям, и эта статья — попытка обобщить весь мой путь проб и ошибок в этой области.
Простая схема измерений
У уже существующих приложений, например, Hitomi MatchBox, всё выглядело довольно загадочно, девушка показывает в профессиональную камеру экран смартфона со включенным приложением, звук от него записывает специальный микрофон, а затем программа на компьютере, получив эти два потока видео и звука по отдельности, высчитывает задержку между ними.
Визуальная метка
На первый взгляд, кажется, что на экране телефона происходит какая-то магия: каждую секунду сменяется qr код, а раз в четыре секунды весь экран вспыхивает лиловым цветом и одновременно выдается мелодичная трель. Оказывается, что всё это нужно для того, чтобы на другом конце земного шара программа могла расшифровать изображение и гармоническую последовательность.
Нетрудно было догадаться, что яркая вспышка — это метка для видео, а музыкальные тоны — для звука. Временная разница между ними и есть требуемое значение задержки, так как при генерации они проигрываются и показываются одновременно.
Цикл работы приложения
Начало разработки
Приложение я решила разрабатывать с использованием flutter, так как последние 4 года пишу код на C++, активно используя этот фреймворк. Основная часть обработки изображений (OpenCV) и звука (Oboe) — на C++, графический интерфейс на dart, общение этих частей происходило с помощью библиотеки ffi (Foreign Function Interface). Приложение планировалось использовать исключительно для Андроид-смартфонов. Генератор и анализатор должны быть не по отдельности, первый на ios, второй на стационарном компьютере, как у Hitomi, а вместе, в одной программе.
Генератор. Видео метка
Я начала свою разработку с генератора вспышки и qr-кода. С ними особых проблем не возникло. Было понятно, что код нужен для отслеживания положения лиловой вспышки, ведь в кадре могут находиться и другие предметы такого же цвета. Например, руки, держащие телефон, также имеют похожий оттенок и определяются компьютерным зрением, как лиловый цвет (см. фото). А используя прямоугольник ограничивающий qr-код, мы можем отсечь ложные цветовые пятна:
Обработка видео фреймов. Кожа человека тоже детектируется как нужный объект.
В qr-код так же можно передавать тайминг для оптимизации обработки кадров. Вспышка появляется каждые 4 секунды (на 4ой, 8ой, 12ой, 16ой секунде и т. д.), назовём секунду, в которой она создается, ключевой. Можно обрабатывать каждый фрейм видео только в ключевой секунде, а в остальных — в большинстве своем пропускать. Дополнительно в коде можно передавать информацию об устройстве, которое работало в качестве генератора (модель и марку телефона), и другие данные, используемые анализатором для уточнения вычисления задержки.
В процессе разработки возник интересный вопрос: почему у вспышки лиловый цвет?
Лиловый цвет вспышки содержит красную компоненту и синюю в равной степени
Лиловый — это смесь красного и синего цветов. А камера Android телефона обычно получает сырое изображение в формате YUV420 (в большинстве моделей устройств). В этом формате изображение состоит из полноформатного черно-белого фото (которое можно использовать сразу), а так же двух дополнительных матриц, хранящих яркость красного и синего соответственно. Это популярный формат кодирования цвета, используемый при сжатии изображений и видео. Он использует большую чувствительность человеческого глаза к изменениям яркости (Y) по сравнению с изменениями цвета (UV), тем самым, уменьшая размер файла, при этом сохраняя качество. YUV420 формат обычно занимает на 50% меньше памяти, чем полное представление через RGB.
Сравнение форматов изображений RGB и YUV420
Для эффективной обработки каждого кадра видео, конечно, лучше использовать не обработанное изображение, а исходное в формате YUV420. А в нём нам доступны без дополнительных сложных преобразований два цвета: красный и синий, это и дает ответ на вопрос: «Почему лиловый цвет?».
Проблема возникла с частотой кадров. Для того, чтобы точность определения задержки была хотя бы близка к одной миллисекунде необходимо, чтобы частота кадров в видео была больше 90 фреймов/кадров в секунду (1 фрейм — 11.1 мс, значит точность определения задержки будет ± 5.6 мс). Flutter же позволяет повышать fps с 30 до 60 только на ios устройствах, а на Андроид нужно писать плагин для камеры, чтобы получить доступ к этим настройкам. Я создала плагин на Kotlin. Он дал возможность в определённых режимах увеличивать кадровую частоту аж до 93 фреймов в секунду, но только на самсунгах версии которых выше s9 (другие телефоны дешевых марок, которые я проверяла такое не поддерживают).
Еще одна сложность возникла с одновременным проигрыванием звука, длиной в 1 секунду, и появлением лиловой вспышки. Библиотечными методами с подгрузкой и кэшем звука не получалось добиться абсолютной точности до 1 миллисекунды и синхронности в появлении. Про генерацию звука слёту тоже прилось забыть. Спасло положение только добавление инициализации звука в памяти в C++ и проигрывание его по запросу из dart. Это убрало системные задержки, которые могли варироваться от 100 до 200 миллисекунд.
Генератор. Звуковая метка
Как закодировать информацию с помощью звука? Можно использовать частотную модуляцию. Каждому кадру можно привязать звуковую метку, зашифровать и проиграть в генераторе, чтобы потом в анализаторе расшифровать звук и сопоставить фрейм и звуковую метку.
Идея проста: каждый фрейм кодируем набором из трёх (или более) нот, назовём это аккордом, скажем (D4, A4, E5) или (F5, F#6, C#7) или (C#6, F6, A#6), а затем в анализаторе с помощью Быстрого Преобразования Фурье получаем из смешанного звука отдельные ноты аккорда, которые можем сравнить с первоначальным защифрованным набором и сделать вывод, какой фрейм должен сейчас идти.
Быстрый анализ Фурье может длиться непозволительно долго, если исследовать большой звуковой файл (например, 4 секунды), поэтому пришлось добавить в начало мультитона группу щелчков, которая будет служить грубым ориентиром для анализатора.
Кодирование раскодирование звука (слева — как генератор воспроизводит звук, справа — как анализатор расшифровывает набор нот)
Было решено, что звуковая метка будет занимать время равное одной секунде. При скорости записи видео в 30 фреймов, длина звукового промежутка, соответствующего 1 фрейму видео, будет составлять 33 миллисекунды: 1000 миллисекунд / 30 фреймов ~ 33 мс/фрейм. 200 миллисекунд будет длиться группа резких щелчков, которую на графике представляет характерная «пила». 800 миллисекунд продолжается частотная модуляция, которая представляет из себя последовательный набор проигранных «аккордов».
Звуковая пила перед частотной модуляцией
Один аккодр шифрует один фрейм
24 аккорда для мультитона
В конечном итоге трёх нот в аккорде не хватило и пришлось добавить ещё одну. Вот так выглядят спектры звуковой трели в различных экспериментах для 24 аккодов, для 48, с различными настройками частоты проигрывание и комбинациями нот:
Полный спектр звуковой метки. 24 фрейма (48000 sample rate)
Спектр звуковой волны мультитона 24 фрейма (для скорости 30 fps)
Спектр звуковой волны мультитона 48 фреймов (для скорости 60 fps)
Для сравнения спектр звуковой метки от Hitomi:
Спектр звуковой волны от Hitomi
Анализатор. Вычисление задержки
Первый этап развития проекта. Онлайн анализатор
Сначала казалось, что всё просто. Измеряем время, когда пришло изображение с камеры, сопоставляем его со временем записанного звука, вычисляем разницу и выдаем в качестве ответа. Препятствие возникло неожиданно, как это всегда бывает. Не получалось правильно соотнести время этих элементов. Возникали неуправляемые скачки измерений ±70 мс, что категорически не устраивало.
Пришлось искать другие пути и написать дополнительное расширение плагина на Kotlin для получения самой точной временной метки, которая хранится в самом изображении. Эта метка представляет мгновение, когда изображение было захвачено датчиком камеры. Измеряется в наносекундах и предоставляет высокоточную информацию, которая необходима для синхронизации кадров или измерения интервалов между кадрами, и я надеялась, что она поможет привести всё к единой системе отсчета времени. Но всё равно остались неуправляемые лаги, источник которых был не ясен. Пока я боролась для достижения точности в районе ±16,5 мс, то есть половина длины фрейма, возникла идея производить измерения не онлайн, а сначала записать видео, а потом его анализировать.
Второй этап развития проекта. Оффлайн анализатор
Я подключила к проекту ffmpeg и с помощью него написала обработку видео: разделение на видео и звук, преобразование видео в набор изображений. И приступила к анализу. Тут всё пошло как по маслу. Четко детектируется фрейм со вспышкой, точно определется мультитон и самое главное, время синхронизировано и можно рассчитать разницу между звуком и видео с точностью до половины фрейма. Да, работает дольше на 4 секунды, но есть ресурсы и мысли, как ускорять.
Итоги
В результате получилось вот такое приложение. Два экрана: генератор и анализатор.
Слева — генератор, справа — анализатор
Меню и проверка лицензии:
И в работе достаточно хорошо распознает даже маленькие qr-коды:
Слева — приложение в работе. Справа — проверка читаемости qr-кода маленького размера с выводом маски, определенной по лиловому цвету
Итак, получилось, что оффлайн анализатор работает с точностью до половины фрейма (± 16.5 мс при 30 fps, ±5.3 мс при 93 fps), онлайн анализатор пока имеет скачки в измерениях, и сейчас в доработке.