В статье рассказывается о том, как обеспечить «почти видеотрансляцию» средствами ffmpeg, wget, JS, html и такой-то матери. По сути, мы создаем слайдшоу с аккуратным его выводом и динамическим обновлением на веб-страничке. Возможно, кому-то пригодится мой опыт.
Знающие люди не найдут в статье ничего нового для себя, но, если прочтут, я бы с удовольствием выслушал замечания по улучшению этой схемы.
Вступление и предыстория
Я работаю системным администратором (специалистом по сетевому обеспечению) в компании, у которой, по роду деятельности, есть около 10 филиалов, расположенных в тайге. Интернет во всех филиалах, кроме одного, самого счастливого, предоставляется через спутник. Спутниковый интернет, когда это единственный способ связи с внешним миром (кроме шуток, ближайший поселок со связью обычно километрах в 70), оказывается очень нестабильной и недешевой штукой.
Помимо прочего (почта, sip-телефония), интернет в нашей компании тратится на видеонаблюдение. Директорат очень желает наблюдать за работами в филиалах. Но трафика мало. На максимальном тарифе FAP.4000+.AST 2Гб трафика в сутки, что при наличии трех двухмегапиксельных камер просто смешно. В принципе, изображение с камер можно порезать (мы так и делаем), но все равно просмотром камер без внутренних ограничений по трафику точку легко загнать в «жесткое ограничение» (при достижении лимита суточного трафика). Одна камера при потоке 128кбитс за сутки съест 1350мб.
Мне была поставлена задача создать для директоров своего рода «видеостену», куда бы выводились изображения со всех камер нашей фирмы. При драконовских ограничениях спутникового интернета речи про прямое получение потока с камеры не идет. Софт типа ПО от Macroscop не поддерживает перекодировку потока с камеры «на лету». Если честно, мне кажется, что Macroscop отдает своему клиенту более жирный поток, чем получает от камеры. Я экспериментировал с VLC (нестабильно, периодически плагин для браузера крашится или самопроизвольно ставит на паузу поток, жрет много ресурсов при транскодинге), ffmpeg+ffserver (так я и не завел эту связку). В итоге я пришел к идее захвата скриншотов с потока от камер и его дальнейшей передачи в центральный офис с заданной периодичностью. Все получилось, но получилось топорно и кондово. Я бы сказал дубово. Но работает. Допиливать буду в будущем.
Техническая часть
Камеры у нас, в основном, Beward. Есть несколько Hikvision. Все камеры (в теории, но о практике позже) поддерживают RTSP. В идеальных условиях его мы и используем. Конечную картинку всегда выдаем через ffmpeg — он подгоняет размер изображения и качество jpg, оперируя этими параметрами, мы можем управлять количеством трафика, которое утекает за сутки. Также, на количество трафика влияет частота запроса кадров с центрального сервера.
Дальше за дело берется JavaScript и немного HTML (и капельку php). На просторах интернетов был найдет скрипт на JS, который динамически обновляет картинку на странице, но только предварительно загрузив её и выловив событие OnLoad. Под это дело была подготовлена страница, которой я передаю параметры (Имя камеры, адрес её админки, ссылка на скриншот).
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<script type="text/javascript">
var speed = 2;
function cam_show()
{
var imac = document.getElementById('camimage');
var time = now();
imac.onload = function()
{
start_show(imac, now()-time);
document.getElementById('speed').innerHTML = ((now()-time)*(speed+5))/1000;
};
imac.src = "<?php echo ($_GET['urlpic']) ?>?r=" + Math.random();
}
function now(){
return (new Date).getTime();
}
function start_show(img, time)
{
setTimeout(function()
{
var ctime = now();
img.onload = function()
{
start_show(img, now()-ctime)
document.getElementById('speed').innerHTML = ((now()-ctime)*(speed+5))/1000;
};
img.src = "<?php echo ($_GET['urlpic']) ?>?r=" + Math.random();
}, time*(speed+5));
}
</script>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1251">
<meta http-equiv="imagetoolbar" content="no">
</head>
<?php error_reporting(E_ALL); ?>
<body onload="cam_show()">
<center><?php echo "<a class='store' href="./cam-get.php?picwidth=60%&urladmin={$_GET['urladmin']}&urlpic={$_GET['urlpic']}&textpage={$_GET['textpage']}" target=_blank alt=test>{$_GET['textpage']}</a>";?></center>
<div align="center">
<img width=<?php echo "{$_GET['picwidth']}";?> id="camimage" src="" alt="">
</div>
<center>
<?php echo "<div id="div1" style="display:{$_GET['visibletxt']}">" ?>
<?php echo "<a class='store' href="{$_GET['urladmin']}" target=_blank>[Админка]</a>";?>
<?php echo "<a class='store' href="{$_GET['urlpic']}" target=_blank>[Картинка отдельно]</a>";?>
</div>
</center>
</body>
</html>
Как это все увязано
В филиалах на серверах (win 2008 r2 или win 7, если это отдельная виртуалка) установлено следующее ПО:
- nnCron Lite (виндовый планировщик мне настроить не удалось, он периодически зависал, спасало только ручное завершение всех экземпляров задания, отключение задания, включение и запуск);
- Apache 2.4;
- ffmpeg;
- wget (да, под винду, вот такой я извращенец).
nnCron запускает задания (он гораздо стабильнее планировщика задач Windows), это обычные .bat, в которых ffmpeg указано что и откуда брать и как и куда складывать. Если у камеры есть трудности с RTSP, начинаются танцы с ftp, выборкой последнего скриншота и гроханьем лишних и подстановкой правильной даты в адрес, потому что бевардовские камеры хотят класть скрины именно по адресу ддммггггимя_камеры1*.jpg. Безобразие, конечно, но куда деваться?
set day=%DATE:~0,2%
set month=%DATE:~3,2%
set year=%DATE:~6,4%
set YYYYMMDD=%year%-%month%-%day%
FOR /L %%i IN (0,1,4) DO (
cd "C:ftpkpp%YYYYMMDD%kpp1"
c:
for /f %%i in ('dir /b /T:A /A:-D /O:-D *.jpg') do (copy /Y "%%i" "c:ftpcopyandat-kpp.jpg")
C:ffmpegbinffmpeg.exe -i "c:ftpcopyandat-kpp.jpg" -f image2 -vframes 1 -y -q:v 15 -s 480x360 C:Apache24htdocsandat-kpp.jpg
del /Q "C:ftpkpp%YYYYMMDD%kpp1*.jpg"
timeout /t 5 /nobreak
)
exit
ffmpeg складывает изображения с нужными размерами и качеством в корневую папку web-сервера. Дальше начинается работа сервера в центральном офисе, где все это складируется и подсовывается главной странице, где и размещена видеостена.
#!/bin/sh
cd /var/www/
mkdir /var/www/archieve/
mkdir /var/www/archieve/tuhterek
mkdir /var/www/archieve/tuhterek/gep1/
mkdir /var/www/archieve/tuhterek/polygon/
cp /var/www/tuht-gep1.jpg /var/www/archieve/tuhterek/gep1/gep1-`date +-%d-%m-%Y-%H%M%S`.jpg
cp /var/www/tuht-polygon.jpg /var/www/archieve/tuhterek/polygon/polygon-`date +-%d-%m-%Y-%H%M%S`.jpg
wget http://172.30.99.80/tuht-gep1.jpg -O tuht-gep1.jpg.bak
cp tuht-gep1.jpg.bak tuht-gep1.jpg
wget http://172.30.99.80/tuht-polygon.jpg -O tuht-polygon.jpg.bak
cp tuht-polygon.jpg.bak tuht-polygon.jpg
<iframe src="./cam-get.php?urlpic=http://192.168.1.239/bak_cam200.jpg&picwidth=92%&textpage=26 Бакинских - Второй въезд&urladmin=http://192.168.0.200"align=center width="340" height="290" scrolling="no">test</iframe>
<iframe src="./cam-get.php?urlpic=http://192.168.1.239/bak_cam201.jpg&picwidth=92%&textpage=26 Бакинских - Площадка перед складом&urladmin=http://192.168.0.201"align=center width="340" height="290" scrolling="no">test</iframe>
<iframe src="./cam-get.php?urlpic=http://192.168.1.239/bak_cam202.jpg&picwidth=92%&textpage=26 Бакинских - КПП&urladmin=http://192.168.0.202"align=center width="340" height="290" scrolling="no">test</iframe>
<iframe src="./cam-get.php?urlpic=http://192.168.1.239/bak_cam203.jpg&picwidth=92%&textpage=26 Бакинских - Коридор&urladmin=http://192.168.0.203"align=center width="340" height="290" scrolling="no">test</iframe>
<iframe src="./cam-get.php?urlpic=http://192.168.1.239/bak_cam205.jpg&picwidth=92%&textpage=26 Бакинских - Склад большой&urladmin=http://192.168.0.205"align=center width="340" height="290" scrolling="no">test</iframe>
<iframe src="./cam-get.php?urlpic=http://192.168.1.239/bak_cam206.jpg&picwidth=92%&textpage=26 Бакинских - Склад мелочевка&urladmin=http://192.168.0.206"align=center width="340" height="290" scrolling="no">test</iframe>
<iframe src="./cam-get.php?urlpic=http://192.168.1.239/bak_cam204.jpg&picwidth=92%&textpage=26 Бакинских - Касса&urladmin=http://192.168.0.204"align=center width="340" height="290" scrolling="no">test</iframe>
<iframe src="./cam-get.php?urlpic=http://192.168.1.239/bak_cam207.jpg&picwidth=92%&textpage=26 Бакинских - Купол&urladmin=http://192.168.0.207"align=center width="340" height="290" scrolling="no">test</iframe>
Сервер периодически (несколько раз в минуту) выкачивает все скриншоты с точек, складывает вместо старых, старые уходят в архив. Ночью из архива их забирает другой скрипт, который бережно собирает все скриншоты в один ролик и именует его по дате и имени камеры.
#!/bin/bash
tochka=$1
kamera=$2
yestday=`date +%d -d yesterday`
year=`date +%Y -d yesterday`
mes=`date +%m -d yesterday`
stime=`date +%H%M%S -d yesterday`
mkdir /var/www/archieve/video/
mkdir /var/www/archieve/video/$tochka
mkdir /var/www/archieve/video/$tochka/$kamera
mkdir /tmp/picture/
rm /tmp/picture/*
echo "$day $mes $year"
cd /var/www/archieve/$tochka/$kamera/
find * -type f -size 0k -exec rm {} ; | awk '{ print $8 }'
X=1;
for i in *.jpg; do
mv $i /tmp/picture/$(printf %07d.%s ${X%.*} ${i##*.})
let X="$X+1"
done
cd /tmp/picture/
ffmpeg -f image2 -r 1 -i %07d.jpg -y /var/www/archieve/video/$tochka/$kamera/$kamera-$yestday-$mes-$year.avi
Дальнейшие планы
Очень хотелось бы избавиться от необходимости n раз прописывать одни и те же параметры в разных скриптах. Я планирую сохранять настройки <куда-нибудь>, брать их оттуда по необходимости и автоматически генерировать нужные скрипты при добавлении нужной камеры.
Планирую прикрутить форму добавления новой камеры.
Хотелось бы все перенести на linux (не везде есть возможность сейчас развернуть виртуалки с ним, на фирме используются преимущественно Win-сервера и hyper-v).
Хотелось бы большей надежности, увеличения качества изображений и их частоты обновления, но без сильного роста трафика.
Это не самая первая версия, но ей еще далеко до совершенства. Это, можно сказать, прототип. Посему критика (конструктивная) приветствуется, а выкрики «велосипедист на костылях» — нет. Сам знаю. Но, так как готовое ПО не выполняет наши желания, приходится изворачиваться и делать самому.
P.S.
Об ошибках в личку, пожалуйста, или на почту.
Автор: Voiddancer