Видеосервис своими руками и организация псевдостримминга
Сегодня я расскажу как работают "видеохостинги" и мы разработаем свой "видеосервис" для закачивания видео-файлов, с последующим конвертирование и созданием картинки для превью. Далее организуем показ во флеш-плеере и эмулируем стримминг (для перемотки на не загруженные части видео-файла).
Для всего этого нам понадобиться:
- Флеш-плеер для проигрывания видео-файлов - flowplayer
- Программа для конвертирования видео-файлов - ffmpeg
- Расширение PHP для получения превью-изображения - php-ffmpeg и библиотека для работы с графикой - GD
- Эмуляция потокового видео (псевдостримминг) с помощью утилиты yamdi, модуля для nginx - http_flv_module и плагина для flowplayer - flowplayer.pseudostreamming

Серверная ОСь по традиции - FreeBSD.
Установка необходимых нам утилит
ffmpeg
Для того чтобы конвертировать видео, нам необходимо установить утилиту ffmpeg. Есть еще аналогичная утилита - mencoder, тоже очень хорошая, но ffmpeg более популярен, поэтому именно его буду сейчас использовать. Эти утилиты с переменным успехом конкурируют между собой, поэтому также рекомендую посмотреть на mencoder в свободное время.
# cd /usr/ports/multimedia/ffmpeg # make config
и выбираем LAME (lame MP3 codec)
# make install
Windows сборки ffmpeg:
yamdi
Далее, необходимо установить утилиту yamdi, которая вшивает мета-данные в видео-файл. Мета-данные необходимы для индексирования файла, то есть для того, чтобы можно было в еще не скачанном (не буферизированном) файле при перемотке перемещаться по индексам.
# cd /usr/ports/multimedia/yamdi # make install
nginx
Теперь подошло время для nginx, который будет отдавать индексируемый (с помощью yamdi) видео-файл. На его месте может быть и lighttpd или еще какой-нибудь другой веб-сервер. Но я поддержу отечественного производителя! :)
# cd /usr/ports/www/nginx # make config
и выбираем HTTP_FLV_MODULE, далее
# make install
Ну или просто при установке укажите опцию "--with-http_flv_module".
nginx-0.7.52. Теперь и под Windows:
Можно конечно использовать в качестве бекенда - PHP, например с помощью такого кода:$start = (int) @$_GET['position']; if ($start < 0) die("You fucking idiot"); // open file for reading $fp = fopen($file, 'r+'); $fsize = filesize($file); if ($start > 0) { // seek to requested position fseek($fp, $start); // FLV header for the movie part. Magic. Just trust me. // Header code is completely taken from flv4php project $header = "FLV" . pack('C', 1 ) . pack('C', 5 ) . pack('N', 9 ) . pack('N', 9 ); header("Content-Length: " . (strlen($header) + $fsize - $start)); echo $header; } else { header("Content-Length: " . $fsize); } set_time_limit(0); while(!feof($fp)) { print(fread($fp, 1024)); usleep(1000);// limit download speed } fclose($fp);
Но мне удобнее использовать для этого модуль nginx'а, да и быстрее будет.
php5-ffmpeg
Это расширение PHP нам понадобиться для того чтобы получать информацию о видео-файле, а также для получения видео-фрейма для превью. Также необходимо установить библиотеку GD и сам PHP в частности.
# cd /usr/ports/graphics/php5-ffmpeg # make install
php5-ffmpeg для Windows: и
Можно конечно получать информацию путем обращения к# ffmpeg -i /path/filename.flv
и делать превью# ffmpeg -i /path/filename.flv -an -ss ВРЕМЯ_ПОЗИЦИИ_В_СЕКУНДАХ -r 1 -vframes 1 -s 320x240 -y -f mjpeg /path/filename.jpg
Но мне нравиться использовать расширение, это удобнее.
И перегрузите apache или nginx, смотря как вы используете PHP.
Конвертирование видео и получение изображения для превью
Для начала нам надо указать путь до исходного и результирующего файлов, прописать пути до превью-файлов, а также указать минимальную позицию фрейма для создания превью.
// Путь до залитого видео-файла, например временный файл полученный по $_FILES['tmp_file'] $inputPath = $_FILES['tmp_file']; //Путь до результирующего файла, то есть отконвертированный и прошитый мета-данными видео-файл $outputPath = "/path/video/filename.flv"; // Указываем минимальную позицию фрейма для захвата превью $previewFrameMin = '3'; // Путь до превью-файла $previewPath = "/path/preview/filename.jpg"; // Путь до мини-превью $previewMiniPath = "/path/preview/filename_mini.jpg"; // Массив для сбора ошибок $errorInfo = array();
Конвертируем исходный файл во временный, в формате FLV и разрешением 320x240, использую в качестве аудио-кодека mp3lame с частотой 44100, остальные настройки по умолчанию. Временный файл нужен для того, чтобы yamdi смог вшить мета-данные, так как он не может вшить в исходный файл.
/* * Конвертируем FLV во временный файл ($outputPath . '_temp'). */ passthru( '/usr/local/bin/ffmpeg -i ' . escapeshellarg($inputPath) . ' -f flv '. ' -s 320x240 '. ' -acodec libmp3lame '. ' -ar 44100 '. escapeshellarg($outputPath . '_temp'), $errorInfo['ffmpeg'] );
Прошиваем в видео-файл мета-данные для индексирования. Утилита yamdi написана на Си и работает очень быстро! А также удаляем временный файл.
/* * Прошиваем metadata (позиции ключевых фреймов) для перемотки */ passthru( '/usr/local/bin/yamdi -i ' . escapeshellarg($outputPath . '_temp') . ' -o ' . escapeshellarg($outputPath), $errorInfo['yamdi']); /* * Удаление временного файла, он нам уже не нужен... */ unlink($outputPath . '_temp');
Теперь получаем информацию о видео-файле и получаем объект фрейма для превью.
/* * С помощью расширения php5-ffmpeg получаем информацию о видео-файле */ $movie = new ffmpeg_movie($outputPath); /* * Вычисляем время воспроизведения видеофайла */ $duration = $movie->getDuration(); /* * Вычисляем количество кадров */ $frameCount = $movie->getFrameCount(); /* * Вычисляем позицию фрейма для его захвата */ $framePosition = ($frameCount > $previewFrameMin) ? $frameCount / 2 : $previewFrameMin; /* * Получаем объект фрейма для превью */ $preview = $movie->getFrame($framePosition);
А теперь получим и сохраним наши превьюшки.
/* * Записываем в файл - Превью (320x240) */ imagejpeg ($preview->toGDImage(), $previewPath); /* * Записываем в файл - Мини-превью (160x120) */ $previewMini = imagecreatetruecolor(160, 120); $r = imagecopyresized( $previewMini, $preview->toGDImage(), 0, 0, 0, 0, 160, 120, 320, 240 ); imagejpeg($previewMini, $previewMiniPath);
Все, файл отконвертирован в FLV и прошит мета-данными, также созданы превью, теперь займемся выводом видео-файла - клиенту.
Плеер и показ видео
Почему flowplayer?
Сначала нам понадобится флеш-плеер, умеющий проигрывать видео в формате FLV и показывать картинки в формате JPEG (для превью).
Из всех аналогов (смотрите ниже, в "Полезные ссылки -> Видео-плееры") мне больше всего понравился flowplayer, он:
- Кроссбраузерный и очень гибкий, и удобный в настройке
- Бесплатный для некоммерческого использования, а коммерческая версия стоит его возможностей!
- Имеет Flash и Javascript API, также поддерживает библиотеку JQuery (там доп. фишки какие-то)
- Имеет кучу полезных плагинов
- Умеет работать со стриммингом и псевдостриммингом
- Легко поменять шаблоны оформления и языковые шаблоны для настройки локализованного интерфейса
- Обширная документация, имеет достаточно большое сообщество в собственном форуме, а также оказывает бесплатную поддержку для коммерческой версии
- Поддерживает плейлисты и имеет полноэкранный режим
- Поддерживаемые форматы: FLV, SWF, MP3, MP4, H.264 video, JPG, PNG
- Сырцы открыты!

Остальное описание возможностей на официальном сайте:
Настройка flowplayer
С плеером мы определились, теперь надо его настроить для показа видео. В дистрибутив плеера входит:
- flowplayer-3.1.1.swf - сам плеер
- flowplayer.controls-3.1.1.swf - flash-controls
- flowplayer-3.1.1.min.js - это так называемый js-api
Для реализации псевдо-стримминга нам не хватает flowplayer.pseudostreaming-3.1.2.swf, который можно скачать и ознакомиться с использованием тут: .
Для работы со всеми видами стриминга есть титуриал:
Итак, приступим:
<!-- Подключаем js-api --> <script type="text/javascript" src="/swf/flowplayer-3.1.1.min.js"></script> <!-- Настраиваем плеер --> <script type="text/javascript"> flowplayer( "player", "/swf/flowplayer-3.1.1.swf", { playlist: [ { url: 'http://example.com/preview/filename.jpg' }, { url: 'http://example.com/video/filename.flv', autoPlay: false, autoBuffering: false, provider: 'pseudo' } ], plugins: { pseudo: { url: '/swf/flowplayer.pseudostreaming-3.1.2.swf' } } } ); </script> <!-- DIV в который мы поместим плеер --> <div style="width:416px;height:312px;display:block;padding:10px;" id="player"></div>
Все опции указанные в настройках можно посмотреть тут:
Настройка http_flv_module
Теперь настроим nginx на отдачу файла со смещением, для эмуляции стримминга.
location ~ \.flv$ { flv; root /path/video; }
То есть, говорим nginx'у чтобы он для расширения "flv" использовал модуль http_flv_module и указываем в root путь до видео-файлов. Документация по http_flv_module: Директивы модуля ngx_http_flv_module
Ну вот и все, теперь у вас полноценный "видеосервис"!
Полезные ссылки
ffmpeg
Работа с видео на php с помощью php ffmpeg
Продвинутый метод воспроизведения flash-видео на веб-страницах (или эмулируем стрим-видео)
Наносим водяной знак на видео с помощью ffmpeg
Как же работают видеохостинги? Часть первая. Сборка.
Как же работают видеохостинги? Часть вторая. Ffmpeg.
Видео-плееры
Подборка видео-проигрывателей на флэше
Видео и аудио плееры для сайтов
Идеальный видео-плеер
Идеальный видеоплеер. Не утопия

Комментарии
неплохой обзорчег. а как же мультидоменность, прогрессбар при загрузке и прочие вкусности?)))))))))))))
Мультидоменность обеспечивает коммерческая версия flowplayer :)
отлично, молодец!
пхп бекэнд это нечто))) работать будет только столько потоков вещания одновременно сколько воркеров в нгинкс-конфиге. про скорость я вообще молчу) так что не вариант)
Спасибо, хороший обзор!
Это кто-то реализовал уже??
Дима, что именно?
Неплохо.а эти бибиотеки позволяют работатьь с mp3 файлами ?
выдирать кусочки, менять битрейт и т.д.?
Сергей, ffmpeg умеет работать с mp3 и flowplayer тоже.
как можно с вами связатьс лично?
Сергей, пройти в раздел сайта "Контакты", а по какому вопросу? Почему не в блог комментировать?
могу и в блог.
мне нужно разработать небольшой скриптик, желательно в связке с флеш, чтобы можно было вырезать кусок mp3 файла. уже неделю не могу найти кто бы мне помог...
Сергей, я во флеше не спец, а на серверной стороне надо просто передавать позиции и всё. Вся задача упирается во флеш.
ну а если без флеш?
какие варианты?, цены?, сроки? и самое главное - кто может сделать?
Сергей, что значит без флеш?
ну без флеш, значит без прослушивания и забиваем ручками в минутах и секундах откуда и до куда вырезаем...
при этом либо должен быть сервер с ssh доступом и freebsd в качестве платформы, либо толковый админ, который все сам настроит что ему скажу
что могу сделать:
* вырезаю аудио по переданным параметрам секунд
* проигрываю обрезанное аудио
* смена битрейта
аську надобы для оперативности ;)
моя тридцать семь десять пять семь.
Сергей, у меня только жаббер и скайп
отписал вам на почту.
ок
Я вот что-то понять не могу... вот этот кусок
До этого мы НИГДЕ не копировали файлик в $outputPath,т.е. получается, что мы грохаем темповый файл и пытаемся создать "из ниоткуда" $outputPath-файл... или я что-то не понимаю?
Все - нашел... мои кривые глаза - ямди прошивает escapeshellarg($outputPath)...
Алексей, угу )
Не знаю как и быть. У меня есть свой плеер, написаный на acstinscript 2. Думаю как бы мне туда запихнуть этот псевдостример... Ведь .as библиотека псевдострименга под 3-ий идет, а я в нем ни бум-бум((( Декомпильнул этот flowplayer - нифига там не понял. У гого есть какие мысли что мне делать7
Либо использовать flowplayer, либо учить AS3
Обнадежил(((... Т.е. других вариантов нет7 Неужели нет какой-нить недокументированного метода или еще какого-нить способа грузить видео с нужной метки как seek(5), например7 Просто чисто для этой задачки пока AS3 учить нет времени, т.к. он сильно отличается от AS2 как я посмотрел, а плеер мне нужен именно мой... там своя специфика и другие плеера не подойдут ну никак... Может хоть какая зацепка есть7 а...7
Я не специалист во Flash/Flex и не знаю язык AS
на http://uppod.ru/talk_177 в первом абзаце понял, что нужно чтобы на сервере был lighttpd. Что это такое7 А флеш-проигрыватель даже не придется трогать. Как я понял просто запрос будет не к file.flv, а file.flv?start=123, например и сервак пришлет видео с нужного места. Если я правильно понял - все не так уж и плохо и возможно даже этот flowplayer ковырять не придется. Тока что это за lighttpd, как его сделать, что прописать в var so = new SWFObject("flash/my_swf.swf", "movie", "100%", "100%", "9", "#ffffff");... html страницы? Вроде что-то проясняется, но непонятно что конкретно.
лайти это веб-сервер, альтернативный nginx
если у вас будет работать с лайти без проблем, то и с nginx тоже будет
[code]
stenogramma
var so = new SWFObject("flash/psevdo_1.swf", "movie", "100%", "100%", "9", "#ffffff");//Мой плеер)))
so.addParam("allowFullScreen", "true");
so.addVariable("start_seek", "1");//Стартовый параметр
so.addVariable('autostart','true');//Тоже откуда-то возможно нужно
so.addVariable("file_name", "http://flowplayer.org/video/Extremists.flv");//Беру с http://flowplayer.org
so.addVariable("streamscript", "lighttpd");//Это я вычитал с форумов всяких
so.write("flashcontent1");
[/code]
Так вот когда я отсылаю с параметром start_seek=0 - все окейно работает. В моем плеере переменная start_seek прибавляется к переменной file_name и получается запрос типа "http://flowplayer.org/video/Extremists.flv?start=0" и видео грузится по этому пути. Как я понял - главное что запрос идет на сервак lighttpd, а http://flowplayer.org должно быть именно то что нужно. и как я понял нужно передавать в GET-е переменную start. Но работает тока с start=0. Не знаю - вроде капаю в нужном направлении, но файл отказывается грузиться если start не равно 0. Мысли есть7
Я не работал с лайти, по идее все должно быть нормально
В строке so.addVariable("streamscript", "lighttpd"); я передаю переменную — как ее должен обработать плеер? Может в этом дело, что я никак эту переменную не обрабатываю во флеше?
попробую поискать на форумах... блин, инфы мало((( В основном рассматривают уже готовые плееры, а что в нем должно быть, чтобы работало нигде нет. Должно быть все просто .....myFlv.flv?start=10, а файл лежать на lighttpd, например. Но не пашет. Чего-то я не учитываю((
А какой для видео нужен хостинг? Т.е. должны ли быть какие-то особые характеристики - выделенный сервер и т.п. Есть два "обычных" хостинга - буфферизация происходит медленно, хотя ролики не очень большие, и смотрю их я один.
Max Folder, нужен установленный ffmpeg, yamdi, nginx (+mod_flv), php-ffmpeg. Но, можно обойтись только ffmpeg, а через скрипт, который я приводил в статье можно эмулировать mod_flv
adw0rd, это я понял. А технические характеристики сервера, толщина канала? Нужно ли что-то специфическое (например, аренда сервера за 10000 рублей в месяц), или я просто беру хостинг, условно говоря, за 200 рублей в месяц и радуюсь жизни?
Условно говоря - да, можете взять хостинг и радоваться жизни :) А там уже узнаете - подходит он вам или нет. У меня прекрасно работает на FirstFDS за 600 руб.
Извиняюсь, за очередной глупый вопрос. FirstFDS (хотя оно где-то упоминается) я не нашел, нашел firstvds.ru - это оно? Виртуальный выделенный сервер.
Max Folder, да, именно FirstVDS, извините, с просони не то написал :/
Друзья, подскажите - какие ключи у хука watermark??? что-то не могу в инете найти нормальное описание. И работает ли корректно watermark.so с водяными знаками в формате png?
Алексей, вам сюда
Я конвертирую видео используя Fedora+mencoder+On2VP6. Качество просто ураган. Нарезку делаю напрямую с DVD iso файла. Качество практически без потерь при битрейте 700кБит.
Тестировал производительность на реальных серверах.
Один WindowsXP другой Fedora конфигурации идентичные. Конвертера настроены идентично.
В Windows XP в 5 потоков конвертации 1 фильм снимает часов так за 14.
В Fedora в 10 потоков конвертации 1 фильм снимает практически в риалтайме - 1.5-2 часа.
Вывод: под Linux-ом фильм конвертируется в 14 раз быстрее.
Здесь полностью подробно описано как настроить качественный HDTV-видеохостинг
http://webmax.do.am/publ/administration/nastroyka_servera_hdtv_videohostinga/1-1-0-1
Настраивал полностью по этой статье.
подскажите кто тестировал данный скрипт скриминга на php, он работает вообще?
и каким плеером передавать GET['position']; ??
я так понимаю тут есть какие-то своя реализация...?
===
$start = (int) @$_GET['position'];
if ($start < 0) die("You fucking idiot");
// open file for reading
....
мне вот захотелось переписать его на perl (или на Си) :)
зашел сюда http://fanno.dk/index.php?option=com_content&task=blogcategory&id=15&Itemid=53, там у