Добро пожаловать в LiveJournal
main_pic
sewer_endemic
Первый пост в новом журнальчике. Посвящается всем любопытным, заглянувшим на огонёк. Чаще всего здесь будут попадаться фотографии и короткие заметки "за жизнь". Кое-что перетащу из старого журнала (pohmelnaya.livejournal.com), занудой быть надоело, теперь переквалифицируюсь в жабиусы. ;-)

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

P/S/ Желающим разместить рекламу в этом дневничке - НИКАК. От слова СОВСЕМ. Я не вижу смысла заниматься пиаром чего-либо, так как, во-первых, сюда никто не ходит, а, во-вторых, тратить своё время на рекламу не считаю разумным.

Secure boot на ноутбуках: как с этим жить?!
main_pic
sewer_endemic
Да всё очень просто!

1. Заходим в интерфейс UEFI
2. Устанавливаем пароль суперпользователя
3. Отключаем secure boot.
4. Удаляем пароль суперпользователя.

Что, не секьюрно?

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

3. Находим раздел "доверенные загрузчики" или что-то в этом роде
4. Добавляем туда UEFI-модуль загрузчика той ОСи, которая отказывалась грузиться (например, для linux это будет grub)
5. Добавленный UEFI модуль перемещаем в списке загрузки в самое его начало
6. Удаляем пароль... или не удаляем - тут уже дело вкуса
7. И вот у нас линукс и виндовс в дуалбуте! Более того, grub умеет сам находить загрузчики и больше нам с секьюр бут возиться не надо будет. Не очень секьюрно, да. А что поделать, "жить захочешь - и не так раскорячишься!"

Все побежали - и я побежал! ;)
main_pic
sewer_endemic
Все фоткают из окон красивые погодные явления. А я чем хуже? ))) Заодно, в кои-то веки, воспользовался "навесной" оптикой для своей мыльницы.

IMG_7387.JPG

Если окирпичился ведроид...
main_pic
sewer_endemic
heimdall flash --RECOVERY ~/Загрузки/twrp-3.1.1-0-i9300.img --no-reboot
Подходящий образ ищем тут: https://eu.dl.twrp.me/
Перезагружаемся в TWRP (home-volume up-power)
Ну и потом прошиваем что больше подходит. )
Ах, да! ДО окирпичивания желательно делать бэкапы тем же TWRP хотя бы раз в месяц. ;)

Небольшая памятка линуксоиду, если он вдруг пользователя из группы sudo удалил, а другого суперюзера
main_pic
sewer_endemic
Небольшая памятка линуксоиду, если он вдруг пользователя из группы sudo удалил, а другого суперюзера/способного суперюзером стать в системе нет...

0. Ненадолго положить систему.
1. Получить доступ к файловой системе в режиме RW
2. Поправить файл /etc/group, прописав юзера в группы sudo и adm
3. Загрузиться, пользоваться. )

Вроде, всё просто, но есть нюанс.

Сначала про п.2. Всё же немытыми руками лезть внутрь системных потрохов - не очень. Потому если есть возможность пользоваться штатной usermod, лучше таки пользоваться! Например, получив рутовый шелл через grub

Теперь про п.1. Если у нас есть физический доступ к машине, то проблем, обычно нет. Или сами консоль в руки и вперёд, к работе над ошибками, либо попросить того, кто может до консоли добраться. А там уже grub или live cd - кому что удобно, у кого что есть... А что делать, если покрошили пользователя в виртуальной машине? У неё-то с консолью в момент загрузки "не очень"! Ну, во-первых, VM всё-таки жедательно бэкапить хотя бы изредка. Что, нет бэкапов? Ну тогда...

Тогда идём вот сюда: http://equivocation.org/node/107
Исчерпывающе и даже не требует перевода! )))

Логирование в Django или детективная история одного ма-а-аленького, но очень гордого велосипеда
main_pic
sewer_endemic
Жил-был некий проект... Да, на Джанго. Развивался потихоньку. И вот в один далеко не прекрасный момент один из пользователей сотворил что-то непотребное, но сознаваться в этом категорически отказался - "а оно само!". А потом и ещё раз, и снова с потерей "очень важных данных". Нет, с защитой от дурака там всё в порядке было, система терроризирует пользователя подтверждениями не хуже винды. Но бывают отдельные особо упорные личности... Начальство сказало - документируй все действия юзверя! И всё заверте........

1. Добавляем в лог дополнительную отладочную информацию: имя пользователя, в сессии которого произошло событие, URI, имя модуля, где произошло событие, имя функции и даже номер строки.
Если посмотрим в документацию по модулю logging, то увидим, что кроме, собственно, сообщения, в методы класса Logger можно передать ещё и необязательный аргумент extra, в который можно положить всё, что душе угодно, в виде словаря. А в формат строки лога включить ключи этого словаря и всё будет прекрасно!
Хм-м... Прекрасно?!

logger.debug('Какая-то сволочь удалила докУмент', extra={'username': name_of_svolotch, 'uri': uri})

Вот после такого вызова в формате можем использовать $(username)s и $(uri)s. Ну а имя функции, номер строки, имя модуля и сам логгер предоставляет. Полностью список атрибутов, доступных "из коробки", можно посмотреть вот тут: Документация по модулю logging

А что, если в формате что-то указано, а в LogRecord этого нет, потому как в extra положить забыли? Exception, а не запись в лог - вот что! И это первые грабли. Про "некрасиво" я уже и не говорю даже. Впрочем, с "некрасиво" ещё можно поработать. Дело в том, что Джанга в любую вьюху отдаёт request - экземпляр класса HttpRequest
Что же там такого интересного, в этом объекте? Если у нас включены приложения django.contrib.auth и django.contrib.sessions, то у нас есть request.user и request.session. Имя пользователя может быть как в session (словарь) и/или в user. Ещё в request нас интересует path. Это, фактически, URL без доменной части. В принципе, это то, что нам и нужно! Всё в одном объекте! Значит, делаем так:

logger.debug('Какая-то сволочь удалила докУмент', extra={'request': request})

Минуточку... А как мы в format string на request сошлёмся? Там ведь нельзя написать $(request.user.name)s, поскольку форматирование строки лога осуществляется совершенно примитивным образом. Заглянув в logging/__init__.py мы увидим что-то вроде s = self._fmt % record.__dict__ (взято из Formatter.format)

Что ж, взглянем на документацию по модулю logging повнимательнее. Принимает сообщение метод класса Logger, а куда же оно девается внутри? Внимательно посмотрим на конфигурацию логгера: там кроме самого логгера, возможно, есть один или несколько форматтеров и как минимум один handler. Formatter просто форматирует строку. Может быть, напишем свой форматтер? Да, идея годная. Но вот захотелось нам ещё в базу данных лог писать и/или использовать удалённое логирование по http, например. И request в этом случае всё равно остаётся объектом... То есть, в форматтере мы опишем способ извлечения нужных данных из request, но для передачи по http данные вообще никак не фоматируются! А работа с БД в модуле logging и вовсе не предусмотрена. Впрочем, про логирование в БД несколько позже, это отдельная печальная история.
А теперь вернёмся к вопросу, что же с сообщением делает экземпляр класса Logger. А он просто создаёт LogRecord, включая в этот объект и данные из аргумента extra и по очереди скармливает этот, по сути, словарь всем зарегистрированным handler'ам! Тут есть некоторая "вилка". Либо пишем свою реализацию класса Logger, которая будет уметь распаковать request нужным нам образом, либо сделать распаковку в handler'е. Забегая вперёд, сразу скажу, что закончилась эта опупея созданием своего Logger'а.
Но в тот момент я ещё не знал, что подменить Logger НАСТОЛЬКО просто! И решил таки понаписать своих handler'ов на все случаи.

Вот как-то так:

class NewFileHandler(RotatingFileHandler):
    def __init__(self, *args, **kwargs):
        super(NewFileHandler, self).__init__(*args, **kwargs)
        self.setFormatter(Formatter(logMessageFormat))

    def emit(self, record):
        return super(NewFileHandler, self).emit(get_extended_record(record))


class NewHTTPHandler(HTTPHandler):
    def emit(self, record):
        return super(NewHTTPHandler, self).emit(get_extended_record(record))

    def mapLogRecord(self, record):
        d = {}
        for k in record.__dict__:
            val = record.__dict__[k]
            if isinstance(val, unicode):
                d[k] = val.encode(localenv.default_coding)
            else:
                d[k] = str(val)
        return d

Видно, что получились они довольно компактными. Но тут случилась пара неприятностей. Во-первых, попытка из модуля подцепить эти хэндлеры из модуля core в settings.py потерпела сокрушительнейшее поражение, ибо... в core была волшебная строчка import project.settings. То есть я попытался сотворить страшное странное - я попытался осуществить циклический импорт! Но опытного костылеходного велосипедиста это, конечно же, не остановило. Для обхода такой ситуации был создан в составе проекта отдельный модуль log. Вторая неприятность. Формат лога формировался динамически, в зависимости от флагов DEBUG и PRODUCTION. Формировался где? Пра-а-авильно! В settings.py, что привело к созданию ещё и отдельного модуля localenv.py. Но вот если настройки локального окружения, различные для девелопмента и для продакшна вынести в отедельный модуль идея явно правильная, то запихиавть туда ещё и формат лога... Потому формат лога тоже был перенесён в log. Отсюда и переопределение __init__, в который добавилась вот эта загадочная строчка:

        self.setFormatter(Formatter(logMessageFormat))

Костыль? Костыль! Однозначно! Формат должен быть определён всё-таки в settings! Но log уже импортируется в settings, а значит попытка импорта чего-либо из project.settings внутри log привдёт... Пра-а-авильно! Отсюда этот безобразный костыль.

В огстальном хэндлеры предельно примитивны. NewFileHandler вызывает функцию, формирующую нужные атрибуты в лог-записи и отправляет её своему родителю. NewHTTPHandler занимается ровно тем же. За небольшим исключением - ему не нужно форматировать запись. Ему нужно перекодировать все атрибуты в тот вид, в котором они могут быть переданы по протоколу http. Собственно, это метод mapLogRecord - в нём определяются и кодируются нужные атрибутв. Поскольку запись в нужном виде формируется в get_extended_record, то просто кодируем и отправляем.

Ну вот мы и добрались до самого "вкусного". Внимание! Людей, стардающих костылевелосипедобоязнью прошу реализацию функции get_extended_record не смотреть!!! X-D

Но для особо отчаянных - вот она! ;)
def get_extended_record(record):
    if not hasattr(record, 'timestamp'):
        # Если этот атрибут уже есть - запись уже обработана, возвращаем её.
        # Иначе - допиливаем до нужного состояния, в том числе весь юникод кодируем в utf-8
        if record.exc_info:
            etype, evalue, tb = record.exc_info
            exc_text = decode_exception_info(etype, evalue, tb).encode(localenv.default_coding)
        else:
            exc_text = ''
        module_path = record.pathname
        project_path = path.normpath(path.join(path.dirname(path.abspath(__file__)), '..'))
        module_path = module_path.replace(project_path, "").replace(".py", ".") + record.funcName
        extra_log_data = {  # Добавляем некоторые дополнительные атрибуты
            'worker': socket.gethostname(),
            'module_path': module_path,
            'logformat': logMessageFormat,
            'dateformat': logDateFormat,
            'timestamp': datetime.now().strftime(logDateFormat),
            'exc_text': u'',
            'exc_info': u'',
            'exception': exc_text,
        }
        if hasattr(record, 'request'):
            user_id = record.request.user.id
            if user_id:
                user_name = record.request.user.login
            else:
                user_name = "?"
            user_ip = record.request.META.get('REMOTE_ADDR', '') or record.request.META.get('HTTP_X_FORWARDED_FOR', '')
            extra_log_data.update({'user_id': user_id, 'user_name': user_name, 'user_ip': user_ip, })
            # Обязательно выкусываем из записи request, т.к оттуда можно вытащить CSRF_TOKEN и ещё много интересного
            del record.request
        else:  # Если забыли передать request - вставляем заглушку. Чтобы обязательно увидеть и добавить в extra
            extra_log_data.update({'user_id': None, 'user_name': u"NoRequest", 'user_ip': u"?.?.?.?", })
        record.__dict__.update(extra_log_data)
        for k in record.__dict__:
            if isinstance(record.__dict__[k], basestring):
                record.__dict__[k] = localenv.try_unicode(record.__dict__[k])


В принципе, в каких-либо комментариях особо не нуждается, но всё же... Первый момент это исключения. Если в тексте exception'а присутствует кириллица, то вместо этого самого exception'а стабильно будем видеть UnicodeDecodeError. А в самом логе - строчки, состоящие из \x## или \u####. Потому из модуля traceback была вытащена реализация decode_exception_info и слегка "допилена" до использования локальной кодировки по-умолчанию. Да, знаю, кодировку можно и через стандартные средства модуля sys сменить, но выглядит сам процесс... жутковато. Нам мой взгляд. Второй момент: атрибут worker. Дело в том, что для пущей устойчивости несколько экземпляров проекта были развёрнуты на нескольких физических серверах, на каждом из которых был набор виртуалок. worker - ссылка на конкретный экземпляр рабочего процесса, которая позволяла идентифицировать в логе сообщение от конкретной виртуальной машины на конкретном физ. сервере. Третий момент это добавленные в состав атрибутов logformat и dateformat. Это тоже костыль. Удалённый логгер представлял собой примитивнейшее django-приложение, которое получало методом POST словарь, извлекало оттуда logformat и dateformat и заканчивалось всё это той самой строчкой s = logformat % dict, которая отадавлась логгеру, формат которого был задан, как %(msg)s. Атрибут timestamp некоторым образом дублирует asctime, но он тут используется как маркер того, что запись уже имеет нужный вид... Примитивно, да?

Я выше писал про логирование в базу данных... Внимательные уже заметили, что в тех фрагментах кода, что приведены выше, нет ни одного обращения к БД! Да, это не просто так. Те же грабли с циклическим импортом! Модуль log импортируется в project.settings. К чему приведёт попытка импортировать хоть одну модель внутри модуля log? Пра-а-авильно!!! Но тут цепочка (точнее, колечко) импортов получилась довольно длинной и процесс осознания "WTF?!?!" занял неприлично много времени. НЕ НАСТУПАЙТЕ НА ЭТИ ГРАБЛИ! ЭТО - БОЛЬНО!!! X-DDD В итоге, логирование в базу данных осуществляется удалённым логгером. То, что он получает по http, пишется и в файл, и в БД.
Сразу возникает вопрос, а зачем конкретному worker'у тоже писать лог в файл? Да тут просто всё - лень человеческая. По общему логу находим проблему и если она в конкретном worker'е - колупаем дальше только его лог! Когда будет реализован просмотр лога в базе данных, необходимость в такой изыбточности отпадёт, логирование в файл в самом проекте можно будет просто выключить, убрав из конфигурации лога ссылку на соотвествующий хэндлер.

Вроде, всё замечательно! Ну, относительно, конечно. Если не принимать во внимание плотность костылёфф и велосипедофф на строчку кода...
И было замечательно ровно до одного далеко не замечательного момента, когда понадобилось добавить логирование в ряд функций, куда request "не доходил", а пользователя таки нужно было идентифицировать... Первый порыв - сделать "проброс" request куда только можно. Оказалось, возможно это не везде. Второй порыв - выбросить к % собачьим отовсюду extra={'request': request}. Подумал-подумал и решил, что правильнее будет второе решение. К тому моменту я уже открыл для себя ещё одну полезную функцию из модуля logging: называется она просто и незатейливо setLoggerClass.

По каковому поводу все настройки логирования вновь были возвращены в project.settings, модуль log был выкинут из проекта, а в модуле core появился класс RequestLogger

Всех костыле-велосипедо-неустойчивых вновь прошу дальше не смотреть! )))

Вот он, потомок славного класса Logger!

class RequestLogger(Logger):

    def findCaller(self):  # Переопределим findCaller, чтобы он ещё и request из стека добывал
        """
        Find the stack frame of the caller so that we can note the source
        file name, line number and function name.
        """
        f = currentframe()
        # On some versions of IronPython, currentframe() returns None if
        # IronPython isn't run with -X:Frames.
        if f is not None:
            f = f.f_back
        rv = None
        req = None
        while f:
            co = f.f_code
            # Если ещё не нашли точку вызова и текущий фрейм стека уже не в модуле logging...
            if rv is None and os.path.normcase(co.co_filename) != loggingsrcfile:
                rv = (co.co_filename, f.f_lineno, co.co_name)
            # А тут смотрим, нет ли в текущем фрейме request'а
            if req is None and 'request' in f.f_locals and isinstance(f.f_locals['request'], HttpRequest):
                req = f.f_locals['request']
            if rv and req:
                break  # Нашли всё, что хотели - можно выходить!
            else:
                f = f.f_back  # Ещё не всё нашли, берём следующий фрейм
        else:  # А если цикли пробежали до конца и чего-то не нашли... То чем богаты, тем и рады! ;)
            rv = rv if rv else ("(unknown file)", 0, "(unknown function)")
        return rv + (req,)

    def _log(self, level, msg, args, exc_info=None, extra=None):
        """
        Low-level logging routine which creates a LogRecord and then calls
        all the handlers of this logger to handle the record.
        """
        #  IronPython doesn't track Python frames, so findCaller raises an
        #  exception on some versions of IronPython. We trap it here so that
        # IronPython can use logging.
        if extra is None:
            extra = {}
        if 'request' in extra:  # Если кто-то заблотливый положил request в extra...
            request = extra['request']  # ... Извлекаем request ...
            del extra['request']  # ... и убираем его из extra, он там не нужен.
            try:  # Соответственно, вызываем немодифицированный (оригинальный) findCaller
                fn, lno, func = super(RequestLogger, self).findCaller()
            except ValueError:
                fn, lno, func = ("(unknown file)", 0, "(unknown function)")
        else:
            fn, lno, func, request = self.findCaller()
        module_path = fn
        module_path = module_path.replace(project_path, "").replace(".py", "")
        if exc_info:
            if not isinstance(exc_info, tuple):
                exc_info = sys.exc_info()
            exc_text = decode_exception_info(*exc_info)
        else:
            exc_text = u""
        worker = socket.gethostname()
        user_id = request.user.id if request and hasattr(request.user, 'id') else None
        user_name = request.user.login if user_id else u"?"
        user_ip = request.META.get('REMOTE_ADDR', '') or \
                  request.META.get('HTTP_X_FORWARDED_FOR', '') if request else u"?.?.?.?"
        req_extra = {
            'timestamp': time.time(),
            'user_id': user_id,
            'user_name': user_name,
            'user_ip': user_ip,
            'level_name': _levelNames[level],
            'worker': worker,
            'module_path': module_path,
            'func_name': func,
            'line_no': lno,
            'log_msg': msg % try_unicode(args) + exc_text
        }
        # Закидываем запись в БД
        Log.objects.create(**req_extra)
        extra.update(req_extra)
        exc_info = None  # Трэйсбэк уже лежит в log_msg, качественно декодированный, без кракозябр...
        record = self.makeRecord(self.name, level, fn, lno, msg, args, exc_info, func, extra)
        self.handle(record)


Собственно, тут видно, что пришлось переопределить всего 2 метода! findCaller и _log
С _log становится всё ясно, если посмотреть на класс Logger в модуле logging - вызов любого метода логирования упирается в этот метод! Кстати, замечу, здесь появилось и логирование в базу данных. Просто удивительно, как просто и органично это получилось в данной реализации! Одной строчкой!

        Log.objects.create(**req_extra)


Да, и это _всё_)))

Попутно: все вызовы c extra={'request': request} можем оставить как есть, они уйдут потом, при очередном рефакторинге. В любом месте вызываем logger.info('Пользователь что-то натворил!') - и видим, кто, что и _где_ сделал! Вот тут, кстати, пара слов про findCaller. В оригинале этот метод выковыривает из стека место вызова логгера. Лёгким движением руки попутно из стека достаём request, если он не был передан явно! Реализация получилась совсем чуть-чуть длиннее оригинала и ненамного дольше работает. Зато логировать стало удобно! ОЧЕНЬ УДОБНО! ;-)))

Ах, да! Чуть не забыл! Обязательно надо сделать вот так:

# Собственно, переопределяем класс логера.
setLoggerClass(RequestLogger)
# Перехватываем логирование у джанги
django.core.handlers.base.logger.__class__ = RequestLogger


При этом, джанга почему-то игнорирует смену класса (не разбирался ещё, честно говоря), потому делается это в 2 строчки - во второй влезаем непосредственно внутрь ядра Джанги. Впрочем, это вполне безопасно, на мой взгляд.

И да, от HTTPHandler всё-таки придётся порождать новый класс... Но это можно сделать прямо перед настройками логирования в project.settigs. Он очень маленький:

# Если хотим отправить сообщение о событии во внешний логгер, то нужно подготовить данные...
class NewHTTPHandler(HTTPHandler):

    def mapLogRecord(self, record):
        return {'id': logID, 'event_data': json.dumps(record.__dict__)}


То есть тупо сериализуем всё содержимое LogRecord и закидываем удалённому логгеру. Кстати, там тоже может присутствовать строчка Log.objects.create(**record), где record десериализуется прямо из запроса!

Что же имеем в итоге?

1. Вызов логера предельно прост, не нужно никаких дополнительных параметров!
2. Логирование осуществляется как в файл, так и в базу данных
3. При кластеризации проекта имеем собственный лог для каждого узла и суммарный - через удалённый логгер.
4. Можно выключить (закомментировав одн строчку) логирование в базу данных приложения и оставить логирование в базу данных удалённого логгера. Более того, для девелопмента предпочтителен лог в базе данных проекта, для продакшна - в базе данных удалённого логгера. То есть, можно ввести соответсвующую настройку в localenv.py.
5. У нас есть удалённый логгер! В настоящее время он допиливается под сбор логов от нескольких разных приложений, при чём написанных не только на питоне.

Только не спрашивайте, чем меня не устроил django-sentry, который делает практически то же самое! Во-первых, у проекта отключена джанго-админка (_категорическое_ требование заказчика!), во-вторых, нет необходимости подключать сторонние компоненты, всё и так у меня в ядре проекта. В-третьих, я попутно создал удалённый логгер, пригодный для использования в целом комплексе софта. Он уже использутся для сбора логов ещё из пары проектов, один из которых написан на PHP. Это, кстати, и послужило причиной смены сериализатора с питоновского cPickle на JSON...

P/S/ Данный текст написан прежде всего для себя. Но если кто-то найдёт здесь что-то полезное для себя - буду только рад! :-)

мистика в машине
main_pic
sewer_endemic
Каждый раз, когда сталкиваюсь с какой-либо мистикой в своём говнокоде, обнаруживаю следующие замечательные факты:
 во-первых, не мистика, а мистификация
 во-вторых, мистификатор - вот он, за клавиатурой сидит, точки и прочие малозаметные символы где не надо расставляет. X-D

Linux и AVerMedia AVer EZCapture
main_pic
sewer_endemic
Карточка определяется, но несколько некорректно. Драйвер bttv, т.к. карточка на Bt878, но вот тип карточки 0x00 "BT878 video ( *** UNKNOWN/GENERIC *** )"

Если скачать исходники ядра, то в них можно найти для этого драйвера список поддерживаемых устройств. А точнее - в файле bttv.h
Там имеется всего 2 устройства AVerMedia:
BTTV_BOARD_AVERMEDIA 0x06
BTTV_BOARD_AVERMEDIA98 0x0d

Делаем rmmod bttv
А затем modprobe -v bttv card=<номер из списка>
Опытным путём (всего-то две попытки! ;-))) ) удалось определить, что имеющейся у меня EZCapture соответствует 0x0d
Но в таком виде карточка будет работать до первой перезагрузки, потом придётся вручную снова перезагружать модуль драйвера с правильным значением card
Неудобно! Впрочем, это можно лекго исправить. Создаём файл /etc/modprobe.d/video4linux.conf (как и все действия - от рута, конечно же) и добавляем туда строчку:
options bttv card=0x0d tuner=-1 i2c_scan=0 i2c_hw=-1
Последние 2 параметра нужны, чтобы драйвер не опрашивал шину i2c при загрузке. Всё равно там он ничего не найдёт. А времени может на это потратить изрядно.

Вроде бы этого достаточно... Но вот тут: http://avreg.net/howto_linux-capture-cards.html советуют ещё initramfs пересобрать. Что ж, пересобираем!

update-initramfs -u

Перезагружаемся...  :-) Продолжение - в следующей серии! )))

Ещё немного на тему видеонаблюдения
main_pic
sewer_endemic
Ещё немного на тему видеонаблюдения. Зафиксировать, так сказать, итог дня. Эпически-фейлового дня.

Краткая история всей этой катавасии:
1. Некогда был собран сервер под видеонаблюдение в весьма скромной конфигурации (intel celeron уже не помню какой, 2GB ОЗУ, 80GB хард. пара карточек видеозахвата avermaedia ezcapture), в качестве софта был выбран наиболее примитивный вариант - motion, тогда ещё под FreeBSD.
Это была первая стабильная конфигурация.
2. Всегда хочется чего-то большего, например, слышать, чего ж там вещали на лестнице очередные кирбиноиды, ибо иногда полезно предупредить родных и близких, какие ещё сравнительно честные методы отъёма денег у населения на нас собираются испытать. В процессе подключения микрофона одна из ezcapture сгорела %-(
3. Вот тут порылась первая собака! ffmpeg нормально брать звук с микрофонного входа отказался, падал в segfault. Попытался обновить дерево портов и пересобрать. Пересобрался и даже поднялся ffserver, отдававший mpjpeg для motion и mp4 для записи. Со звуком, блин! Вот только motion упал. Совсем. Пересобрать из свежих портов не удалось, он оказался уже deprecated. В надежде на лучшее обновил саму фряху с 9.что-то-там на 10.2, motion и тут не ставился из пакетов и не собирался из портов. Так на сервере воцарилась убунта. Непривычно было поначалу жутко! С железом намучался уже тогда, ezcapture определилась криво и вместо номрального изображения был поток какой-то цветовой мути. К сожалению, весь процесс вправления мозгов убунте канул в Лету вместе с .bash_history. Зато с полпинка поднялся ffserver и тут же завёлся motion! Казалось бы, вот оно, счастье. Но! Старенький соплерон, собранный "из говна и палок", был загружен в среднем на 95%, а при попытке поток видео ещё и захватить, пусть и без перекодирования, стабильно 100% с пропуском фрагментов видео.
4. Попытка водрузить vloopback на убунту 12.04 благополучно провалилась, т.к. на тот момент v4l уже помер, а v4l2loopback по какой-то трудно устранимой причине (уже не помню!) не собрался. Пришлось оптимизировать работу ffmpeg на сколько это вообще возможно было.
5. Вот тут порылась вторая собака! По случаю необходимости поднятия дома платформы для разработки с кучей виртуальных машин внутри серверу был сделан неплохой апгрейд, теперь там core i7. Про виртуализацию как-нибудь в другой раз, а вот про видео - забавная история с настоящим сюрпризом и трагическим финалом прямо сейчас! ;-) Собрал, воткнул единственную оставшуюся у меня ezcapture и... Прямо как в недалёкое прошлое вернулся! По экрану полосы сплошные! "Кадры бегут" - сказал бы мой дедушка, если бы дожил до сего дня. В общем, синхронизации видео мне так и не удалось добиться. Есть предположение, что всё дело опять в драйверах. Вместо ezcapture был воткнут уже давно не использоваовшийся TV-тюнер avermedia avertv 307 studio, у которого, на первый(!!!) взгляд с драйверами всё хорошо оказалось.
6. Крайняя стабильная конфигурация софта: с /dev/video0 поток забирается ffserver'ом и транскодируется в 3 формата - mjpg, flv, mp4. motion мониторит mpjpeg-поток и даёт команду на запись/остановку mp4-потока.
7. И вот тут порылась третья собака! Почему-то самые свежие сборки ffmpeg транслируют видео без звука! Хотя, если непосредственно запустить захват в файл, звук есть. В общем, ffserver на данный момент молчит, как партизан и сдаваться не собирается, поплёвывая свысока на любой подсунутый ему конфиг (точнее, частично его игнорируя). Ах, да! Ещё один "приятный" момент, связанный с ffmpeg'ом. Он сам не умеет выбирать input у v4l2-девайса. И по умолчанию тащит поток из input=0, где ни шиша нет. А есть поток на input=1 (композитный вход), но переключать надо уже после запуска ffserver'а. При чём, после перезагрузки не всегда отрабатывает переключение входа из скрипта автозапуска.
8. И вот настал тот самый момент, когда подобное поведение мне изрядно надоело. Предпринял сегодня попытку заменить громоздкий внешне, но примитивный внутри ffserver на громоздкий и сложный внутри, но весьма компактный в использовании VLC. Как подружить VLC с чипом saa7134, я написал чутка ранее. Но. Не взлетело. VLC отдаёт mjpg в столь неудобоваримом виде, что motion не желает его жрать! Никакие уговоры motion и  VLC помириться и начать, наконец, конструктивный диалог, не помогли...
9. План дальнейших действий: попытаться в очередной раз собрать v4l2loopback, чтобы не занимать проессор транскодированием постоянно. Схема такая: motion присасывается к /dev/video0, а запись осуществляется с проброшенного gstreamer'ом потока с /dev/video0 через vloopback /dev/video10 (например 10 ;-).

Ну и вот, наконец-то, суть. Собираем v4l2loopback.

Вообще-то, по идее, достаточно выполнить apt-get install v4l2loopback-dkms
Но! Опять у меня всё не тик-так! Пакет скачался, установился (вроде бы), а modprobe v4l2loopback упорно говорит, что такого модуля НЕТ!
После трёх часов изрядного мозготраха выяснил, что в убунтовском репо версия 0.8.0, которая с относительно свежим же ядром не дружит. А значит, идём на страницу v4l2loopback на гитхабе и...

git clone https://github.com/umlaeute/v4l2loopback.git
cd v4l2loopback
make && make install

ВСЁЁЁЁЁЁЁЁЁ!

modprobe v4l2loopback
lsmod | grep v4l2loopback

v4l2loopback 36864 0
v4l2_common 16384 4 bttv,tuner,saa7134,videobuf2_core
videodev 159744 7 bttv,tuner,saa7134,v4l2loopback,v4l2_common,videobuf2_core

Что ж, на данный момент сие внушает некоторый... оптимизм. Завтра (точнее, уже сегодня) попытаюсь gstreamer'ом клонировать устройство и понизить роль ffmpega и VLC до поди-принеси-подай-пшёл вон, в смысле с клонированного устройства взять поток, перекодировать его как надо и по kill корректно завершить свою работу. А motion снова будет получать сырой (не транскодированный поток).

Ффффух! Всё, теперь спать! :-)))

Upd:

Продолжаем.
И так, лупбэк установлен, создаём виртуальные видео-устройства:
Если ранее уже выполняли модпроб, то выковыриваем модуль обратно
rmmod v4l2loopback
Теперь можно создавать
modprobe v4l2loopback video_nr=10,11

Я просто прибавил 10 к номеру реального устройства, у меня их 2. avertv и ezcapture
И вот результат:

root@srv:~# ls /dev/ | grep video
video0
video1
video10
video11

И это за-ме-ча-тель-но!
Чтобы каждый раз при перезагрузке не создавать лупбэк-устройства заново, в файл /etc/modules добавим строчку

v4l2loopback video_nr=10,11

Теперь gstreamer. В убунте 14.04.3 предустановлен gstreamer версии 0.10.36, попробуем воспользоваться!
Пробуем так:
gst-launch-0.10 v4l2:///dev/video0 ! v4l2sink device=/dev/video10
А в ответ... В ответ получаем замечательное по своей информативности сообщение "элемент v4l2sink не найден"

Ну хорошо, вот нет его, этого элемента. И даже всезнающий гугл с яндексом напополам не помогли сообразить на троих, где ж его взять?!
Ладно, эксперементировать. так уж эксперементировать, apt-get сообщаето некотором количестве gstreamer0.10-plugins...
Пропущу всё лишнее, нашёл v4l2sink в пакете gstreamer0.10-plugins-good

Ну а теперь-то - взлетит? А вот хрен вам поперёк всей вашей необъятной физиономии, товарищ эксперементатор!!!
Вот что говорит нам gstreamer

Установка конвейера в состояние PAUSED...
libv4l2: error getting pixformat: Недопустимый аргумент
ОШИБКА: Конвейер не хочет становиться на паузу.
ОШИБКА: из элемента /GstPipeline:pipeline0/GstV4l2Src:v4l2src0: Could not negotiate format
Дополнительная отладочная информация:
gstbasesrc.c(2830): gst_base_src_start (): /GstPipeline:pipeline0/GstV4l2Src:v4l2src0:
Check your filtered caps, if any
Установка конвейера в состояние NULL...
Освобождение конвейера...

Так... gstreamer не поднялся сам и "загнул" ffserver - после установки плагинов ffserver валится в segfault с тем же конфигом, с которым до этого прекрасно работал. Удаление плагинов gstreamer'а и переустановка ffmpeg'а не помогли.

Продолжаем колупать gstreamer, надо же как-то "растаскивать" поток на обнаружение движения и на запись?

Накопалось вот что. Вот эта самая ошибка вылезает, если исходный поток - interlaced, добавляем в конвейер деинтерлейсинг и всё запускается:

gst-launch-0.10 v4l2:///dev/video0 ! deinterlace method=linear ! v4l2sink device=/dev/video10

Но и тут пока результата нет, на выходе что у video0, что у video10 - синий экран. input по-умолчанию 0, а нужен 1
Пытаемся переключить:
v4l2-ctl -i 1 -d /dev/video0

Результат тот же, то есть полное наличие отсутствия этого самого результата...

Зато "починился" VLC, ему для корректной отдачи mpjpeg не хватало ровно одной вещи - в модуле standard вместо просто access=http указать более расширенно, вместе с mime-типом и разделителем:
access=http{mime=multipart/x-mixed-replace; boundary=--7b3cc56e5f51db803f790dad720ed50a}

Да, появился звук. Запись теперь идёт со звуком. Но теперь надо подобрать битрейт и что-то сделать с соотношением сторон изображаения. mjpg нормальный, 720*576, а вот h264 почему-то хоть и содержит, вроде бы, корректный размер изображения 720*576 (в свойствах потока VLC на удалённой машине пишет именно так!), но реально воспроизводится 720*630. Можно принудительно 5:4 при воспроизведении ставить, но это же не наш метод!!! ;-)
Ах, да! Ещё несколько снизилась загрузка процессора. У меня включён HT, доступно 8 виртуальных ядер. Из них ffserver кушал поболее 2-х (205-230%). VLC даёт 150-170%, т.е. "полтора ядра", что гораздо приятнее. Но у vlc пока что не нашёл, как добавить шумодав, аналогичный ffmpeg (фильтр hqdn3d), что даёт некоторый рост битрейта, шум плохо жмётся...

AverMedia AverTV 307 studio против VLC
main_pic
sewer_endemic
Сегодня попытался завести видеонаблюдение с захватом видео через старый ТВ-тюнер, т.к.  не менее старая плата видеозахвата avermedia ezcapture на новой материнке тупо не завелась.

Но и тюнер сходу начал чудить. Вот это посыпалось непрерывным потоком в консоль:
rawvideo decoder warning: invalid frame size (622080 < 933120)
Поиск дал только тот факт, что имеет место быть глюк или недоработка в v4l-драйверах для saa7134, на базе которого и создан этот тюнер. Ссылок на "правильные" драйвера никто не даёт, но есть некий workaround - задать вполне конкретную цветовую модель, а именно YUYV.
Вот так:
--v4l2-chroma=YUYV
После этого VLC начал получать изображение с утсройства.

Следующая проблема - mjpeg, формируемый VLC, содержит в себе нечто такое, от чего motion плюётся и ругается матом. Пока не решено.
Да, ezcapture на самом деле завелась, но отсутствует как класс синхронизация изображения. Предположительно, это тоже некорректный драйвер, но уже для Bt878...

?

Log in

No account? Create an account