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