Author Archives: Valor

Linux DLNA Server под Debian

Еще одна запись из серии «На память».

Не так давно приобрел себе консоль Sony PlayStation3 и по мимо игровых функций обнаружил, что она может выступать в роли сетевого медеа-плеера, что при наличии FullHD телека и WiFi люто сократит количество проводов да и вообще удобно..

И так сонька ищет в сети UPnP-DLNA (о том, что такое DLNA прочитайте тут) сервера, о том как расшарить контент с ПК в сеть по данной технологии в доках к PS есть только гайд для винды, что для меня не комильфо.. Порывшись в интернетах нарыл  проект Minidlna. О том, что его надо бы скачать и скомпилить думаю догадаетесь сами, благо процедура до боли простая.

Единственное, что я в данном случае сделал это собрал .deb пакет для ленивых, скачать его можно тут. Правда пока только для x86-64 :(

Новости — Java — Вышла …

Вышла IntelliJ IDEA 11. Тихо и незаметно вышла очередная версия IntelliJ IDEA — Java (и не только) IDE от JetBrains. Изменения коснулись буквально всех подсистем: UI, редактор кода, инструменты и инте…

VLC Mass IP cams transcoding

В общем есть такой тип проектов, в которых используют большое количество ip камер. Как правило это проекты связанные с городской безопасностью. Например http://www.podryad.tv/live_cam/ один из таких проектов..

В данной статье я расскажу как просто и эффективно реализовать подобный сервис. Соответственно будем считать, что камеры по городу или объекту расставлены, работают и доступны по сети. В данном конкретном случае будут использоваться камеры AXIS различных моделей. Благо интерфейс доступа к потоку у них стандартизирован.

Еще один не маловажный момент это однородность состава камер, я имею ввиду, что важно иметь камеры одного вендора и даже модели это позволит избежать мучений с настройкой и обслуживанием.

Кратко опишу техническую задачу.

Есть некоторое количество камер потоки с которых нужно транслировать на сайт предварительно перекодировав поток с заворачиванием в FLV контейнер. Как дальше оно будет разруливаться не особо важно.

Для решения задачи я взял всем известный проект VideoLan , который является не только прекрасным проигрывателем мультимедиа, но и не менее прекрасным медиа-сервером.

Небольшая ремарка по поводу производительности..

Рабочий проект содержащий > 60 камер, обслуживался одним сервером Dell R710 с двумя процессорами Intel Xeon SixCore X5650 @ 2.67GHz c 6Gb RAM при этом потоки принимались на один гигабитный сетевой интерфейс и через такой же отдавались.. Интенсивность потока в среднем 400Mb/s при разрешении картинки в 840х480.. Средняя загрузка системы за 5ть минут составляла примерно 30 единиц.

Работать все будет под управлением OS GNU/Debian 6.0. В принципе под другими дистрибутивами наверно будет работать не хуже.

И так начнем. Для начала нужно поставить необходимые пакеты, в частности сам vlc (а точнее vlc-noc) со всеми зависимостями. так же советую подключить репозитории проекта debian-multimedia если вам понадобится кодировать потоки например в h264.

Когда установка будет закончена, надо создать не привилегированного пользователя от имени которого и будет работать vlc.

Теперь собственно о том как настроить и как всем этим управлять..

В VLC есть такая приятная фишка как  VLM, по сути простой стрим лист. возможности которого по настоящему безграничны.

Вот пример такого листа:

new cam26 broadcast enabled
setup cam26 input http://user:*****@XXX.XXX.XXX.XXX/axis-cgi/mjpg/video.cgi
setup cam26 output #transcode{vcodec=FLV1,vb=5000,fps=25,ab=192,samplerate=48000}:std{access=http{mime=video/x-flv},mux=ffmpeg{mux=flv},dst=0.0.0.0:8080/stream26.flv}
control cam26 play

new cam25 broadcast enabled
setup cam25 input http://user:*****@XXX.XXX.XXX.XXX/axis-cgi/mjpg/video.cgi
setup cam25 output #transcode{vcodec=FLV1,vb=5000,fps=25,ab=192,samplerate=48000}:std{access=http{mime=video/x-flv},mux=ffmpeg{mux=flv},dst=0.0.0.0:8080/stream25.flv}
control cam25 play

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

Немного об опциях транскодинга и вывода.

#transcode{vcodec=FLV1,vb=5000,fps=25,ab=192,samplerate=48000}

  • vcodec — собственно видео кодек в нашем случае это флеш кодек.
  • vb — видео буфер — значение подбирается экспериментально, если изображение мигает или сыпется, данный параметр нужно увеличить. Помните, что увеличивая его вы увеличиваете количество пожираемой ОЗУ.
  • fps — количество кадров в секунду, советую устанавливать значение равное значению выставленному на камере.. Если сервер транскодинга слабоват, значение можно уменьшить, но и качество соответственно будет хуже.
  • ab — аудио буфер — если планируется трансляция звука.
  • samplerate — входная частота дискретизации.

{access=http{mime=video/x-flv},mux=ffmpeg{mux=flv},dst=0.0.0.0:8080/stream25.flv}

  • access — указывает на тип доступа к потоку, в нашем случае http
  • mime — mime тип для транспорта
  • mux — мультиплексор, в нашем случае ffmpeg, который будет заворачивать поток в контейнер flv
  • dst — собственно url по которому наш поток будет доступен для клиентов.

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

cvlc -d -q —pidfile=/var/run/vlc.pid —ttl 8 -I telnet —telnet-host=0.0.0.0 —telnet-password=********* —sout-transcode-high-priority —http-reconnect —http-caching=10000 —logmode=syslog —rt-priority —vlm-conf /home/vlc/vlc-stream-list.cfg

Разберем ее по порядку:

  • -d — опция которая говорит vlc запускаться как демон
  • -q — опция подавляющая вывод в stdout/stderr сообщений.
  • —pidfile — опция указывающая где будет создан файл идентификатора процесса, так как наш сервер будет запущен с опцией -d то это будет актуально, особенно если вы захотите в дальнейшем написать скрипт для /etc/init.d
  • —ttl — параметр не особо важный, но все же. Его назначение это указание маршрутизаторам время жизни транспортного пакета.. по истечению которого оный будет уничтожен.. Если трансляция осуществляется в рамках одного сегмента, можно установить параметр в единицу, меньше мусора будет.
  • -I тип интерфейса управления, в нашем случае telnet, есть еще http и что-то еще я уже не помню..
  • —telnet-host — с какого адреса ждать подключения к telnet консоли.
  • —telnet-password — тут все понятно..
  • —sout-transcode-high-priority — опция говорит что процессы транскодинга должны выполнятся с наивысшем приоритетом (аналог системной утилиты nice).
  • —http-reconnect — опция заставляющая vlc пытаться восстановить соединение с http транспортом камер. (При условии что таковой используется, если забор потока идет через rtp сия опция бесполезна)
  • —http-caching — кеш http трафика полезная опция при высокой нагрузке на сеть
  • —logmode логи работы vlc будут обрабатываться системными средствами, в частности syslog
  • —rt-priority очень важная опция заставляющая работать процессы vlc  с приоритетом реального времени. ВНИМАНИЕ! Если на сервере транскодинга запущены другие программы фонового характера работы.. Данная опция может серьезно снизить их производительность.
  • —vlm-conf путь до стрим листа.

Вот собственно и все..

Дальше все, что вам нужно сделать это забить стрим лист данными о потоках и запустить сервер.

Вот так после запуска это выглядит у меня :

Работа vlc транскодера на 60 потоках.

Теперь надо подумать о том как этим управлять..

Для управления я сделал следующее:

  1. Создал таблицу в бд где храню информацию о потоках, их номера параметры кодирования и вывода, IP адреса логины и пароли для доступа к камерам и многое другое, что сильно упрощает жизнь.
  2. На сервер я поместил скрипт который по крону или в ручную регенерирует стрим лист и перезагружает сервер.
  3. Написал небольшую программку для управления записями в БД.

Собственно все.. Кому будет интересно могу предоставить структуру таблицы и сходный код программки..

За сим откланюсь, надеюсь изложенная информация будет вам интересна и полезна.

Успехов.

Новая запись в блог…

Новая запись в блоге. Посвящена процессу организации трансляции потоков с IP камер на сайтах.

Iptables and Ipset in Debian

Включаем поддержку ipset в iptables.

Актуально для ядер > 2.6.30

# apt-get install module-assistant xtables-addons-source
# module-assistant prepare
# module-assistant auto-install xtables-addons-source

# # modprobe ipt_set

Python 2.5 & python-mysqldb : TypeError: not all arguments converted during string formatting

Баг в python-mysqldb Debian 5.0.6

Фаил : /usr/site/lib/python2.5/site-packages/MySQLdb/cursors.py

Пакет :

Package: python-mysqldb
Priority: optional
Section: python
Installed-Size: 360
Maintainer: Debian Python Modules Team Architecture: amd64
Version: 1.2.2-7

Пример :

sql = '''INSERT INTO call_fail_log (`callerid`,`date_stamp`,`cause`) VALUES(%s,FROM_UNIXTIME(%s),%s)'''
        cursor = self.db_init().cursor()
        cursor.executemany(sql,[(callerid,datestamp,cause)])
        self.db_init().close()

Третий плейсхолдер не работоспособен, причина — неправильная работа регулярного вырождения при парсинге.

Решение :

Патч.

--- cursors.py~ 2007-02-11 07:48:19.000000000 -0800
 +++ cursors.py 2008-10-09 14:31:27.000000000 -0700
 @@ -6,7 +6,14 @@
 """

 import re
 -insert_values =
 re.compile(r"
\svalues\s*(\(((?<!\\)'.*?\).*(?<!\\)?'|.)+?\))",
 re.IGNORECASE)
 +
 +restr = (r"
\svalues\s*"
 + r"
(\(((?<!\\)'[^\)]*?\)[^\)]*(?<!\\)?'"
 + r"
|[^\(\)]|"
 + r"
(?:\([^\)]*\))"
 + r"
)+\))")
 +
 +insert_values= re.compile(restr)
 from _mysql_exceptions import Warning, Error, InterfaceError, DataError,
 \
DatabaseError, OperationalError, IntegrityError, InternalError, \
NotSupportedError, ProgrammingError

Накладываем патч на /usr/site/lib/python2.5/site-packages/MySQLdb/cursors.py , радуемся.

Asterisk логирование не обслужанных звонков

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

Замутим логирование кодов закрытия канала связи..

Что есть : Asterisk 1.4 , Python 2.5 , MySQL 5.1

Создадим табличку для MySQL в которую и будем складывать высеры Астериска.

Будем сохранять:
callerid — собственно номер звонившего
date_stamp — timestamp обрыва канала (HANGUP,CANCEL, etc..)
cause — код причины
status и comment — зело не обязательно, статус записи (типа обслужено или нет) и комментарий

CREATE TABLE `asterisk`.`call_fail_log` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`callerid` VARCHAR(11) DEFAULT NULL,
`date_stamp` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`cause` VARCHAR(15) DEFAULT NULL,
`status` tinyint(1) DEFAULT '0',
`comment` text,
PRIMARY KEY (`id`),
KEY `call_index` (`callerid`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8

Теперь сварганим махонький python agi скрипт который непосредственно сохранять данный в базу..

#!/usr/bin/env python

'''
Created on 05.08.2011

@author: valor
'''

import MySQLdb
import sys

class CallFailLog:

    DBHOST = "192.168.158.160"
    DBUSER = "agi"
    DBPASS = "asterisk"
    DBNAME = "asterisk"

    def __init__(self):
        pass

    def db_init(self):
        connect = MySQLdb.connect(db=self.DBNAME, host=self.DBHOST, user=self.DBUSER, passwd=self.DBPASS)
        return connect

    def main(self,callerid, datestamp, cause):
        sql = '''INSERT INTO call_fail_log (`callerid`,`date_stamp`,`cause`) VALUES(%s,FROM_UNIXTIME(%s),%s)'''
        cursor = self.db_init().cursor()
        cursor.executemany(sql,[(callerid,datestamp,cause)])
        self.db_init().close()

if __name__ == '__main__':
    app = CallFailLog()
    app.main(sys.argv[1], sys.argv[2], sys.argv[3])

Как видим все просто и легко..
Теперь лезем в extensions.conf и сделаем макрос обработки кодов + посмотрим откель его можно вызвать..

Собственно макрос:

[macro-logfailcall]
exten => s,1,GotoIF($["${ARG1}" = "16"]?goin:end)
exten => s,n(goin),DeadAGI(callfaillog.py,${CALLERID(num)},${EPOCH},${ARG1})
exten => s,n(end),NoOp(&gt;&gt;&gt; Unknown cause ${ARG1})
exten => s,n,Hangup()

Код завершения 16 NORMAL_CLEARING  — есть не что иное как положенная абонентом трубка.

Вызвать его можно в любом контексте, например так..

...
exten => h,1,Macro(logfailcall,${HANGUPCAUSE})
...

a: Вызывается, когда пользователь нажимает ‘*’ во время проигрывания приветствия системы голосовой почты.
h: Вызывается, по завершению вызова. 
i: Вызывается, при попытке вызова неизвестного екстеншена.
o: Расширение оператора, используется для обработки нажатия нуля в системе голосовой почты.
s: Стартовое расширение в контексте.
t: Вызывается, при наступлении состояния таймаута.
T: Вызывается, при наступлении состояния абсолютного таймаута, заданного функцией AbsolutTimeout?.
e: Перехват расширений i,t и T для обработки ошибки в едином месте. Для выяснения типа ошибки может быть использована функция EXCEPTION(?)
failed: Используется, если auto-dial out вызов завершился неудачно (который имеет определённый контекст, приоритет и расширение).
fax: Используется для определения факса на Zap каналах.
talk: Используется в конъюнкции с BackgroundDetect?.

Полезные ссылки : Коды причин закрытия канала

Удачи..

VsFTPD and Virtual users on Debian

На заметку..

Пакеты:

apt-get install libpam-pwdfile vsftpd

Предположим, что сайтики лежат в /srv/www/{sitename — вида www.mysite.ru}/htdocs , соответственно ограничиваем пользователя доступом в корень, т.е. не выше директории www.mysite.ru.

И так чистим дефолтный конфиг vsftpd

echo "" > /etc/vsftpad.conf

И записываем туды следующие..

listen=YES
anonymous_enable=NO
local_enable=YES
write_enable=YES
local_umask=022
# пользователя vsftpd нужно создать системным без шела, например useradd -M -r vsftpd
nopriv_user=vsftpd
virtual_use_local_privs=YES
guest_enable=YES
user_sub_token=$USER
# Токен $USER предполагает что к пути /srv/www/ будет добавлено имя пользователя, он и будет являться корнем для него.
local_root=/srv/www/$USER
chroot_local_user=YES
hide_ids=YES
guest_username=vsftpd
chown_uploads=YES
# От имени и группы www-data пашет Apache.
chown_username=www-data
guest_enable=YES
guest_username=www-data
chown_upload_mode=0775

use_localtime=YES
xferlog_enable=YES
xferlog_file=/var/log/vsftpd.log
xferlog_std_format=YES

dirmessage_enable=YES
ascii_upload_enable=YES
ascii_download_enable=YES
ftpd_banner=Welcome developer to WWW server!
ls_recurse_enable=YES
secure_chroot_dir=/var/run/vsftpd/empty

pasv_enable=YES
pasv_min_port=62000
pasv_max_port=64000

Далее надо настроит pam аутентификацию, для сего лезим в /etc/pam.d/vsftpd и комментим все попутно добавив туда две строчки

auth required pam_pwdfile.so pwdfile /etc/ftpd.passwd
account required pam_permit.so

Собственно создаем файлик /etc/ftpd.passwd командой

htpasswd -c /etc/ftpd.passwd

Добавление пользователя осуществляется аналогично, только без опции -с и с названием корневой директории сайта в качестве пользователя

htpasswd /etc/ftpd.passwd www.mysite.ru

Ребутим vsftpd и радуемся..

Asterisk — запись исходящих звонков.

Задача: Записывать исходящие звонки от некоторого подмножества абонентов корпоративной телефонной сети.

Что есть в наличии: Asterisk 1.4* — вот с ним то и будем плясать..

Решение:  Список жертв будет храниться в бд, в моем случае это MySQL, для этого сварганим не хитрую табличку:

CREATE TABLE `asterisk`.`callout` (
`id` INT(3) NOT NULL AUTO_INCREMENT,
`callerid` INT(4) NOT NULL,
`status` TINYINT(1) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`)
)
ENGINE = MyISAM;

Поясню: Поле  callerid — будет хронить 4х значный номер внутреннего абонента. status — просто флаг который позволит нам например временно исключать номер из списка записывающихся..

Далее давайте напишим AGI скрипт, который будет пробивать переданный ему в качестве аргумента callerid и если таковой там имеется, то вновь созданный канал будет записан.

Пример скрипта:

#!/usr/bin/env python
'''
Created on 26.05.2011

@author: valor
'''

import MySQLdb
from asterisk.agi import AGI
import sys

class calloutrec:

DBHOST = "localhost"
DBUSER = "agi"
DBPASS = "asterisk"
DBNAME = "asterisk"

DEBUG = 3

agi = AGI()

def db_init(self):
connect = MySQLdb.connect(db=self.DBNAME, host=self.DBHOST, user=self.DBUSER, passwd=self.DBPASS)
return connect

def main(self,arg):

    self.agi.verbose(">>> start executing AGI script ", self.DEBUG)

    cursor = self.db_init().cursor()
    cursor.execute('''SELECT c.callerid FROM callout c WHERE c.status = 1 AND c.callerid =%s''',arg)
    res = cursor.fetchone()
    if res != None:
        self.agi.set_variable("ISREC", 1)
    else:
        self.agi.set_variable("ISREC", 0)

if __name__ == '__main__':
app = calloutrec()
app.main(sys.argv[1])

В основе скрипта лежит мини фреймворк pyst , один из немногих еще не здохших проектов — рекомендую, жизнь облегчит.

Теперь осталось подправить extensions.conf , для начала создадим макрос следующего содержания

[macro-calloutrec]
exten => s,1,AGI(callaoutrec.py,${ARG1})
exten => s,n,NoOp(${ISREC})
exten => s,n,GotoIF($["${ISREC}" = "1"]?rec:norec)
exten => s,n(rec),MixMonitor(/raid/outmonitor/${EPOCH}-${CALLERID(num)}.wav)
exten => s,n(norec),NoOp(${CALLERID(num)} >>> No record call)

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

Осталось применить макрос в контексте через который идут исходящие звонки. примерно так:

exten =&gt; _XXXXXX.,n,Macro(calloutrec,${CALLERID(num)})

Вот и все.. записанные файлики в формате wav будут складываться в директорию  /raid/outmonitor/ с именем фала вида utc-callerid.wav .

ЗЫ. Для сортировки полученных фалов можно использовать скрипт из предыдущей статьи.

Asterisk — укладываем фалики по полочкам.

Для тех кто использует Asterisk для построения колцентров..

Если вы ведете запись входящих звонков в очередях (asterisk: Queues), то рано или поздно встает вопрос об вменяемой организации нескольких десятков (может быть и сотен) тысяч файлов в нечто удобное для поиска и прочих манипуляций..

Я в свою очередь предлагаю хранить все файлы как дерево вида: YYYY/MM/DD/ , для сего написал небольшой скриптик на питоне..

#!/usr/bin/env python
'''
Created on 03.01.2011

@author: valor
'''

import time
import os
from shutil import move

class AstMonFLSorter:

    store_root = "/raid/monitor/"

    def __init__(self):
        pass

    def utc_get_date(self,utc):
        return time.strftime("%Y:%m:%d", time.localtime(utc))

    def main(self):
        count = 0
        for fname in os.listdir(self.store_root):
            utc = fname[:10]
            year = self.utc_get_date(float(utc))[:4]
            month = self.utc_get_date(float(utc))[5:7]
            day = self.utc_get_date(float(utc))[8:10]
            curday = self.utc_get_date(time.time())[8:10]

            if not os.path.isdir(self.store_root+year):
                print ">>> create "+year+" folder"
                os.mkdir(self.store_root+year)
            if not os.path.isdir(self.store_root+year+"/"+month):
                print ">> create "+month+" folder"
                os.mkdir(self.store_root+year+"/"+month)
            if not os.path.isdir(self.store_root+year+"/"+month+"/"+day):
                print "> create "+day+" folder"
                os.mkdir(self.store_root+year+"/"+month+"/"+day)
            else:
                if os.path.isfile(self.store_root+fname) and day != curday:
                    move(self.store_root+fname, self.store_root+year+"/"+month+"/"+day)
                    count +=1
                    print "=="+str(count)+"== moved file "+fname+" to > "+self.store_root+year+"/"+month+"/"+day


if __name__ == '__main__':
    astmon = AstMonFLSorter()
    astmon.main()

Вот как-то так.. Есть замечания или предложения, или может есть ошибки? Пишите все поправим..