среда, 7 октября 2009 г.

Qt4 эмуляция сокета с помощью QBuffer в юнит тестах

В одной своей разработке, я решил реализовать работу с сетью не напрямую через QTcpSocket, а через общий интерфейс для всех устройств ввода вывода имеющийся в Qt4, QIODevice. И в первую очередь решил сэкономить время при написании юнит тестов, используя вместо честных сокетов QBuffer, имплементацию интерфейса QIODevice, пишущую и читающую данные из буфера в оперативной памяти. Но не тут то было.

Первое, что я выяснил, что сигнал QIODevice::readyRead() выбрасывается из EventLoop'а даже в случае такого простого класса, как буфер. Так что первое что пришлось сделать это понять как во время выполнения теста, не надолго отлучиться из тестовой функции в EventLoop. Первая идея завести свой QEventLoop прямо в тесте запустить, его не забыв предварительно задать условия его остановки, например так:
QTimer::singleShot(500,myELoop,SLOT(quit()))

За пол секунды EventLoop заведомо сделает всё необходимое, и тест можно будет завершать. Но выглядит это решение откровенно говоря не очень. Покопавшись в документации, я нашёл более элегантное решение:
void QTest::qWait(int ms)

приостанавливает выполнение текущего теста на заданное количество миллисекунд отдавая управление EventLoop'у. Причём экспериментальным путём было выяснено, что все необходимые операции цикл обработки событий успевает проделать за одну миллисекунду. Подозреваю, что получив управление EventLoop не возвращается раньше, чем сгенерирует все сигналы, хотя это я не проверял.

Для эмуляции сокета я решил создать два объекта QBuffer с общим буфером, но фокус не прошёл, так как класс QBuffer не генерирует сигнал readyRead если содержимое буфера изменилось без помощи его методов. По всей видимости просто нету надёжного механизма сделать это, так как QByteArray используемый в качестве буфера данных не имеет никаких механизмов оповещения об изменении содержащихся в нём данных. После внимательного перечитывание документации по Qt4 и нескольких экспериментов я пришёл к следующему решению. Создаём два объекта QBuffer после чего пишем в один из них данные и выполняем "передачу данных по сети вручную":
 quint64 pos = dev2.pos();  
 dev2.write( dev1.buffer() );  
 dev2.seek(pos);  
 QTest::qWait(1);  

Тут стоит отметить, что тот факт, что сигнал readyRead() генерируется в цикле обработке событий, а не непосредственно в функции write() есть очень хорошо. Ведь если бы он генерировался сразу, то его обработчик был бы вызван раньше, чем я верну указатель на текущую позицию в устройстве в прежнее значение и обработчик счёл бы, что на самом деле новых данных не прибыло. Вышеприведённый код имеет смысл обернуть в некую функцию, чтобы не страдать копипастом понарпасну.

В общем на изобретение такого метода ушло около двух часов, но результат того стоил. Мне не нужно создавать QTcpServer и слушать какой-нибудь порт, который по абстрактным соображениям вроде никто другой слушать не должен. Я могу эмулировать сетевое соединение используя пару буферов в оперативной памяти.

воскресенье, 7 декабря 2008 г.

Полноценный ffmpeg в Ubuntu

В связи с тем, что в некоторых странах (Америка, Австралия) можно патентовать алгоритмы, поддержка некоторых форматов в ffmpeg в Ubuntu искуственно отключена. В частности пострадал формат mp3. Бинарный пакет из репозитория может декодировать mp3, но не может его создавать. Дабы обойти сие ограничение, необходимо пересобрать пакет вручную.

Итак, для начала потребуются кое какие доп пакеты:
 sudo aptitude install build-essential fakeroot  
Достаём исходный код ffmpeg:
 apt-get source ffmpeg  
Будет выкачан и распакован архив с исходным кодом, на него будут наложены специфичные для дистрибутива патчи, и будут добавлены скрипты сборки пакета. Далее необходимо перейти в директорию с исходными кодами ffmpeg-debian... название директории может зависеть от версии пакета.

Теперь необходимо разрешить сборку дополнительных кодеков. Для этого нужно добавить в переменную DEB_BUILD_OPTIONS ключ externalcodecs (это актуально для 8.10, меня терзают смутные сомнения, что раньше нужно было использовать ключ risky):
 export DEB_BUILD_OPTIONS+=externalcodecs  
После этого нужно установить библиотеки необходимые для сборки пакета:
 sudo aptitude build-dep ffmpeg  
Теперь можно собрать и установить deb-пакеты:
 dpkg-buildpackage -b  
 cd ../  
 sudo dpkg -i *.deb  
Теперь можно использовать 100% возможностей ffmpeg

Video watermark

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

Но есть куда более ндёжный способ. Можно добавить в видео водяной знак. Он отчётливо виден, но не акцентирует на себе внимания, и его достаточно проблемно устранить. Настолько проблемно, что из любительского видео его точно вырезать никто не станет.

Итак, изначально у нас есть полностью отредактированное видео в файле final.dv, мы хотим получить видео с водяным знаком, которое можно раздовать друзьям for_share.dv. Я продпочитаю работать с сырым DV, но, на самом деле, формат не имеет значения, главное, чтобы его понимал ffmpeg. Собственно добавлять водяной знак будет именно он.

Для начала создадим шаблон водяного знака. Это должен быть рисунок с однородным цветом фона, лучше всего если это будет рисунок в оттенках серого. Сам шаблон водяного знака это всё то, что отличается от цвета фона. Я создаю шаблон следующим образом:
 convert -size 1024x576 xc:#000000 -font Bookman-DemiItalic -pointsize 50 -fill "#909090" -draw "text 810,560 'VestniK'" -gaussian 2,10 -shade 180x30 watermark.gif  

для видео в формате 16:9. Если требуется соотношение сторон 4:3, то нужно поменять размер.

Терерь можно подписать исходное видео:
 ffmpeg -i final.dv -vhook '/usr/lib/vhook/watermark.so -f watermark.gif -t 0x7F7F7F' -target dv for_share.dv  


Ещё одна мелочь по поводу видео. На видео хостингах как правило закаченное видео пережимается в flv со звуком в mp3. Если заливать видео в этом формате, то оно пережиматься не будет. Так можно получить больший контроль над качеством видео. Готовый рецепт по сохранению видео для публикации в интернете выглядит следующим образом:
 ffmpeg -i for_share.dv -f flv -vcodec flv -qscale 15 -maxrate 512k -bufsize 1M -s 480x270 -padtop 30 -padbottom 60 -acodec mp3 -ab 128k -ar 44100 for_internet.flv  

Помимо всего прочего здесь я подгоняю видео под соотношение сторон 4:3, так как у меня видокамера снимает видео с соотношением сторон 16:9. Если у вас видео имеет соотношение сторон 4:3, то команда для конвертации видео упростится:
 ffmpeg -i for_share.dv -f flv -vcodec flv -qscale 15 -maxrate 512k -bufsize 1M -s 480x360 -acodec mp3 -ab 128k -ar 44100 for_internet.flv  
.

среда, 29 октября 2008 г.

QtXml DOM создание некорректного документа

В Qt4 есть библиотека классов для работы с XML. И там есть набор классов для работы с DOM. Только что обнаружил там весьма неприятную фичу. Дело в том, что QDomDocument является наследником QDomNode и следовательно к нему можно добавлять любое количество child нод с помощью функции QDomNode::appendChild(). При этом добавлении не проверяется, валидности этой операции с точки зрения структуры XML документа и можно создать например такой документ:
 <!DOCTYPE MyDocType>  
 <root1>  
   <tag>text<tag>  
 </root1>  
 <root2>  
   <tag>text<tag>  
 </root2>  

В котором содержится более чем один корневой элемент.
Что примечательно, если попытаться распарсить такой XML с помощью функции QDomDocument::setContent(), то будет выданна ошибка.
Как будет время покопаюсь в багрепортах и списках рассылки Qt4. Я эту фичу обнаружил в Qt 4.4.

суббота, 7 июня 2008 г.

Создание SVN репозитория

Итак, я решил создать SVN репозиторий, для одного проекта, который мы неоднократно обсуждали с одним моим другом. И я даже уже начал писать его части.
Раньше я неоднократно порывался создать единый репозиторий для всех проектов, но натыкался на некоторые неудобства работы с таким глобальным репозиторием. Поэтому я решил создавать для каждого проекта свой репозиторий. Все репозитории я решил складывать в одну директорию /mnt/store2/svn. В первую очередь необходимо создать каталог для моего проекта, и создать репозиторий в нём:
 mkdir /mnt/store2/svn/MahJong-Night  
 svnadmin create /mnt/store2/svn/MahJong-Night  
В принципе репозиторий создан, и можно импортировать в него код, но пока что код можно импортировать только локально указывая в качестве имени репозитория file:///mnt/store2/svn/MahJong-Night. Чтобы иметь возможность работать с этим репозиторием по сети необходимо запустить SVN сервер. Это делается следующим образом:
 svnserve -d -T -r /mnt/store3/svn  
Ключ -d необходим для запуска сервера в режиме демона, -T для обработки запроса в отдельном потоке вместо того, чтобы создавать отдельные процессы, а -r для того, чтобы при работе по сети были доступны только репозитории расположенные в указанной директории.
После того, как сервер запущен необходимо изменить настройки доступа к репозиторию по сети. Они храятся в файле conf/svnserve.conf в директории репозитория (соответственно полный путь будет /mnt/store2/svn/MahJong-Night/conf/svnserve.conf). Так как я хочу обеспечить аноимный доступ на чтение и доступ на дапись только для авторизованных пользователей, то содержимое этого файла должно быть следующим:
 [general]  
 anon-access = read  
 auth-access = write  
 password-db = passwd  
 realm = MahJong-Night project repository.  
Так же необходимо подредактировать файл passwd в той же директории, приведя его примерно к следующему виду:
 [users]  
 vestnik = *******  
Думаю, что комментарии к содержимому этих файлов излишни. Да, пароль здесь храниться в явном виде, а это наводит меня на мысль о настроке доступа через ssh+svn с авторизацией по ключам. Но это как-нибудь в другой раз.
Теперь можно импотировать написанный код. Переходим в директорию с кодом и выполняем:
 svn import mahjong-night svn://localhost/MahJong-Night --username vestnik --password *******