Текущая страница: ГлавнаяНастройка сервера → Сервер потокового вещания на основе ffserver

Сервер потокового вещания на основе ffserver

Сразу отмечу, что изначально я ставил ffmpeg из пакетов, но получил достаточно большое количество всяких ошибок и непоняток в процессе кодирования файлов, поэтому в конечном итоге установил ffmpeg из git. Количество багов существенно упало.
Собственно ffserver устаналивается в качестве приятного бонуса вместе с ffmpeg.
Конфигурационный файл /etc/ffserver.conf

Сначала идет блок основных настроек, все это хорошо описано в примере конфига и в дополнительном обсуждении не нуждается.
Единственное — опция #NoDaemon — она, как утверждается устарела и ffserver и так по умолчанию запускается в этом режиме.
Далее секции feed.
Здесь мы описываем откуда наш сервер будет получать ведеопоток.
Как написано в комментах конфига, получать он его будет от ffmpeg или другого ffserver
Пример:ffmeg http://localhost:8090/feed1.ffm
Что при этом происходит?
ffmpeg читает файл feed1.ffm, берет из него опции кодирования и пишет в файл видеопоток в заданном формате.
Например если в секции Stream стоит VideoBitRate 64 это будет эквивалентно указанию ffmpeg -b:v 64k
Параметры, которые можно задавать в секции feed
Замечание — имя фида должно выглядить feed-name.ffm, временный файл при этом будет /tmp/feed-name.ffm, его можно переопределить опцией File.
File
1.Сервер при запуске читает секцию stream в которой используется данный фид, устанавливает в файл параметры кодирования
2.ffmpeg который пишет данные в этот файл ставит параметры кодирования взятые из этого же файла, если мы меняем описание кодирования в секции stream файл удаляется и создается заново при перезапуске сервера, который видит что параметры кодирования изменились и пересоздает файл фида, (иногда правда я замечал что не пересоздает….)
3.Если нет секции stream в которой указан этот feed — записываются параметры кодирования по умолчанию.
4.Если после запуска сервера удалить файл вида, при попытке записать данные в фид получим I/O Error, т.е. сервер динамически не создает файл фида если его по какой-либо причине не стало.
FileMaxSize — ограничение файла по размеру, значение по умолчанию 5M
ReadOnlyFile
Все аналогично File но — файл ffm уже должен существовать на момент запуска сервера. Тогда на него устнавливаются права readonly и писать туда невозможно — можно только считывать данные.
Launch
1.ffserver запускает ffmpeg с заданными параметрами кодирования + параметры кодирования из секции stream
2.Если заданные параметры кодирования конфликтуют с параметрами из stream — ffmpeg завершает работу с кодом ошибки.
3.Перекодированные данные пойдет в http://localhost:установленный-в-конфиге-порт/имя-фида при этом физически они будут записываться в файл определяемый опцией File для фида заданного в имя-фида.
4.Когда будет достигнут максимальной размер файла (по умолчанию 5МБ) — файл начнет записываться сначала.
5.Если мы определим эту опцию, то при попытке записать в этот фид данные из консоли c помощью ffmpeg получим ошибку I/O Error.
Что логично….получилось бы что в файл одновременно пишет ffmpeg запущенный из Launch и мы….
ACL allow IP — разрешает доступ к фиду с определенных IP
Далее секции stream
Feed — ну это понятно, указываем с какого фида берем видеданные
File — видеоданные можно брать не только из фида, но и тупо из файла.
Format — формат нашего потока, в конфиге приведен список поддерживаемых форматов.
PreRoll n — отдавать данные клиенту через n секунд.
ACL — ограничения доступа по IP
Далее идут всякие опции видео и аудиопотока.

Установить частоту кадров выходного файла до 30 кадров в секунду
VideoFrameRate 30 соответствие ffmpeg -r 30

Установить битрейт видеопотока 512 килобит в секунду
VideoBitRate 512 соответствие ffmpeg -b:v 512k

Установить размер кадра
VideoSize 1280×720 соответствие ffmpeg -s 1280×960

Устанавливает размер видеобуфера.
VideoBufferSize 16M соответствие ffmpeg -bufsize 16M
Коррелирует с битрейтом видео и размером кадра, при слишком маленьком значении буфера выдаются предупреждения rc buffer underflow
Например при значении VideoBitRate 512 я ставил примерно 16М

Установить максимальную и минимальную скорость передачи видео (кбит в секунду)
VideoBitRateRange 512-2400 соответствие ffmpeg -minrate 512k -maxrate 2400k
При этом необходимо указать опцию размер видеобуфера.

Замечание 1:
Если мы берем данные из файла, а не из Feed то указывать тут что-то кроме File бесполезно — не сработает!
Завершает пример конфига секция в которой мы видим как посмотреть статус сервера:

<Stream stat.html>
Format status
ACL allow localhost
FaviconURL http://pond1.gladstonefamily.net:8080/favicon.ico
</Stream>

Статьи по теме:
ffserver-guide на одном из блогов.
ffmpeg на help.ubuntu.ru/wiki
Статья на хабре
Неплохой обзор опций ffmpeg
Официальная документация по ffserver
Ну и конечно документация которая ставится при установке ffmpeg /usr/share/doc/ffmpeg

Смотрим какие кодеки поддерживает ffserver
Допустим нас интересует h264
ffserver -codecs | grep h264
Видим буквы DEV — т.е. доступно декодирование и кодировка видео.
Декодер h264, енкодер libx264 формат VideoCodec codec_name (encoding,video)
Ставим VideoCodec libx264

Запускаем сервер так ffserver -f /etc/ffserver.conf -v debug

Теперь посмотрим на практике как работает feed

Берем тестовый видеофайл 15М и будет пускать его в фид из консоли.
Ставим в секции Stream параметры кодирования:
Format mpeg
VideoCodec mpeg1video
AudioCodec mp2
AuduoSampleRate 24000
Не указываем Launch в фиде.
В настройках фида указываем максимальный размер файла 100М
Запускаем сервер
Пытаемся подключиться к потоку — страница долго грузится, статус подключения — WAIT_FEED
Запускаем данные в фид: ffmpeg -i test.mp4 http://localhost:8090/feed1.ffm
На странице появляется видео…все идет…
Поток из консоли читает тестовый файл, пишет в файл вида, дочитывает до конца и завершается.Ок.
Браузер читает поток до тех пор пока в него пишутся данные, иначе картинка замирает и имеем статус — WAIT_FEED
Т.е. если мы хотим читать из потока в него постоянно надо писать, а файл фида это всего лишь буфер, где хранятся промежуточные данные.
Хорошо…устанавливаем Launch ffmpeg -i test.mp4, ставим максимальный размер файла 512к
Рестартуем сервер.
Подключаемся к потоку
Изображение идет первые секунды, потом замирает…
Как я понимаю ffmpeg передает данные в фид с большой скоростью ибо читаются они из файла на диске да и кодируются недолго,все это отправляются серверу — клиент читает данные передаваемые сервером существенно медленнее…Сервер кеширует данные в файл фида и клиент получает их из файла. Поскольку ffmeg передает данные очень быстро, файл также быстро заполняется и перезаписывается — в конечном итоге возникает ситуация — клиенту передали фреймы от 0 до 1000, он хочет получить следующую порцию, а их уже нет….ffmpeg уже забил файл фреймами с 5000 и выше…
Имеем замирание картинки и сообщение: non monotonically increasing dts to muxer in stream.
Кстати в логах сервера периодически идут сообщения: feed1.ffm Pid xxx exited with status 0 after 50 sec.
50 секунд как раз идет тестовый файл т.е. сервер запускает ffmpeg из секции Launch, он отрабатывает и завершается — сервер запускает его снова ))))
Ставим максимальный размер файла чуть меньше размера нашего тестового файла — 10M
Сначала изображение идет хорошо, потом ошибки монотонности, он их проскакивает и изображение снова идет дальше…потом ffmpeg завершает работу и выходит. Сервер перезапускает его заново. В этот момент изображение замирает снова…и не стартует пока не обновлю страницу, после обновления страницы изображение идет с самого начала…Мдя….Таким способом файл мне не просмотреть….
Возьмем тестовый файлик побольше…200 мегов…
Изображение начинает идти, потом invalid stream index в логах сервера….
Обновляем страницу….изображение пошло с какого-то момент, кусок мы похоже не увидели…10 секунд и такая же ошибка….
Похоже синхронизировать скорости приема-передачи данные ffserver не умеет в принципе….
Конечно если взять тестовый файл 200М и поставить размер файла фида 200М то в принципе большую часть файла мы увидим, ошибки полезут ближе к концу.Попробуем так и сделать…ролик у нас на 15 минут, минут 7 видео идет нормально, потом видимо происходит то что описано выше, нам в лог летят ошибки:
non monotonically increasing dts to muxer in stream
потом ошибки:
invalid stream index
Клиент всеж пытается это победить, мужественно читает данные…они идут с какими-то артефактами, причем с начала файла, причем без звука…видимо звук упал не выдержав ошибок индексов и поруганной монотонности….
Конечно, можно сказать что данные из фида надо брать с устройств типа вебкамера, но по сути разница заключается лишь в том, что с вебкамеры данные пойдут с более низкой скоростью, чем когда мы читаем их прямо с диска. Разница между скоростью чтения данных клиентом и чтением данных из вебкамеры будет существенно меньше и коллизии будут возникать реже — но при наличии разницы скоростей получения-отправки данных возникать они все-таки будут.

Попробуем различные кодеки.

Format mpeg
AudioCodec aac
Strict -2
AVOptionAudio flags + global_header
VideoCodec libx264
При попытке отправить данные ffmpeg -i test.mp4 http://localhost:8090/feed1.ffm
получаем: broken ffmpeg fefault setting detected
Так…какие установки по умолчанию делаются для секции stream, для всех параметров кодирования видео?
Смотрим пример…Нужен специальный файл для h264….
Смотрим что в файле установок — содержимое я стащил откуда-то после гугления:

cat ~/.ffmpeg/libx264-default.ffpreset
coder=1
flags=+loop
cmp=+chroma
partitions=+parti8x8+parti4x4+partp8x8+partb8x8
me_method=hex
subq=5
me_range=16
g=250
keyint_min=25
sc_threshold=40
i_qfactor=0.71
b_strategy=1
qcomp=0.6
qmin=10
qmax=51
qdiff=4

Замечание:
Вообще эти файлы должны быть в каталоге ffmpeg/ffpresets и при установке копироваться в /usr/local/share/ffmpeg/
но у меня их почему-то не оказалось.
Проверяем:
ffmpeg -i test1.mp4 -vcodec libx264 -vpre default video.mp4
В конфиге этот файл прописывается так:
AVPresentVideo default
добавляем как в примере из документации:
AVOptionVideo flags + global_header
Перезапускаем сервер, пробуем открыть поток через браузер….
Страница пытается загрузиться…в лог летит куча ошибок:
buffer underflow st=1 bufi=xxxx size=136654
packet too large, ignoring buffer limits to mux it
Ни видео, ни аудио не идет.
Поиграл с настройками битрейта и буфера — ничего….
Посылая данные таким образом
ffmpeg -i test.mp4 http://localhost:8090/feed1.ffm -loglevel debug
видим, что передача идет нормально, ошибок, предупреждений и всего прочего нет.
Ошибки вылетают при подключении клиента, но куда копать….?

Посмотрим как ffserver умеет просто отдавать файл — без feed

С какими параметрами видео и аудио нам кодировать файл для видеосервера?
Посмотрим как это делает ютуб….
Видео с камеры: размер 680 метров, разрешение 1920 на 1080, битрейт видео 4908к, аудио 48000Hz 127к, формат mp4
Это же видео после закачивания на ютуб: размер 218 метров, разрешение 1280 на 720, битрейт видео 1416к, аудио 44000 192к, формат mp4
(видеокодек h264, аудиокодек aac в обоих случаях)
Перекодирую видео в MPEG-1 формат:(aac в него запихнуть нельзя ибо не соответствует он MPEG-1, воспользовался mp3)

ffmpeg -i p1f1.mp4 -vcodec libx264 -b:v 1416k -s 1280x720 -maxrate 1608k -bufsize 1000k -acodec libmp3lame -b:a 192k -f mpeg p1f1.mpg -y
ffprobe p1f1.mpg

Получился файл:число фреймов 32541,видеоданные 185816кБ, аудиоданные 25448кБ разрешение 1280 на 720, битрейт общий 1603к, аудио 48000 190к
Почему ютуб выбрал именно такие параметры битрейта? Считаем….
Размер одного видеофрейма сжатого h264 185816/32541=5,7 килобайт.
Допустим мы хотим передавать 30 кадров с секунду, тогда нам надо передать 30*8*5,7=1368 килобит в секунду+немного полосы на аудио
Общее время проигрывания ролика получается так — (185816*8/1413)/60 получается около 17 минут (1413=1603-190)
Замечание 6:
Иногда при различном перекодировании, включив опцию -loglevel debug мы можем увидеть сообщения вида
first_dts xxxxx not matching first dts yyyyy in the queue
или
Application provided invalid, non monotonically increasing dts to muxer in stream 0. Error writing frame to output.
в логах ffserver.
Они всплывают при перекодировании, либо в логах ffserver при попытке получить данные из stream — это ошибки кодирования, для того чтобы от них избавиться надо корректно перекодировать файл, см. фильтры ffmpeg, например -absf aac_adtstoasc и опции связанные с dts: ffmpeg -h long | grep dts.
В конечном итоге мне помогло обновление ffmpeg из git до последней версии.
Для тестирования используем кусок в 1 минуту, всего 1795 фреймов, как видно при конвертации:
Создаем файл 1 для тестирования:(из него конвертим все остальное)

ffmpeg -i p1f1.mp4 -ss 00:02:00 -to 00:03:00 -vcodec copy -acodec copy -f mp4 test1.mp4 -y

Создаем файл 2 для тестирования:MPEG-4

ffmpeg -i test1.mp4 -vcodec libx264 -b:v 1416k -s 1280x720 -maxrate 1608k -bufsize 1000k -acodec copy -f mp4 test2.mp4 -y -loglevel debug

Создаем файл 3 для тестирования:MPEG-1

ffmpeg -i test1.mp4 -vcodec mpeg1video -b:v 1416k -s 1280x720 -maxrate 1608k -bufsize 1000k -acodec libmp3lame -f mpeg test3.mpg -y -loglevel debug

Создаем файл 4 для тестирования:MPEG-2

ffmpeg -i test1.mp4 -vcodec libx264 -b:v 1416k -s 1280x720 -maxrate 1608k -bufsize 1000k -acodec aac -strict -2 -f mp2 test4.mp2 -y -loglevel debug

Конвертация проходит без ошибок, но как-то резко заканчивается….ffprobe test4.mp2 выдает ошибку.
В дебаге видно сообщение — EOF on sink link ouput stream. Not more output streams to write to, finishing. Видео — 0кБ….
Пробовал разные кодеки и варианты — не идет….Похоже имеем эпик фейл…
Создаем файл 4 для тестирования:AVI

ffmpeg -i test1.mp4 -vcodec libx264 -b:v 1416k -s 1280x720 -maxrate 1608k -bufsize 1000k -acodec copy -strict -2 -f avi test4.avi -y -loglevel debug

Создаем файл 5 для тестирования:AVI — классический вариант

ffmpeg -i test1.mp4 -vcodec mpeg4 -b:v 1416k -s 1280x720 -maxrate 1608k -bufsize 1000k -acodec ac3 -f avi test5.avi -y -loglevel debug

Создаем новую секцию Stream

<Stream file1>
File "/tmp/test1.mp4"
</Stream>

Рестартуем сервер — обращаемся к stat.html и видим в логах падение сервера с ошибкой Segmentation fault
Где-то я читал, что

ffserver это вечная альфа

….похоже так оно и есть. Stream без расширения указывать явно недопустимо ))))
Вариант 2:

<Stream file2.mp4>
File "/tmp/test2.mp4"
</Stream>

Это — mp4 (в списке поддерживаемых форматов в файле ffserver.conf его нет, так что работа с ним не гарантируется)
Пои подключении VLC видим ошибки в логе сервера:
Tag avc1/0×31637661 incompatible with output codec id 28. Error writing with output header.
Смотрим ffprobe test.mp4 Видеокодек h264 (avc1/0×31637661)
Причина в том, что описание видеокодека в контейнере MP4 не воспринимается правильно — поддержку MP4 разработчики ffserver еще не сделали.
Я специально сделал несколько файлов из одного используя один и тот же кодек, но разные форматы — mpeg и avi.
В одном случае mpeg — формат MPEG-1, контейнер MPEG-1
В другом случае avi — формат MPEG-4, контейнер AVI
Вариант 3:

<Stream file3.mpg>
File "/tmp/test3.mpg"
</Stream>

Это — MPEG-1 со сжатием mpeg1video.
Открывает из VLC — Пошел звук и видео. Вполне прилично.
Вариант 4:

<Stream file4.mpg>
File "/tmp/test4.mpg"
</Stream>

Это — avi файл со сжатием libx264 видео и aac аудио.
Открывает из VLC — Пошел звук и видео, но очень дерганно и как-то скрежеща, замирая и повизгивая….
Вариант 4 я просто использовал для того, чтобы выяснить, что libx264 все-таки воспринимается сервером и ошибка с несовместимостью кодека из варианта 2 говорит о том что mp4 не поддерживается, правда удивляет что h264 так плохо работает, почему — сходу не ясно, но если пускать поток через feed то все нормально обрабатывается, значит что-то не нравится в том как перекодирован сам файл.
Что интересно, при попытке открыть stream прямо по ссылке, из браузера имеем:
The Windows Media Player plugin has crached…Причем крошится он и при обновлении страницы…
Вариант 5:

<Stream file5.avi>
File "/tmp/test5.avi"
</Stream>

Это — avi файл со сжатием mpeg4 видео и ac3 аудио.
Открывает из VLC — Пошел звук и видео. Вполне прилично.

Резюме:
Неплохо читаются данные из файлов стандарта MPEG-1 и MPEG-4 в контейнере avi.
С кодеком h264 непонятки, возможно надо как-то использовать профили AVPresentVideo
Попытка перекодировать в MPEG-2 не удалась — видимо баг где-то в ffmpeg

В целом — если, если например взять задачу организации видеохостинга, то ffserver для этого не очень годится. Тут лучше использовать другое решение — организация видеохостинга используя модуль h264 для веб-сервера nginx.
Если надо транслировать данные с камер, решение построить конечно можно но….много всяких вопросов возникает.

Добавить комментарий