Еще раз о видеонаблюдении, камерах, 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…

ну и обработка «всего» этого

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. Посмотреть — запускает Vlc
  2. Склеить и посмотреть — клеит файлы и запускает Vlc
  3. Выход

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

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

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

Что делает 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") 

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

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

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

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, видимо потому что они выпускают «свои» камеры.

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

Git hub:

ссылка на оригинал статьи http://habrahabr.ru/post/233237/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *