12.10.2017

ffmpeg с вебкамеры

Мне нужно было записать 5 минут видео с веб-камеры и уместить его в 5 мегабайт.

До сего момента с ffmpeg я не сталкивался, поэтому попытка записать видео с камеры для последующего его просмотра из веб-браузера делалась методом гугления проб и ошибок (мы вновь говорим про Ubuntu).

Если кто-то скажет, мол читай маны, там все понятно. Нет! Сначала нужно попробовать как оно работает в различных вариантах, а потом читать маны, вот тогда уже они понятны.



1. Проверка камеры

Для начала воткнем камеру в usb и проверим ее наличие командой:
ls /dev/video*
Если все хорошо, то в консоль будет выдано имя камеры типа:
/dev/video0

2. Простая запись

Самый простой пример записи с камеры будет вот таким:
ffmpeg -f v4l2 -i /dev/video0 -t 10 1.mp4

где:
-f v4l2 - API видео;
-i /dev/video0 - откуда берется видеопоток;
1.mp4 - куда кладется видеопоток;
-t 10 - ограничение 10 секунд (но прервать запись можно в любой момент, нажав q).

Результат можно посмотреть в браузере.
Однако, есть несколько но:
- разрешение будет не то, которые вы ожидали;
- размер файла будет большой;
- на слабом компьютере изображение будет дергать.
- в браузере андройда вы скорее всего не увидите результат.

Давайте резберем все по очереди.

Вот что нужно запомнить: перед -i ставятся все настройки для входящего потока, после -i, для записываемого.

3. Тюнингуем

3.1. Возможности камеры

Проверим возможности камеры, выполнив команду:
ffmpeg -list_formats all -i /dev/video0

У меня выдается:
[video4linux2,v4l2 @ 0x1a74790] Raw       :     yuyv422 :           YUYV 4:2:2 : 640x480 160x120 176x144 320x176 320x240 432x240 352x288 544x288 640x360 752x416 800x448 864x480 960x544 1024x576 800x600 1184x656 960x720 1280x720 1392x768 1504x832 1600x896 1280x960 1712x960 1792x1008 1920x1080 1600x1200 2048x1536 2592x1944
[video4linux2,v4l2 @ 0x1a74790] Compressed:       mjpeg :          Motion-JPEG : 640x480 160x120 176x144 320x176 320x240 432x240 352x288 544x288 640x360 752x416 800x448 864x480 960x544 1024x576 800x600 1184x656 960x720 1280x720 1392x768 1504x832 1600x896 1280x960 1712x960 1792x1008 1920x1080 1600x1200 2048x1536 2592x1944

Из выдачи мы видим:
- поток с камеры может быть не сжатый (Raw) и сжатый (Compressed);
- разрешение с камеры может быть от 160x120 до 2592x1944.

3.2. Выбираем разрешение и входной поток

Теперь зададим параметры камеры для входящего потока, которые нас интересуют, подставив их перед -i.
Меня интересует сжатый формат 640x460. Выполним пример:
ffmpeg -f v4l2 -input_format mjpeg -video_size 640x480 -i /dev/video0 -t 10 2.mp4

где:
-input_format mjpeg - поток сжатого видео;
-video_size 640x480 - разрешение;
- остальное описано выше.

3.3. Уменьшаем выходной поток

Записанный файл из сжатого видео гораздо меньше чем из не сжатого, но все равно огромен.
Выберем кодек, которым будем сжимать видео.
Нас интересует кодек, который позволит проиграть видео в браузере с использованием HTML5.

ffmpeg -f v4l2 -input_format mjpeg -video_size 640x480 -i /dev/video0 -f mp4 -t 10 2.mp4

где:
-f mp4 - контейнер выходного видео;
- остальное описано выше.

Вот теперь файл должен заметно уменьшится и должен проигрываться из браузера, с помощью кода:
<video controls width="640" height="480" autoplay>
  <source src="http://site.ru/2.mp4" type="video/mp4">
</video>

3.4. Еще уменьшаем выходной поток


Еще уменьшить выходной поток можно с помощью понижения fps.

fps у нас - это количество кадров в секунду, стандартно их 24-25, но если видео у нас в большинстве своем статичное, можно и до 5 понизить и до 1 даже.
Обратите внимание, если компьютер слабый, то при кодировке он не сможет вытянуть 24 кадра в секунду и будет пропускать кадры, так что изображение будет дергаться неравномерно. Поэтому для слабых компьютеров понижение fps - это даже необходимость.
При чем, например входной поток не всегда можно понизить сразу, устройство может не работать с таким fps, которым вы от него хотите.
Поэтому можно понизить и входной поток до допустимо возможного, чтобы не перегружать процессор и выходной, чтобы поджать видео.

ffmpeg -f v4l2 -input_format mjpeg -video_size 640x480 -r 5 -i /dev/video0 -r 1 -f mp4 -t 10 2.mp4
где:
-r 5 - ограничение входного потока (до директивы -i) до 5 fps, дабы не грузить процессор;
-r 1 - ограничение входного потока (после директивы -i) до 1 fps, дабы поджать видео еще больше.
- остальное описано выше.

3.5. Еще уменьшаем выходной поток (альтернатива)

Альтернативно выходной поток можно понизить с помощью изменения битрейта (размера потока видео в секунду).
Забудем про пример выше (после такого уменьшения fps битрейт нам уже не поможет). Сделаем средний битрейт по размеру 100 килобит в секунду:
ffmpeg -f v4l2 -input_format mjpeg -video_size 640x480 -i /dev/video0 -b:v 100k -f mp4 -t 10 2.mp4

где:
-b:v 100k - средний битрейт 100 килобит в секунду;
- остальное описано выше.

В результате видео вы увидите страшные квадраты, как будто видео вы смотрите по модему, но файл заметно уменьшился.

Побирайте битрейт, читайте документацию про минимальный битрейт, максимальный битрейт, буфер и т.д. и т.п.

3.6. А как же браузер андройд?


На самом деле в процессе кодировки ffmpeg сам подсказывает что делать:
No pixel format specified, yuvj422p for H.264 encoding chosen.
Use -pix_fmt yuv420p for compatibility with outdated media players

Вот и добавим эту дерективу в выходной буфер:
 ffmpeg -f v4l2 -input_format mjpeg -video_size 640x480 -i /dev/video0 -vf format=yuv420p -f mp4 -t 10 2.mp4

где:
-vf format=yuv420p - формат, совместимый с медиаплеерами в сматрфонах;
- остальное описано выше.

P.S.
На сервере в .htaccess не лишне будет записать:
AddType video/mp4 .mp4 .m4v

P.S.S.
Тестируйте на динамичных изображениях. Статика будет маленькой сама по себе.

Ну вроде все для того чтобы посоревноваться в сжатии. У меня получается 5 мегабайт на 5 минут видео. А у вас?


12.06.2017

Расследование зависания cURL при передаче большого файла

Экспозиция
Потребовалось мне как-то с одного сервера на другой заливать файл размером около мегабайта. Вроде бы ничего сложного, используется библиотека cURL, которая позволяет все что хошь, какими хошь методами заливать. Мне принципиально нужен был raw файл методом POST (и да, мы говорим про Ubuntu).  Все шло как по учебнику. 

Завязка
Когда все было протестировано, скрипты уехали в продакшн на облегчённый сервер и тут-то файл перестал заливаться. Заливка отваливалась по тайм-ауту. На приемнике даже создавался файл размером 0 байт, но дальше ничего. Проблема воспроизвелась на всех облегченных серверах, кроме тестового и облегчённого сервера «на выезде». Облегченные серверы немного отличались платформами и имели одну операционку, а вот облегчённый сервер «на выезде», имел операционку другой сборки. 
Нужно было понять в чем проблема. 

Кульминация
Маленькие файлы в несколько байт заливались, проблемы начинались на килобайтах. 
Для того чтобы исключить проблему с библиотекой cURL, была попытка залить файл из консоли и... точно также заливка отваливалась по тайм-ауту. Было выкурено много манов. 

Но ведь не толко cURL умеет заливать файлы? На нем свет клином не сошёлся.
Была попытка залить файл через ssh утилитой scp. Каково же было удивление, когда файл в 600 килобайт завис на середине заливки и скорость начала падать до 0. Тут уже нужно было курить совсем другие маны. 

Штудирование все большей и большей информации начало приводить к советам изменить такие настройки, о существовании которых не знала даже операционная система. 

Развязка
Курение различных манов напомнило о проблеме десятилетней давности - “подбирайте MTU!”

После смены MTU с 1500 на 1400 командой:
Ifconfig eth0 mtu 1400
Данные пошли и, как мне кажется, ещё веселее, чем до этого. 

Эпилог
Вселенная интернета обширна и полна информацией, главное правильно сформулировать вопрос ко вселенной и он будет содержать не только половину ответа, но, того глядишь, и весь ответ. 



12.05.2017

Для it-шников, которые не любят магию

Как сделать из сервиса http, который висит на непонятно каком порту внутри сети, сервис с https на 443 порту, когда под рукой есть Ubuntu и Apache.



Имеем:
- сервер Windows (или нет, не важно) в DMZ с условным адресом 10.10.10.10, на котором крутится сервис http (потому что https не придумали) на условном порту 1234 (потому что порт 80 занят).

Задача:
- организовать доступ из сети интернет на этот сервис по условному адресу: https://my.service.ru

Под рукой:
- сервер в сети интернет c условным адресом 8.8.8.8 под управлением Ubuntu, на котором установлен Apache.

Понеслась:

0. На целевом сервере

0.1.

В брандмауэре сервера открываем входящий трафик на 10.10.10.10 порт 1234.
Не обязательно для всего интернета, можно для 8.8.8.8.

0.2.

Открываем на фаерволе сети входящий трафик на сервер 10.10.10.10 порт 1234, чтобы из сети интернет до него можно было добраться (соответсвенно, серверу нужно дать внешний IP, например 9.9.9.9).
Проверяем:
telnet 9.9.9.9 1234

1. Прописываем DNS

my.service.ru 9.9.9.9

2. Делаем прокси

2.1. Пишем конфигурацию сервера

Правим файл:
sudo nano /etc/apache2/sites-available/my.service.ru.conf
И пишем в него:

<VirtualHost *:80>
        ServerName my.service.ru
  <Proxy *>
    Order deny,allow
    Allow from all
  </Proxy>

  ProxyPass / http://9.9.9.9:1234
  ErrorLog /var/log/apache2/ts_error_log
  CustomLog /var/log/apache2/ts_log common
RewriteEngine on
RewriteCond %{SERVER_NAME} = my.service.ru
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,QSA,R=permanent]
</VirtualHost> 
Все было ради этого

2.2. Добавляем сайт и перезапускаем сервер

sudo a2ensite my.service.ru

service apache2 reload 

2.3. Добавляем доступ по https


sudo letsencrypt --apache -d my.service.ru

Все!