Блог Синявского
  • Разделы
  • Метки
  • Все статьи

Вещание IP камер в формате HLS

1

Как показала практика, лучшим транспортом для видео по сравнению с RTMP, является HLS. Причины этого:

  1. Очень простое проксирование, с кэшированием через nginx. На первое месте, потому что, камера, как устройство не может обслуживать, как правило, одновременно более 10 подключений. В этом смысле гарантированное проксирование RTMP потоков, возможно только через платные решения, и требует больших мощностей. Не нужно специального серверного программного обеспечения.

  2. Упрощение всей серверной инфраструктуры. В силу идеи видео отдается по кусочкам, через 80 порт по http. За отдачу статики может отвечать сам nginx. Отдача статики (видео кусочки по 50кБ) задача для nginx очень легкая.

  3. Поскольку количество кусочков постоянно, старые удаляются, новые добавляются, жесткий диск никогда не переполнится.

  4. Распространенность больше, чем у RTMP. HLS с кодировкой видео H.264 поддерживается iOS и работает без лишних действий. По данным на 1 июля 2014 с хабры подключений потокового видео с транспортом HLS - 55%, RTMP - 26%, RTSP - 15% и MPEG-DASH менее 1%.

  5. Поддержка большинством мобильных устройств, дестктопов, планшетных компьютеров прямо из браузера.

  6. Гораздо проще, чем вещание в RTSP в принципе. Так как нет таких процедур, как push (публикация потока) или pull (получение потока).

  7. Гораздо более http friendly формат.

Недостатки следующие:

  1. Все таки не все устройства поддреживают этот формат. Android версий меньше 4.2 официально не поддерживает кодек H.264 и транспорт, но на Android вместо браузера для просмотра можно использовать стороннее приложение - например MX Player

  2. Все зависит от камеры. Если камера глючная, например Dlink DCS-3010, то вся система будет работать из рук вон плохо (ffmpeg постоянно отваливается). Например камеры AXIS M1011-W, HIKVISION DS-2CD2412F-IW работают в такой связке хорошо (до месяца без нареканий (дольше просто не тестировал)). Так же большое значение имеет прокладка кабеля. В этом смысле будем рассматривать идеальный вариант.

Что такое транспорт HLS

Видео поток в кодировке h.264 (Кстати: profile baseline понимают Android устройства), делится на кусочки с расширением *.ts, например по 5 секунд, создается плэйлист в live.m3u8, с последовательным описанием этих кусочков. Предварительно определется длина плэйлиста например 10 кусков. Когда появляется 11 кусочек видео, 1 кусок видео удаляется, плейлист пересоздается. Более подробно можно посмотреть на сайте разработчика.

Рекомендации по настройке камер

Для работы системы настроим изображение с камер так, как мы хотим видеть на сайте, формат картинки и качество изображения. На сервере перекодировать не будем. Камера для того и придумана чтобы отдавать такое изображение какое нужно. В камерах обычно есть несколько профилей. Можно настроить один профиль для H.264, для HLS, а второй c MPEG4 для MPEG-DASH. Так же можно настроить различное качество, для широкого и узкого канала интернет. Думайте сами - решайте сами.

Важно! Камера должна на выходе иметь изображение, которое не нужно перекодировать.

Структурная схема для высокой нагрузки

Камера(rtsp) ----->

-----> одно подключение FFmpeg(rtsp->hls) -> Nginx(nginx-rtmp-module) ----->

-----> одно подключение к промежуточному proxy nginx c большим кэшем =====>

=====> много клиентов JWPlayer(hls)

Наш сервер подключается с помощью ffmpeg к камере и регистрируется в приложении nginx hls. nginx создает кусочки и плэйлист в определенной директории. Далее отдает эти кусочки на прокси сервер. Клиенты подключаются к прокси серверу с помощью JWPlayer.

Настройка nginx application

Соберем nginx с nginx-rtmp-module. Эта процедура детально описывается в статье RTMP вещание с web-камер.

Допустим у нас несколько камер, разделим их по порядковому номеру. Опишу конфигурацию nginx для 2 камер. Статические изображения кэшируем на 5 минут в локальном кэше, если картинка не грузится в течении 5 секунд отдаем статическую заставку.

# nano /etc/nginx/nginx.conf

Отредактируем конфигурацию nginx

user www-data;
worker_processes auto;
pid /run/nginx.pid;
error_log  /var/log/nginx/nginx_error.log debug; env PATH;

events {
     # multi_accept on;
}

http {
     access_log /var/log/nginx/access.log;
     error_log /var/log/nginx/error.log;
     include       mime.types;
     default_type  application/octet-stream;
     sendfile        on;
     keepalive_timeout  65;

     proxy_cache_path  /var/www/cache/local levels=1:2 keys_zone=nginx_local_cache:1m inactive=30m max_size=512M;
     proxy_temp_path   /var/www/cache/local/tmp;


     server {
         listen 80;

         # rtmp stat

         location /stat {
             rtmp_stat all;
             rtmp_stat_stylesheet stat.xsl;
         }

         location /stat.xsl {
             # you can move stat.xsl to a different location
             root /etc/nginx;
         }

         location / {
             rtmp_control all;
         }

         error_page   500 502 503 504  /50x.html;

         location = /50x.html {
             root   html;
         }

         include cameras_http_locations.conf;
     }
}

rtmp {
     access_log /var/log/nginx/rtmp_access.log;
     server {
         listen 1935;
         ping 30s;

         notify_method get;
         include cameras_rtmp_applications.conf;
     }
}

Создадим путь для кэш # mkdir /var/www/cache/local Поправим права для кэш:

# chmod -R 755 /var/www/cache/local
# chown -R www-data:www-data /var/www/cache/local`

Создадим http locations для камер:

# nano cameras_http_locations.conf
types {
    application/vnd.apple.mpegurl m3u8;
    video/mp2t ts;
}

# отдаем изображение с камеры 1 - /1/img/
# для всех камер разные, т.к. ip адреса камер разные
location /1/img/{
    proxy_cache nginx_local_cache;
    proxy_cache_key $request_uri;
    expires 1m; # кэшируем на 1 минуту
    add_header Cache-Control public; # для кэширования на проксе
    proxy_ignore_headers Cache-Control; # для удаления заголовков с камеры
    proxy_pass "http://192.168.0.2/GetImage.cgi?CH=1";
    proxy_set_header Authorization "Basic ";
    error_page 502 504 404 @fallback_img;
}
# отдаем изображение с камеры 2 - /2/img/
location /1/img/{
    proxy_cache nginx_local_cache;
    proxy_cache_key $request_uri;
    expires 1m; # кэшируем на 1 минуту
    add_header Cache-Control public; # для кэширования на проксе
    proxy_ignore_headers Cache-Control; # для удаления заголовков с камеры
    proxy_pass "http://192.168.0.3/GetImage.cgi?CH=1";
    proxy_set_header Authorization "Basic ";
    error_page 502 504 404 @fallback_img;
}


# отдаем плэйлист - /1/hls/live.m3u8 или /3/hls/live.m3u8
# кэширование на локальном компьютере не требуется
# плэйлист кэшируется 10 секунд на проксе
location ~* /hls/.*\.m3u8$ {
    rewrite "/(.*)/hls/(.*)$" /hls-$1/$2 break; # переделываем запрос /1/hls/ в /hls-1/
    root /tmp/;
    expires 10s;
    add_header Cache-Control public;
}

# отдаем кусочек видео с камер - /1/hls/live-12345678.ts или /2/hls/live-12345678.ts
# кэширование на локальном компьютере не требуется
# кусочек кэшируется 3 минуты на проксе
location ~* /hls/.*\.ts$ {
    rewrite "/(.*)/hls/(.*)$" /hls-$1/$2 break;
    root /tmp/;
    expires 3m;
    add_header Cache-Control public;
}

# именованный location если картинки нет
location @fallback_img {
    rewrite (.+) /fallback.jpg break;
    root /etc/nginx/;
}

Создадим файл hls конфигурации сервера rtmp с applications для наших камер:

# nano cameras_rtmp_applications.conf
chunk_size 4000;


application hls_1 {
    live on;
    sync 10ms;

    exec_static ffmpeg -i rtsp://admin:secret@192.168.0.2:554/live1.sdp -c copy -f flv -an rtmp://localhost:1935/hls_1/live 2>>/var/log/nginx/ffmpeg_1.log;

    hls on;
    hls_path /tmp/hls-1/; # путь хранения кусочков на сервере
    hls_fragment_naming timestamp; # использовать timestamp для именования кусочков
}

application hls_2 {
    live on;
    sync 10ms;

    exec_static ffmpeg -i rtsp://admin:secret@192.168.0.3:554/live1.sdp -c copy -f flv -an rtmp://localhost:1935/hls_2/live 2>>/var/log/nginx/ffmpeg_2.log;

    hls on;
    hls_path /tmp/hls-2/;
    hls_fragment_naming timestamp;
}

Содержимое директории /tmp/hls-1/

$ ls /tmp/hls-1/
live-10458360.ts  live-13292010.ts  live-16129440.ts  live-18963270.ts
live-10930050.ts  live-13767390.ts  live-16602660.ts  live-19435050.ts
live-11405250.ts  live-14239260.ts  live-17072820.ts  live.m3u8
live-11878560.ts  live-14710860.ts  live-17544960.ts
live-12348630.ts  live-15182550.ts  live-18020160.ts
live-12821760.ts  live-15658740.ts  live-18492750.ts

Пример файла live.m3u8

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:35
#EXT-X-TARGETDURATION:5
#EXTINF:5.224,
live-16602660.ts
#EXTINF:5.246,
live-17072820.ts
#EXTINF:5.280,
live-17544960.ts
#EXTINF:5.251,
live-18020160.ts
#EXTINF:5.228,
live-18492750.ts
#EXTINF:5.242,
live-18963270.ts

Решение проблемы с отваливающимися камерами

Самое правильное решение, поменять глючную камеру. Это помогает в 90% случаев. Если нет возможности и нужно как то жить дальше, то поможет следующее решение.

Это решение состоит из двух - взаимодополняющих:

  1. Запускать отдельный процесс nginx на каждую камеру и общий процесс по отдаче статики. То есть для двух камер написать отдельные конфиги с rtmp серверами и один общий с http. Тогда глючная камера не будет влиять на общий процесс.

  2. Если поток с камеры нарушается в результате ее глючности (перегрева, плохой прокладки витухи, недостаточное питание по PoE и т.п.), то камера отвалится, дочерний процесс ffmpeg будет отбраковывать пакеты и nginx перестанет писать кусочки видео. А когда процесс ffmpeg завершится, nginx удалит все файлы из директории с кусочками. Этот момент очистки папки мы вычисляем по cron и рестартим необходимый процесс nginx.

В для каждой камеры создается в /etc/init.d/ создается исполняемый скрипт, копия nginx, c именем camera_1 и camera_2

# cp /etc/init.d/nginx /etc/init.d/camera_1
# cp /etc/init.d/nginx /etc/init.d/camera_2
# chmod +x /etc/init.d/camera_1
# chmod +x /etc/init.d/camera_2

Редактируем скрипт запуска nginx.

nano /etc/init.d/nginx

Меняем переменную DAEMON_OPTS. Основной демон nginx будет отдавать всю статику. А так же будет запускать и останавливать демоны отвечающие за камеры.

DAEMON_OPTS="-c /etc/nginx/nginx_0.conf"

Добавляем в функцию do_start:

# start cameras
if [ -f "/etc/init.d/camera_1" ]; then
        /etc/init.d/camera_1 start
fi
if [ -f "/etc/init.d/camera_2" ]; then
        /etc/init.d/camera_2 start
fi

Добавляем в функцию do_stop:

# stop cameras
if [ -f "/etc/init.d/camera_1" ]; then
        /etc/init.d/camera_1 stop
fi
if [ -f "/etc/init.d/camera_2" ]; then
        /etc/init.d/camera_2 stop
fi

Добавляем в функцию do_reload:

# reload cameras
if [ -f "/etc/init.d/camera_1" ]; then
        /etc/init.d/camera_1 reload
fi
if [ -f "/etc/init.d/camera_2" ]; then
        /etc/init.d/camera_2 reload
fi

Редактируем скрипт запуска nginx для камеры 1 camera_1 и для камеры 2 camera_2 по примеру.

# nano /etc/init.d/camera_1

Меняем переменные DAEMON_OPTS и DESC

DESC="camera_1 for CAMERA-1"
DAEMON_OPTS="-c /etc/nginx/nginx_1.conf"

Редактируем скрипт запуска nginx для камеры 2 camera_2 по примеру.

В /etc/nginx/nginx_0.conf с http locations я пишу волшебные строки:

# DIR-PROCESS-NAME /tmp/hls-1/ camera_1
# DIR-PROCESS-NAME /tmp/hls-2/ camera_2

В них указано через пробел искомое слово DIR-PROCESS-NAME директория и наименование процесса который нужно перезагрузить.

Проверка:

# service nginx start
# service camera_1 restart
 * Restarting camera_1 for CAMERA-1 configuration nginx
# service camera_2 restart
 * Restarting camera_2 for CAMERA-2 configuration nginx

Скрипт который перезагружает камеры. Он проходится по папкам с кусочками, ищет, где нет файлов *.m3u8. Если в папке нет файлов, ищет соответствующий демон по конфигу основного демона, по строчке DIR-PROCESS-NAME. Перезагружает его.

# nano /script/cameras_reloader.sh
#!/bin/bash

PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin

mask="*.m3u8"
dir="/tmp/hls-*"

function find_process(){
    process_str=$(cat /etc/nginx/nginx_0.conf | grep "# DIR-PROCESS-NAME" | grep $1 | cut -d' ' -f4)
    echo $process_str
}

for hls_dir in $dir; do
    find_result=$(find $hls_dir -name $mask -type f)
    if [ -z $find_result ]; then
    process=$(find_process $hls_dir)
    service $process restart
    fi
done

sleep 15s

Сравнение HLS с MPEG-DASH

MPEG-DASH это аналог HLS созданный компанией Google, в качестве транспорта для MPEG-4. Этот транспорт малораспространен и практически не поддерживается. Его идеалогия такая же, разбить поток на кусочки, только кусочков больше, отдельные куски для видео, отдельные для аудио. В nginx-rtmp-module этот формат настраивается аналогично HLS.

Пробуйте, копируйте, дерзайте!

Если статья была вам полезна просьба щелкнуть по рекламе. Спасибо!



  • ← сюда
  • туда →

comments powered by Disqus

Опубликовано

16.09.2014

Обновление

05.05.2022

Категории

nginx

Тэги

  • ffmpeg 6
  • nginx 11
  • streaming 7

Всегда на связи

  • Блог Синявского - Ничего не переносить на завтра, это тоже проблема с прокастинацией?
  • © Алексей Синявский, по лицензии CC BY-SA если не указано иное.
  • С использованием Pelican. Тема: Elegant от Talha Mansoor