Добрый день!
Недавно в нашей компании возникла потребность создания своего видеоресурса, закрытого, но в тоже время немного публичного. И вот наконец, он закончен и я готов поделиться знаниями и применениями.
Задача была следующая:
Создать видеоресурс, способный проводить многопоточные односторонние трансляции с web камеры, а так же из любого файла (это например для защиты от прямого скачивания), видеошару с возможностью просмотреть видео в разных форматах и битрейтах.
В основу лег освободившийся сервер! Не очень мощный, но довольно таки подходящий.
Intel® Xeon® CPU L5520 @ 2.27GHz
количество ядер 16
оперативной памяти 16372 Мб
Немного забегу вперед, при декодировании видео процессорная нагрузка достигает 500% (примерно 6 ядер);
Начнем с самого начала, из ОС я выбрал Ubuntu Server 13.04 x64 ввиду того, что больше времени провожу с ней и собственно разбираюсь я в ней лучше чем в других семействах Linux.
В качестве WEB сервера я выбрал связку nginx+php5-fpm, потому что nginx довольно успешно справляется с нагрузками, а так же отдачей видео.
nginx по умолчанию ставится без потокового модуля, поэтому ставим из сорцов
Необходимые зависимости для сборки пакетов:
apt-get install build-essential checkinstall subversion unzip yamdi imagemagick php5-curl libssl-dev zlib1g-dev libpcre3-dev rpl php5-fpm git
Скачиваем исходники:
cd /tmp
wget http://nginx.org/download/nginx-1.5.2.zip
unzip nginx-1.5.2.zip -d nginx/
rm -f nginx-1.5.2.zip
cd nginx
Скачиваем необходимые модули для стриминга:
mkdir modules
git clone https://github.com/masterzen/nginx-upload-progress-module.git modules/nginx-upload-progress-module
wget http://www.kernel-video-sharing.com/files/nginx_mod_h264_streaming-2.3.2.zip
unzip nginx_mod_h264_streaming-2.3.2.zip -d modules/
rm -f nginx_mod_h264_streaming-2.3.2.zip
git clone https://github.com/arut/nginx-rtmp-module.git modules/nginx-rtmp-module
Для удобства создаем установочный скрипт:
touch nginx.sh
nano nginx.sh
с содержимым
./configure
--conf-path=/etc/nginx/nginx.conf
--error-log-path=/var/log/nginx/error.log
--pid-path=/var/run/nginx.pid
--lock-path=/var/lock/nginx.lock
--http-log-path=/var/log/nginx/access.log
--http-client-body-temp-path=/var/lib/nginx/body
--http-proxy-temp-path=/var/lib/nginx/proxy
--http-fastcgi-temp-path=/var/lib/nginx/fastcgi
--with-debug
--with-http_stub_status_module
--with-http_secure_link_module
--with-http_gzip_static_module
--with-http_realip_module
--with-http_mp4_module
--with-http_flv_module
--with-http_ssl_module
--with-http_dav_module
--with-md5=/usr/lib
--add-module=modules/nginx-upload-progress-module
--add-module=modules/nginx-rtmp-module
--add-module=modules/nginx_mod_h264_streaming-2.3.2
make -j16 (16 - количество ядер. ускоряет сборку пакета. Можно узнать командой "grep -c processor /proc/cpuinfo")
checkinstall
Возможно в процессе компиляции могут возникнуть ошибки. Поэтому делаем так:
В файле auto/cc/gcc комментируем строчку:
#CFLAGS="$CFLAGS -Werror"
Запускаем:
sh nginx.sh
После установки создаем необходимые симлинки и директории (если не создались):
ln -s /usr/local/nginx/sbin/nginx /usr/sbin/nginx
mkdir -p /var/lib/nginx/body
mkdir /var/lib/nginx/proxy
mkdir /var/lib/nginx/fastcgi
chown -R root /var/lib/nginx/
wget http://nginx-init-ubuntu.googlecode.com/files/nginx-init-ubuntu_v2.0.0-RC2.tar.bz2
tar -jxvf nginx-init-ubuntu_v2.0.0-RC2.tar.bz2 -C /etc/init.d/
chmod 715 /etc/init.d/nginx
/usr/sbin/update-rc.d -f nginx defaults
rm -f nginx-init-ubuntu_v2.0.0-RC2.tar.bz2
rpl 'DAEMON=/usr/local/sbin/nginx' 'DAEMON=/usr/local/nginx/sbin/nginx' /etc/init.d/nginx
rpl 'NGINX_CONF_FILE="/usr/local/nginx/conf/nginx.conf"' 'NGINX_CONF_FILE="/etc/nginx/nginx.conf"' /etc/init.d/nginx
На этом установка nginx и php5-fpm завершена.
К настройке вернемся позже.
Следующим на очереди стоит ffmpeg. Установка apt-get не желательна, проект уже депрекейтет и многое отказывается работать. В поисках адекватной и более свежей инструкции я провел почти 2 ночи. Не скрою, нашел доповольно таки хороший пак для установки.
Как ни странно проект называется www.ffmpeginstaller.com и даже при наличии всех инсталяторов в открытом доступе предлагает свои услуги за 50 баксов.
А делается все довольно просто.
Скачиваем пакет:
cd /tmp
wget http://mirror.ffmpeginstaller.com/old/scripts/ffmpeg7/ffmpeginstaller.7.4.tar.gz
tar -xzf ffmpeginstaller.7.4.tar.gz ffmpeg
cd ffmpeg
После первой установки я понял… не хватает кодека. Отмотаем <<< Ставим кодек до установки:
apt-get install libvpx
Открываем ffmpeg.sh и добавляем кодек в установку:
nano ffmpeg.sh
./configure --prefix=$INSTALL_DDIR --enable-shared --enable-nonfree
--enable-gpl --enable-pthreads --enable-libopencore-amrnb --enable-decoder=liba52
--enable-libopencore-amrwb --enable-libfaac --enable-libmp3lame
--enable-libtheora --enable-libvorbis --enable-libx264 --enable-libxvid <b>--enable-libvpx</b>
--extra-cflags=-I/usr/local/cpffmpeg/include/ --extra-ldflags=-L/usr/local/cpffmpeg/lib
--enable-version3 --extra-version=syslint
На этом подготовка завершена. Можно устанавливать: sh install.sh
Установка займет минут 15-20 в зависимостти от возможностей железа. Можно пойти попить чаю (или кофе).
После установки выполняем:
hash x264 ffmpeg ffplay
Все. Поздравляю, мы это сделали!
Теперь нам нужна CMS для управления этими инструментами
Вариантов было немного, а точнее всего 2 (а под мои нужды подходила только одна — cumulusclips).
Код исходников понятен, без лишней нервотрепки разобрался с составляющей. Вот только пришлось проект переписывать с mysql на mysqli. Весь код CMS структурирован и темлпейты лежат отдельно и гибко настраиваются. За основу выбрал шаблон псевдо Ютуба.
Пришлось полностью перекрутить проигрыватель, т.к. jwplayer был неспособен переключать видеопотоки. Немного полазив по github нашел незамысловатый плеер под простым названием jQplayer.
Данный плеер способен легко переключать потоки. Правда есть один минус. Проигрывание файла начинается с начала. И это не оказалось проблемой — видеофайлы легко режутся nginx из коробки.
Теперь нам нужно настроить web
Прилагаю небольшой скрипт для автоматизации данного процесса. В комплекте с CMS лежал .htaccess — а nginx его понимать отказывается, поэтому я его переписал под нужды данного web сервера.
#!/bin/bash
echo -n "Введите имя создаваемого хоста: "
read host
echo -n "Введите имя пользователя nginx: "
read users
sap=/etc/nginx/sites-available/$host.conf
mkdir -p /var/hosting/
touch $sap
chmod 777 $sap
directives="upstream backend-${host} {server unix:/var/run/php5-${host}.sock;}
server {
listen 80;
server_name ${host} www.${host};
root /var/hosting/${host}/www;
access_log /var/log/nginx/${host}-access.log;
error_log /var/log/nginx/${host}-error.log;
index index.php;
rewrite_log on;
if ($host = '${host}' ) {
rewrite ^/(.*)$ http://www.${host}/$1 permanent;
}
location /im {
rewrite ^/im/(.*)$ /cc-core/controllers/thumbs.php?$1 last;
}
location /videos {
rewrite ^/videos/([0-9]+)/(.*)$ /cc-core/controllers/play.php?vid=$1 last;
rewrite ^/videos/page/([0-9]+)/$ /cc-core/controllers/videos.php?page=$1 last;
rewrite ^/videos/(most-recent|most-viewed|most-discussed|most-rated)/$ /cc-core/controllers/videos.php?load=$1 last;
rewrite ^/videos/(most-recent|most-viewed|most-discussed|most-rated)/page/([0-9]+)/$ /cc-core/controllers/videos.php?load=$1&page=$2 last;
rewrite ^/videos/([a-zA-Z0-9-]+)/$ /cc-core/controllers/videos.php?category=$1 last;
rewrite ^/videos/([a-zA-Z-]+)/page/([0-9]+)/$ /cc-core/controllers/videos.php?category=$1&page=$2 last;
rewrite ^/videos/([0-9]+)/comments/$ /cc-core/controllers/comments.php?vid=$1 last;
rewrite ^/videos/([0-9]+)/comments/page/([0-9]+)/$ /cc-core/controllers/comments.php?vid=$1&page=$2 last;
rewrite ^/videos/$ /cc-core/controllers/videos.php last;
}
location /private {
rewrite ^/private/get/$ /cc-core/controllers/play.php?get_private=true last;
rewrite ^/private/videos/([a-zA-Z0-9]+)/$ /cc-core/controllers/play.php?private=$1 last;
rewrite ^/private/comments/([a-zA-Z0-9]+)/$ /cc-core/controllers/comments.php?private=$1 last;
rewrite ^/private/comments/([a-zA-Z0-9]+)/page/([a-z0-9]+)/$ /cc-core/controllers/comments.php?private=$1&page=$2 last;
}
location /members {
rewrite ^/members/page/([0-9]+)/$ /cc-core/controllers/members.php?page=$1 last;
rewrite ^/members/([a-zA-Z0-9]+)/$ /cc-core/controllers/profile.php?username=$1 last;
rewrite ^/members/([a-zA-Z0-9]+)/videos/$ /cc-core/controllers/member_videos.php?username=$1 last;
rewrite ^/members/([a-zA-Z0-9]+)/videos/page/([0-9]+)/$ /cc-core/controllers/member_videos.php?username=$1&page=$2 last;
rewrite ^/members/$ /cc-core/controllers/members.php last;
}
location /search {
rewrite ^/search(/page/([0-9]+))?/$ /cc-core/controllers/search.php?page=$2 last;
}
location /login {
rewrite ^(.*)$ /cc-core/controllers/login.php last;
}
location /login/forgot {
rewrite ^(.*)$ /cc-core/controllers/login.php?action=forgot last;
}
location /logout {
rewrite ^(.*)$ /cc-core/system/logout.php last;
}
location /register {
rewrite ^(.*)$ /cc-core/controllers/register.php last;
}
location /activate {
rewrite ^(.*)$ /cc-core/controllers/activate.php last;
}
location /opt {
rewrite ^/opt-out/$ /cc-core/controllers/opt_out.php last;
}
location /contact {
rewrite ^(.*)$ /cc-core/controllers/contact.php last;
}
location /embed {
rewrite ^/embed/([0-9]+)/$ /cc-core/system/embed.php?vid=$1 last;
}
location /page {
rewrite ^(.*)$ /cc-core/system/page.php last;
}
location /translation {
rewrite ^(.*)$ /cc-core/system/translation.php last;
}
location /notify {
rewrite ^(.*)$ /cc-core/system/notify.php last;
}
location /language/get {
rewrite ^(.*)$ /cc-core/system/language.php?get last;
}
location /language {
rewrite ^/language/set/(.*)/$ /cc-core/system/language.php?set&language=$1 last;
}
location /feed {
rewrite ^/feed(/([a-zA-Z0-9]+))?/$ /cc-core/system/feed.php?username=$2 last;
}
location /video {
rewrite ^/video-sitemap(-([0-9]+))?.xml$ /cc-core/system/video_sitemap.php?page=$2 last;
}
location /myaccount/upload/avatar {
rewrite ^(.*)$ /cc-core/system/avatar.ajax.php last;
}
location /myaccount/upload/validate {
rewrite ^(.*)$ /cc-core/system/upload.ajax.php last;
}
location /myaccount/grab/validate {
rewrite ^(.*)$ /cc-core/system/grab.ajax.php last;
}
location /actions/username {
rewrite ^(.*)$ /cc-core/system/username.ajax.php last;
}
location /actions/flag {
rewrite ^(.*)$ /cc-core/system/flag.ajax.php last;
}
location /actions/favorite {
rewrite ^(.*)$ /cc-core/system/favorite.ajax.php last;
}
location /actions/subscribe {
rewrite ^(.*)$ /cc-core/system/subscribe.ajax.php last;
}
location /actions/rate {
rewrite ^(.*)$ /cc-core/system/rate.ajax.php last;
}
location /actions/comment {
rewrite ^(.*)$ /cc-core/system/comment.ajax.php last;
}
location /actions/post {
rewrite ^(.*)$ /cc-core/system/post.ajax.php last;
}
location /actions/stream {
rewrite ^(.*)$ /cc-core/system/stream.ajax.php last;
}
location /actions {
rewrite ^/actions/mobile-(videos|search)/$ /cc-core/system/mobile_$1.ajax.php?mobile last;
}
location /myaccount {
rewrite ^/myaccount/upload/complete/$ /cc-core/controllers/myaccount/upload_complete.php last;
rewrite ^/myaccount/upload/video/$ /cc-core/controllers/myaccount/upload_video.php last;
rewrite ^/myaccount/upload/$ /cc-core/controllers/myaccount/upload.php last;
rewrite ^/myaccount/myvideos(/page/([0-9]+))?/$ /cc-core/controllers/myaccount/myvideos.php?page=$2 last;
rewrite ^/myaccount/myvideos/([0-9]+)/$ /cc-core/controllers/myaccount/myvideos.php?vid=$1 last;
rewrite ^/myaccount/editvideo/([0-9]+)/$ /cc-core/controllers/myaccount/edit_video.php?vid=$1 last;
rewrite ^/myaccount/myfavorites(/page/([0-9]+))?/$ /cc-core/controllers/myaccount/myfavorites.php?page=$2 last;
rewrite ^/myaccount/myfavorites/([0-9]+)/$ /cc-core/controllers/myaccount/myfavorites.php?vid=$1 last;
rewrite ^/myaccount/privacy-settings/$ /cc-core/controllers/myaccount/privacy_settings.php last;
rewrite ^/myaccount/change-password/$ /cc-core/controllers/myaccount/change_password.php last;
rewrite ^/myaccount/subscriptions(/([0-9]+))?/$ /cc-core/controllers/myaccount/subscriptions.php?id=$2 last;
rewrite ^/myaccount/subscriptions(/page/([0-9]+))?/$ /cc-core/controllers/myaccount/subscriptions.php?page=$2 last;
rewrite ^/myaccount/subscribers(/page/([0-9]+))?/$ /cc-core/controllers/myaccount/subscribers.php?page=$2 last;
rewrite ^/myaccount/message/inbox(/page/([0-9]+))?/$ /cc-core/controllers/myaccount/message_inbox.php?page=$2 last;
rewrite ^/myaccount/message/inbox/([0-9]+)/$ /cc-core/controllers/myaccount/message_inbox.php?delete=$1 last;
rewrite ^/myaccount/message/read/([0-9]+)/$ /cc-core/controllers/myaccount/message_read.php?msg=$1 last;
rewrite ^/myaccount/message/send/([a-zA-Z0-9]+)/$ /cc-core/controllers/myaccount/message_send.php?username=$1 last;
rewrite ^/myaccount/message/reply/([0-9]+)/$ /cc-core/controllers/myaccount/message_send.php?msg=$1 last;
rewrite ^/myaccount/$ /cc-core/controllers/myaccount/myaccount.php last;
}
location /myaccount/profile {
rewrite ^(.*)$ /cc-core/controllers/myaccount/update_profile.php last;
}
location /myaccount/profile/reset {
rewrite ^(.*)$ /cc-core/controllers/myaccount/update_profile.php?action=reset last;
}
location /myaccount/message/send {
rewrite ^(.*)$ /cc-core/controllers/myaccount/message_send.php last;
}
location /m {
rewrite ^/m/v/([0-9]+)/$ /cc-core/controllers/mobile/play.php?mobile&vid=$1 last;
rewrite ^/m/v/$ /cc-core/controllers/mobile/videos.php?mobile last;
rewrite ^/m/s/$ /cc-core/controllers/mobile/search.php?mobile last;
rewrite ^/m/$ /cc-core/controllers/mobile/index.php?mobile last;
}
location /system {
rewrite ^/system-error/$ /cc-core/controllers/system_error.php last;
}
location /t {
rewrite ^/t/(.*)$ /cc-core/system/translation.php last;
}
location / {
if (!-e $request_filename){
#rewrite ^/(.*)$ /$request_uri/ permanent;
rewrite ^/(.*)$ /cc-core/system/page.php last;
}
}
location ~ .php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass backend-${host};
}
location ~* ^.+.(jpg|jpeg|gif|css|png|js|ico|bmp)$ {
access_log off;
expires 10d;
break;
}
location ~ .(flv|mp4|webm|ogg|ogv|mp3)$ {
mp4;
mp4_buffer_size 1m;
mp4_max_buffer_size 5m;
}
location ~ /. {
deny all;
}
}
"
echo "$directives">$sap
sap_poll=/etc/php5/fpm/pool.d/$host.conf
touch $sap_poll
chmod 777 $sap_poll
directives_poll="[${host}]
listen = /var/run/php5-${host}.sock
listen.mode = 0666
user = ${users}
group = ${users}
chdir = /var/hosting/${host}
php_admin_value[upload_tmp_dir] = /var/hosting/${host}/tmp
php_admin_value[soap.wsdl_cache_dir] = /var/hosting/${host}/tmp
php_admin_value[date.timezone] = Asia/Yekaterinburg
pm = dynamic
pm.min_spare_servers = 10
pm.max_spare_servers = 20
pm.start_servers = 10
pm.max_children = 40"
echo "$directives_poll">$sap_poll
ln -s /etc/nginx/sites-available/$host.conf /etc/nginx/sites-enabled/$host.conf
mkdir -p /var/hosting/$host/www/
mkdir -p /var/hosting/$host/tmp/
dir=/var/hosting/$host/www
chown -R $users:$users "$dir";
find "$dir" -type d -exec chmod 0755 '{}' ;
find "$dir" -type f -exec chmod 0644 '{}' ;
/etc/init.d/nginx restart
/etc/init.d/php5-fpm restart
На этом все. Данная конфигурация способна кодировать видео в разные форматы, а так же стримить поток. Если данная статья заинтересует кого либо, я с удовольствием приведу живые примеры стриминга.
К сожалению полной начинки показать не могу, но вот что получилось у меня stream.etagi.com
Спасибо за внимание. Надеюсь обилие кода не помешает выдаче инвайта.
Автор: Scherbakov