Comments 11
Кстати, как у вас обстоят дела с собственной системой видео наблюдения? Текущие реалии позволяют брать h264 камеры со стоп кадром (почти все камеры с onvif имеют его, либо собственный датчик движения, можно подписаться на эвенты onvif). И по схеме 10 минут записи в ram диск и миграция через ffmpeg на любой NAS на core i5 дает возможность держать больше 30ти камер. У меня 20-25 потоков h264 грузят одно ядро процессора на 80-180% (ядер 4 — 400%) с детектором движения на motion.
До законченного продукта не дошла — ушёл из видеонаблюдения на гораздо более высоко оплачиваемую работу. А потом и её сменил на фриланс.
Не надо писать в комментариях про организацию на большую букву «I». Их Server использует SQLite, SSL, avcodec (ffmpeg)… но я не видел у них поддержку Onvif, видимо потому что они выпускают «свои» камеры.

Не стал бы писать, если бы вы не вводили в заблуждение читателей Хабра.

1. Организация на большую букву «I» прекрасно использует ONVIF для обнаружения камер в сети. Делается это в момент запуска мастера установки.

2. У нас нет «своих» камер. Есть производители, вроде Philips, Samsung, Hikvision, Microdigital, D-Link и некоторые другие, которые поддержали работу с облачным сервисом Ivideon прямо в своей прошивке. Для таких камер вообще не требуется такое архаичное решение как видеосервер на компьютере. Они работают из коробки. За этим будущее. Никто из наших клиентов не хочет держать какой-то все время включенный компьютер у себя в офисе, только для того, чтобы вывести, например, трансляцию на своем сайте из кухни, где готовят пиццу:



А вообще спасибо за статью. Я очень рад, что на Хабре появляются посты, посвященные видеонаблюдению. К сожалению, эта тема все еще очень обособлена от IT и очень мало информации по этому поводу.

Спасибо, поправил. Видимо у вас идет поиск по камерам для которых включено обнаружение по onvif, но если я хочу добавить свою, у которой выключено обнаружение, то не могу найти в вашем сервере подобную настройку (3.4.5 build 78).
Архаичное решение как «сервер» нужен для архаичных камер, которые имеют совершенно разный h264 кодек с совершенно разными звуковыми дорожками и хочешь-не хочешь для вещания в web нужно всё декодировать.
А статьи про видео наблюдение обычно заканчиваются комментариями «Откройте для себя Ivideon».
Тема от IT обособлена по одной причине. Проще поставить коробку (DVR) и воткнуть туда камеры. Это обкатанный вариант, но не совсем бюджетный. Рынок довольно насыщенный и забит системами безопасности. И «пока оно работает» люди не особо хотят что то трогать.
А статей мало потому что есть либо zoneminder, либо скриптовый монстр на VLC (У меня такой есть), либо вы :)
Пока все производители камер не перейдут на «православный» web h264 ситуация особо не изменится. А как показывает практика и общение с разными организациями и безопасниками лучше иметь обособленную медиа среду для безопасности, нежели гнать по общему lan с его узлами. Сразу скажу что общение происходило в тех организациях, которые экономят на всём и штат в них до 20 человек. Т.е. им дешевле обслуживать путь Камера-провод-DVR, нежели всю инфраструктуру lan (которая строится на каком нибудь asus rt-n66)
Опыт используемый в этой статье был взят из проекта который «не выстрелил».

А сервер нужен для стандартизации потоков с камер. После этого можно и в web, и в файлы, и в космос.
Вы написали достаточно много про то, как забирать видео по RTSP.

Осталась в стороне синхронизация времени с камеры по RTCP.

0,0,0,1 — это префикс AnnexB записи H264. Это не часть H264 NAL-юнита, а разделитель между юнитами.

Также я не рассказал о том как «собирать» udp поток.

RTCP для сохранения потока не столь важен :)
Кадры есть в SDP (SPS, PPS), для поддержки связи используется GET_PARAMETER, OSD есть в кадре. Минимум соблюден. При желании через onvif можно поставить правильное время.
Ссылка на RFC: tools.ietf.org/html/rfc3550#page-19
Камеры шлют RTCP senders report (200), мы должны слать Receiver Report (201)

До реализации RTCP не дошло, так как не было в этом надобности (точнее она была только при прошивке 1.00 у Dlink DCS-2103, на прошивке 1.20 надобность отпала)
/**
 * Created by calc on 17.07.14.
 * http://tools.ietf.org/html/rfc3550#page-19
 *
 * From wireshark:
 * RTCP senders report (200)
 *  01234567 01234567 01234567 01234567
 * +--------+--------+--------+--------+
 * |V P RC  | type201| length (octets) |
 * v-2bit
 * p-1bit
 *  01234567 01234567 01234567 01234567
 * +--------+--------+--------+--------+
 *   SSRC
 *  NTP timestamp MSW
 *  NTP timestamp LSW
 *  RTPWrapper timestamp
 *  senders packet count
 *  senders octet count
 *
 *  RTCP source description (202)
 * +--------+--------+--------+--------+
 * |V P SC  | type202| length          |
 * chunks:
 * Chunk 1:
 * +--------+--------+--------+--------+
 *   Identifier (SSRC?)
 * SDES:
 * +--------+--------+
 *  type     length    Text... End(0)
 *
 *
 * +--------+--------+--------+--------+
 * |V P RC  | type201| length (octets) |
 *  ssrc
 *  Sources:
 *  +-Source1:
 *    +-Identifier (4bytes)
 *    +-SSRC content
 *      + fraction lost 1byte
 *      + Cumulative number of packets lost: -1 (3 bytes)
 *    + extended highest sequence number received
 *      + cycle 2b
 *      + number 2b
 *    + jitter 4b
 *    + LSR (middle of NTP timestamp)
 *    + Delay since last SR 4b
 *
 *
 *
 */

public static byte[] response201(RTCP rtcp, int loop, int seq, int channel, long last, int jitter){
        byte[] buffer = new byte[32];

        int i = 0;

        //header
        buffer[i++] = (byte)0x81;   //1000 0001
        buffer[i++] = (byte)RTCP.TYPE_RECEIVER_REPORT;
        buffer[i++] = 0; buffer[i++] = 7;   // 32/4-1

        //my ssrc
        System.arraycopy(ssrc, 0, buffer, i, ssrc.length);
        i += ssrc.length;
        buffer[i-1] = (byte)channel;

        //source 1
        System.arraycopy(rtcp.getBuffer(), rtcp.getSSRCStart(), buffer, i, 4);
        i += 4;

        //fraction lost
        buffer[i++] = (byte)0x00;
        //Cumulative number of packets lost: -1
        /*buffer[i++] = (byte)0xff;
        buffer[i++] = (byte)0xff;
        buffer[i++] = (byte)0xff;*/
        buffer[i++] = 0;
        buffer[i++] = 0;
        buffer[i++] = 0;

        //Extended highest sequence number received:
        buffer[i++] = BIT.HiByte(BIT.LoWord(loop));
        buffer[i++] = BIT.LoByte(BIT.LoWord(loop));
        buffer[i++] = BIT.HiByte(BIT.LoWord(seq));
        buffer[i++] = BIT.LoByte(BIT.LoWord(seq));

        //Interarrival jitter:
        buffer[i++] = BIT.HiByte(BIT.HiWord(jitter));
        buffer[i++] = BIT.LoByte(BIT.HiWord(jitter));
        buffer[i++] = BIT.HiByte(BIT.LoWord(jitter));
        buffer[i++] = BIT.LoByte(BIT.LoWord(jitter));

        //Last SR timestamp: 3810671619 (0xe3223c03)
        buffer[i++] = BIT.HiByte(BIT.LoWord(rtcp.getHiNTPTimestamp()));
        buffer[i++] = BIT.LoByte(BIT.LoWord(rtcp.getHiNTPTimestamp()));
        buffer[i++] = BIT.HiByte(BIT.HiWord(rtcp.getLowNTPTimestamp()));
        buffer[i++] = BIT.LoByte(BIT.HiWord(rtcp.getLowNTPTimestamp()));

        //Delay since last SR timestamp: 71531 (1091 milliseconds)
        //1/65536
        long now = System.currentTimeMillis();
        int range = (int)(((double)(now - last)/1000) * 65536);

        buffer[i++] = BIT.HiByte(BIT.HiWord(range));
        buffer[i++] = BIT.LoByte(BIT.HiWord(range));
        buffer[i++] = BIT.HiByte(BIT.LoWord(range));
        buffer[i++] = BIT.LoByte(BIT.LoWord(range));
        /*buffer[i++] = 0;
        buffer[i++] = 0;
        buffer[i++] = 0x06;
        buffer[i++] = 0x68;*/

        return buffer;
    }

Рекомендованный интервал между RTCP пакетами — 5 (секунд)
Задача в том, чтобы сохранить последний RTCP (200) и спустя некоторое время ответить.
private Thread createRTCPThread(){
            return new Thread(new Runnable() {
                private void send(Source s){
                    if(s.lastRTCP == null) return;

                    ByteArrayOutputStream bo = new ByteArrayOutputStream();

                    byte[] frame = {'$', (byte)s.controlCh, 0, 52};
                    try {
                        bo.write(frame);
                        RTCP r = s.lastRTCP;
                        do {
                            if(r.getPT() == RTCP.TYPE_SENDER_REPORT){
                                bo.write(
                                        RTCP.response201(r, s.loop, s.sequence, s.controlCh,
                                                s.lastRTCPTime, (int)s.jitter));
                            }
                            else if(r.getPT() == RTCP.TYPE_SOURCE_DESCRIPTION){
                                bo.write(RTCP.response202(r, s.controlCh));
                            } else {
                                System.out.println("RTCP Thread - Херня какая то: " + r.getPT());
                            }
                        }while ( (r = r.getNextRTCP()) != null);
                        out.write(bo.toByteArray());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

                @Override
                public void run() {
                    while(!stop){
                        try {
                            Thread.sleep(4500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        send(sources.get(0));
                        //send ch1
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //send ch2
                        send(sources.get(1));
                    }
                }
            });
        }

github.com/Calc86/camRecorder/blob/master/src/com/net/rtp/RTCP.java

Также нужно понимать о существовании RTCP source description (202)
Каждый Source у камеры имеет уникальное число (32 бита, SSRC)
Аудио и видео это разные Source.
Алгоритмы вычисления lost и jitter можно посмотреть в приложениях RFC 3550, или вольная интерпретация тут: github.com/Calc86/camRecorder/tree/master/src/com/net/rtp/rfc — но его надо тестить.
Такого класса вполне достаточно для ответа на RTCP 200
private class Source{
        public int ch;
        public int controlCh;
        public int SSRC;
        public RTCP lastRTCP;
        public long lastRTCPTime;
        public long jitter;
        public long transit;
        public int loop;
        public int sequence;

        //Перевезти в эти переменные массив os[]
        private OutputStream dataOut;
        private OutputStream controlOut;

        public void calculate(RTPWrapper rtp){
            long ts = uInt.get(rtp.getTimestamp());
            long arrival = System.currentTimeMillis() / 1000L;
            long transit = arrival - ts;
            long d = transit - this.transit;
            this.transit = transit;
            if(d < 0) d = -d;
            jitter += (1./16.) * ((double)d - jitter);

            int oldSeq = sequence;
            int newSeq = rtp.getSequence();
            if(newSeq < oldSeq) loop++;
            sequence = newSeq;
        }
    }

Ну а вот что делать со всеми этими Timestamp — хрен его знает. Если пакеты потерялись, то не будем же мы нули писать в поток. По этому берем поток, отправляем в OutputStream и всё.
Сейчас последняя версия на сайте 3.5.0. Насколько я помню поддержка ONVIF появилась с версии, где был проведен редизайн мастера установки камер и он стал выглядеть так:



Если у вас он другой, то в этой версии, скорей всего поддержка ONVIF отсутствует.

Пока все производители камер не перейдут на «православный» web h264

Этого не произойдёт никогда. Есть стандарт H264, как и RTSP и ONVIF, и есть огромное количество производителей, которые ему следуют совершенно как вздумается. Те кто работали и работают с камерами, например, erlyvideo знают не по наслышке о том, как по RTSP приходят от китайских камер заголовки вида «Content-Length5034» и подобные опусы. Всегда приходится использовать заплатки для таких решений.

А сервер нужен для стандартизации потоков с камер. После этого можно и в web, и в файлы, и в космос.

Для этого, в том числе, у нас и есть облако. Вся обработка «не таких» потоков производится в нём. Мы облегчаем жизнь пользователя, как это банально не звучало бы.

И не останавливайтесь) Пишите ещё! Тема очень интересная! Кстати, мы постоянно ищем интересных и разбирающихся в теме авторов. Готовы оплачивать посты по теме в нашем блоге и всячески поддерживать!)
Понятно что все не перейдут.

А что писать то)
В сентябре новая работа будет искаться. Буду заниматься видео — что нибудь напишу, а так пока мысли только о том как себя ведет TCP при большой нагрузке (window size и etc)
В сторону SIP может быть занесет и гоняние видео по нему, но кто знает.

У меня «облако» это был Core-i5 с Proxmox на борту, 3 машины (DVR, Mysql, Web) и второй гроб FreeNAS. Для нас было «накладно» перекодировать на нашем железе, по этому искались решения как можно проще и «в лоб». 30 камер 720p держал влет с онлайн просмотром архива и детектором движения. Онлайн просмотр был страшным монстром. Иногда h264 елся, иногда тупо mjpeg отдалавли (4 кдра в секунду, что позволяло смотреть даже на nokia c5-00). Ну и тайплапсы видео за день (1 секунда = 3 минуты), что позволяло за 5-8 минут проверить работу целой толпы людей. Кто был, кто не был и кто когда пришел. Так как проект потонул, вот и описываю «опыт».
Доходило до изобретения «железяки» на openwrt, которая напрямую поток с камеры кладет на хард, который воткнут в эту «железяку». Это был любой роутер TP-link и OpenWRT прошивка. Идея была в том, чтобы через OpenWRT хоть как то стандартизировать получение потока, стопкадра и безопасности.

Можно конечно и всё это описать, но там велосипед на велосипеде.
Ivideon Server 3.5.0 Build 95
onvif discovery не сработал у меня.
Да и как я говорил напрямую нельзя «скормить» ссылку на onvif камеры.
Мне просто попадались IP камеры где есть ссылка на onvif, но для ссылки на rtsp приходилось рыться в интернете, даже поставщик не мог сказать их.
Может в будущем вы реализуете что нибудь подобное
alpha.hstor.org/files/2f7/8e1/7f1/2f78e17f107742fb8ab445bb85149f7f.png

IP camera Name	DCS-2103
Time & Date	Wed Jan 16 07:32:58 2013
Firmware Version	1.20.00
MAC Address	F0:7D:68:09:E4:F4
IP Address	10.112.28.231
IP Subnet Mask	255.255.255.0
Default Gateway	10.112.28.253
Primary DNS	10.112.1.1
Secondary DNS	10.112.2.1
PPPoE	Disable
DDNS	Disable

у меня 10.112.28.33, воткнуты в один свич, udp не режется.
Понял. Да. Это факт. Напрямую скормить ссылку в Ivideon Server пока нельзя.

В сентябре новая работа будет искаться.


Если в Москве, приходите к нам. У нас много чего дорабатывать есть. В том числе и задачи по улучшению работы ONVIF, подключение и поддержка на уровне аппаратных детекторов новых камер и т.д.
Only those users with full accounts are able to leave comments. Log in, please.