Постобработка видеопотока с WDM (CopyMemory в Callback ф-ци)

Программирование на Visual Basic, главный форум. Обсуждение тем программирования на VB 1—6.
Даже если вы плохо разбираетесь в VB и программировании вообще — тут вам помогут. В разумных пределах, конечно.
Правила форума
Темы, в которых будет сначала написано «что нужно сделать», а затем просьба «помогите», будут закрыты.
Читайте требования к создаваемым темам.
Mihail_
Обычный пользователь
Обычный пользователь
 
Сообщения: 96
Зарегистрирован: 31.03.2008 (Пн) 20:57

Постобработка видеопотока с WDM (CopyMemory в Callback ф-ци)

Сообщение Mihail_ » 17.09.2010 (Пт) 18:56

Всем привет. Столкнулся с проблемой постобработки видеопотока при снятии изображения с WDM драйвера. Если использовать callback функцию на VideoStream (не путать с Frame), то какие-то некорректные данные приходят в видеозаголовке VIDEOHDR.

Тоесть если использовать callback на функцию MyFrameCallback:
Код: Выделить всё
Function MyFrameCallback(ByVal lwnd As Long, ByVal lpVHdr As Long) As Long
    Dim VideoHeader As VIDEOHDR
    Dim VideoData() As Byte
   
    '//Fill VideoHeader with data at lpVHdr
    RtlMoveMemory VarPtr(VideoHeader), lpVHdr, Len(VideoHeader)
   
    '// Make room for data
    ReDim VideoData(VideoHeader.dwBytesUsed)
   
    '//Copy data into the array
    RtlMoveMemory VarPtr(VideoData(0)), VideoHeader.lpData, VideoHeader.dwBytesUsed

   Debug.Print "Frame      " & Now, "dwBufferLength:" & VideoHeader.dwBufferLength, "dwBytesUsed:" & VideoHeader.dwBytesUsed, "dwFlags: 0x" & Hex(VideoHeader.dwFlags)
End Function


..то все замечательно, все обрабатывается, получается такой лог:
Код: Выделить всё
Frame      17.09.2010 19:39:18            dwBufferLength:1216512      dwBytesUsed:1216512         dwFlags: 0x8
Frame      17.09.2010 19:39:19            dwBufferLength:1216512      dwBytesUsed:1216512         dwFlags: 0x8
Frame      17.09.2010 19:39:19            dwBufferLength:1216512      dwBytesUsed:1216512         dwFlags: 0x8
Frame      17.09.2010 19:39:19            dwBufferLength:1216512      dwBytesUsed:1216512         dwFlags: 0x8
Frame      17.09.2010 19:39:19            dwBufferLength:1216512      dwBytesUsed:1216512         dwFlags: 0x8
Frame      17.09.2010 19:39:19            dwBufferLength:1216512      dwBytesUsed:1216512         dwFlags: 0x8
Frame      17.09.2010 19:39:20            dwBufferLength:1216512      dwBytesUsed:1216512         dwFlags: 0x8
Количество байт соответствует разрешению видеоисточника 704*576*3=1216512

А если те же преобразования проделать с видеозаголовком из функции MyVideoStreamCallback, то получается абра-кадабра.. Туда что, вместо указателя на видеозаголовок lpVHdr приходит указатель на что-то другое? по докам вроде все то же самое должно быть...
Код: Выделить всё
VideoStream 17.09.2010 19:39:22           dwBufferLength:0            dwBytesUsed:0 dwFlags: 0x10000
VideoStream 17.09.2010 19:39:23           dwBufferLength:512          dwBytesUsed:33554434        dwFlags: 0x2000202
VideoStream 17.09.2010 19:39:23           dwBufferLength:0            dwBytesUsed:0 dwFlags: 0x200
VideoStream 17.09.2010 19:39:23           dwBufferLength:131584       dwBytesUsed:33554946        dwFlags: 0x2000202
VideoStream 17.09.2010 19:39:23           dwBufferLength:262144       dwBytesUsed:66048           dwFlags: 0x10200
VideoStream 17.09.2010 19:39:23           dwBufferLength:131584       dwBytesUsed:2 dwFlags: 0x0
VideoStream 17.09.2010 19:39:23           dwBufferLength:131072       dwBytesUsed:512             dwFlags: 0x0
VideoStream 17.09.2010 19:39:23           dwBufferLength:512          dwBytesUsed:33554434        dwFlags: 0x2000002
VideoStream 17.09.2010 19:39:23           dwBufferLength:131072       dwBytesUsed:512             dwFlags: 0x0
VideoStream 17.09.2010 19:39:23           dwBufferLength:512          dwBytesUsed:33554434        dwFlags: 0x2000202
VideoStream 17.09.2010 19:39:23           dwBufferLength:16908289     dwBytesUsed:66048           dwFlags: 0x400
VideoStream 17.09.2010 19:39:23           dwBufferLength:131584       dwBytesUsed:33554946        dwFlags: 0x2000002
VideoStream 17.09.2010 19:39:23           dwBufferLength:1            dwBytesUsed:0 dwFlags: 0x2000002
VideoStream 17.09.2010 19:39:23           dwBufferLength:16777217     dwBytesUsed:65536           dwFlags: 0x10000
VideoStream 17.09.2010 19:39:23           dwBufferLength:0            dwBytesUsed:0 dwFlags: 0x2
VideoStream 17.09.2010 19:39:23           dwBufferLength:131072       dwBytesUsed:66048           dwFlags: 0x10000
VideoStream 17.09.2010 19:39:23           dwBufferLength:131584       dwBytesUsed:33554946        dwFlags: 0x2000002
VideoStream 17.09.2010 19:39:23           dwBufferLength:262144       dwBytesUsed:1024            dwFlags: 0x10200
VideoStream 17.09.2010 19:39:23           dwBufferLength:0            dwBytesUsed:33554434        dwFlags: 0x2000002
VideoStream 17.09.2010 19:39:23           dwBufferLength:33554434     dwBytesUsed:131072          dwFlags: 0x10000
VideoStream 17.09.2010 19:39:23           dwBufferLength:512          dwBytesUsed:33554434        dwFlags: 0x2000002
VideoStream 17.09.2010 19:39:24           dwBufferLength:0            dwBytesUsed:0 dwFlags: 0x2000002

В результате хотелось бы получить массив пикселов из функции MyVideoStreamCallback, т.к. она позволяет это делать с существенно большим FPS, чем MyFrameCallback. Прошу помощи.

-
Полный код в архиве, чтобы начала работать функция MyVideoStreamCallback, вверху программы надо выбрать Control -> Start


------- UPD готовый файл проекта с решенной проблемой в архиве vbmemcap_fin.zip
Вложения
vbmemcap.zip
(8.81 Кб) Скачиваний: 71
vbmemcap_fin.zip
готовый проект
(88.62 Кб) Скачиваний: 73
Последний раз редактировалось Mihail_ 24.09.2010 (Пт) 14:50, всего редактировалось 2 раз(а).

pronto
Постоялец
Постоялец
 
Сообщения: 597
Зарегистрирован: 04.12.2005 (Вс) 6:20
Откуда: Владивосток

Re: Постобработка видеопотока с WDM

Сообщение pronto » 18.09.2010 (Сб) 18:08

И какой FPS нужен? Если камера (или другой источник) может выдать максимум 30~60 кадров в секунду. 25 fps достаточно в большинстве случаев. Установить
Код: Выделить всё
capPreviewRate lwndC, 40
и использовать FrameCallback, потому что VideoStream пишет поток на диск и, скорее всего, применяется кодек.
Add: Небезынтересно почитать тему Веб-камера. Там много ссылок на полезную информацию.
O, sancta simplicitas!

Mihail_
Обычный пользователь
Обычный пользователь
 
Сообщения: 96
Зарегистрирован: 31.03.2008 (Пн) 20:57

Re: Постобработка видеопотока с WDM

Сообщение Mihail_ » 19.09.2010 (Вс) 15:45

в моем случае не камера, а многострадальная плата видеозахвата, но не суть - типа драйвера тот же.. вне зависимости от уменьшения задержки PreviewRate, FrameCallback у меня не выдает значений выше 5-7fps, причем с разными устройствами на разных конфигурациях системы.. и в свернутом не работает.. в то время как видеопоток хоть и потребляет больше системных ресурсов, но со своей задачей справляется намного лучше.. видеопоток не сжатый и "запись" производится макросом capCaptureSequenceNoFile, да и кадры-то насколько я знаю сначала должны обрабатываться callback функциями, а уже потом выводиться на экран, сжиматься и все осталное..
меня особо смущают флаги, извлекаемые из видеозаголовка, когда работает VideoStreamCallbac, они явно не соответствуют тому, что допускается в VIDEOHDR, не говоря уже о возвращаемой длине буфера кадров...

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

djalex777
Постоялец
Постоялец
 
Сообщения: 461
Зарегистрирован: 23.03.2006 (Чт) 16:02

Re: Постобработка видеопотока с WDM

Сообщение djalex777 » 19.09.2010 (Вс) 22:08

Всё там совпадает... В callback-функции перед параметрами поставь ByVal

pronto
Постоялец
Постоялец
 
Сообщения: 597
Зарегистрирован: 04.12.2005 (Вс) 6:20
Откуда: Владивосток

Re: Постобработка видеопотока с WDM

Сообщение pronto » 20.09.2010 (Пн) 16:37

Mihail_, спасибо тебе за ссылку http://rcorp.boom.ru/chap4.htm!
O, sancta simplicitas!

Mihail_
Обычный пользователь
Обычный пользователь
 
Сообщения: 96
Зарегистрирован: 31.03.2008 (Пн) 20:57

Re: Постобработка видеопотока с WDM

Сообщение Mihail_ » 22.09.2010 (Ср) 19:32

djalex777 писал(а):Всё там совпадает... В callback-функции перед параметрами поставь ByVal

спасибо, я очень невнимателен.. оно кстати по дефолту разве не должно быть как ByVal?

итак, ситуация улучшилась, после того как мы поправили функцию:
Function MyVideoStreamCallback(ByVal lwnd As Long, ByVal lpVHdr As Long) As Long
Код: Выделить всё
Function MyVideoStreamCallback(ByVal lwnd As Long, ByVal lpVHdr As Long) As Long

    Dim VideoHeader As VIDEOHDR
    Dim VideoData() As Byte
   
    '//Fill VideoHeader with data at lpVHdr
    RtlMoveMemory VarPtr(VideoHeader), lpVHdr, Len(VideoHeader)
   
    '// Make room for data
    ReDim VideoData(VideoHeader.dwBytesUsed)
   
    '//Copy data into the array
    RtlMoveMemory VarPtr(VideoData(0)), VideoHeader.lpData, VideoHeader.dwBytesUsed
 
  Debug.Print "VideoStream " & Now, "dwBufferLength:" & VideoHeader.dwBufferLength, "dwBytesUsed:" & VideoHeader.dwBytesUsed, "dwFlags: " & VideoHeader.dwFlags

End Function

В дебаге все работает, все замечательно.. Но стоит скомпилировать программу, как она начинает падать в этой функции на первой же строчке
Код: Выделить всё
RtlMoveMemory VarPtr(VideoHeader), lpVHdr, Len(VideoHeader)

Что характерно - функция MyFrameCallback с идентичным кодом работает нормально и никаких падений не вызывает... Проблема в RtlMoveMemory?
Код: Выделить всё
Declare Sub RtlMoveMemory Lib "kernel32" (ByVal hpvDest As Long, ByVal hpvSource As Long, ByVal cbCopy As Long)

Самое главное - код работает в FrameCallback, работает в среде разработки в VideoStreamCallback, а в скомпилированном exe-шнике приводит к крашу при запуске VideoStreamCallback.. НУ КАК ТАКОЕ МОЖЕТ БЫТЬ?
Прошу помочь разобраться.. Поправленный пример прилагаю.

2pronto не за что, побольше бы таких материалов в сети и всем было бы счастье
Вложения
vbmemcap_fix1.zip
(8.76 Кб) Скачиваний: 70

Debugger
Продвинутый гуру
Продвинутый гуру
Аватара пользователя
 
Сообщения: 1667
Зарегистрирован: 17.06.2006 (Сб) 15:11

Re: Постобработка видеопотока с WDM

Сообщение Debugger » 22.09.2010 (Ср) 20:26

Код: Выделить всё
RtlMoveMemory VideoHeader, ByVal lpVHdr, Len(VideoHeader)

Может помочь

djalex777
Постоялец
Постоялец
 
Сообщения: 461
Зарегистрирован: 23.03.2006 (Чт) 16:02

Re: Постобработка видеопотока с WDM

Сообщение djalex777 » 22.09.2010 (Ср) 21:26

Debugger писал(а):
Код: Выделить всё
RtlMoveMemory VideoHeader, ByVal lpVHdr, Len(VideoHeader)

Может помочь

Каким образом?

Mihail_ писал(а): Но стоит скомпилировать программу, как она начинает падать в этой функции на первой же строчке
Код: Выделить всё
RtlMoveMemory VarPtr(VideoHeader), lpVHdr, Len(VideoHeader)


Объяви CopyMemory (RtlMoveMemory) в TLB и всё заработает

Mihail_
Обычный пользователь
Обычный пользователь
 
Сообщения: 96
Зарегистрирован: 31.03.2008 (Пн) 20:57

Re: Постобработка видеопотока с WDM

Сообщение Mihail_ » 23.09.2010 (Чт) 20:42

Я не совсем понял что значит "объявить CopyMemory в TLB", хотя често гуглил всевозможные варианты по словам "VB TLB Declare CopyMemory RtlMoveMemory kernel32", поясните пожалуйста.

Просто объявлять в модуле и править функцию под copymemory я пробовал и раньше, вот так:
Код: Выделить всё
Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByVal Destination As Any, ByVal Source As Any, ByVal Length As Long)

Function MyVideoStreamCallback(ByVal lwnd As Long, ByVal lpVHdr As Long) As Long ' ByVal - IMPORTANT!
    Dim VideoHeader As VIDEOHDR

    '//Fill VideoHeader with data at lpVHdr
    CopyMemory VarPtr(VideoHeader), lpVHdr, Len(VideoHeader)

  Debug.Print "VideoStream " & Now, "dwBufferLength:" & VideoHeader.dwBufferLength, "dwBytesUsed:" & VideoHeader.dwBytesUsed, "dwFlags: " & VideoHeader.dwFlags
End Function
Результат аналогичный, в среде разработки все работает, в скомпилированном экзешнике падает.

НО, пока искал - нашел несколько интересных тем, где как раз обсуждали проблемы работы CopyMemory в VB, если она вызывается из Callback функций. В частности тут и тут, люди прямо в коде пишут комментарии "any attempt to run RtlMoveMemory fails when compiled!" причем во второй теме поясняют - что это происходит если Callback функция работает в отдельном потоке, а в нашем случае функция VideoStreamCallback именно такая.. дескать пока программа собрана в дебаге - все работает в одном потоке, поэтому ошибок нет, FrameCallback видимо всегда работает в общем потоке, поэтому проблем не вызывает, но стоит скомпилировать проект, как начинаются неприятности.
Что примечательно, судя по кускам кода люди напарываются на это как раз в работе видео, поэтому считаю целесообразным общими усилиями домучить-таки этот проект, хотя бы для будущих поколений.

Debugger
Продвинутый гуру
Продвинутый гуру
Аватара пользователя
 
Сообщения: 1667
Зарегистрирован: 17.06.2006 (Сб) 15:11

Re: Постобработка видеопотока с WDM (CopyMemory в Callback ф

Сообщение Debugger » 23.09.2010 (Чт) 21:17

Код: Выделить всё
' ByVal - IMPORTANT!

Код: Выделить всё
Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
Dim BufInit As Boolean
Dim FrameBytes() As Byte
Function FrameCallback(ByVal hwnd As Long, ByRef struc As VIDEOHDR) As Long
    ...
    If Not BufInit Then
        ReDim FrameBytes(struc.dwBytesUsed)
        BufInit = True
    End If
    ...
    CopyMemory FrameBytes(0), ByVal struc.lpData, struc.dwBytesUsed
End Function

Как-то так.
Отлично пашет как из среды, так и из скомпилированного проекта. Проверил на нескольких компьютерах.
это происходит если Callback функция работает в отдельном потоке, а в нашем случае функция VideoStreamCallback именно такая

С чего ты взял, что это так?
Последний раз редактировалось Debugger 24.09.2010 (Пт) 17:21, всего редактировалось 1 раз.

Mihail_
Обычный пользователь
Обычный пользователь
 
Сообщения: 96
Зарегистрирован: 31.03.2008 (Пн) 20:57

Re: Постобработка видеопотока с WDM (CopyMemory в Callback ф

Сообщение Mihail_ » 23.09.2010 (Чт) 21:50

У камеры есть несколько callback функций для работы с видеопотоком, одна - тормознутая FrameCallback, которая запускается когда включена камера, при этом окно программы не свернуто и т.д. - мне с нее не удавалось получить больше 7 FPS, кстати, то что она не работает, когда окно программы свернуто имхо лишний раз свиделеьствует, что она работает в общем потоке программы..
и другая функция - VideoStreamCallback, которая запускается когда мы осуществляем "запись" с камеры (не важно как - в файл на диск, или макросом capCaptureSequenceNoFile) - вот она, хоть и потребляет чуть больше ресурсов системы, но работает уже железобетонно, с заданным количеством fps, независимо от поведения основной программы и т.д. вот она походу получается работает в отдельном потоке, что вызывает проблемы после компиляции проекта т.к. как пишут в ранее приведенных ссылках, пока программа собрана в IDE она многопоточностью не обладает и ошибок не возникает..
То что она в отдельном потоке я на 100% не уверен, но судя по тому что имеем, получается что так..

То есть на данный момент проблема в том, что мы в скомпилированном экзешнике при запуске "записываемого" видеопотока не можем извлечь из заголовка (VIDEOHDR) никакую инфорацию, потому что программа падает при использовании внутри этой функции CopyMemory.
djalex777 говорит надо ее как-то по-хитрому задекларировать чтобы можно было использовать, у меня вот пока не получилось.
Приведенный выше код (только измененный с FrameCallback на VideoStreamCallback
Код: Выделить всё
...Dim BufInit As Boolean
Function FrameCallback...
работает так же как и предыдущие варианты - тоесть падает за пределами IDE.

djalex777
Постоялец
Постоялец
 
Сообщения: 461
Зарегистрирован: 23.03.2006 (Чт) 16:02

Re: Постобработка видеопотока с WDM (CopyMemory в Callback ф

Сообщение djalex777 » 23.09.2010 (Чт) 23:12

Debugger писал(а):
Код: Выделить всё
    ...
    Dim FrameBytes() As Byte
    If Not BufInit Then
        ReDim FrameBytes(struc.dwBytesUsed)
        BufInit = True
    End If
    ...


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

Mihail_ писал(а):Я не совсем понял что значит "объявить CopyMemory в TLB"

Нужно создать TLB с объявленной в ней функцией CopyMemory (rtlMoveMemory)

Mihail_
Обычный пользователь
Обычный пользователь
 
Сообщения: 96
Зарегистрирован: 31.03.2008 (Пн) 20:57

Re: Постобработка видеопотока с WDM (CopyMemory в Callback ф

Сообщение Mihail_ » 24.09.2010 (Пт) 0:55

Я правильно понял, по результатам поиска что TLB это особый вид библиотек "Type Library", которые в vb можно добавлять через references. Что создаются эти библиотеки специальными инструментами из комплекта C++, с использованием языка Interface Definition Language и потом компилируются в tlb файл, который и можно добавить к проекту? Или все-таки под короткой фразой "Нужно создать TLB с объявленной в ней функцией CopyMemory" подразумевается что-то что делается обычными средствами vb в пару кликов? Я действительно извиняюсь за свою неосведомленность, просто в обычной справочной литературе по vb (включая бумажную версию vbstreets) про это как бы не говорится, да и поиск результатами не радует.. Может быть можно эти библиотеки где-то в готовом виде взять?
Я был бы действительно очень признателен, если бы Вы этот вопрос осветили подробнее, может быть хотя бы в виде ссылки на источник "как создать TLB с объявленной в ней функцией".

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16478
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: Постобработка видеопотока с WDM (CopyMemory в Callback ф

Сообщение Хакер » 24.09.2010 (Пт) 1:12

Ты мою статью в википедии читал?
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

pronto
Постоялец
Постоялец
 
Сообщения: 597
Зарегистрирован: 04.12.2005 (Вс) 6:20
Откуда: Владивосток

Re: Постобработка видеопотока с WDM (CopyMemory в Callback ф

Сообщение pronto » 24.09.2010 (Пт) 10:35

Конечно, есть готовые tlb! Ищи по форуму, уже неоднократно поднималась эта тема.
Например, здесь
O, sancta simplicitas!

Mihail_
Обычный пользователь
Обычный пользователь
 
Сообщения: 96
Зарегистрирован: 31.03.2008 (Пн) 20:57

Re: Постобработка видеопотока с WDM (CopyMemory в Callback ф

Сообщение Mihail_ » 24.09.2010 (Пт) 14:40

Хакер писал(а):Ты мою статью в википедии читал?

Эту? читал конечно, хорошая статья, с описанием принципов работы на низком уровне.. - она меня во многом напугала :)

pronto спасибо огромное! я узко мыслил все пытался найти "kernel32 TLB", "TLB RtlMoveMemory" и т.д. а надо было сразу замахиваться на все основные Win32API! да еще выражение "создать TLB" повергло в тихий ужас (у нас на форуме даже соответствующие утилиты нашлись).

Итак, для будущих поколений - в архиве TLB файл с основными Win32API, а так же утилита для его регистрации в системе и просмотра списка функций. После его регистрации в системе и привязки к проекту через Project -> References основные функции и константы в своем проекте декларировать не надо будет.
Вложения
TLB.zip
(99.71 Кб) Скачиваний: 65

Mihail_
Обычный пользователь
Обычный пользователь
 
Сообщения: 96
Зарегистрирован: 31.03.2008 (Пн) 20:57

Re: Постобработка видеопотока с WDM (CopyMemory в Callback ф

Сообщение Mihail_ » 24.09.2010 (Пт) 14:48

Итак, финальная версия проекта по сабжу в архиве (чтобы все работало читаем readme). Все стало работать не только стабильнее, но и ощутимо быстрее!
Всем большое спасибо!
Вложения
vbmemcap_fin.zip
(88.62 Кб) Скачиваний: 90

iDDD
Начинающий
Начинающий
 
Сообщения: 3
Зарегистрирован: 26.11.2011 (Сб) 22:10

Re: Постобработка видеопотока с WDM (CopyMemory в Callback ф

Сообщение iDDD » 27.11.2011 (Вс) 16:46

Извиняюсь за некропостинг.

Столкнулся с теми же самыми проблемами, что и топикстартер. Но наткнулся на другую.
После попытки передать что-либо из MyVideoStreamCallback в другую функцию, в большинстве случаев приложение вылетает.
Например, из MyVideoStreamCallback я получаю массив VideoData, неважно в каком цветовом профиле там изображение.

Передаю его на простейший анализатор движения, который сравнивает каждый элемент массива с записанным ранее, и если обнаруживает движение, то должен записать кадр, при помощи, ну скажем WM_CAP_SINGLE_FRAME_OPEN + WM_CAP_SINGLE_FRAME + WM_CAP_SINGLE_FRAME_CLOSE.

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

Можно конечно VideoData перекодировать сразу в bmp, но во-первых, надо писать перекодировщик под все цветовые профили, во-вторых, быстродействие будет просто адовое, и в-третьих, далеко не факт что приложение не порушится при попытке сохранения файла.

Что делать, уже не знаю, но глюки страшенные, если не ide падает, то exe-шник. В какую хоть сторону копнуть посоветуете?


Вернуться в Visual Basic 1–6

Кто сейчас на конференции

Сейчас этот форум просматривают: AhrefsBot, Yandex-бот и гости: 27

    TopList  
cron