пятница, 23 марта 2018 г.

Запись RTSP потока с китайской IP-камеры по датчику движения.


Прикупил я как-то, китайскую IP-Камеру ( Unitoptek Official Store ) на Aliexpress. Давно хотел понаблюдать за курильщиками на лестничной площадке.
Камера ничем не примечательна, повелся на ценник.
Из нужных мне характеристик:
- Поддержка 720p и 1080p
- Сетевые протоколы: TCP/IP, UDP, RTP, RTSP, RTCP, HTTP, DNS, DDNS, DHCP, FTP, NTP, PPPOE, SMTP и UPNP
- Датчик движения ONVIF
- RTSP URL-адрес для медиапроигрывателя VLC:
rtsp://$(IP):$(PORT)/user=$(USER)&password=$(PWD)&channel=$(Channel)&stream=$(Stream).sdp?real_stream
- Сведения о порте:
ONVIF 2.0, порт 8899; RTSP, порт 554; HTTP, порт 80; медиапорт 34567

Цена: US $23.99

Все остальное стандартное как и у всех китайских камер. Кому интересно сходите по ссылке и почитайте про нее.

После получения, как и положено поигрался с ней, повесил в подъезде, и начал думать про систему видеонаблюдения.
Для себя решил, что все буду делать на Ubuntu Server. Кроме системы наблюдения, его можно использовать как хранилище файлов, фильмов ну и т.д. и т.п. А рулить им можно с любого домашнего компа по SSH.
Почитал форумы, перепробовал все что предлагается из бесплатного. Все не то... Но наткнулся на интересную тему в форуме: Motion + ffmpeg + rtsp с IP-камеры без перекодирования.
Вот то, что мне нужно! Идея задействовать детектор движения камеры, а видео писать командой openRTSP, мне очень понравилась. Тут все должно быть очень просто! Минимум настроек. Можно уложиться в 20...30 строчек кода.
Но так как продолжения темы нет - решил сам накидать программку.
И так начнем!
--- НАСТРОИМ КАМЕРУ ---
- Настройка камеры идет через NETSurveillance WEB и только в Internet Explorer. Он установится при первом заходе по IP адресу камеры (в моем случае это 192.168.20.45).
При установке NETSurveillance WEB руганулся антивирус (у меня стоит ESET NOD32). На время установки пришлось его отключить.

Пароль на вход по умолчанию пустой:
В дальнейшем поменяйте его обязательно! В настройках камеры это можно сделать.
Далее находим и включаем датчик движения:

Заходим в сетевые службы и настраиваем Тревожный сервер:


Не забудем зайти в менеджер конфигураций и настроить Тип сигнала -> Движение -> Вкл.

- Настраиваем тревожный сервер на камере:
IP: 192.168.20.75
порт: 2345
По движению в кадре будет отправляется тревожный сигнал на наш сервер.
По желанию можно настроить компрессию камеры. На этом останавливаться не буду. Для себя выбрал 720P (1024x720). Битрейт видеопотока поменьше, а качество меня и такое устроило.
--- ДЕЛАЕМ СВОЙ СЕРВЕР ---
- Для эксперимента нашел старую материнку ASRock G31M-S. Процессор - Intel(R) Celeron(R) CPU 430 @ 1.80GHz. Оперативки 2 Гига.
Поставил сервер на Ubuntu 14.04.5 LTS. Работает в этой же сети IP сервера - 192.168.20.75!
Сделал ssh доступ по 22 порту. Работать будем из консоли. Монитор больше не нужен будет. Я хочу сервер запрятать и забыть про него.

Что нужно для нашей задачи?
- Скрипт который постоянно слушает порт: 2345. Если с камеры поступил тревожный сигнал, командой openRTSP пишем поток в файл.
- Не забудьте установить openRTSP на сервер!
Пролистал темы по сокетам. В итоге набрел на хорошее решение: сервер-демон на языке perl прослушивающий tcp сокет. Очень все подробно описывается и работает на ура!!!
Взял за основу этот скрипт и немного допилил под свои нужды. В итоге получаем:
На сервере скрипт socket_server.pl запущенный демоном слушает порт 2345.
Как только камера стукнула ему, что есть движение, он запускает еще один перловский скрипт: write_video_file.pl - который пишет видео длительность 30 секунд. Если движение в кадре еще продолжается, пишется второй кусок 30 секундного видео.
Особенность этого скрипта в том, что он использует модуль File::Flock::Tiny ( Использование pid-файлов для предотвращения повторного запуска скрипта...) Зачем я использовал этот модуль? А вот для чего: с камеры идет не один сигнал тревоги, а целая пачка... У меня было до десяти сигналов за 3-4 секунды. socket_server запускал до 10 параллельных потоков записи видео. А модуль File::Flock::Tiny эту проблему как раз и исправил!
Ну все! Тестил пару дней. Все работает. Глюков пока не словил. Меня пока полностью удовлетворяет.

--- САМИ СКРИПТЫ ---
socket_server.pl
#!/usr/bin/perl -w

###Подключение всех необходимых модулей###
use strict;
use POSIX;
use POSIX ":sys_wait_h";
use IO::Socket;
use IO::Handle;

### Создаем процесс-демон ###
my $pid= fork();
exit() if $pid;
die "Couldn't fork: $! " unless defined($pid);
### Создаем связь с новым терминалом ###
POSIX::setsid() or die "Can't start a new session $!";
### Переменная - бесконечное время жизни сервера ###
my $time_to_die =0;
### Переменная - интернет-сокет или сервер ###
my $server;
### Функция обработчик  сигналов INT и TERM ###
### Она срабатывает перед этими сигналами ###
sub signal_handler{
    $time_to_die = 1;
    close($server);
}

$SIG{INT}= $SIG{TERM} = \&signal_handler;

### Функция обработчик сигнала  CHLD - для уборки процессов зомби ###
sub REAPER {
    while ((my $waitedpid = waitpid(-1,WNOHANG)) > 0) { }
    $SIG{CHLD} = \&REAPER;
}

### Создаем интернет сокет на порту 2345 ###
my $server_port=2345;
$server= new IO::Socket::INET(  LocalHost => '192.168.20.75', # IP нашего сервера!!!
                                LocalPort => $server_port,
                                TYPE => SOCK_STREAM,
                                Reuse => 1,
                                Listen => 1 # maximum queued connections
                                )
or die "Couldn't be a tcp server on port $server_port: $@\n";
###Сервер работает до бесконечности пока его не вырубит Term ###

until($time_to_die){

my $client;
###Обрабатываем входящие подключения
while($client = $server->accept()){
###Включаем обработку зомби###
$SIG{CHLD} = \&REAPER;
### Тот который постучался, отделяем в отдельный процесс ###
defined(my $child_pid=fork()) or die "Can't fork new child $!";
### Родительский процесс идет в конец и ждет следующего подключения ###
next if $child_pid;
### Дочернему процессу копия сокета не нужна, её закрываем ###
if($child_pid == 0) {
    close($server);
}
###Очистка буфера###
$client->autoflush(1);
my $is_def_command=0;

### Считываем комады от клиента построчно ###
while(<$client>){
### Если строка пустая переходим в конец блока ###
next unless /\S/;
###Запоминаем полученную строку###
my $full_enter_str = $_;
chomp($full_enter_str);

# begin write log
# Если не нужно писать лог, удалите эти строки до - end write log
#
$full_enter_str =~ m/{(.*?)}/m;
open(my $fh, '>>', '/home/user/socket_server/socket_log.txt'); # полный путь к log файлу
print $fh POSIX::strftime('%d-%b-%H-%M-%S', localtime())." : ";
print $fh $1."\n";
close $fh;
# end write log

my $start_params="MotionDetect";
### Если в строке есть команда MotionDetect - запускаем запись ###
###########################################################
if($full_enter_str =~ /$start_params/){
# укажите полный путь для запуска скрипта записи потока в файл #
do "/home/user/socket_server/write_video_file.pl"; 
}
}
continue {
$is_def_command=0;
}
exit;
}
continue {
close($client);
}

}

write_video_file.pl
#!/usr/bin/perl

use strict;
use File::Flock::Tiny;
# cpan install File::Flock::Tiny - если нужно устанавливаем модуль!!!
use POSIX ();
my $pid = File::Flock::Tiny->write_pid('/home/user/socket_server/write_video_file.pid') or do {
exit(0);
};
my @months = qw( jan feb mar apr may jun jul aug sep oct nov dec );
my $mon = POSIX::strftime('%m', localtime());
my $catalog = POSIX::strftime('%d', localtime())."-$months[$mon-1]";
my $namefile = POSIX::strftime('%H-%M-%S', localtime());
# проверка на существование каталога
if (!( -d "/mnt/video/$catalog" )){
mkdir "/mnt/video/$catalog",0777;
}
### Пишем видео с камеры ###
my $video_file="/mnt/video/$catalog/$namefile.mp4";
system("openRTSP -b 900000 -V -v -4 -d 30 -w 1280 -h 720 -f 25 
'rtsp://192.168.20.45:554/user=admin&password=password&channel=1&stream=0' > $video_file");

$pid->close;
exit(0);

Вполне возможно, что для работы этих скриптов понадобится добавить модуль для перла.
Для установки необходимых модулей:
cpan install File::Flock::Tiny

Если не получилось, то воспользуемся еще одним методом:
sudo apt-get install build-essential
дальше ставим сам модуль:
sudo perl -MCPAN -e 'install File::Flock::Tiny'


--- ПРОВЕРКА РАБОТЫ ---
Запустим сервер:
./socket-server.pl
Если не руганулся, то все нормально. Для проверки введите следующую команду:
netstat -plutn
в листинге можно увидеть что-то подобное:
tcp 0 0 192.168.20.75:2345 0.0.0.0:* LISTEN 1201/perl

Дополнительно проверить можно так:
nc -v 192.168.20.75 2345
В ответ получим:
Connection to 192.168.20.75 2345 port [tcp/*] succeeded!
Все!!! Сервер работает!

Можно отдельно запустить скрипт записи rtsp потока:
./write_video_file.pl
Должен записаться 30-секундный кусок видео!

Все... Меняйте под свои настройки, добавляйте свой rtsp поток, и т.д. и т.п. Пользуйтесь на здоровье! За код сильно ногами не пинайте, главное, что он работает. Удачи в использовании...