Еще раз о видеонаблюдении, камерах, RTSP, onvif. И «велосипед»!

    Информация уже была на хабре: habrahabr.ru/post/115808 и habrahabr.ru/post/117735
    Там описывается Motion-JPEG (MJPEG).
    Мир не стоит на месте и видео наблюдение тоже. Всё чаще и чаще используются другие кодеки.
    Тут описываю свой опыт в этом «мире».
    Профессионалы ничего нового не узнают, другим может будет просто интересно.
    Разрабатывалось всё в качестве обучения и тренировки.
    Речь пойдет о RTP, RTSP, h264, mjpeg, onvif и всём вместе.
    Перед прочтением обязательно прочитать статьи другого автора, указанные выше.

    Что такое RTSP можно прочитать:

    Особенность RTSP в том, что он сам по себе не передаёт нужные нам видео данные. После установки связи вся работа осуществляется по протоколу RTP (RFC).

    По RTP протоколу нужно различать 2 вида передачи
    1. Non-Interleaved Mode (UDP)
    2. Interleaved Mode (TCP)


    Non-Interleaved Mode.
    RTSP устанавливает связь и передает в камеру информацию о том «куда слать» данные (UDP порты).
    Пример общения RTSP

    //INFO: connect to: rtsp://10.112.28.231:554/live1.sdp
    
    OPTIONS rtsp://10.112.28.231:554/live1.sdp RTSP/1.0
    CSeq: 1
    User-Agent: LibVLC/2.1.4 (LIVE555 Streaming Media v2014.01.21)
    
    RTSP/1.0 200 OK
    CSeq: 1
    Date: Tue, Jan 15 2013 02:02:56 GMT
    Public: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, GET_PARAMETER, SET_PARAMETER
    
    
    DESCRIBE rtsp://10.112.28.231:554/live1.sdp RTSP/1.0
    CSeq: 2
    User-Agent: LibVLC/2.1.4 (LIVE555 Streaming Media v2014.01.21)
    Accept: application/sdp
    
    
    RTSP/1.0 200 OK
    CSeq: 2
    Date: Tue, Jan 15 2013 02:02:56 GMT
    Content-Base: rtsp://10.112.28.231/live1.sdp/
    Content-Type: application/sdp
    Content-Length: 667
    //667 - Размер SDP пакета, о нем позже
    
    
    SETUP rtsp://10.112.28.231:554/live1.sdp/track1 RTSP/1.0
    CSeq: 3
    User-Agent: LibVLC/2.1.4 (LIVE555 Streaming Media v2014.01.21)
    Transport: RTP/AVP;unicast;client_port=49501-49502
    
    
    RTSP/1.0 200 OK
    CSeq: 3
    Date: Tue, Jan 15 2013 02:02:56 GMT
    Transport: RTP/AVP;unicast;destination=10.112.28.33;source=10.112.28.231;client_port=49501-49502;server_port=6970-6971
    Session: 7BFE9DAA
    
    
    SETUP rtsp://10.112.28.231:554/live1.sdp/track2 RTSP/1.0
    CSeq: 4
    User-Agent: LibVLC/2.1.4 (LIVE555 Streaming Media v2014.01.21)
    Transport: RTP/AVP;unicast;client_port=49503-49504
    Session: 7BFE9DAA
    
    
    RTSP/1.0 200 OK
    CSeq: 4
    Date: Tue, Jan 15 2013 02:02:56 GMT
    Transport: RTP/AVP;unicast;destination=10.112.28.33;source=10.112.28.231;client_port=49503-49504;server_port=6972-6973
    Session: 7BFE9DAA
    
    
    PLAY rtsp://10.112.28.231:554/live1.sdp RTSP/1.0
    CSeq: 5
    User-Agent: LibVLC/2.1.4 (LIVE555 Streaming Media v2014.01.21)
    Session: 7BFE9DAA
    Range: npt=0.000-
    
    
    RTSP/1.0 200 OK
    CSeq: 5
    Date: Tue, Jan 15 2013 02:02:56 GMT
    Range: npt=0.000-
    Session: 7BFE9DAA
    RTP-Info: url=rtsp://10.112.28.231/live1.sdp/track1;seq=7746;rtptime=0,url=rtsp://10.112.28.231/live1.sdp/track2;seq=13715;rtptime=0
    


    Запоминаем
    Transport: RTP/AVP;unicast;destination=10.112.28.33;source=10.112.28.231;client_port=49501-49502;server_port=6970-6971

    Interleaved Mode.
    Разница с Non-Interleaved Mode в том что все пакеты будут сыпаться в этот же порт.
    Пример:

    OPTIONS rtsp://10.113.151.152:554/tcp_live/profile_token_0 RTSP/1.0
    CSeq: 1
    User-Agent: LibVLC/2.1.4 (LIVE555 Streaming Media v2014.01.21)
    
    
    RTSP/1.0 200 OK
    CSeq: 1
    User-Agent: LibVLC/2.1.4 (LIVE555 Streaming Media v2014.01.21)
    Public: OPTIONS, DESCRIBE, SETUP, PLAY, TEARDOWN, SET_PARAMETER
    
    
    DESCRIBE rtsp://10.113.151.152:554/tcp_live/profile_token_0 RTSP/1.0
    CSeq: 2
    User-Agent: LibVLC/2.1.4 (LIVE555 Streaming Media v2014.01.21)
    Accept: application/sdp
    
    
    RTSP/1.0 200 OK
    CSeq: 2
    Content-Type: application/sdp
    Content-Length: 316
    
    
    SETUP rtsp://10.113.151.152:554/tcp_live/profile_token_0/video/h264 RTSP/1.0
    CSeq: 3
    User-Agent: LibVLC/2.1.4 (LIVE555 Streaming Media v2014.01.21)
    Transport: RTP/AVP/TCP;unicast;interleaved=0-1
    
    
    RTSP/1.0 200 OK
    CSeq: 3
    Session: 52cd95de
    Transport: RTP/AVP/TCP;interleaved=0-1;unicast
    
    
    SETUP rtsp://10.113.151.152:554/tcp_live/profile_token_0/audio/pcma RTSP/1.0
    CSeq: 4
    User-Agent: LibVLC/2.1.4 (LIVE555 Streaming Media v2014.01.21)
    Transport: RTP/AVP/TCP;unicast;interleaved=2-3
    Session: 52cd95de
    
    
    RTSP/1.0 200 OK
    CSeq: 4
    Session: 52cd95de
    Transport: RTP/AVP/TCP;interleaved=2-3;unicast
    
    
    PLAY rtsp://10.113.151.152:554/tcp_live/profile_token_0 RTSP/1.0
    CSeq: 5
    User-Agent: LibVLC/2.1.4 (LIVE555 Streaming Media v2014.01.21)
    Session: 52cd95de
    Range: npt=0.000-
    
    
    RTSP/1.0 200 OK
    CSeq: 5
    Session: 52cd95de
    


    Запоминаем
    Transport: RTP/AVP/TCP;unicast;interleaved=0-1

    Теперь смотрим что и как.
    Камеры шлют видео и аудио в разные RTP потоки. 2n поток — данные, 2n+1 поток — RTCP.
    На видео нам идет 0 и 1 канал, на аудио 2 и 3 канал.
    Теперь смотрим
    Transport: RTP/AVP;unicast;destination=10.112.28.33;source=10.112.28.231;client_port=49501-49502;server_port=6970-6971
    Transport: RTP/AVP/TCP;unicast;interleaved=0-1

    В первом случае указаны порты, во втором каналы.

    С с Non-Interleaved Mode всё понятно. Просто RTP пакеты сыпятся в порты и их можно читать как то так:
    DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
    s.receive(packet);


    Проблемы начинаются с Interleaved mode.
    По факту ни каких проблем быть не должно. По RFC мы ищем magic char "$", следующий байт — канал (он указывается в подключении 0-4 у нас) и 2 байта Length. Всего 4 байта.
    Но есть не нормальные камеры. Например D-ling DCS-2103 «Досыпает» какие то данные после rtp пакета. frame дает размер 1448,
    шлет 1448 фрейма, и после 827 байт какого то мусора. (Так делает Dlink DCS-2103 прошивка 1.00 и 1.20)

    И такое у «них» происходит постоянно. Этим частенько страдают китайские камеры. Qihan (356) этим не страдали.
    Кроме как пропускать этот мусор идей больше нет.
    В RTP сыпятся полезные данные. При DESCRIBE RTSP возвращается SDP пакет
    Примеры SDP (h264, mjpeg, mpeg4):
    v=0
    o=- 1357245962093293 1 IN IP4 10.112.28.231
    s=RTSP/RTP stream 1 from DCS-2103
    i=live1.sdp with v2.0
    t=0 0
    a=type:broadcast
    a=control:*
    a=range:npt=0-
    a=x-qt-text-nam:RTSP/RTP stream 1 from DCS-2103
    a=x-qt-text-inf:live1.sdp
    m=video 0 RTP/AVP 96
    c=IN IP4 0.0.0.0
    b=AS:1500
    a=rtpmap:96 H264/90000
    a=fmtp:96 packetization-mode=1;profile-level-id=640028;sprop-parameter-sets=Z2QAKK2EBUViuKxUdCAqKxXFYqOhAVFYrisVHQgKisVxWKjoQFRWK4rFR0ICorFcVio6ECSFITk8nyfk/k/J8nm5s00IEkKQnJ5Pk/J/J+T5PNzZprQCgDLSpAAAAwHgAAAu4YEAAPQkAABEqjve+F4RCNQ=,aO48sA==
    a=control:track1
    m=audio 0 RTP/AVP 97
    c=IN IP4 0.0.0.0
    b=AS:64
    a=rtpmap:97 G726-32/8000
    a=control:track2
    
    v=0
    o=- 1357245962095633 1 IN IP4 10.112.28.231
    s=RTSP/RTP stream 3 from DCS-2103
    i=live3.sdp with v2.0
    t=0 0
    a=type:broadcast
    a=control:*
    a=range:npt=0-
    a=x-qt-text-nam:RTSP/RTP stream 3 from DCS-2103
    a=x-qt-text-inf:live3.sdp
    m=video 0 RTP/AVP 26
    c=IN IP4 0.0.0.0
    b=AS:1500
    a=x-dimensions:640,360
    a=control:track1
    m=audio 0 RTP/AVP 97
    c=IN IP4 0.0.0.0
    b=AS:64
    a=rtpmap:97 G726-32/8000
    a=control:track2
    
    v=0
    o=- 1357245962094966 1 IN IP4 10.112.28.231
    s=RTSP/RTP stream 2 from DCS-2103
    i=live2.sdp with v2.0
    t=0 0
    a=type:broadcast
    a=control:*
    a=range:npt=0-
    a=x-qt-text-nam:RTSP/RTP stream 2 from DCS-2103
    a=x-qt-text-inf:live2.sdp
    m=video 0 RTP/AVP 96
    c=IN IP4 0.0.0.0
    b=AS:1500
    a=rtpmap:96 MP4V-ES/90000
    a=fmtp:96 profile-level-id=1;config=000001B001000001B509000001010000012000845D4C29402320A21F
    a=control:track1
    m=audio 0 RTP/AVP 97
    c=IN IP4 0.0.0.0
    b=AS:64
    a=rtpmap:97 G726-32/8000
    a=control:track2
    


    Прочитать про SDP
    Так как мода была mjpeg и текущая на h264, то рассмотрим их.
    С MJpeg всё предельно ясно. А вот с H264 начинаются различия в камерах.
    Формат h264 состоит из блоков с NAL заголовками (7.4.1 NAL unit semantics).
    Чтобы можно было декодировать h264 необходимо помимо данных самого h264 иметь данные SPS (Sequence parameter set) и PPS(Picture parameter set). Первый описывает последовательность, второй параметры картинки. Так как сам кодек h264 знаю очень плохо, то большего описания не будет. SPS имеет тип 7, PPS 8. Без них невозможно декодировать h264.
    Самое интересное — Qihan шлет SPS и PPS прям в RTP пакетах, Dlink не шлет их в RTP пакетах. Но SPS и PPS шлется в SDP пакете в параметре sprop-parameter-sets в кодировке base64.
    sprop-parameter-sets=Z2QAKK2EBUViuKxUdCAqKxXFYqOhAVFYrisVHQgKisVxWKjoQFRWK4rFR0ICorFcVio6ECSFITk8nyfk/k/J8nm5s00IEkKQnJ5Pk/J/J+T5PNzZprQCgDLSpAAAAwHgAAAu4YEAAPQkAABEqjve+F4RCNQ=,aO48sA==
    Шлются они через запятую
    Вариант декодирования.
    //split по ','
    sps = Base64.decode(props[0].getBytes());
    pps = Base64.decode(props[1].getBytes());
    


    Так как камеры 720p или 1080p, то в 1 RTP пакет ни jpeg фрейм, ни h264 фрейм не поместится, то они режутся на пакеты.
    RTP Payload Format for JPEG-compressed Video
    RTP Payload Format for H.264 Video

    JPEG
    RTP пакет содержит main JPEG header
        0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       | Type-specific |              Fragment Offset                  |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       |      Type     |       Q       |     Width     |     Height    |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    

    а дальше может варьироваться от Type и Q
    if(getType() < 64){
                return JPEG_HEADER_SIZE;
            } else if(getType() < 128){
                //we have 3.1.7.  Restart Marker header
                return JPEG_HEADER_SIZE + JPEG_RESTART_MARKER_HEADER_SIZE;
            }
    

    Для декодирования jpeg нужно знать или вычислить quantization tables.
    В моих камерах quantization tables шли в стартовом пакете Jpeg, по этому они просто брались оттуда.
    Все вычисления есть в RFC.
    Последний пакет фрейма вычисляется по RTP header Marker bit. Если он 1, то это последний пакет фрейма.

    H264
    NAL Header
          +---------------+
          |0|1|2|3|4|5|6|7|
          +-+-+-+-+-+-+-+-+
          |F|NRI|  Type   |
          +---------------+
    


    Single NAL Unit Packet
    Это как раз SPS и PPS. Type=7 или Type=8
         0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |F|NRI|  Type   |                                               |
        +-+-+-+-+-+-+-+-+                                               |
        |                                                               |
        |               Bytes 2..n of a single NAL unit                 |
        |                                                               |
        |                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |                               :...OPTIONAL RTP padding        |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    


    Если фрейм h264 не влезает в RTP пакет (1448 байт), то фрейм режется на фрагменты. (5.8. Fragmentation Units (FUs))
    Type = 28
         0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        | FU indicator  |   FU header   |                               |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                               |
        |                                                               |
        |                         FU payload                            |
        |                                                               |
        |                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |                               :...OPTIONAL RTP padding        |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    

    Эти заголовки следуют сразу после RTP заголовка
    public int getH264PayloadStart() {
            switch(getNAL().getType()){
                case NAL.FU_A:
                    return rtp.getPayloadStart() + 2;
                case NAL.SPS:
                case NAL.PPS:
                    return rtp.getPayloadStart();
                default:
                    throw new NotImplementedException("NAL type " + getNAL().getType() + " not implemented");
            }
        }
    


    Для декодера h264 NAL — нужная информация. Если идет фрагментация фрейма, то NAL нужно восстанавливать. (FU)
    нужно взять первые 3 бита из FU indicator и слить их с 5 последними FU header.

    Теперь самое главное — сохраняем поток.
    Jpeg
    public void writeRawJPEGtoStream(OutputStream out) throws IOException {
            //if(isMustBeZero()){
            if(isStart()){
                //first
                //System.out.println("first");
                byte[] headers = new byte[1024];
                int length = makeJpeg(headers);
                out.write(headers, 0, length);
                out.write(rtp.getBuffer(), getJPEGPayloadStart(), getJPEGPayloadLength());
            }else
            //if(getMarker()){
            if(isEnd()){
                //end
                //System.out.println("end");
                out.write(rtp.getBuffer(), getJPEGPayloadStart(), getJPEGPayloadLength());
                //EOI
            } else {
              //middle
                //System.out.println("middle");
                out.write(rtp.getBuffer(), getJPEGPayloadStart(), getJPEGPayloadLength());
            }
        }
    

    h264
    public static final byte[] NON_IDR_PICTURE = {0x00, 0x00, 0x00, 0x01};
    
    public void writeRawH264toStream(OutputStream out) throws IOException, NotImplementedException {
            switch (nal.getType()){
                case NAL.FU_A:    //FU-A, 5.8.  Fragmentation Units (FUs)/rfc6184
                    FUHeader fu = getFUHeader();
    
                    if(fu.isFirst()){
                        //if(debug) System.out.println("first");
                        out.write(H264RTP.NON_IDR_PICTURE);
                        out.write(getReconstructedNal());
                        out.write(rtp.getBuffer(), getH264PayloadStart(), getH264PayloadLength());
                    } else if(fu.isEnd()){
                        //if(debug) System.out.println("end");
                        out.write(rtp.getBuffer(), getH264PayloadStart(), getH264PayloadLength());
                    } else{
                        //if(debug) System.out.println("middle");
                        out.write(rtp.getBuffer(), getH264PayloadStart(), getH264PayloadLength());
                    }
                    break;
                case NAL.SPS: //Sequence parameter set
                case NAL.PPS: //Picture parameter set
                    //System.out.println("sps or pps write");
                    out.write(H264RTP.NON_IDR_PICTURE);
                    out.write(rtp.getBuffer(), rtp.getPayloadStart(), rtp.getPayloadLength());
                    break;
                default:
                    throw new NotImplementedException("NAL type " + getNAL().getType() + " not implemented");
            }
        }
    

    NON_IDR_PICTURE — необходим для декодирования, «разделяем» фреймы. (h264)
    Тут нужно меня поправить, так как это просто «костыль» и обоснований пока нет. Просто работает.
    Получается такой поток: 00000001 + SPS + 00000001 + PPS + 00000001 + NAL…
    erlyvideo: 0,0,0,1 — это префикс AnnexB записи H264. Это не часть H264 NAL-юнита, а разделитель между юнитами.

    ну и обработка «всего» этого
    while(!stop){
                    IRaw raw = rtp;
                    //читаем фрейм
                    try {
                        while(!frame.fill(in));
    
                        //полюбому читаем rtp пакет
                        rtp.fill(in, frame.getLength());
                        try {
                            raw = rtp.getByPayload();
                        } catch (NotImplementedException e) {
                            if(log.isLoggable(Level.FINE)) log.fine("rtp seq=" + rtp.getSequence() + ": " + e.getMessage());
                        }
                    } catch (SocketException e) {
                        log.warning(e.getMessage()); //socket closed?
                        break;
                    }
    
                    byte ch = frame.getChannel();
    
                    //RTCP? //прошивка D-link DCS2103 1.00 слала RTCP и interleaved
                    Source s = sources.get(source(ch));
                    if(rtp.getPayloadType() == RTPWrapper.TYPE_RTCP){
                        byte[] rb = new byte[frame.getLength()];
                        System.arraycopy(buffer, 0, rb, 0, rb.length);
                        s.lastRTCP = new RTCP(rb, rb.length);    //save last rtsp
                        s.lastRTCPTime = System.currentTimeMillis();
                        System.out.println(frame.getLength());
                    } else {
                        s.calculate(rtp); //вычисление для source параметров (для нужд RTCP)
                    }
    
                    if(os.length <= ch){
                        log.warning("Нужно больше out стримов: " + ch);
                        continue;
                    }
    
                    profiler.stop();
                    counter.count(profiler.getLast(), frame.getLength() / 1000.0);
                    //profiler.print(0);
                    if(os[ch] == null) continue;
    
                    //Нужна была синхронизация, так как os[ch] менялся, сейчас он постоянно rotator
                    synchronized (os[ch]){
                        raw.writeRawToStream(os[ch]);
                    }
                }
    

    в 2х словах. Получаем RTSP Interleaved Frame (например Channel: 0x00, 1448 bytes), читаем 1448 байт, делаем writeRawToStream, полиморфизм делает свое дело.

    Дальше это нужно обкатать.
    Казалось бы что для поддержания потока RTSP нужно делать RTCP отчеты, но нет, всё оказалось проще
    Dlink, Qihan, VLC просто «едят» GET_PARAMETER:
    GET_PARAMETER rtsp://10.112.28.231:554/live3.sdp RTSP/1.0
    CSeq: 7
    User-Agent: LibVLC/2.1.4 (LIVE555 Streaming Media v2014.01.21)
    Session: 327B23C6
    

    шлем его раз в 55 секунд и всё.

    Теперь сам велосипед
    Просто программа в которую можно добавить ссылку на камеру (http или rtsp) и она будет сохранять поток. База SQLite. «Нормализация» потока через ffmpeg, просмотр через Vlc.
    Нет переподключения после каких либо разрывов связи, файловых проблем и т.д.
    Нет половины проверок и подобных штук.
    Как выглядит
    Кнопки
    1. Добавить
    2. Удалить
    3. Запустить
    4. Остановить
    5. Архив
    6. Настройка
    7. Выход

    1

    Настройки :)
    2

    Архив
    1. Посмотреть — запускает Vlc
    2. Склеить и посмотреть — клеит файлы и запускает Vlc
    3. Выход

    3

    При простом просмотре генерируется m3u файл и кормится в VLC
    4

    При склеивании ffmpeg клеит, после запускается VLC
    5

    Программа нарезает поток на файлы, интервал задается в настройках

    Что делает ffmpeg:
    Клеит
    String command = String.format("%s -y -f concat -i concat.txt -codec copy concat.mp4",
    

    «Нормализует» (просчитывает заголовки и т.д.)
    String command = String.format("%s -i %s -codec copy %s",
                        settings.getFfmpegPath(),
                        settings.getFullTmpPath() + archive,
                        settings.getArchivePath() + "/" + settings.getRecPath() + "/" + archive + ".mp4")
    


    На выходе куча файлов
    6

    По хорошему можно писать в любой OutputStream
    Git hub
    Дальнейшей жизни программы может и не быть. Возможно допишу когда нибудь RTP классы для звука. (так как увлекаюсь до сих пор SIP)

    Ну и самое вкусное.
    Есть стандарт видео наблюдения ONVIF
    Есть профессиональные железки, которые с камерами работают только по нему.
    Есть камеры, которые работают по нему (Qihan, он же Proline), а ссылки rtsp приходится гуглить.
    Есть опенсорсный продукт Onvif device manager для управления подобными железяками.
    Я же в программу добавил поддержку onvif без авторизации и с авторизацией.
    7
    Git hub

    В 2х словах об Onvif: Это soap.
    Работа простая. 1. Шлем POST-XML, 2. Получаем XML
    Код на гитхабе. Ключ -s сохраняет все запросы и ответы XML.
    пример запроса:
    <ns3:Envelope xmlns:xmime="http://www.w3.org/2005/05/xmlmime"
    xmlns:ns2="http://schemas.xmlsoap.org/soap/envelope/" 
    xmlns:ns4="http://www.onvif.org/ver10/device/wsdl" 
    xmlns:ns3="http://www.w3.org/2003/05/soap-envelope" 
    xmlns:ns6="http://www.w3.org/2005/08/addressing" 
    xmlns:ns5="http://www.onvif.org/ver10/schema" 
    xmlns:ns8="http://docs.oasis-open.org/wsrf/bf-2" 
    xmlns:ns7="http://docs.oasis-open.org/wsn/b-2" 
    xmlns:ns13="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" 
    xmlns:ns9="http://docs.oasis-open.org/wsn/t-1" 
    xmlns:ns12="http://www.onvif.org/ver10/media/wsdl" 
    xmlns:ns11="http://www.w3.org/2004/08/xop/include" 
    xmlns:ns14="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
    <ns3:Body><ns4:GetCapabilities>
    <ns4:Category>All</ns4:Category>
    </ns4:GetCapabilities>
    </ns3:Body>
    </ns3:Envelope>
    

    Если пройтись по ссылкам выше, то можно получить всю документацию по Onvif.
    Ответ:
    <?xml version="1.0" encoding="UTF-8"?>
    <SOAP-ENV:Envelope 
    xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope" 
    xmlns:SOAP-ENC="http://www.w3.org/2003/05/soap-encoding" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
    xmlns:wsa5="http://www.w3.org/2005/08/addressing" 
    xmlns:xmime5="http://www.w3.org/2005/05/xmlmime" 
    xmlns:xop="http://www.w3.org/2004/08/xop/include" 
    xmlns:tt="http://www.onvif.org/ver10/schema" 
    xmlns:tds="http://www.onvif.org/ver10/device/wsdl" 
    xmlns:tptz="http://www.onvif.org/ver20/ptz/wsdl" 
    xmlns:tev="http://www.onvif.org/ver10/events/wsdl" 
    xmlns:wsnt="http://docs.oasis-open.org/wsn/b-2" 
    xmlns:trt="http://www.onvif.org/ver10/media/wsdl" 
    xmlns:timg="http://www.onvif.org/ver20/imaging/wsdl" 
    xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" 
    xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" 
    xmlns:ter="http://www.onvif.org/ver10/error" xmlns:tns1="http://www.onvif.org/ver10/topics" 
    xmlns:wstop="http://docs.oasis-open.org/wsn/t-1">
    <SOAP-ENV:Body>
    <tds:GetCapabilitiesResponse>
    <tds:Capabilities><tt:Device><tt:XAddr>http://10.112.28.231:80/onvif/device_service</tt:XAddr>
    <tt:Network><tt:IPFilter>false</tt:IPFilter>
    <tt:ZeroConfiguration>false</tt:ZeroConfiguration><tt:IPVersion6>false</tt:IPVersion6>
    <tt:DynDNS>false</tt:DynDNS></tt:Network><tt:System><tt:DiscoveryResolve>false</tt:DiscoveryResolve>
    <tt:DiscoveryBye>true</tt:DiscoveryBye><tt:RemoteDiscovery>false</tt:RemoteDiscovery>
    <tt:SystemBackup>false</tt:SystemBackup><tt:SystemLogging>false</tt:SystemLogging>
    <tt:FirmwareUpgrade>true</tt:FirmwareUpgrade><tt:SupportedVersions>
    <tt:Major>1</tt:Major><tt:Minor>2</tt:Minor></tt:SupportedVersions>
    <tt:Extension></tt:Extension></tt:System><tt:IO></tt:IO><tt:Security>
    <tt:TLS1.1>true</tt:TLS1.1><tt:TLS1.2>false</tt:TLS1.2>
    <tt:OnboardKeyGeneration>false</tt:OnboardKeyGeneration>
    <tt:AccessPolicyConfig>false</tt:AccessPolicyConfig>
    <tt:X.509Token>false</tt:X.509Token><tt:SAMLToken>false</tt:SAMLToken>
    <tt:KerberosToken>false</tt:KerberosToken><tt:RELToken>false</tt:RELToken>
    </tt:Security></tt:Device><tt:Events><tt:XAddr>http://10.112.28.231:80/onvif/device_service</tt:XAddr>
    <tt:WSSubscriptionPolicySupport>false</tt:WSSubscriptionPolicySupport>
    <tt:WSPullPointSupport>true</tt:WSPullPointSupport>
    <tt:WSPausableSubscriptionManagerInterfaceSupport>false</tt:WSPausableSubscriptionManagerInterfaceSupport>
    </tt:Events><tt:Imaging><tt:XAddr>http://10.112.28.231:80/onvif/device_service</tt:XAddr>
    </tt:Imaging><tt:Media><tt:XAddr>http://10.112.28.231:80/onvif/device_service</tt:XAddr>
    <tt:StreamingCapabilities><tt:RTPMulticast>false</tt:RTPMulticast><tt:RTP_TCP>true</tt:RTP_TCP>
    <tt:RTP_RTSP_TCP>true</tt:RTP_RTSP_TCP></tt:StreamingCapabilities></tt:Media>
    </tds:Capabilities></tds:GetCapabilitiesResponse></SOAP-ENV:Body></SOAP-ENV:Envelope>
    

    Дальнейшее общение по onvif без авторизации идет в этом же ключе.

    А вот пример общения но уже с авторизацией
    <ns3:Envelope xmlns:xmime="http://www.w3.org/2005/05/xmlmime" xmlns:ns2="http://schemas.xmlsoap.org/soap/envelope/" 
    xmlns:ns4="http://www.onvif.org/ver10/device/wsdl" xmlns:ns3="http://www.w3.org/2003/05/soap-envelope" 
    xmlns:ns6="http://www.w3.org/2005/08/addressing" xmlns:ns5="http://www.onvif.org/ver10/schema" 
    xmlns:ns8="http://docs.oasis-open.org/wsrf/bf-2" xmlns:ns7="http://docs.oasis-open.org/wsn/b-2" xmlns:ns13="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" 
    xmlns:ns9="http://docs.oasis-open.org/wsn/t-1" xmlns:ns12="http://www.onvif.org/ver10/media/wsdl" xmlns:ns11="http://www.w3.org/2004/08/xop/include" xmlns:ns14="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
    <ns3:Header>
    	<Security xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" ns3:mustUnderstand="1">
    		<UsernameToken>
    			<Username>admin</Username>
    			<Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">KSsJz8Lx0xPJd4pYdMuFblluNac=</Password>
    			<Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">Y2FsY09udmlm</Nonce>
    			<Created xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2013-01-15T08:00:57.000Z</Created>
    		</UsernameToken>
    	</Security>
    </ns3:Header>
    <ns3:Body><ns12:GetProfiles/></ns3:Body></ns3:Envelope>
    

    Т.е. нужно слать заголовок. (тестилось на D-link DCS-2103, остальные камеры без авторизации работали, китай).

    Timestamp (Created)
    public static String getOnvifTimeStamp(DateTime dateTime){
            return String.format("%4d-%02d-%02dT%02d:%02d:%02d.000Z",
                    dateTime.getDate().getYear(),
                    dateTime.getDate().getMonth(),
                    dateTime.getDate().getDay(),
                    dateTime.getTime().getHour(),
                    dateTime.getTime().getMinute(),
                    dateTime.getTime().getSecond()
            );
        }
    

    Nonce
    public String getNonceDigest(){
            return base64(getNonce().getBytes());
        }
    

    и пароль (Password_Digest = Base64 ( SHA-1 ( nonce + created + password ) ))
    public String getPasswordDigest(){
            //Password_Digest = Base64 ( SHA-1 ( nonce + created + password ) )
            String line = getNonce() + timestamp + password;
            try {
                line = base64(sha1(line.getBytes()));
                return line;
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            }
            return "";
        }
    


    Всё было сделано в образовательных целях. Если есть вопросы и вдруг понадобиться более подробное описание чего либо — пишите.
    Надеюсь кому нибудь пригодится.

    PS Не надо писать в комментариях про организацию на большую букву «I». Их Server использует SQLite, SSL, avcodec (ffmpeg), а в папке \Resources есть божественный файлик с названием camera_list.json, но моя наглость не позволила его прикрутить к своей программе :) Но я не видел у них поддержку Onvif, видимо потому что они выпускают «свои» камеры. UPDATED: см комментарии от ivideon

    Если прикрутить к программе OpenVPN и OpenCV, то будет забавное решение и «велосипед»
    Ну и вот вам полезная ссылка на базу ссылок потоков камер

    Git hub:
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 11

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

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

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

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



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

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

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

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

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

              0
              Также я не рассказал о том как «собирать» 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 и всё.
              0
              Сейчас последняя версия на сайте 3.5.0. Насколько я помню поддержка ONVIF появилась с версии, где был проведен редизайн мастера установки камер и он стал выглядеть так:



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

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

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

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

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

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

                А что писать то)
                В сентябре новая работа будет искаться. Буду заниматься видео — что нибудь напишу, а так пока мысли только о том как себя ведет 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 хоть как то стандартизировать получение потока, стопкадра и безопасности.

                Можно конечно и всё это описать, но там велосипед на велосипеде.
                  0
                  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 не режется.
                    0
                    Понял. Да. Это факт. Напрямую скормить ссылку в Ivideon Server пока нельзя.

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


                    Если в Москве, приходите к нам. У нас много чего дорабатывать есть. В том числе и задачи по улучшению работы ONVIF, подключение и поддержка на уровне аппаратных детекторов новых камер и т.д.

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое