# Проксування потоку m3u8 засобами ffmpeg в Icecast Я не маю значного досвіду з адміністрування стрімінгових сервісів, раніше на базі Icecast робив тільки локальну [ротацію музичної колекції](https://devzone.org.ua/post/veb-radio-v-linux-vstanovlennia-servera-icecast-ta-bazove-nalashtuvannia-rotatsiyi-z-ezstream), але зацікавив проєкт довкола-айтішних стрімів [eQtv](https://tv.equalitie.org/uk/live/). Цей матеріал - невеличка нотатка про налаштування проксі на прикладі сервісу [eQtv українською мовою для локальних мереж](https://devzone.org.ua/topic/retransliatsiia-eqtv-ukrayinskoiu-movoiu-dlia-lokalnykh-merez-audio-32-kbs). Витягнути потік виявилось задачею не тривіальною, я її постійно відкладав але згодом таки знайшов в дебагах (`ctrl+shift+i`) наступні доріжки: * `https://eqtv.live:8083/eqtvua/eqtvua480/chunks_dvr.m3u8` - схоже, що відповідає за відео * `https://eqtv.live:8083/eqtvua/eqtvua_hd_ukr/chunks_dvr.m3u8` - відповідає за аудіо-ряд (їх 3) ## ffmpeg Вже звичний мені ezstream не вміє проксувати потоки з URL, тому віднайшов спосіб з ffmpeg: ``` bash ffmpeg -i "https://eqtv.live:8083/eqtvua/eqtvua_hd_ukr/chunks_dvr.m3u8?nimblesessionid=xxx" -c:a copy icecast://user:password@127.0.0.1:8000/eQtv.aac ``` У прикладі вище - потік передається в Icecast "як є" у форматі AAC, але такий формат мені не зовсім підходить, бо я хочу окрім оверлейних мереж Yggdrasil і Mycelium ще й стрімити в I2P з його "вузьким" каналом. Хоч конвертація вимагає додаткових ресурсів CPU, все ж вирішив її застосувати, звівши до поширеного формату MP3 з бітрейтом 32 kb/s: ``` bash ffmpeg -i "https://eqtv.live:8083/eqtvua/eqtvua_hd_ukr/chunks_dvr.m3u8?nimblesessionid=xxx" -b:a 32k icecast://user:password@127.0.0.1:8000/eQtv.mp3 ``` Як видно на прикладах вище, я додав до URL джерела аргумент `?nimblesessionid=xxx` - він потрібен для того, щоб ffmpeg не плодив нові сесії в процесі читання. Тут я ще не знаю, як довго протримається поточна сесія, але якщо будуть проблеми - напишу скрипт, що витягає актуальний її номер та доповню цей матеріал. UPD. очікувано, сесія прожила менше доби, тому створив такий скрипт, будемо пускати його в `ExecStart` сервісу systemd, замість сирої команди `ffmpeg`: ``` /home/eqtv/stream.sh #!/bin/bash # завантажуємо актуальний файл m3u8, що містить активний номер сесії # та витягаємо перше знайдене значення в змінну SESSION_ID CHUNKS_DVR="/home/eqtv/chunks_dvr.m3u8" wget -O $CHUNKS_DVR https://eqtv.live:8083/eqtvua/eqtvua_hd_ukr/chunks_dvr.m3u8 SESSION_ID=$(grep -oP '(?<=sessionid=)\d+' $CHUNKS_DVR -m 1) if [ -z "$SESSION_ID" ]; then echo "SESSION_ID is empty. Exiting script." exit 1 fi # запускаємо стрім з актуальним значенням SESSION_ID ffmpeg -i "https://eqtv.live:8083/eqtvua/eqtvua_hd_ukr/chunks_dvr.m3u8?nimblesessionid=$SESSION_ID" -b:a 32k icecast://user:password@127.0.0.1:8000/eQtv.mp3 ``` * цей скрипт передбачає роботу з systemd: під час помилки процесу, буде виконано `Restart=on-failure`, таким чином ключ сесії буде актуалізовано UPD2. згодом, додав аргумент `-re` оскільки стрім вилітав через помилку затримки кодування: ``` bash ffmpeg -re -i ... ``` UPD3. також, на прикладі eQtv, збільшив гучність аргументом `-af volume=5.0`: ``` bash ffmpeg -re -i ... -af volume=5.0 ... ``` ## Icecast Декілька слів про налаштування сервера Icecast. Раніше, для локальних колекцій, мета-інформація про стрім в мене обслуговувалась сервером ezstream. Тут його немає, тому я додав такий набір до конфігурації точки монтування Icecast: ``` /etc/icecast2/icecast.xml /eQtv.mp3 user password eQtv українською мовою (аудіо, 32 kb/s) eQtv — це проект eQualitie, неприбуткової організації, що розробляє технології для підвищення цифрової стійкості, особливо для спільнот, яким загрожують цензура, стеження, зміна клімату та мережева ізоляція. https://tv.equalitie.org/uk/live ``` * відповідно, `user:password` мають відповідати тим, що вказані в команді `ffmpeg` ## Systemd Команду `ffmpeg` я виконую від системного сервісу, створивши відповідного користувача: ``` /etc/systemd/system/eqtv-mp3.service #/etc/systemd/system/eqtv-mp3.service [Unit] # якщо Icecast локальний, можна додати icecast2.target поряд з network-online # After=network-online.target icecast2.target After=network-online.target [Service] Type=simple User=eqtv Group=eqtv # Затримка потрібна у моєму випадку через залежність від Icecast # ExecStartPre=/bin/sleep 5s # ExecStart=/usr/bin/ffmpeg -i ... # ExecStart=/bin/bash /path/to/script.sh ExecStart=/path/to/script.sh # Журнали я вимкнув, але можна продебажити наступним чином StandardOutput=null # file:///home/eqtv/eqtv-mp3-debug.log StandardError=null # file:///home/eqtv/eqtv-mp3-error.log # Бажано увімкнути, якщо замість команди ffmpeg в ExecStart # використовується скрипт оновлення номеру сесії Restart=on-failure [Install] WantedBy=multi-user.target ``` * в `ExecStart` треба вказати відповідний набір атрибутів `ffmpeg` або шлях до скрипта (вище) ## Nginx Як видно з прикладу, мій сервер Icecast крутиться на інтерфейсі `127.0.0.1` для локальних потреб (щоб не ганяти трафік через оверлей). Окрім локалхосту, в мене відбувається ретрансляція на різні мережі IPv6, тому для зручності я проксую клієнтський трафік через Nginx: ``` /etc/nginx/sites-available/default #/etc/nginx/sites-available/default server { listen [202:68d0:f0d5:b88d:1d1a:555e:2f6b:3148]:8000; listen [505:6847:c778:61a1:5c6d:e802:d291:8191]:8000; listen xx.xx.xx.xx:8000; access_log /var/log/nginx/icecast.access.log; location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_redirect off; } } ``` * на прикладі `listen` - проксі на Yggdrasil та Mycelium і одна на локальну мережу IPv4 * `proxy_pass` - може бути відмінний локальний порт від вказаного у прикладах