8 нояб. 2015 г.

Некоторые мысли по поводу стриминга видео своими силами

Идея сделать кормушку для птиц зрела давно. Года три.

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

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

Но, как оказалось, сделать кормушку — это только начало большого пути. Потому что в реальность вместо созерцания пасторали выглядела так: пришёл вечером с работы — давно темно — засыпал в бутылку семечки — повесил за окно — утром встал и ушёл на работу затемно. А птицы — ВНЕЗАПНО — ночью не летают!

Тема потокового видео была в целом неизвестна. Зато имелась USB-вебкамера с заявленным разрешением аж 720p и желание поглядеть вблизи на всяких дятлов. Почитал интернет, оказалось, что инструмент вещания существует, и это, хы-хы, опять ffmpeg, в комплекте которого для подобных дел есть ffserver.

Связка ffserver+ffmpeg работает следующим образом: ffmpeg берёт поток с источника (в моём случае с вебкамеры), перекодирует его и отдаёт перекодированный поток ffserver'у, который может находиться (и, в моём случае, находится) хоть на другой стороне Интернета. ffserver дальше полученный поток раздаёт клиентам. Между ffmpeg и ffserver данные всегда передаются в единственном экземпляре. Параметры кодирования видео определяются на ffserver'е. ffmpeg'у при работе с сервером в командной строке передаются только параметры, касающиеся вебкамеры, всё остальное он получает от сервера в начале работы.

Дальше два вечера ушло на понимание того, почему поток «заедает» и выбор оптимального сочетания кодеков и контейнеров. Здесь засада, потому что такового нет:
  • H.264 в MP4 контейнере не умеет live streaming (по крайней мере, ffserver'ом). Какие-то засады с требованием наличия возможности позиционирования по MP4 потоку, которое, понятно, невозможно в случае живого вещания. То есть отпадает.
  • H.264 невозможен ни в каком другом из контейнеров, допустимых для HTML5 тэга <video>
  • В WebM контейнере возможен только VP8/VP9 кодек. Кодирование в VP8 при сопоставимом уровне качества жрёт CPU в три раза больше, чем libx264, и создаёт поток в два с половиной раза больше. То есть отпадает из-за ограничений по процессору, каналу и хостингу.
  • Остался OGM контейнер с Theora в качестве видеокодека. Даже не пробовал. То есть отпадает.
Вот и получилось, что все нативные методы отображения видео в браузере на сей день для меня неприменимы. Что остаётся? Правильно, флэш + H.264 + FLV контейнер. Со всеми вытекающими в виде невозможности нормально смотреть на телефонах и необходимости дальнейшей некрофилии с flash-плагинами в браузерах.
 
Решений пока вижу два: первое моё — вещать во флэш с указанием ссылки, по которой идёт поток и которую можно просмотреть с помощью отдельного видеоплеера (VLC, mplayer, MX player, KMplayer и т.п.) — второе правильное — таки добить VP8 кодирование и WebM контейнер, чтобы он генерировал приемлемый по битрейту поток и не грел процессор как бешеный.

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

Пояснения на тему "Ну ты лошара, кто ж так делает стриминг, надо вот так жы!" приветствуются. Только исходите пожалуйста из того, что поток должен быть ограничен 300 kbit/s при разрешении 640×360 и делаться под линуксом бесплатными инструментами.


Appendix A: текущие настройки потока.
 <Stream test.flv>
    Feed feed.ffm
    Format flv
    VideoCodec libx264
    VideoFrameRate 20
    VideoBitRate 600
    VideoSize 640x360
    AVOptionVideo crf 28
    AVOptionVideo preset medium
    AVOptionVideo me_range 16
    AVOptionVideo qdiff 4
    AVOptionVideo qmin 10
    AVOptionVideo qmax 51
    AVOptionVideo keyint_min 15
    AVOptionVideo bf 6
    PixelFormat yuv420p
    AVOptionVideo flags +global_header
    PreRoll 10
    Noaudio
    StartSendOnKey
</Stream>