Программист / сисадмин
0,1
рейтинг
16 апреля 2014 в 09:06

Разработка → Что нам стоит пофиксить баг, которого «нет»

Итак, у нас есть задача: пофиксить баг, производитель от которого открещивается, клиенты не замечают, а жить хочется. Есть камера, поток от неё на UDP просто адово ломается, поток на TCP работает, но постоянно рвутся коннекты (и при каждом обрыве пропадает 3-5 сек видео). Виновны в проблеме все (и камера и софт), но обе стороны утверждают что у них всё зашибись, то есть ситуация обычная: ты баг видишь? нет. А он есть.

Так как софт обновляется гораздо чаще, чем камера, имеет смысл править то место, которое потом не придётся трогать. Значит, будем фиксить со стороны камеры.

Исследование плацдарма


Перво-наперво берём свежайшую прошивку (в моём случае — firmware_TS38ABFG031-ONVIF-P2P-V2.5.0.6_20140126120110.bin), и выясняем что же она такое:
$ file firmware_TS38ABFG006-ONVIF-P2P-V2.5.0.6_20140126120110.bin
firmware_TS38ABFG006-ONVIF-P2P-V2.5.0.6_20140126120110.bin: data

$ du -b firmware_TS38ABFG006-ONVIF-P2P-V2.5.0.6_20140126120110.bin
15222724	firmware_TS38ABFG006-ONVIF-P2P-V2.5.0.6_20140126120110.bin

$ xxd firmware_TS38ABFG006-ONVIF-P2P-V2.5.0.6_20140126120110.bin | head
0000000: 4649 524d 5741 5245 6481 db15 c447 e800  FIRMWAREd....G..
0000010: 0300 0000 1406 0000 b0f1 1b00 4c21 815d  ............L!.]
0000020: 5453 3338 4f45 4d41 4246 475f 4c49 4e55  TS38OEMABFG_LINU
0000030: 5800 0000 0000 0000 0000 0000 0000 0000  X...............
0000040: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000050: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000060: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000070: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000080: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000090: 0000 0000 0000 0000 0000 0000 0000 0000  ................

$ binwalk firmware_TS38ABFG006-ONVIF-P2P-V2.5.0.6_20140126120110.bin
DECIMAL   	HEX       	DESCRIPTION
-------------------------------------------------------------------------------------------------------
1556      	0x614     	uImage header, header size: 64 bytes, header CRC: 0xB21E2C9F, created: Sun Sep 22 11:07:02 2013, image size: 1831280 bytes, Data Address: 0x80008000, Entry Point: 0x80008000, data CRC: 0x1F4EFBAB, OS: Linux, CPU: ARM, image type: OS Kernel Image, compression type: none, image name: "Linux-2.6.18_pro500-davinci_IPNC"
14468     	0x3884    	gzip compressed data, from Unix, last modified: Sun Sep 22 11:07:02 2013, max compression
1832900   	0x1BF7C4  	CramFS filesystem, little endian size 13389824 version #2 sorted_dirs CRC 0xc832a8c3, edition 0, 7334 blocks, 2607 files


Итак, формат его неизвестен, стартовая метка «FIRMWARE» навевает мысли о том, что это что-то своё, наличие внутри uImage ядра и cramfs файлухи подсказывает, что по факту это что-то простое. Наличие строки TS38OEMABFG_LINUX подскзывает что это что-то даже напоминает какой-то вариант архива.

Так как нам сейчас надо просто найти где искать — просто вытаскиваем оттуда файловую систему, и ищем виновный модуль:
$ dd if=firmware_TS38ABFG006-ONVIF-P2P-V2.5.0.6_20140126120110.bin bs=1832900 skip=1 of=cramfs
7+1 записей получено
7+1 записей отправлено
 скопировано 13389824 байта (13 MB), 0,161755 c, 82,8 MB/c
$ fakeroot cramfsck -x fs cramfs
$ grep LIVE555 -R fs/
Двоичный файл fs/opt/topsee/rtsp_streamer совпадает
$ strings fs/opt/topsee/rtsp_streamer | grep TCP
sendRTPOverTCP
12RTCPInstance
sendRTPOverTCP failed, sock: %d, chn: %d
 is not a RTCP instance
RTCPInstance::RTCPInstance error: totSessionBW parameter should not be zero!
RTP/AVP/TCP
%sTransport: RTP/AVP/TCP;unicast;destination=%s;source=%s;interleaved=%d-%d
/TCP;unicast
Failed to create RTCP socket (port %d)
MediaSession::initiate(): unable to create RTP and RTCP sockets
Failed to create RTCP instance
Received RTCP "BYE" on "
18RTCPMemberDatabase


Хохохо! «sendRTPOverTCP failed, sock: %d, chn: %d» говорит нам о том, что код оттранслирован с отладочными принтами, а значит, объём работы снижен на порядки!

Итак, у нас есть модуль, содержащий искомую ошибку, модуль с кучей отладочных строк внутри, а значит процесс раздербанивания значительно упрощается.

Локализация и фикс проблемы


Грузим модуль в дизассемблер, ищем по «OverTCP» строку отладочную, от неё ищем код вызывающий распечатку => мы нашли функцию sendRTPOverTCP.
Проглядывая её бегло видим два вызова функции send() — одна с 4 байтами, одна с буфером переданным на вход. Значит, нам досталась версия не самая старая, а когда уже объеденили буфер, но еще не сделали функции sendDataOverTCP (подробности о различиях реализации — в прошлом< посте.

Теперь возникает вопрос, как можно поправить баг в бинарном виде, когда практически нет запаса по месту (пустого пространства внутри файла нет).
Идём в функцию выше, которая вызывает sendDataOverTCP — sendPacket. Её код от версии к версии не менялся, он по сути один — foreach(streams) { sendDataOverTCP(packet, stream) }.

По счастью, код был щедро напичкан отладочными fprintf'ами, и это нас спасает! Вот как этот цикл выглядит в бинарном виде:
loop_next_5FAE4:
                 LDR     R4, [R4,#4]
                 CMP     R4, #0
                 BEQ     loc_5FB68

loop_body_5FAF0:
                 MOV     R3, R4
                 MOV     R1, R5
                 MOV     R2, R7
                 MOV     R0, R6
                 BL      sendRTPOverTCP
                 CMP     R0, #0
                 BGE     loop_next_5FAE4
                 MOV     R1, #0
                 LDR     R2, =aS_10      ; "%s():  "
                 LDR     R3, =aSendpacket ; "sendPacket"
                 MOV     R0, #STDERR_FILENO
                 BL      fprintf_0
                 LDRB    R12, [R4,#0xC]
                 LDR     R3, [R4,#8]
                 MOV     R1, #7
                 LDR     R2, =aSendrtpovert_0 ; "sendRTPOverTCP failed, sock: %d, chn: %"...
                 MOV     R0, #STDERR_FILENO
                 STR     R12, [SP,#0x350+var_350]
                 BL      fprintf_0
		 ......

Это фактически спасение! Просто вырезка отладочного куска даёт нам место в размере 12 инструкций (у ARM все инструкции ровно по 4 байта, и это очень хорошо).

Итак, у нас есть место в 12 инструкций, чтобы что-то сделать для улучшения ситуации. Но что? Полноценно впихнуть сюда код sendDataOverTCP из последней версии будет сильно затруднительно…
Хотя стоп. А зачем? Я, вроде, подробно описал, что даже использование корректной формы sendDataOverTCP всё равно плохо… А если не видно разницы — почему бы просто не обернуть вызов в makeSocketBlocking()..makeSocketNonBlocking()?

Действительно, если место в системном буфере есть — send() выполнится моментально. Если места нет — то и их реализация sendDataOverTCP всё равно залипнет (почему залипнет а не вывалится сразу с нулём — смотри предыдущий пост).

Отлично! Путем быстрой отмотки назад от функции fcntl находим makeSocketBlocking и makeSocketNonBlocking, после чего рисуем какой должен получиться код:
loop_next_5FAE4:
                 LDR     R4, [R4,#4]
                 CMP     R4, #0
                 BEQ     loc_5FB68
loop_body_5FAF0:
                 ; Сперва сделаем сокет блокирующим
                 LDR     R0, [R4,#8]
                 BL      makeSocketBlocking
                 ; Затем остаётся прежний вызов отправки
                 MOV     R3, R4
                 MOV     R1, R5
                 MOV     R2, R7
                 MOV     R0, R6
                 BL      sendRTPOverTCP
                 ; Сохраним результат вызова функции
                 STMFD   SP!, {R0}
                 ; Сделаем сокет снова неблокирующим
                 LDR     R0, [R4,#8]
                 BL      makeSocketNonBlocking
                 ; Восстановим результат вызова функции отправки
                 LDMFD   SP!, {R0}
                 ; Зацикливание остаётся как и раньше
                 CMP     R0, #0
                 BGE     loc_5FAE4
                 ; Ну и занопливаем остатки отладочной распечатки
                 NOP
                 NOP
                 NOP
                 NOP
                 NOP
                 NOP

Для того чтобы запатчить, либо открываем документацию на арм и транслируем в уме, либо пишем код в отдельном файле и транслируем его (не забываем ORG'ами выставлять точные адреса, чтобы все переходы (BL/BGE/ИТД) пересчитаны были корректно), а я всего лишь находил подходящие инстуркции в коде и на их основе вычислял нужные опкоды (первый раз редактирую ARM, уж извините).

В результате получаем rtsp_streamer с наложенным на него патчем, защищающим TCP поток от порчи.

Пайка взрывом, сборка трезвым


Итак, у нас есть новый rtsp_streamer, и есть firmware...bin в который его надо встроить. Ну тут, вроде, всё просто: надо распаковать cramfs, заменить файл, запаковать обратно, заменить его внутри bin'а:
$ fakeroot -s .fakeroot cramfsck -x repack cramfs
$ cp rtsp_streamer repack/opt/topsee/
$ fakeroot -i .fakeroot mkcramfs repack newcramfs
$ dd if=firmware_TS38ABFG006-ONVIF-P2P-V2.5.0.6_20140126120110.bin of=firmware_new.bin bs=1832900 count=1
$ cat newcramfs >> firmware_new.bin

Заливаем полученный firmware_new.bin в камеру… 0 эффекта. Камера съедает прошивку, но ничего не происходит. Неприятно. Значит, надо разобраться в формате этого .bin'а.

Откроем его в hex редакторе, и начнём кумекать:
(0) «FIRMWARE» — 100% заголовок, 8 байт.
(8) 64 81 DB 15 — 4 байта, назначение непонятно. по запаху — контрольная сумма
(12) 0x00E847C4=15222724 — ага, 4 байта, размер прошивки. проверяем _new.bin — нет, размер не изменился, значит, он не при чем.
(16) 0x00000003 — 4 байта хз что. версия заголовка может?
(20) 0x00000614=1556 — так, а это смещение до ядра внутри
(24) 0x001BF1B0=1831344 — а это размер ядра (1831344+1556=1832900)
(28) 4C 21 81 5D — хм. опять что-то на контрольную сумму похожее.
(32) «TS38OEMABFG_LINUX» и куча нулей потом — 100h байт, явно место под название раздела
(288) 0x001BF7C4=1832900 — ага, смещение до следующей секции
(292) 0x00CC5000=13389824 — ага, размер секции
(296) «TS38OEMABFG_V2.5.0.6» и куча нулей — опачки. 100h байт, явно под название раздела.
Но контрольной суммы перед ней нет O_O
(552-1556) — нечто неизвестной наружности.

Итак, примерно суть ясна. Размер нашей cramfs не изменился, значит, это не размеры, а значит, контрольные суммы.
Извлекаем ядро, и считаем его контрольную сумму длиной 4 байта:
$ dd if=firmware_TS38ABFG006-ONVIF-P2P-V2.5.0.6_20140126120110.bin of=kernel bs=1 skip=1556 count=1831344
$ crc32 kernel 
5d81214c


Ну-ка ну-ка… по смещению 28 как раз 0x5D81214C. Повезло — это стандартная CRC32. Повезло потому, что по стандартная тулза умеет только его считать. Иначе пришлось бы запускать питон и считать уже «сложнее» :)

Итак, контрольные суммы у нас — crc32. А какая контрольная сумма у оригинальной cramfs?.. 37499eef. Так-так-так. По смещению 552 как раз и записано 0x37499eef. Значит, для файловой системы почему-то контрольная сумма ПОСЛЕ имени раздела записана. Ну ОК, чего нам, мы не гордые. Обновляем табличку:
(28) 0x5D81214C — crc32 раздела ядра
(552) 0x37499eef — crc32 раздела FS
(556-1556) — нечто неизвестной наружности

Пересчитываем crc32 newcramfs, hex редактором вписываем по смещению 552 его в бинарь, заливаем в камеру.
И… ничего O_O. Значит, чутьё не подвело — по смещению 8 действительно crc32, но от чего?
Тут действуем просто — начинаем брутфорсить.
$ python
>>> from zlib import crc32
>>> d=open("firmware_TS38ABFG006-ONVIF-P2P-V2.5.0.6_20140126120110.bin", "r").read()
>>> hex(crc32(d[12:]))
'-0x781aca29' # нет
>>> hex(crc32(d[12:1556]))
'-0x6f8f1744' # нет
>>> hex(crc32(d[0:8]+d[12:1556]))
'0x6d29f056' # нет
>>> hex(crc32(d[0:8]+d[12:]))
'-0x652ac4fd' # нет
>>> hex(crc32(d[0:8]+"\0\0\0\0"+d[12:1556]))
'0x15db8164' # Опа! Оно!


Отлично, быстро справились. Значит, обновляем табличку:
(8) 0x15DB8164 — CRC32 заголовка (первых 1556 байт), сперва занулив это поле

Итак, тут же в питоне быстро пересчитываем crc32 от заголовка firmware_new.bin и вписываем в hex редакторе его в начало.
Заливаем в камеру… Она уходит в ребут. И не отвечает… не отвечает… еще кирпич… О! Пинги пошли! Фууух.

Берём cam-resync.py, и опять тыкаем палочкой нашу камеру. И… и поток не ломается! Вот прямо вот так, с первой попытки! Уииии :)

На хлеб мажется, и есть уже можно, но что-то не то


Ранее упомянутый Андрей Сёмочкин тем временем собрал свою прошивку с исправленным мною rtsp_streamer'ом и залил её на одну из проблемных камер, с которых шло много разрывов. В результате тестирование показало, что поток не ломается, однако начались артефакты видео, такие же, как при потере пакетов на UDP. Так как я ничего не встраивал такого, стало любопытно, что же это было — пришлось еще раз заглянуть в код. Для начала, заглядываем в strings, у нас же куча отладочных строк. «checkBufferTimeout for %d seconds!!!», «buffered data more than %d ms, drop all the buffered data!!!».

Ага! Оказывается, производители сделали защиту от переполнения! И если по какой-то причине пока синхронный send() завис на больше чем надо (по дефолту — 1 сек), он излишки дропает. Это защищает от OOM и от отставания видео, если оно не влезает в тонкий канал. Но код раньше явно не работал из-за использования неблокирующих сокетов и send()'а.

После оборачивания в Blocking...NonBlocking — код начал работать :)
Однако есть небольшая проблемка: 1 сек это мало. Если канал начинает сбоить, однако достаточно толстый, то вероятность дропа становится всё сильнее. После любого такого дропа видео восстанавливается только после кейфрейма. Обычно кейфрейм достаточно редко (раз в 5-10 сек)… И получается неприятная ситуация — если был сбой, то 5-10 сек надо ждать до следующего кейфрейма чтоб видео починилось. Если сократить частоту кейфрейма — это автоматически увеличивает объём передаваемых данных, так как кейфреймы довольно толстые, а значит увеличивает частоту помирания канала. Замкнутый круг.

В общем, я дополнительно увеличил таймаут буферизации до 10сек — этого должно быть недостаточно чтобы словить OOM, но достаточно чтобы спокойно переживать ретрансмиты и лаги на не суперстабильных каналах.

Кстати, «хитрый» алгоритм лечения


Я в прошлой статье пообещал рассказать, как же легко починить ситуацию. Решение просто как валенок — так как мы отправляем RTP пакеты, у нас в каждом есть timestamp. Достаточно в sendRTPorRTCPPacketOverTCP проверять ДО отправки срок жизни пакета, и если он меньше настроенного (я всё-таки считаю что 1 сек это мало на TCP, надо именно 6-10 сек) то отправлять, иначе просто молча не отправлять его.

Автоматизируем сборку-разборку


Осталось дело за малым: автоматизировать сборку-разборку прошивки.

unpack.sh
Скрытый текст
fw=${1?Please give firmware bin as argument}
if [ -e $fw.unpack ]; then
    echo "Already exists: $fw.unpack"
    exit 1
fi
# Check format
if [ "$(dd if=$1 bs=8 count=1 2>/dev/null)" != "FIRMWARE" ]; then
    echo "Wrong file"
    exit 1
fi
mkdir -p $fw.unpack
echo "Extract header..."
dd if=$1 of=$fw.unpack/00header bs=1556 count=1 2>/dev/null
echo "Extract kernel..."
ksize=$(dd if=$1 bs=1 count=4 skip=24 2>/dev/null | perl -e 'print unpack("l", <>);')
dd if=$1 of=$fw.unpack/01kernel bs=1 skip=1556 count=$ksize 2>/dev/null
echo "Extract filesystem..."
foff=$(dd if=$1 bs=1 count=4 skip=288 2>/dev/null | perl -e 'print unpack("l", <>);')
dd if=$1 of=$fw.unpack/02cramfs bs=$foff skip=1 2>/dev/null
echo "Unpack filesystem..."
cd $fw.unpack
fakeroot -s .fakeroot cramfsck -x root 02cramfs
chmod +r -R root/
echo "Done"

Так как нам неизвестно занчение куска заголовка, собирать произвольные мы не можем, чтоб не убивать камеру, поэтому все куски сохраняем как есть.
Так как у нас внутри прошивки лежат блочные и прочие устройства, работать с ней от простого пользователя не получается. Но тут на помощь приходит fakeroot, который умеет сохранять состояние во внешний файл. Поэтому распаковываем используя fakeroot.
Однако с ним есть микропроблема — файл в итоге должен быть доступен на чтение текущему пользователю. Если вы «настоящий» рут, то вы спокойно можете читать файл, даже если он «chmod -r». А вот fakeroot ломается на таком файле. Поэтому сразу после распаковки, я меняю права чтения для всех файлов. НО правильные права доступа сохранены в дампе состояния fakeroot'а, поэтому обратная сборка проходит на ура.

В остальном распаковка не имеет никаких интересных моментов.

pack.sh
Скрытый текст
dir=${1?Please give path to a directory with unpacked firmware}
nfw=${2?Please give name for a newly packed firmware}
if [ ! -e $dir ]; then
    echo "Directory not exists: $dir"
    exit 1
fi
if [ -e $nfw ]; then
    echo "Firmware already exists: $nfw"
    exit 1
fi
# repack cramfs
if [ ! -e $dir/02cramfs.bak ]; then
    mv $dir/02cramfs $dir/02cramfs.bak 2>/dev/null
fi
fakeroot -i $dir/.fakeroot mkcramfs $dir/root/ $dir/02cramfs
# construct new firmware
dd if=$dir/00header bs=1556 of=$nfw conv=notrunc 2>/dev/null
# remove old header crc32
dd if=/dev/zero bs=1 seek=8 count=4 of=$nfw conv=notrunc 2>/dev/null
# save kernel size
if [ $(stat -c %s $dir/01kernel) -ge 2097152 ]; then
    echo "WARN: size of kernel is more than 0x200000. FW probably will not flash"
fi
perl -e 'print pack("l", -s "'$dir/01kernel'")' | dd bs=1 seek=24 count=4 of=$nfw conv=notrunc 2>/dev/null
# save kernel crc32
crc32 $dir/01kernel | perl -e 'print pack("l", oct("0x".<>));' | dd bs=1 seek=28 count=4 of=$nfw conv=notrunc 2>/dev/null
# save fs offset
perl -e 'print pack("l", 1556+(-s "'$dir/01kernel'"))' | dd bs=1 seek=288 count=4 of=$nfw conv=notrunc 2>/dev/null
# save fs size
if [ $(stat -c %s $dir/02cramfs) -lt 8388608 ]; then
    echo "WARN: size of filesystem is less than 0x800000. FW probably will not flash"
fi
if [ $(stat -c %s $dir/02cramfs) -ge 15728640 ]; then
    echo "WARN: size of filesystem is more than 0xF00000. FW probably will not flash"
fi
perl -e 'print pack("l", -s "'$dir/02cramfs'")' | dd bs=1 seek=292 count=4 of=$nfw conv=notrunc 2>/dev/null
# save fs crc32
crc32 $dir/02cramfs | perl -e 'print pack("l", oct("0x".<>));' | dd bs=1 seek=552 count=4 of=$nfw conv=notrunc 2>/dev/null
# save full FW size
perl -e 'print pack("l", 1556+(-s "'$dir/02cramfs'")+(-s "'$dir/01kernel'"))' | dd bs=1 seek=12 count=4 of=$nfw conv=notrunc 2>/dev/null
# Update header crc32
crc32 $nfw | perl -e 'print pack("l", oct("0x".<>));' | dd bs=1 seek=8 count=4 of=$nfw conv=notrunc 2>/dev/null
# concat rest
cat $dir/01kernel >> $nfw
cat $dir/02cramfs >> $nfw
echo "Done"

А вот упаковка уже чуток сложнее. Нам надо запаковать обратно cramfs, обновить в заголовке длины отдельных файлов и общую длину; пересчитать и контрольные суммы, включая заголовок и только после этого слить всё вместе.
Вообще, камера проверяет граничные значения сама, однако для удобства я добавил проверку на границы размеров файловой системы и ядра, чтобы если при сборке её размеры выползают за пределы, получить предупреждение и поудалять лишние хвосты из прошивки.

Результат трудов


Итак, я собрал следующие исправленные прошивки последней версии 2.5.0.6:
  1. firmware_TS38ABFG006-ONVIF-P2P-V2.5.0.6_20140126120110-TCPFIX.bin
  2. firmware_TS38CD-ONVIF-P2P-V2.5.0.6_20140126121011-TCPFIX.bin
  3. firmware_TS38HI-ONVIF-P2P-V2.5.0.6_20140126121444-TCPFIX.bin
  4. firmware_TS38LM-ONVIF-P2P-V2.5.0.6_20140126121913-TCPFIX.bin
  5. firmware_HI3518C-V4-ONVIF-V2.5.0.6_20140126124339-TCPFIX.bin


Если вдруг вам потребуется фикс на какой-либо другой модуль этого же производителя — пишите в комментах, буду посмотреть по возможности.

p.s.: всем, кому что-то еще надо от этих прошивок, выложил скрипты в более удобное место — на гитхаб.
Anton Fedorov @datacompboy
карма
113,0
рейтинг 0,1
Программист / сисадмин
Реклама помогает поддерживать и развивать наши сервисы

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

Самое читаемое Разработка

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

  • +1
    Спасибо! :)
  • +9
    Круто, но это нужно иметь такой хороший опыт в дебаге прошивок, чтобы угадывать хеш-суммы по виду и смещения.
    Особенно, момент когда оказалось, что хеш сумма идет после раздела фс.
    Интересно, сколько времени потратил автор на эту работу.
    • +4
      просто не малый опыт реверса и низкоуровневого программирования. + немного психологии программирования.
      суммарно убито чистыми около 10 часов.
      • 0
        А как камера себя поведет, если она вещает поток 2мбит/с, и подключено 2 клиента: у одного канал, предположим, 10мбит/с, а у другого — 1 мбит/с. Правильно ли я понимаю, что у 10мбит/с клиента будут невероятные лаги?
        Как вообще правильно должна себя вести камера в таких случаях? Может знаете, как ведут себя в таком случае серверы-ретрансляторы?
        • +1
          При текущем решении — да, 10мбит будет лагать так же как и мегабитный клиент. По этой причине я бы не стал подцепляться к камере напрямик мобильным клиентом — только через сервера.
          Правильно должна камера дропать на мегабитном потоке, и отдавать всё 10мегабитному.
          Для этого надо держать для каждого клиента «aghtung» буфер размером в 1 кадр.
          Все отправки — только неблокирующие.
          Если ушло не всё — остаток копируется в ахтунг буфер, и при следующей отправке отправляется не кадр, а досылается ахтунг.
          Если ахтунг пустой — отсылаем данные.

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

          Правильные ретрансляторы ведут себя правильно :) erlyvideo себя правильно ведёт.
          • 0
            Я все же не понимаю, что именно она должна дропать. Расскажите, пожалуйста, чуть больше о процессе энкода. Камера использует H.264 Main Profile, или Baseline? Если Main, то использует ли B-фреймы? Если да, то как много.
            В общем, мой вопрос такой: какие именно мы можем дропать кадры, чтобы картинка хотя бы не полностью развалилась?
            • 0
              Я, наверное, дурак. Никто ж не будет B-фреймы в камерах использовать.
              Но, в любом случае, нам нельзя дропать I-фрейм. Так что камера должна знать тип кадра.
              • 0
                Можно дропать всё. Но лучше, конечно, ключевые кадры не дропать, если только в буфере не два кадра — но повторюсь, это повышенное усложнение…
            • 0
              О, если вопрос ставить _так_, то случай сложный.
              Дело в том, что rtsp стример ортогонален энкодеру. И когда к стримеру приходит пакет, максимум что мы можем сделать — это дропнуть целиком _кадр_. Именно это позволит делать анализ таймстампа — все пакеты одного кадра выходят одновременно и с одним таймстампом.
              Если в стример передавать море доп. информации, то всё сильно усложняет и вводит жесткую зависимость частей. Это неправильно.
              • 0
                нормальная история — сигнализировать энкодеру, что бы он вставил ещё keyframe или снизил битрейт
                • 0
                  передать снизу вверх информацию о том, что не пролазит — да.
                  анализировать внизу что проходит и интеллектуально выкидывать — нет.
          • +1
            Позвольте немного побыть Grammar Nazi: упомянутое вами слово на немецком таки пишется как «achtung».
            • +1
              угу. каждый раз когда вижу его написаное, удивляюсь, и обещаю запомнить. и каждый раз пишу неправильно :)
  • +2
    Apple вот решил эту проблему в корне, брутально изобрев свой протокол стриминга.
    Там поток режут на кусочки в банально складывают в папку. А клиент их банально
    выкачивает по HTTP. В результате TCP соединения постоянно переоткрываются и ошибка
    не накапливается.

    Вот к чему приводит нежелание читать книжки папы Стивенса (W. Richard Stevens).
    • 0
      постоянно переоткрываемое соединение = регулярный лаг на открытие соединения = неравномерный стриминг…
      протокол RTP over TCP совершенно корректен. а вот для корректной реализации алгоритма надо иногда знать чуток больше, чем названия функций.
      • +1
        Можно «немножко кешировать» пару секунд и пускать несколько TCP потоков на скачивание. Это даст лаг от realtime но видео будет гладкое и пушистое.

        Кстати, VLC уже умеет делать стриминг в стиле Apple.

        RTP хочет полосу. Изначальная заявка (в первой статье, насколько я помню) была именно борьба бобра с ослом, протиснуть поток там где он не пролезает.

        Насчет квалификации программистов — согласен.
        • 0
          Да, хочет. Мы же говорим о реалтайм стриме? :)
          Проблемы начинаются, когда он не пролазит, либо где-то застреёт.
          Тут начинаются эффекты реализации.
    • +1
      это хорошо для броадкаста, где не страшна задержка в минуту или час, но не годится для риалтайм стриминга, где хочется уложиться в секунду-две.
      • 0
        Кусочки можно делать маленькие, по секунде. На кодеке H.264 1080p это примерно 4Mbit/s то есть порождается 500К файлик каждую секунду. Да, их надо хранить на диске хотя бы штук 20. Ну и что. Диски быстрые.
        • 0
          Получаем во-1х задержку на формирование кусочка.
          Во-2х на время скачивания кусочка.
          В-3их буферизация защитная в клиенте.
          В-4ых задержки на подключение и возможные ретрансмиты.
          Итого получается, что задержка будет от 5 секунд (что уже в реалтайме неприемлемо).
          Но в целом, протокол имеет свою нишу.
    • 0
      HLS был придуман для несколько иной задачи, и от части с заделом на особенности применения на мобильных девайсах.
  • +1
    У меня наблюдаются похожие проблемы с Hikvision камерой. Но патчить прошивку не хватает квалификации.
    • +1
      Хе, это та самая камера, которая собрана на том же модуле, похоже?
      Странно, мне репортили что с ней нет проблем. Вероятно, её при этом подцепляли по своему личному протоколу.

      бегло глянул — прошивка архив похоже сразу с fs внутри, ни cram ни cpio ни tar оберток не нашел почему-то; но это не сильно критично.
      судя по строкам типа
      «Error! sendRtpPacket sendLen = %d»
      rtp_tcp_sendstream close and pnet_connect = 0x%x
      check_payload failed
      rtp_tcp_sendstream close and pnet_connect=0x%x
      wtitev rtp data error: %d
      Timeout: Lost frame data retval = %d send_len %d errno = %d
      rtpoverrtsp_sendstream: setsockopt:TCP_NODELAY ERROR:
      rtpoverrtsp_sendstream: setsockopt:TCP_CORK ERROR:
      <RTP_OVER_RTSP_Send()>frame_readidx=%d,rtpBufwriteIdx=%d,frameCnt=%d,overlaped_threshold=%d

      похоже, что за значениями присматривают, вот только чутьё подсказывает, что они при проблемах закрывают со своей стороны.
      в теории можно запатчить, на практике — попробуйте написать производителю камер о проблеме.
      • 0
        Спасибо, что посмотрели.
        • +3
          а есть возможность выставить её на немного в сеть? (урл и логпасс в личку плиз).
          я потыкаю палочкой
        • +1
          Спасибо за доступ! Потыкал палочкой. Камера вполне корректно буферизует и поток _вроде_ не ломает.
          Но какой-то шлак и мусор можно поймать после лага:
          UNK NAL = 27
          UNK NAL = 31
          UNK NAL = 18
          UNK NAL = 13
          и камера бывает рвёт по своему желанию.
          то есть такие какая-то некорректность у неё есть.
  • +4
    > патчем, защищающим TCP поток от порчи

    «Патч, защищающий от порчи» — такое пол-Хабра с руками оторвет! ) Особенно, если не только по поводу TCP-потока
  • 0
    А нельзя это было объединить с предыдущим постом?
    А за статью спасибо.
    • 0
      Предполагалось, что эта статья будет только в пятницу. Но так как сегодня ночью спать мне не удалось, зато появилось время её дописать.
    • 0
      чуть не забыл, еще почему нельзя объединять — это статьи о разном. та статья — теория, эта — практика. и они довольно параллельны ^_^
  • 0
    Сначала я хотел подумал что «вот нищеброд и бездельник, вместо того чтобы выкинуть китайскую камеру, ковыряет её», но когда прочитал подход и способ — проникся таким уважением к автору. Автор, ты просто молодец! Так держать!
    P.S. Кстати не прочитал и половины, не разбираюсь я в этом однако автор все равно немерено крут ))
    • 0
      вот три внутренних камеры, которые на HiSilicon собраны (HI3516C 53H20L) скорее всего именно выкину. спишу в расход по 150$ за каждую, но сил моих уже нет — лагалище еще то. вот там совсем мрак и жуть внутри.
      • 0
        Так и не удалось их победить несмотря на ежемесячное обновление их прошивок китайцами?
        У меня одна такая живёт и ещё 5 подобных ползут из Китая. Вроде как с родным видеорегистратором они ещё дружат, но там, похоже, используется какой-то свой протокол.
        • 0
          если хочешь, я могу три своих (объективы 4мм либо 2.8мм) сдать тебе за недорого :)

          их DVR цепляет через свой протокол.
          Но падение fps при совершенно непонятных условиях меня убивает.
          Кроме просадки от числа коннектов, fps падает от освещённости.
          Причем выключить эту лажу где — не представляю.
          Они low lux походу интеграцией кадров получили.

          Я выставил ограничение выдержки <40ms (то есть 25фпс на ура пролазит), всё равнок вечеру fps на обеих 8.33.
          утром на ярком свету — снова 25fps.
          • 0
            Есть такая мысль, что ночью включается подсветка, лезут помехи из-за отсутствия фильтров по питанию, изображение покрывается какой-то рябью, а чип не справляется с возросшим потоком «мусора».
            • 0
              ну я смотрел — кондёры по питанию от подсветки стоят; плюс сама подсветка линейная — ИК диод и всё…
              опять же, тогда бы и днём если закрыть фотодатчик fps же не падает…
              не знаю что делать. еще поковыряю и сдамся.
          • 0
            Я такую американской сборки за 430 баков видел (хохо) что уж говорить про китайцев.
            Вот её фамилия: IQinVision IQD32SV-F1, ближе к ночи после заката — 1 кадр в минуту.
            • 0
              в конце всех кручений, когда убрал DWDR, отрубил чего-то там еще, в общем, они стали себя вести лучше.
              но всё равно, в моём случае она бесполезна — мне важнее видеть кто прошел и куда, чем какого цвета была шапка.
              поэтому снял, все три лежат в коробках, на их место повесил на базе топсишных модулей.
  • +3
    Захотел узнать про команду strings, вбил в гугл «man strings». Зря.
    • +1
      Кажется, вы не тем интересуетесь. Мне гугл первым делом именно ман странциу от утилиты strings показывает.
      • 0
        Мне тоже, но ниже приложил картинки.
        • 0
          «Доктор, а откуда у вас такие картинки?» :)
  • 0
    А в каком месте rtsp_streamer можно увеличить TCP таймаут до 10 секунд? Автоматическим патчером из следующей статьи пропатчил, обрывы прекратились но картинка рассыпается.
    • 0
      а не знаю. в последних билдах найти место с таймаутом мне не удалось.
      + самые последние билды вообще надо заново расковыривать формат прошивки.
      • 0
        у меня TS38JK 2.5.1.0, сейчас еще раз протестировали со своей пропатченной и с прошивкой с datacompboy.ru/camfw/

        ffmpeg -i rtsp://192.168.0.123/mpeg4 -vcodec copy test.avi
        время от времени сыпет
        [avi @ 0x138af00] H.264 bitstream error, startcode missing3 bitrate=3928.5kbits/s
        Last message repeated 19 times

        Но картинка без существенных артифактов.
        • 0
          проще уже самому прошивку написать.

          Мы это и делаем.
          • 0
            они бутлоадер и формат паршивок попортили.
            так что, возможно, теперь только over uart или вообще на программаторе паршивить
            • 0
              они — кто они?
              • 0
                топси. (о которых в статье речь)

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