Создание полноценного видеохостинга своими руками (nginx+php5-fpm+ffmpeg+cumulusclips)

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

Задача была следующая:
Создать видеоресурс, способный проводить многопоточные односторонние трансляции с 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