18 февраля 2014 в 20:12

Китайские видеокамеры и TCP: баг или фича?

В связи с узурпацией должности управдома нашего МКД, и необходимости «причесать» общественный беспорядок, потихоньку конструирую систему видеонаблюдения.
Разумеется, финансирование минимальное, планы — грандиозные, поэтому собирается всё из подножного корма.
Подробности чуть позже, а вот один из интересных багов, который заставил перебрать много чего.

Итак, началось всё с малого — простенькая купольная камера, бралась самая паршивая по принципу "только б IP и купол". После пошел процесс перебора софта (тема отдельного поста, будет, опять же, позже)… В конце зафиксировался на Macroscop в один канал. Стояла пару месяцев, есть-пить не просила, наркоманов гонять помогала.

И вот работает вроде всё, но как-то подлагивает периодически. Грешил на всё: на проц, на сеть, на софт, на погоду на марсе… Саппорт по логам говорит, что-де камера периодически отваливается.

Upd

Да были закуплены еще камеры, качеством и ценою значительно выше. Поборовши лень в три прекрасных дня в разные недели было это всё смонтировано на 1м этаже, заведено в комп, подключено. И пошла вторая итерация перебора софта, так чтоб на камер побольше, ценою гуманней, интерфейс удобней… После второго раунда перебора, пока стоит AxxonNext. Да вот только лаги вышли на новый уровень: камеры отваливаются чуть ли не синхронно каждые 5-10 минут если включить TCP; а если держать UDP, то артефакты лезут как не знаю что.

Пожаловался я в жуйке на эту тему, а maxlapshin возьми да и скажи, что это стабильный баг китайских камер, где прошивка создана их собственными силами: если клиент не успевает выгребать, то в поток лезет мусор. Так как продаван на ali прикормленный, было решено попытаться решить проблему капитально. Несколько раз разными словами пытался баг описать, но понял, что английский у нас обоих оставляет желать лучшего, поэтому надо просто «show code».

Первый этап раскопок: найти причину



Итак, сперва берём tcpdump, и ждём ситуацию обрыва. Ждать недолго, за 5 минут поймалось аж 3 штуки. Как понять что произошло? Поток в полтора миллиона пакетов… Для начала фильтруем, оставляя только одну камеру. Затем Ctrl+F => tcp.flags.syn==1 => находим начало реконнекта, откуда листаем вверх, чтобы понять что случилось…



Наблюдаем, что коннект был закрыт со стороны компа… Вот только смущает что перед этим Win=11460, Win=10200, Win=8940 — то есть, похоже, клиент и правда не успевает. Стоп-стоп-стоп. Как это не успевает? AMD Phenom II X6 1100T? Не, странно. IO? Так запись на отдельный диск, и не упёрлось в полку. Да и тогда была бы зависимость от просмотра, например — а её нет… В общем, листаем еще выше чуть-чуть:



Так буквально секунду назад вообще в ZeroWindow уходили. Точно не успевает. Но почему обрыв-то?! Листаем ниже, смотрим другой обрыв…



Так, наблюдаем ZeroWindow в течение аж 2.5 секунд — совсем ни в какие ворота не лезет. Листаем ниже, и, кажется, начинаем понимать:




Смотрим — совсем нехорошо себя повел TCP стек (камеры? вероятно...), после чего RTP поток сбился — в один прекрасный момент вместо DynamicRTP-Type-96 пошли RTSP Continuation.

Итак, делаем вывод: всё что нужно для симуляции, это запросить RTP поток, немного вытянуть, а потом сделать sleep(), и смотреть сломается ли поток.

Поиск частей для франкенштейна



Как поступить, когда надо быстро накидать тестовый кусок? Взять скриптовый язык, набор готовых библиотек, слепить всё это вместе, радоваться. Лезем в гугль. Python+ONVIF. Тухло. Ruby+ONVIF. О, есть ruby-onvif-client. ОК, берём. URL стрима поймали. Отлично, а если :protocol покрутить?.. UDP, HTTP, TCP… итог один — отлично, по onvif ловить URL научился, теперь его бы слить.

Curl? Не жуёт. Ладно, Ruby + RTSP… Ура, есть либа. Скушиваем ему урл, и облом. Пытается авторизоваться исключительно через Basic. Еще немного гугля. Облом. Тогда остаётся один метод — напильник. Впаиваем калёным железом, по пути матерясь на объёмы магии руби (кто мне скажет, как правильно сделать «честную» рекурсию, чтобы работал и return и yield? правильно, а как догадаться по описанию функции, что оно может потребоваться? правильно… проще избавиться от неё — и чище код, и шелковистее волосы).

И снова ликуем, rtsp_client работает. Открываем во втором окне tcpdump… Блин! Я же в ONVIF запрашивал :protocol=>«TCP». Что за черт, почему UDP?

Паяльник в руки… Ха! UDP прибит гвоздями в lib/rtsp/client.rb@request_transport. Так, впаиваем туда теми же гвоздями /TCP. Запускаем — падает. Почему? Куда? Ага, он требует наличия client_port… Какой client_port, если это TCP? Хардкодим rtp_port если его нет в 554. Так, IP надо сервера — хардкодим. Опа, не может, говорит, при'bind()'иться на 554 порт не рутом. Логично. Так, а зачем? Ну-ка… Это в RTP::Receiver… Смотрим на init_socket в режиме :TCP и удивляемся — а зачем ему TCPServer для Receiver? Что-то не то. Явно, вот явно на TCP никто не отлаживал.

После пары минут попыток понять логику, до Зоркого Глаза дошло, что стенки-то нет:
transport:      RTP/AVP/TCP;unicast;destination=172.28.1.199;source=172.28.1.95;interleaved=0-1


Ну-ка ну-ка… что за interleaved? Google: rtp interleaved => Wikipedia => Find on page «interleaved» => Ага! Так RTP и RTSP валятся в этом же самом коннекте!

Выкидываем rtsp либу, так как для моей задачи править либо откровенно не хочется — тут надо явно по-хорошему уже переделывать архитектуру.

Выращиваем амёбу



Итак, мы снова в нуле: у нас есть rtsp:// урл камеры, есть необходимость его слить и поиграться во время слива с коннектом… Стоп! Сначала. Есть урл. Есть камера. Есть задача воспроизвести на ней баг. Зачем мне ONVIF? Зачем RTSP либы? Надо просто запросить и качать ответ.

Сказано — сделано. DESCRIBE? А зачем он нам… Нам надо только сперва SETUP, откуда забрать Session: затем PLAY с ней.

Первый же опыт показал, что ему даже авторизация тут не требуется.
Отлично! Первый же блин выстрелил сразу: простнький скрипт прекрасно воспроизводил баг — после sleep()'а поток ломался на ура.

Но чтобы поиграться с TCP Window надо бы иметь возможность задать TCP_WINDOW_CLAMP. А для этого надо сделать setsockopt ДО connect но после создания сокета.
А как это в руби сделать? Эм… Заглядываем в гугль… Пусто. Заглядываем в исходник — init_inetsock_internal… фиг там! сперва создаём rsock_socket(), а затем сразу rsock_connect(). Блин.

Ладно, я всё равно руби не очень люблю. За 2 минуты переписываем на питон, добавляем setsockopt. Добавляем анализ номера RTP пакета.

Итог анализа: от размера начального окна меняется объём инфромации, сколько успевает камера передать нормального, прежде чем сломается. Впрочем, неважно, закоммитил итог, и написал багрепорт продавцу, дабы они передали девелоперам прошивки.

Так бы и успокоился, но maxlapshin спрашивал можно ли как-то жить с этим багом. И это потребовало дополнительных раскопок.

В принципе, как с этим жить?


1) Можно отслеживать сбой и пересоединяться самостоятельно
2) Можно отслеживать собственный лаг (не знаю как, но можно) и присоединиться сразу, не дожидаясь поломки потока
3) Можно попытаться восстановить синхронизацию с потоком.

Итак, втыкаем вместо вызова raise — вызов reconnect, и анализируем потери по разнице номеров RTP пакетов. Если действуем по пункту 1, то потери составляют около 1500-1800 RTP пакетов (примерно 600 пакетов на секунду sleep()).

Втыкаем reconnect сразу после sleep(). Итог в точности такой же.

Втыкаем ресинхронизацию методом поиска "$\x00" — работает отвратно. Втыкаем ресинхронизацию методом поиска "$\x00[LEN]{len bytes}$\x00" — работает стабильно, потеря составляет в полтора раза меньше, чем при reconnect. Но самое главное — TCP соединение при этом не падает, а значит, алгоритм адаптации TCP Window и буферов приема продолжают работать. Вследствие чего, спустя 1-2 сбоя в начале соединения, поток просто перестаёт ломаться — sleep() продолжают регулярно усыплять клиента, а поток не падает.

Полученный тест-скрипт качеством кода не блещет, но прекрасно выполняет функцию proof-of-concept.

И теперь, наконец-то, мы пришли к вопросу, заданному в заголовке.

Баг ли это, или фича?



Моё личное мнение — это реально лучше, чем рвать соединение целиком: номера RTP пакетов в коннекте есть, так что без проблем потерянный объём можно замерить.
Если канал тоньше, чем пропускная способность сети — то потери будут расти, таким образом, обнаружив потери больше чем на 5 сек — можно спокойно жаловаться на толщину канала (реконнектиться с меньшим качеством, попросить поменять кабель, взорвать АЭС, или любой другой способ реакции на эту проблему); если же проблема в том, что приёмник просто не успевает по какой-то причине выгребать — resync поведение = наше щасте. Получаем объединение плюсов одновременно UDP и TCP подходов: если совсем что-то плохо — кусочек потеряли; в остальных случаях — ретрансмиты автоматические и проблем не доставляют.

Ну и на закуску, поговорим о том, что за баг в прошивке… А баг прост: send(somedata, somelen) возвращает <0 в случае ошибки, число байт, если отработало. И любмая ошибка всех начинающих сетевых разработчиков: send(somedata, somelen) может вернуть что-то МЕНЬШЕ, чем somelen — в буфер не влезло.

Если это не обрабатывать, хвост от somedata просто теряется — его и не отправили, и выбросили.

Как правильно починить?



Починить так: надо запомнить недоотправленный кусок, и прекратить посылать что бы то ни было, пока send() этого остатка не отправит всё целиком. После этого надо начать посылать _следующие_ пакеты (выбросив те, которые были всё время, что буфер был занят). Тогда мы получим то самое поведение помеси TCP и UDP, но без необходимости клиенту заниматься магической ресинхронизацией с границами пакетов.

Надеюсь, производители поправят этот баг, и через некоторое время, все китайские прошивки для китайских камер будут радовать корректной работой без единого разрыва&tm;

Источник радости: LIVE555 юрского периода



Саппорт macroscop обратил моё глупое невнимание на тот факт, что LIVE555 анонсируется 2011 года… Так что через binwalk+dd+mount заглянул в потроха прошивки. rtsp_streamer и правда, от 2011 года.
Заглянул в diff между двумя версиями live.2013.10.09.tar.gz и live.2014.02.19.tar.gz:
diff --git a/liveMedia/RTPInterface.cpp b/liveMedia/RTPInterface.cpp
index d45e5a8..3d88d55 100644
--- a/liveMedia/RTPInterface.cpp
+++ b/liveMedia/RTPInterface.cpp
@@ -324,19 +325,23 @@ Boolean RTPInterface::sendRTPorRTCPPacketOverTCP(u_int8_t* packet, unsigned p
 }
 
 Boolean RTPInterface::sendDataOverTCP(int socketNum, u_int8_t const* data, unsigned dataSize, Bool
-  if (send(socketNum, (char const*)data, dataSize, 0/*flags*/) != (int)dataSize) {
-    // The TCP send() failed.
-
-    if (forceSendToSucceed && envir().getErrno() == EAGAIN) {
-      // The OS's TCP send buffer has filled up (because the stream's bitrate has exceeded the cap
+  int sendResult = send(socketNum, (char const*)data, dataSize, 0/*flags*/);
+  if (sendResult < (int)dataSize) {
+    // The TCP send() failed - at least partially.
+
+    unsigned numBytesSentSoFar = sendResult < 0 ? 0 : (unsigned)sendResult;
+    if (numBytesSentSoFar > 0 || (forceSendToSucceed && envir().getErrno() == EAGAIN)) {
+      // The OS's TCP send buffer has filled up (because the stream's bitrate has exceeded
+      // the capacity of the TCP connection!).
       // Force this data write to succeed, by blocking if necessary until it does:
+      unsigned numBytesRemainingToSend = dataSize - numBytesSentSoFar;
 #ifdef DEBUG_SEND
-      fprintf(stderr, "sendDataOverTCP: resending %d-byte send (blocking)\n", dataSize); fflush(st
+      fprintf(stderr, "sendDataOverTCP: resending %d-byte send (blocking)\n", numBytesRemainingToS
 #endif
       makeSocketBlocking(socketNum);
-      Boolean sendSuccess = send(socketNum, (char const*)data, dataSize, 0/*flags*/) == (int)dataS
+      sendResult = send(socketNum, (char const*)(&data[numBytesSentSoFar]), numBytesRemainingToSen
       makeSocketNonBlocking(socketNum);
-      return sendSuccess;
+      return sendResult == (int)numBytesRemainingToSend;
     }
     return False;
   }


А вот вырезка из changelog.txt:
2013.12.04:
- Updated the "sendDataOverTCP()" function (in "RTPInterface.cpp") to allow for the possibility of
one of the "send()" calls partially succeeding - i.e., writing some, but not all, of its data.
- Fixed a couple of minor bugs. (Thanks to "maksqwe1ukr.net".)

Выводы? Выводы слегка не цензурные. Причем как относительно производителя прошивки, так и относительно самого LIVE555 сервера.

К слову, производитель прошивки и модулей -- topsee; а используемый внутри линукс и обвязки от MontaVista. Написал всем участникам этой цепочки, жду надеюсь и верю.
Anton Fedorov @datacompboy
карма
116,0
рейтинг 0,1
Программист / сисадмин
Похожие публикации
Самое читаемое Разработка

Комментарии (77)

  • +3
    Спасибо, как раз искал в чем причина.
    Тоже долго думал в чем может быть проблема, искал узкое место и грешил на старые alight telesyn, кстати проблема не только на дешевых китайских камерах, была замечена даже на не которых d-link, хотя может их тоже можно отнести к оным)
    • +4
      очень похоже на то, что код фидера у них откуда-то из example тупо скопирован, а обработок ошибок не дописано.
      посмотрим, что ответят техники — прямого выхода на разработчиков у меня нет, мучаюсь через тройной прокси из продавца-его техников-техников дальше…

      а самое главное — я не считаю, что проблема в камере — проблема в софте. если выгребать всё из потока — сбоев нет.
      самое смешное — «родной» китайский софт обслуживания камер всё выкачивает без проблем.
      а крутые платные софтины — запинаются. но это тема отдельного поста, который в разработке.
      • +4
        Антон, есть ли у вас возможность проверить то же самое с Ivideon? Если проблемы точно такие же, то будем срочно исправлять. На всех китайских камерах, которые у нас есть — все работает. Мы сталкивались с вариантами вида: «Content-Length5323» и прочее.
        • 0
          Имеете ввиду запустить софт ваш, или найти вашу камеру?
        • 0
          Так, поставил Ivideon сервер, пытаюсьподцепить камеру.
          Выбираю «Другой производитель», ожидая что это и есть ONVIF подключение — фиг там. урл при этом /axis-media/ и, разумеется, не подключается.
          Просто ONVIF камер в списке не вижу.

          Выставил тип «Airlink101» — подцепило её.
          Начал добавлять вторую камеру — софтина молча сдохла. Сейчас еще раз пытался её добавить — стабильно помирает софтина при попытке просмотреть превью второй камеры. Вероятно дело в том, что на первой сейчас FullHD разрешение (1920*1080), а на второй 3MP (2048*1536).
          Вот при попытке посмотреть превью 3MP камеры — Ivideon сервер помирает.
        • 0
          Так, походу таки обрывается.
          Постоянно перезапускается проигрывание.
          Я оставлю пока запущенный сервер с одной камерой (с которой не падает)
          Сервер «G86P4», аккаунт datacompboy.
          Поглядите. Если надо какую еще информацию — предоставлю.
          • +1
            Спасибо большое! Насколько я понял, есть две проблемы.

            1. Та что вы описали в статье.
            2. Превью с камеры 3MP.

            Есть ли у вас возможность выслать логи нам на support@ivideon.ru. При этом сначала повторить 2-. проблему, а затем отключить камеру и дождаться первой. В теле письма можно просто дать ссылку на Хабр. Постараемся поправить.
            • 0
              письмо ушло.
              правда с крешем плохо — без inspectr.exe крешлог только адрес и код и больше ничего
              а с inspectr.exe, которым я обычно креши пишу на винде, он ругается на таймаут Debug Event при креше вашей софтины.
              но если очень надо — поставлю отладчик, сниму креш им.
            • 0
              Странно, что вы эту проблему не видели до этого. Она происходит абсолютно со всеми китайскими камерами, когда работаешь с ними по TCP.
              • 0
                Наш основной продукт это камеры, где наше ПО стоит на борту. Мы тщательно отлаживаем такие камеры, сообщаем о найденных проблемах их разработчику, иногда полностью переписываем весь софт на борту. Ivideon Server для Linux и Windows дает возможность подключить к нашему облаку существующие камеры. Но у нас нет возможности протестировать все имеющиеся на рынке камеры. Поэтому собираем и тщательно анализируем обратную связь от наших пользователей, чтобы сделать наш продукт лучше.
                • 0
                  вот я и говорю: странно, что вы с этой проблемой у китайчатины не встречались.
                  • 0
                    Мы не интегрировались с дешевым Китаем. Samsung и Microdigital это Корея. Axis это Швеция. Hikvision язык не поворачивается назвать китайщиной, так как это достаточно качественные и проработанные камеры.
                    • 0
                      Я бы тоже не сказал, что пользуюсь дешевым китаем. Совсем не дешевый китай. Можно было вполтора раза дешевле с теми же ТТХ найти…
            • 0
              Судя по поведения — баг#2 убрали, баг#1 еще на месте.
              • 0
                вру, баг#1 не совсем на месте — поток восстанавливается реально без реконнекта, судя по логу.
                хотя почему вообще идут побои потока мне по-прежнему непонятно…
            • 0
              как-либо у вас выключить ведение локального архива возможно?
              не по глазам мне, хочу проверить теорию, что отвал связан именно с записью на диск.
              • 0
                Да. Можно. Нажмите на кнопку «Настройки» или «Settings» и уберите галочку «Archive».

                • 0
                  ага, выключил. воспроизводится :(
                  скинул вам логи.
                  • 0
                    Посмотрели логи. Разрыв соединения связан с тем, что от камеры приходят совершенно «левые» данные с точки зрения видео и аудио. По крайней мере плеер не может их воспроизвести.
                    Вы написали выше, что штатные средства показывают отлично. А они точно работают по RTSP? Дело в том, что часто у китайцев RTSP есть для галочки и на самом деле толком не работает. А собственные приложения у них работают по собственному протоколу.
                    А у вас есть Скайп? Можно в личку.
                • 0
                  крешнулся сервер еще дважды. мне это начинает нравиться :D
      • +1
        Интересна ваша методика написания поста- Вы, похоже, пишите его прямо в процессе поиска бага.
        • 0
          Нет. Я просто восстановил события по памяти. Изначально пост писать не планировал. Я вообще мечтал что пожалуюсь на support@, и всё поправится. Ага. Щазз.
    • 0
      а можно модель D-Link'а?
  • +15
    Именно так выглядят управдомы в XXI веке.

    Гибсон? Нильсен? Винж? Не совсем, но явно потянуло киберпанком.
    • +6
      Админ дома.
      • +1
        Причем во всех трёх смыслах.
  • +13
    Короче, наркоман напрягся.
  • –1
    > взорвать АЭС

    Вы бы приписали, что «шутка», а то, знаете, есть такие «борцы с плохишами», вообще без чувства юмора которые… весь сайт которым не слабо заблочить за единую юморинку.
    • +18
      Настоящий тоталитаризм начинается не с нелигитимной власти или фальсификаций на выборах. Он начинается с самоцензуры, которую вы и предлагаете топикастеру.

      Так что все ок, АЭС взорвут только после распыления зарина в метро.
      • –10
        Теперь мне кажется, не только неулыбчивые «борцы с плохишами» не понимают шуток. В этом смысле интернет не только дает, но и отнимает: постоянно замечаю, что погруженные в Сеть люди юмора, если смайлик не проставлен, в упор не замечают.

        Ок, пусть будет АЭС, я не против, если после зарина-то. Смайлик.
  • +5
    После прочтения возник вопрос в пустоту: а есть ли такого рода сетевые камеры с открытым кодом? Можно, конечно, сочинить что-то на базе RPI, но хотелось бы что-то более компактное и экономичное.
    • 0
      ну как, с открытым кодом. если вы закупаете модули камеры (DSP точнее их), вам в нагрузку sdk с примером выпишут. дописывайте на здоровье в исходниках.

      А готовых open hardware тушек с open software прошивками — я не видел.
    • +1
      на данный момент в том виде, в каком вы хотите — нет, не существует. Даже болванки для камер продают с готовым стриминговым софтом, написанным плохо.
    • 0
      Тоже недавно задумался этим вопросом — хотел поставить камеру в подъезд. После осмотра доступных вариантов и достоинств/недостатков (Там поток не вытащишь, т.е. на андроиде нормально не посмотреть, там он рвется, там еще глюки какие-то) пришел к выводу, что мне возможно хватит 320x240 / 5 кадров с обычной веб-камеры, висящих на каком-либо Tp-Link TL-MR3020 и передающих через mjpeg streamer или что-то подобное. Доступнее для обычного пользователя (не оптового заказчика модулей и т.д.) судя по всему не найти :(

      Присоединяюсь к вопросу, если кто-то знает варианты получше
    • 0
      Дык! Правда, я ещё не нашёл как ONVIF сервер реализовать, пока только Виртурилка как обычная RTSP камера задействована, в HD разрешении. Если кто подскажет где посмотреть примеры ONVIF сервера (не клиента!) — киньте ссылочку, плиз. А то сервы есть, подключаются без проблем, хочу стандартный PTZ сбацать (пока без Z, правда).
    • +1
      • +1
        Ух лепота какая, спасибо за ссылочку! У нас на Виртурилке как раз проц из этого семейства, DM365.
        • 0
          сборка настоятельно рекомендуется в debian stable. (я на jessie, разгребаю проблемы, но мне это в кайф)
  • –1
    Узнал из статьи, что RTP может работать поверх TCP, и этим даже кто-то пользуется…
    • 0
      не все DVR работают под QNX, поэтому возможны лаги на записывающей стороне. RTP/TCP позволяет без проблем писать всё без потерь используя Windows в качестве OS сервера…
      • 0
        Хм. Узнал из вашего сообщения, что потери UDP-пакетов бывают не только в сети, но и на хосте-приемнике… А ведь мне предлагали по-быстрому свой сервер видеонаблюдения написать. Хорошо, что не согласился!
        • +2
          когда предлагают что-то «по-быстрому» написать, особенно «да там делать-то» — я сразу говорю нет, не вдаваясь в подробности.
          запасы подводных камней безграничны.
  • 0
    А баг прост: send(somedata, somelen) возвращает <0 в случае ошибки, число байт, если отработало. И любмая ошибка всех начинающих сетевых разработчиков: send(somedata, somelen) может вернуть что-то МЕНЬШЕ, чем somelen — в буфер не влезло.


    Насколько я помню, это назывется partial write, и вживую я его еще ни разу в жизни не видел. Подскажите старику, на каком сетевом стеке и с какими настройками сокета это можно воспроизвести?
    • 0
      с маленькими буферами и большим потоком данных. Небольшое колебание скорости прокачки данных и всё, приехали.
      • 0
        Я в свое время пытался подыграть на разных операционках, с разными вариантами. Например, send(мегабайт) на медленном канале и в какой-то момен времени сервер прекращал принммать. На блокирующих сокетах send() всегда блокировал до появления возможности отправить. На нелокирующих — всега либо брал на отслку весь буфер либо ничего. Вы лично видели как оно слеучается «с маленькими буферами и большим потоком данных» — или это предположение?
        • 0
          То есть send(мегабайт) вообще ничего не отправлял на сервер в неблокирующем режиме? Не верю.
          • 0
            Если window уползло в 0 и буфера забиты, то send(мегабайт) в неблокирущем режиме скажет WSAWOULDBLOCK на Windows и EWOULDBLOCK на OSX/*nix. Но я не священник, я инженер, вопросы веры в айти меня не очень интересуют. Мне очень интересно подыграть ситуацию с partial write — возможно, я не так и не там проверял.
            • 0
              Поставьте на сервере буфер приема в 4 килобайта, на клиенте — буфер передачи в 4 килобайта, и попробуйте отправить 12 килобайт одним вызовом.

              В неблокирующем режиме обязана произойти эта самая операция partial write, независимо от платформы и используемой библиотеки.
              • 0
                Поставьте на сервере буфер приема в 4 килобайта, на клиенте — буфер передачи в 4 килобайта, и попробуйте отправить 12 килобайт одним вызовом. В неблокирующем режиме обязана произойти эта самая операция partial write, независимо от платформы и используемой библиотеки.


                Почему обязана, если не секрет? Есть еще как минимум 64 килобайт окна, физический буфер сетевой карты, буфер драйвера сетевой карты, который неконтроллируем снаружи.
                • +1
                  Потому что перечисленные вами буферы — глобальные, одни на все сокеты. Информация, которая относится к конкретному сокету, в котором она может (потенциально) быть забыта сервером на полчаса, не будет храниться в этих буферах.
                  64х же килобайт окна просто не будет. Будут 4 килобайта окна приема на сервере, потому что окно приема — это и есть оставшееся место в приемном буфере.
                  • 0
                    В целом согласен. Увы, практика показывает обратно. Вот написанный за пару минут пример, который я запустил на доступных мне машинах. Везде отправило 12 килобайт :(. Где я ошибся?

                    import sys, socket, time
                    PORT = 1234
                    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                    s.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 4 * 1024)
                    s.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 4 * 1024)
                    if 'server' == sys.argv[1]:
                      s.bind(('', PORT))
                      s.listen(1)
                      conn, _ = s.accept()
                      print("connected")
                      time.sleep(-1)
                    if 'client' == sys.argv[1]:
                      s.connect((sys.argv[2], PORT))
                      s.setblocking(0)
                      ret = s.send('*' * 12 * 1024)
                      print("send() returned {0}".format(ret))
                    
                    • 0
                      Забавно. Похоже, что windows игнорирует размеры буферов и пытается выделить память динамически при вызове send.

                      А вот на linux все получилось сразу же:
                      send() returned 6144
                      • 0
                        Точную модель линукса, пожалуйста?
                        • 0
                          datacompboy@nuuzerpogodible:~/work/self/chinacambug$ uname -a
                          Linux nuuzerpogodible.river-net 3.11-2-amd64 #1 SMP Debian 3.11.8-1 (2013-11-13) x86_64 GNU/Linux
                          datacompboy@nuuzerpogodible:~/work/self/chinacambug$ cat /etc/*release
                          PRETTY_NAME=«Debian GNU/Linux jessie/sid»
                          NAME=«Debian GNU/Linux»
                          ID=debian
                          ANSI_COLOR=«1;31»
                          HOME_URL=«www.debian.org/»
                          SUPPORT_URL=«www.debian.org/support/»
                          BUG_REPORT_URL=«bugs.debian.org/»
                        • 0
                          debian stable со стандартными сетевыми настройками, разве что карточек сетевых многовато и используется как роутер

                          root@network-server:~# uname -a
                          Linux network-server 3.2.0-4-686-pae #1 SMP Debian 3.2.51-1 i686 GNU/Linux
                          root@network-server:~# cat /etc/*release
                          PRETTY_NAME="Debian GNU/Linux 7 (wheezy)"
                          NAME="Debian GNU/Linux"
                          VERSION_ID="7"
                          VERSION="7 (wheezy)"
                          ID=debian
                          ANSI_COLOR="1;31"
                          HOME_URL="http://www.debian.org/"
                          SUPPORT_URL="http://www.debian.org/support/"
                          BUG_REPORT_URL="http://bugs.debian.org/"
                          
                    • 0
                      чуть-чуть модифицируем:
                      if 'server' == sys.argv[1]:
                        s.bind(('', PORT))
                        s.listen(1)
                        conn, _ = s.accept()
                        print("connected")
                        time.sleep(100)
                      if 'client' == sys.argv[1]:
                        s.connect((sys.argv[2], PORT))
                        s.setblocking(0)
                        ret = 1707
                        while True:
                          ret = s.send('*' * 1707)
                          print ret
                      


                      И запускаем на одной машине:
                      datacompboy@nuuzerpogodible:~/work/self/chinacambug$ python tester.py client localhost
                      1707
                      1707
                      1707
                      1707
                      682
                      Traceback (most recent call last):
                        File "tester.py", line 17, in <module>
                          ret = s.send('*' * 1707)
                      socket.error: [Errno 11] Resource temporarily unavailable
                      
                      • 0
                        Опять же — на чем запускали?
                        • 0
                          ответил выше
                      • 0
                        Есть контакт, подобрал линукс, работает. Большое спасибо! Наконец-то я это увидел :).
        • 0
          я постараюсь PoC сервера накидать сегодня как время будет.
          • 0
            Буду бесконечно признателен. PoC это…?
            • +1
              Proof of Concept, наверное.
  • 0
    Кстати, а есть такие купольные камеры с приводом, чтобы можно было удаленно управлять?
    • 0
      Да. За PTZ примерно +100$ наценка
      • 0
        Странно, я покупал VGA камеру с PTZ всего за 50 баксов.
        Например Wanscam работает уже больше года у меня.
        • 0
          Возможно, зависит от типа камеры, и условий использования.
          Я смотрел на купольные компакты — там разница порядка 100$ была.
          В принципе, сейчас разница от 30$ до 50$ составляет
  • 0
    А все-таки, почему в приемнике заканчивается буфер, софтина из него читает настолько редко?
    Можно увеличить размер буфера на приемнике.
    Если же вы вообще не успеваете обрабатывать поступающие данные, придется часть их отбрасывать: в tcp это сделать будет сложно.
    Какой packet loss до камер, как у вас сделана сеть? может стоит настроить QoS?
    Каждый выпавший из потока пакет замораживает последующие данные в буфере, поэтому приемный буфер нужно настроить больше чем в величину емкости канала.
    • 0
      > А все-таки, почему в приемнике заканчивается буфер, софтина из него читает настолько редко?
      Вот это самый главный вопрос, ответ на который, похоже, искать только по запаху — я пока найти не могу, но занят этим.
      саппорт софтин на эти вопросы реагируют только по принципу «наша хата с краю»

      > Если же вы вообще не успеваете обрабатывать поступающие данные, придется часть их отбрасывать:
      > в tcp это сделать будет сложно.
      Не вижу никаких сложностей — RTP пакеты идут весьма помеченными, и часть из них вполне можно выкидывать.

      > Какой packet loss до камер,
      0%

      > как у вас сделана сеть?
      Кинуты самые обычные UTP5 кабеля. Все свичи гигабит.

      > может стоит настроить QoS?
      свичи простые, но по сети кроме камер никто не бегает; так что QoS тут ни при чем.
      • 0
        почему же большие потери в UDP и Out-of-order сегменты в дампе, если нет потерь?
        • 0
          А потери не в UDP, а в обработке UDP…

          А вот происхождение Out-of-order мне тоже интересно — подозреваю, что TCP/IP стек в камере себя как-то нехорошо чувствует.
  • +1
    Антон,

    Мы хотели бы обратить внимание на то, что программное обеспечение, идущее в комплекте с камерой, в большинстве случаев подключается не по стандарту RTSP, а по протоколу собственной разработки, надстроенному над протоколом TCP. Вы можете убедиться в этом, используя сниффер. Это объясняет тот факт, что вы не замечали никаких проблем в «родной» программе. К сожалению, поддержка RTSP у китайских производителей оставляет желать лучшего и сделана, чтобы соответствовать известным стандартам ONVIF и PSIA. Из-за этого часто возникают проблемы с обрывами, причем причины могут быть самые разные.

    Мы рекомендуем вам сделать следующее:

    1) Изменить путь подключения к камере: :554/user=admin&password=&channel=1&stream=0.sdp?

    Здесь специально опущен параметр --rtp-caching=100, который, по нашему предположению, отвечает за буферизацию rtp пакетов со стороны камеры. Чем меньше этот параметр, тем меньше будет отставать видео от реального времени, но тем более стабильным должно быть сетевое соединение. Можно попробовать этот параметр оставить, но сделать его в 5-10 раз больше.

    2) Запросить последнюю прошивку у поставщика, иногда это сразу же решает проблему с обрывами.

    Нам также хотелось бы узнать, как часто происходят обрывы с данной камерой? Есть ли закономерность во времени?
    • 0
      Добрый день!

      Все камеры, которые сейчас у меня стоят, все работают именно по RTSP в браузере — они ставят свой плагин (что доставляет неудобств в настройке). Wireshark'ом я снимал трейс.

      У той камеры, которой я ел вам мозг изначально, rtp-caching был выкинут первым делом, она подключена без него, именно для отсутствия отставания видео. (Что, впрочем, некоторым другим софтинам не мешало показывать видео с задержкой в 2-3 секунды, что как по мне, так не приемлемо для системы видеонаблюдения).

      Все прошивки обновлены до последних — у одного типа камер от января прошивка, у другого от декабря.

      Сейчас Macroscop выключен, да и когда включен был — визуально он никак не сообщал об обрыве.
      Активно сейчас используется софт AxxonNext — в нем эта камера практически не отваливается.

      Остальные 4 камеры имеют привычку отваливаться зачастую синхронно, но не всегда — иногда отваливаются не все, иногда только одна.
      Частота отвала — от 3 минут до часа. Закономерности не нашел.

      В принципе, мне удалось поймать свал без sleep() воткнув запись на диск f.write(rtppack) в том же потоке, вызывая write для каждого RTP пакета.
      причем я поймал этот свал на ноутбуке, где пишу на SSD (так что жаловаться на нехватку IO нельзя).
      Если включить буферизацию и писать на диск блоками по 128k — отвалов нет.

      Я в задумчивости.
    • 0
      Кстати, хочу сказать спасибо за вашу поддержку ONVIF: все камеры спокойно цепляются по ONVIF, причем цепляются оба потока — и основной и низкого разрешения.
      У AxxonNext ловится только один (причем не всегда большой); ONVIF с поддержкой двух потоков просто не работает; и на всё один ответ — «эти камеры официально не поддерживаются» :D
    • 0
      Спасибо про вопрос про закономерность по времени. Запустил свой скрипт на чисто слив RTSP, отключив все какие бы то ни было задержки и задрав буфера. Соединение закрывается со стороны камеры спустя ровно 180 секунд.
      Вот этого я никак понять уже не могу… Необходимость OPTIONS периодически нужна или нет? Надо еще тестировать-с.
      • 0
        добавил периодический пинг в канале через GET_PARAMETER. 3хминутного обрыва нет, так что отбой тревоги.
        еще раз посмотрел на архив записей — абсолютно никаких тенденций по времени.
        то рвётся через полторы минуты, то через 7 минут.

        и очень часто синхронно на нескольких камерах — но это как раз легко объясняется каким-либо лагом компа.
  • 0
    Это незадокументированная фича
    • 0
      недореализованная фича… надо всего лишь допочинить как я в конце написал — и станет очень няшной фичей.

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