В начале своего пути наша облачная платформа voximplant позволяла работать только с голосовыми звонками. Но прогресс не стоит на месте, и со временем мы добавили передачу видео, текстовые сообщения, presence и множество других возможностей. А недавно закончили разработку функции записи видео: теперь во время видеозвонка достаточно вызвать функцию record из управляющего звонком javascript, чтобы получить ссылку на записываемый видеофайл.

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

Видеозвонки бывают разные. Можно звонить из браузера с использованием модной технологии webRTC. Если браузер старый и технологий в нем нет, можно делать откат на flash. Можно вообще обойтись без браузера и использовать программу-телефон, так называемый “sip софтфон”. Можно подключить к интернет физический видеотелефон с поддержкой SIP и звонить через него. Можно сделать мобильное приложение, звонящее через интернет: специально для этого у нас есть sdk для ios, android и react native.

Все эти способы объединяет использование протокола SDP, благодаря которому участвующие в видеозвонке стороны договариваются о разрешении видео, используемых кодеках и множестве других нужных для организации звонка вещах.И в этот момент мы получаем первую проблему.

Сложности с кодеками


Кодеки бывают разные. SIP телефоны могут попробовать договориться вообще о любых. Для flash это h.264. А вот для webRTC история довольно интересная. Комитет по стандартизации webRTC долго ломал голову какой их кодеков сделать обязательным для протокола: vp8 не требует патентных отчислений, зато h.264 есть в каждом холодильнике. В конце концов они плюнули на это дело и сделали оба кодека mandatory to implement. Так что несмотря на то, что де-факто webRTC использует VP8, поддержка H.264 указана в стандарте и, возможно, получит какое-то распространение.

В чем же проблемы? Как это ни странно, с контейнерами. Мы не знаем заранее о каком кодеке договорятся звонящие стороны. Более того — видео может включаться и выключаться в процессе звонка. Не все контейнеры одинаково полезные, и для большинства контейнеров библиотеки требуют сразу указать кодек для видео — который мы на момент начала записи еще не знаем. А клиентам хочется отдавать видео в самых распространенных контейнерах, и чтобы была хорошая совместимость с браузерами. А браузеры при проигрывании видео не поддерживают все возможные комбинации. К примеру, большинство браузеров не смогут проиграть mp4 с видео кодеком VP8. И это я еще не говорю про “неудачные” комбинации видео и аудио кодеков.

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

Запись видео


Сначала мы попробовали пойти самым простым путем и воспользоваться де-факто стандартной библиотекой для работы с видео, великим и могучим ffmpeg. Но реальность часто вносит коррективы в амбициозные планы. Оказалось, что модуль mkv для записи одноименного контейнера, сложный. Нет, не так — он СЛОЖНЫЙ. Потратив несколько дней на не очень успешную борьбу с api и изучение многочисленных вопросов без ответов в поиске гугла мы поменяли тактику и начали искать замену. Оказалось, далеко ходить не надо: libmatroska от команды mkv проста в использовании и работает “из коробки”, что не могло не порадовать наших разработчиков.

В результате получилась следующая архитектура:

  1. Javascript клиента, выполняемый у нас в облаке, вызывает метод call.record() с настройками “{video: true}” для записи видео.
  2. Наш сервер создает mkv файл и записывает в него видео для соответствующего участника звонка (если есть) и две аудиодорожки, по одной на участника звонка.
  3. Если javascript подписался на эвент начала записи, то вызывается соответствующий callback с url записываемого файла (об этом чуть позже).
  4. После окончания звонка выполняется python скрипт, анализирующий имеющиеся в mkv файле дорожки и конвертирующий его либо в .mp4, либо в webM в зависимости от использованного кодека. Звуковые дорожки при этом объединяются в одну, стерео или моно по выбору. В случае стерео, голос первого участника звонка помещается в левый стереоканал, а второго участника в правый.
  5. Получившийся файл загружается на Amazon, где он становится доступен по выданному ранее URL. Файл в URL не имеет расширения (так как мы не знаем его на момент начала записи), поэтому его тип передается клиенту MIME полем в HTTP заголовке.


Бонус для Хабра


Open source мы любим не меньше, чем javascript и webrtc. Раз уж мы потратили столько времени на запись звонков, то можно и нужно поделиться с сообществом. Специально для хабрачитателей мы выложили на github созданную нами c++ библиотку для записи mkv видео в несколько строк кода. Так что если когда-нибудь перед вами встанет похожая задача — вы сможете воспользоваться нашими наработками и сэкономить немного времени. Библиотека доступна под лицензией MIT в нашем официальном репозитории.

Выводы


Изначально мы хотели “запилить” запись видео за пару недель. Получилось за пару месяцев. Почему так случается? Бурное развитие индустрии разработки программ привело к быстрой смене технологий и появлению огромного количества узких специализаций. Поэтому при планировании, если у вас в команде нет профильного узкого специалиста, можно серьезно “промахнуться”, не не учтя какой-нибудь нюанс. Agile подход рекомендует перед планированием технически сложных проектов повести ряд экспериментов, чтобы убедиться в правильном понимании работы технологического стека и отсутствии крупных сюрпризов. Собственно, именно это мы и делали первую неделю — смотрели разные библиотеки для записи видео и проверяли их на наших данных. Но даже бережно разложенная соломка не всегда может уберечь от сюрпризов на более поздних сроках разработки. Для нас такими сюрпризами стала возможность поставить видео на паузу, потери пакетов и прочие мелкие нюансы. Как оказалось, писать видеозвонок намного сложнее, чем TCP стрим от камеры.

Созданная нами запись видеозвонков произвела благоприятное впечатление на первых попробовавших клиентов, и они уже начали отгружать нам свои соображения по “улучшению и расширению”. Посмотрим, насколько созданная архитектура получилась адекватной поставленной задаче: надеюсь, через полгода мы не напишем статью “как мы переделывали запись видео” :). Также мы планируем в ближайшее время сделать конвертирование не только в «родной» контейнер, а сразу в оба. Нагрузка на вычислительные мощности, конечно, возрастет, зато клиенты смогут воспроизводить записанное видео в любом современном браузере.

Образец записанного видеозвонка, файл в 10 мегабайт можно скачать здесь:

Комментарии (8)


  1. dizer
    01.12.2015 11:37

    со временем мы добавили… текстовые сообщения

    Неужели наконец можно отправлять SMS через Voximplant?


    1. eyeofhell
      01.12.2015 11:48

      Нет, текстовый чат :). Из нашего javascript можно делать http вызовы — отправляйте SMS кем вам больше нравится. Twilio, например. Или OneAPI.


      1. dizer
        01.12.2015 11:57

        И платить за два сервиса, делающих отдаленно одно и то же. Не удобно.


        1. eyeofhell
          01.12.2015 12:46

          Эмм… Так все сервисы же pay-as-you-go :). Или есть такие что «платите только за минуты телекома, а sms в подарок, хоть миллион отправляйте»?


        1. dizer
          01.12.2015 12:54

          Неудобно следить за балансом двух систем. Неудобно копаться в документации двух систем. Неудобно отлаживать две внешние системы.


          1. eyeofhell
            01.12.2015 13:52
            +1

            SMS — это отдельная сложная задача с кучей ограничений. Мы хотим концентрироваться на одном сервисе и делать его качественно. Так что нет, sms не планируется.


  1. AlexanderG
    03.12.2015 21:06

    В случае стерео, голос первого участника звонка помещается в левый стереоканал, а второго участника в правый.

    ИМХО, неудачное решение. Когда будет кто-то один говорить (особенно длинную реплику), то будет ощущение глухоты на одно ухо. Двух говорящих одновременно всё равно понимать невозможно, в одном канале ли одни или в двух. В наушниках такая разводка по каналам невероятно раздражает, хочется уши пальцем почистить или кажется, что наушники сдохли. Возможно, имеет смысл микшировать их с небольшой стереобазой (если действительно так хочется локализовать говорящих).


    1. eyeofhell
      03.12.2015 21:07
      +2

      Настраивается — можно смикшировать.