0
<< предыдущая заметкаследующая заметка >>
25 января 2021
LiveStream Raspberry+ffmpeg+nginx

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

Для тех, кому это может быть полезно (а в основном для себя, чтобы позже мог зайти в этот пост), я подробно расскажу, как настроить Raspberry, собрать трансляционный nginx на сервере, как сделать правильные настройки ffmpeg, как подключить датчик температуры и вообще оборудовать всё полезными скриптами, чтобы само жило и не висло.

Одновременно у меня есть и вопросы к вам. В основном вопрос почему у меня не заработала встроенная Raspberry-камера, ну и может кто-то даст совет по организации интернет-радио, раз уж мы увлеклись трансляциями.

Итак, для начала смотрим видео:

Две кнопки — потому что есть два разных формата веб-вещания, один для для мобильных устройств и Айфонов HLS, другой — для стационарных компьютеров DASH (Firefox, например, отказывается показывать HLS). Андроид показывает оба варианта, но HLS плавнее. Поскольку есть JS-плеер, который поддерживает HLS даже там, где его нет, то формат DASH предлагаю не рассматривать вовсе, дабы не плодить сущностей и машинного времени.

Ну а теперь кому интересно — расскажу по порядку, как я это настраивал. Там несложно.

Raspberry

Берется Raspberry 3, хотя лучше бы 4. Берется сама простая прошивка Raspberry Pi OS Lite, накатывается на карточку:

sudo dd if=Raspberry.iso of=/dev/mmcblk0 status=progress

Логин «pi», пароль «raspberry». При первом включении потребуется дисплей HDMI или телевизор, иначе не настроить. Это вообще праздник, потому что мне Юра принес кучу Raspberry с выжженными дисплейными портами (для наших задач не нужно), пришлось выбрать рабочую и согнать отца с телевизора на пять минут, у меня дисплей не поддерживает HDMI. Настраивается в консоли через sudo raspi-config, а именно:

1. Настраиваем WiFi (я подключил LAN, но пусть будет): «1 System Options» -> «S1 Wireless LAN»

2. Идем в «3 Interface Options», чтобы включить доступ по SSH «P2 SSH», интерфейс датчика если собираемся его подключить «P7 1-Wire» и интерфейс камеры, если она есть «P1 Camera»

3. В конце обязательно расширяем систему на всю флешку, потом перезагрузимся: «6 Advanced Options» -> «A1 Expand Filesystem»

Настройка закончена.

nginx

Для веб-ретрансляций удобнее всего использовать nginx на любом принадлежащем вам сервере, лучше большом, мощном и далёком. Как это сделать?

Выкиньте нахуй и навсегда Apache2 «sudo apt remove apache2» и поставьте nginx «sudo apt install nginx». Это вообще давно пора было всем сделать. Но сам по себе из коробки nginx работать как видеострим не будет, его надо пересобрать самому с дополнительными опциями и модулем. Поэтому поставив штатный со всей обвязкой, уберите сам файл «sudo mv /usr/sbin/nginx /usr/sbin/nginx.old» и займитесь сборкой новой версии:

# Устанавливаем всё, что понадобится для сборки:

sudo apt-get install libpcre3 libpcre3-dev libssl-dev

# Качаем последние исходники nginx, распаковываем:

wget http://nginx.org/download/nginx-1.17.8.tar.gz

# Качаем модуль трансляции, распаковываем рядом:

wget https://github.com/arut/nginx-rtmp-module/zipball/master -O nginx-rtmp-module-master.zip
unzip nginx-rtmp-module-master.zip -d nginx-rtmp-module-master

# заходим в папку исходников nginx, запускаем конфигурацию, но специальную:

cd nginx
./configure --prefix=/usr --add-module=../nginx-rtmp-module-master/ \
--pid-path=/var/run/nginx.pid --conf-path=/etc/nginx/nginx.conf \
--error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log \
--with-http_ssl_module

# собираем и устанавливаем

make

sudo make install

sudo cp ../nginx-rtmp-module-master/stat.xsl /etc/nginx/

# запускаем

sudo service nginx start

Теперь нужно прописать правильные секции: sudo mcedit /etc/nginx/nginx.conf

Начинаться он должен так:

user www-data;
worker_processes auto;
pid /run/nginx.pid;

rtmp_auto_push on;

rtmp {
    record all;
    live on;
    server {
        listen 1935;
        application streamer {
            hls on;
            hls_path /tmp/hls;
            hls_fragment 5s;
        }
    }
}

[...]

Мы завели канал по имени streamer, где будем создавать любое количество нужных нам трансляций. И включили HLS. Чтобы дать браузерам посетителей доступ к файлам трансляций в /tmp, надо пойти уже в другой файл — файл описания сайта /etc/nginx/sites-enabled/default.conf и добавить туда доступ к /tmp/hls внутрь секции server:

server {
listen 80;
    listen [::]:80 ipv6only=on default_server;
    server_name lleo.me *.lleo.me;
    index index.html index.htm index.php;

    location /hls {
        root /tmp;
    }

[...]

Настройка закончена.

ffmpeg

Трансляцию готовим с помощью утилиты ffmpeg. Она умеет делать всё с видео и аудио. В нашей задаче:

1. Брать живое видео с камеры

2. Наложить звук из файла mp3, зациклив его (трансляция без звуковой дорожки обычно нигде не работает)

3. Наложить на изображение титры из файла text.txt, указав, что он обновляемый (мы параллельно будем его ежесекундно обновлять другими стредствами)

4. Заливать полученное на трансляционный сервер, которым может быть Youtube или наш удаленный nginx.

Создаем на Raspberry скрипт трансляции /home/pi/lleo/streamGon.sh

#!/bin/sh

while true ; do

/usr/bin/ffmpeg -f video4linux2 -i /dev/video0 \
\
-filter_complex "amovie='/home/pi/lleo/sea.mp3':loop=999,asetpts=N/SR/TB,\
aformat=sample_fmts=fltp:sample_rates=44100:channel_layouts=stereo,volume=0.8" \
\
-vf drawtext="fontfile=OpenSans.ttf:textfile='/tmp/text.txt':reload=1:fontsize=28:\
fontcolor=white:borderw=3:bordercolor=black:x=10:y=10" \
\
-vcodec h264_omx -preset ultrafast \
-c:v h264_omx -x264opts "keyint=24:min-keyint=24:no-scenecut" -r 24 \
-bf 1 -b_strategy 0 -sc_threshold 0 -pix_fmt yuv420p \
-c:a aac -b:a 128k \
\
-f flv rtmp://lleo.me/streamer/moe_okno

done
}

Обратите внимание, что мы используем на Raspberry аппаратный кодек h264_omx вместо классического libx264, что снижает нам загрузку процессора раз в пять (спасибо Кириллу за совет).

Вспомогательные скрипты

Отдельно готовим титры — я написал демона /home/pi/lleo/temperTextStart.php для постоянного обновления файла титров /tmp/text.txt

#!/usr/bin/php

<?php

exec
("/usr/bin/ls /sys/bus/w1/devices",$o);
if(
sizeof($o)!=2) die('Error Wire');
$unit=$o[0];

while(
1) {
    
$T=trim(file_get_contents("/sys/bus/w1/devices/".$unit."/temperature"));
    
$T=floor(1*$T/10)/100;
    
$T2=exec("/usr/bin/vcgencmd measure_temp");
    
$T2=preg_replace("/[^\d\.]+/s",'',$T2);
    
$Z=exec("/usr/bin/cat /proc/loadavg");
    list(
$Z,)=explode(' ',$Z,2);
    
$Z=floor($Z*100/2.2);
    
$D=date("Y-m-d H:i:s");
    
$o="$D\nДатчик: ${T}°C\nПроцессор: ${T2}°C\nЗагрузка: ${Z}\%";
    
file_put_contents("/tmp/text.txt",$o);
    
usleep(200000);
}

?>
?>

Чтобы все работало и перезапускалось в случае крэша само, пишу следящего демона демонов /home/pi/lleo/GoneDaemon.sh

#!/bin/bash

фрагмент для остановки «/home/pi/lleo/GoneDaemon.sh stop»
if [ "X${1}== "Xstop" ] ; then

S
=`/usr/bin/ps awuux | /usr/bin/grep [t]emperTextStart.php | /usr/bin/awk '{print $2}'`
echo 
"--> $S"
if [ "X$S!= "X" ] ; then
    
echo "Stop temperTextStart.php: kill -9 $S"
    
kill -9 $S
fi

S
=`/usr/bin/ps awuux | /usr/bin/grep [s]treamGon.sh | /usr/bin/awk '{print $2}'`
echo 
"--> $S"
if [ "X$S!= "X" ] ; then
    
echo "Stop streamGon.sh: kill -9 $S"
    
kill -9 $S
fi

S
=`/usr/bin/ps awuux | /usr/bin/grep '/[u]sr/bin/ffmpeg' | /usr/bin/awk '{print $2}'`
echo 
"--> $S"
if [ "X$S!= "X" ] ; then
    
echo "Stop /usr/bin/ffmpeg: kill -9 $S"
    
kill -9 $S
fi

exit
fi

#раскомментарить exit
#exit
#--------------------------------------------------
<?php

S
=`/usr/bin/ps awuux | /usr/bin/grep [t]emperTextStart.php | /usr/bin/awk '{print $2}'`

if [ 
"X$S== "X" ] ; then
    
/home/pi/lleo/temperTextStart.php &
fi

S
=`/usr/bin/ps awuux | /usr/bin/grep [s]treamGon.sh | /usr/bin/awk '{print $2}'`

if [ 
"X$S== "X" ] ; then

    S
=`/usr/bin/ps awuux | /usr/bin/grep '/[u]sr/bin/ffmpeg' | /usr/bin/awk '{print $2}'`
    if [ 
"X$S!= "X" ] ; then
        
echo "Stop /usr/bin/ffmpeg: kill -9 $S"
        
kill -9 $S
    fi

    
/home/pi/lleo/streamGon.sh &

fi
?>

И прописываю его в «crontab -e» чтобы работал каждые 5 секунд (с долями минут в кронтабе сложно, поэтому так):

* * * * * for ((i=0;i<12;i++)); do /usr/bin/bash /home/pi/lleo/GoneDaemon.sh & /usr/bin/sleep 5done
?>

Плеер на сайте

Итак, удаленная Raspberry делает видеопоток и гонит на трансляционный сервер. Тот его разбивает на кусочки для веб-трансляции. Чтобы смотреть на сайте, нужен плеер. Плеера, увы, два — я пока не понял, как сделать один и для десктопов и для мобильных. Для мобильного формата HLS достаточно было бы вставить ссылку на автоматические сознанный заголовок потока:

<!doctype html>
<
html>
<
head>
<
script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
</
head>
<
body>
<
center>
   <
video id='video' controls></video>
</
center>

<
script>
if(
Hls.isSupported()) {
    var 
video document.getElementById('video');
    var 
hls = new Hls();
    
hls.loadSource('/hls/moe_okno.m3u8');
    
hls.attachMedia(video);
    
hls.on(Hls.Events.MANIFEST_PARSED,function() { video.play(); });
}
</
script>

</
body></html>

Датчик температуры

Датчик температуры подключить к Raspberry проще простого. Называется он DS18B20, стоит копейки. Имеет смысл брать в варианте герметичного шланга. Подключается к трём пинам на плате, но между двумя проводками (сигнальным и +5) надо впаять резистор 4.7 килоом:

Чтобы прочитать датчик температуры, надо найти его на шине как номер устройства:

$ ls /sys/bus/w1/devices
28-011936686716  w1_bus_master1

И можно читать его (будет небольшая задержка):

$ cat /sys/bus/w1/devices/28-011936686716/temperature
3250

Полученное значение температуры делить на 1000 (да, в Москве сегодня за окном +3). Не обольщайтесь, точность у него не тысячные доли градуса, а ± 0.5

Трансляция на Ютуб

Тем же способом можно делать онлайн-трансляцию на Ютуб. Для этого на Ютубе надо выбрать «создать трансляцию» и взять в открывшейся странице личный ключ типа такого: secm-2qtw-qdez-vqfr-263w Далее все то же самое, но трансляционный сервер Ютуба с ключом. Я играл со своего компа с такими настройками кодеков (напоминаю, без звуковой дорожки не заработает):

ffmpeg -f video4linux2 -i /dev/video0 \
-filter_complex "[...]" \
-vf drawtext="[...]" \
-c:v libx264 -shortest -preset ultrafast -crf 24 -g 3\
-f flv rtmp://a.rtmp.youtube.com/live2/secm-2qmw-qdez-vrfr-263w

Что не понравилось: Не нашел способа создавать трансляцию автоматически. Надо копошиться руками на сайте, потом запустить трансляцию, и только тогда она стартует всякий раз c новым URL. При сбое потока длиной секунд в пять — решительно завершится. И тогда надо на сайте создавать руками новую, а одновременно на компе отключать и включать заново поток, что является сигналом нового старта. В этом смысле свой сервер лучше.

Интернет-радио

Любопытно, что при помощи того же ffmpeg и тех же потоков можно сделать, например, интернет-радио:

while true; do
ffmpeg -re -i "`find /r/mp3/ДЕТСКИЕ -type f -name '*.mp3'|sort -R|head -n 1`" -vn -c:a aac -ar 44100 -ac 2 -f flv rtmp://localhost/streamer/radio;
done

Встраивается так же, только тэг AUDIO вместо VIDEO. Я уже было подумал создать поэтически-музыкальную радиостанцию, как мы когда-то хотели с Янгом (Алекс, давай создадим?), но мне пока не понравилась технология. Что мне не понравилось? Глючит, заедает при смене песни. Чисто веб-решение, нет нормального непрерывного mp3-потока. Если кто-то здает, как сделать иначе — скажите.

Проблема с камерой

Вы наверно заметили, что изображение пиздец какое хуевое? Это потому, что была наспех найдена в хламе под диваном помоечная USB-камера двадцатилетней давности. Для Raspberry существует камера-чип на шлейфе в специальный разъем (Raspberry Pi Camera rev. 1.3 камера, 5Мп. OmniVision OV5647), но она у меня загадочным образом НЕ ЗАРАБОТАЛА. Причем, раньше на Raspberry 2, помнится, работала. То ли в камере что-то повредилось (хотя при загрузке системы она два раза моргает своим красным светодиодом), то ли что-то поломали сборщики Raspberry OS последней заливки. Но камера не детектится на всех имеющихся у меня Raspberry:

vcgencmd get_camera
supported
=1 detected=0
?>

Если кто-то с таким сталкивался, подскажите.

На этом Шехерезада закончила дозволенные речи и отправилась по делам.

PS: Как вы понимаете, камера в этом посте будет работать не всегда, конечно я ее отключу через денек-другой.

<< предыдущая заметка следующая заметка >>
пожаловаться на эту публикацию администрации портала
архив понравившихся мне ссылок

Комментарии к этой заметке скрываются - они будут видны только вам и мне.

Оставить комментарий