Как показала практика, лучшим транспортом для видео по сравнению с RTMP, является HLS. Причины этого:
-
Очень простое проксирование, с кэшированием через nginx. На первое месте, потому что, камера, как устройство не может обслуживать, как правило, одновременно более 10 подключений. В этом смысле гарантированное проксирование RTMP потоков, возможно только через платные решения, и требует больших мощностей. Не нужно специального серверного программного обеспечения.
-
Упрощение всей серверной инфраструктуры. В силу идеи видео отдается по кусочкам, через 80 порт по http. За отдачу статики может отвечать сам nginx. Отдача статики (видео кусочки по 50кБ) задача для nginx очень легкая.
-
Поскольку количество кусочков постоянно, старые удаляются, новые добавляются, жесткий диск никогда не переполнится.
-
Распространенность больше, чем у RTMP. HLS с кодировкой видео H.264 поддерживается iOS и работает без лишних действий. По данным на 1 июля 2014 с хабры подключений потокового видео с транспортом HLS - 55%, RTMP - 26%, RTSP - 15% и MPEG-DASH менее 1%.
-
Поддержка большинством мобильных устройств, дестктопов, планшетных компьютеров прямо из браузера.
-
Гораздо проще, чем вещание в RTSP в принципе. Так как нет таких процедур, как push (публикация потока) или pull (получение потока).
-
Гораздо более http friendly формат.
Недостатки следующие:
-
Все таки не все устройства поддреживают этот формат. Android версий меньше 4.2 официально не поддерживает кодек H.264 и транспорт, но на Android вместо браузера для просмотра можно использовать стороннее приложение - например MX Player
-
Все зависит от камеры. Если камера глючная, например 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% случаев. Если нет возможности и нужно как то жить дальше, то поможет следующее решение.
Это решение состоит из двух - взаимодополняющих:
-
Запускать отдельный процесс nginx на каждую камеру и общий процесс по отдаче статики. То есть для двух камер написать отдельные конфиги с rtmp серверами и один общий с http. Тогда глючная камера не будет влиять на общий процесс.
-
Если поток с камеры нарушается в результате ее глючности (перегрева, плохой прокладки витухи, недостаточное питание по 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.
Пробуйте, копируйте, дерзайте!
Если статья была вам полезна просьба щелкнуть по рекламе. Спасибо!