![](https://habrastorage.org/getpro/habr/upload_files/c10/a9d/1b2/c10a9d1b2d3c3a03b8137bceed00fa06.jpg)
Как известно, у YouTube отсутствует фича захвата RTSP потока. Возможно, это сделано не случайно, а исходя из голой прагматики, чтобы люди не повесили на YouTube статическое видеонаблюдение за своими подъездами и не утилизировали его каналы, которые, как оказалось в пандемию, вовсе не резиновые. Напомним, что некоторое время назад имели место истории с ухудшением и ограничением качества стримов до 240p. Или есть еще одно предположение: стримы с IP камер — это зло для YouTube, потому что у них чуть более ноля зрителей, на которых не накрутишь миллион просмотров рекламы. Так или иначе, фича не представлена, и мы постараемся заполнить этот пробел - помочь YouTube осчастливить пользователей.
![](https://habrastorage.org/getpro/habr/upload_files/1b4/54f/a25/1b454fa25716ea2f96e9ba656e215361.png)
Допустим, мы хотим взять обычную уличную IP камеру, которая отдает H.264 поток по RTSP и перенаправить ее на YouTube. Для этого потребуется принять RTSP поток и сконвертировать в RTMPS поток, который принимает YouTube. Почему именно в RTMPS, а не RTMP? Как известно несекьюрные протоколы отмирают. HTTP предан гонениям, и его участь постигла другие протоколы, не имеющие буквы S - значит Secure на конце. От RTMP потока отказался Facebook, но спасибо, оставил RTMPS. Итак, конвертируем RTSP в RTMPS. Делаем это Headless способом (без использования UI), т.е. на сервере.
![](https://habrastorage.org/getpro/habr/upload_files/aac/9b4/817/aac9b4817005fc82fcc1ab5941706515.png)
Для одного RTSP потока потребуется один YouTube аккаунт, который будет принимать поток. Но что делать если камер не одна, а много?
Да, можно насоздавать вручную несколько YouTube-аккаунтов, например, чтобы покрыть видеонаблюдением приусадебный участок. Но это с огромной вероятностью нарушит условия пользовательского соглашения. А если камер не 10, а все 50? Создавать 50 аккаунтов? А дальше что? Смотреть это как? В этом случае на помощь может прийти микшер, который объединит камеры в один поток.
Посмотрим, как это работает на примере двух RTSP камер. Результирующий поток mixer1 = rtsp1 + rtsp2. Отправляем стрим mixer1 на YouTube. Все работает - обе камеры идут в одном потоке. Здесь стоит заметить, что микширование - достаточно ресурсоемкая по использованию CPU операция.
![](https://habrastorage.org/getpro/habr/upload_files/ba7/f66/c41/ba7f66c41320d4fee65757745795878d.png)
При этом, так как мы уже имеем RTSP поток на стороне сервера, мы можем перенаправить этот поток на другие RTMP endpoints, не неся при этом дополнительных расходов по CPU и памяти. Просто снимаем трафик с RTSP стрима и тиражируем на Facebook, Twitch, куда угодно без дополнительного RTSP захвата и депакетизации.
![](https://habrastorage.org/getpro/habr/upload_files/63f/411/52e/63f41152e0ecefc91bdad56ca59d3ff5.png)
Меня, как девопса, будет мучать совесть, если я не заскриптую то, что можно было бы заскриптовать. Автоматизации способствует наличие REST API для управления захватом видео с камеры и ретрансляцией.
Например, с помощью запроса:
/rtsp/startup
можно захватить видеопоток от IP камеры.
Запрос:
/rtsp/find_all
позволит получить список захваченных сервером RTSP потоков.
Запрос для завершения RTSP сессии выглядит так:
/rtsp/terminate
Захватом и ретрансляцией видеопотоков можно управлять или с помощью простого браузера и любого удобного REST клиента, или с помощью минимального количества строчек кода встроить функционал управления сервером в свой web проект.
Давайте подробно рассмотрим, как это можно сделать.
Небольшой мануал, как с помощью минимального кода организовать Live трансляцию на YouTube и Facebook
В качестве серверной части мы используем demo.flashphoner.com. Для быстрого развертывания своего WCS сервера воспользуйтесь этой инструкцией или запустите один из виртуальных инстансов на Amazon, DigitalOcean или в Docker.
Предполагается, что у вас имеется подтвержденный аккаунт на YouTube и вы уже создали трансляцию в YouTube Studio, а так же создали прямую видеотрансляцию в своем аккаунте на Facebook.
Для работы Live трансляций на YouTube и Facebook нужно указать в файле настроек WCS flashphoner.properties следующие строки:
rtmp_transponder_stream_name_prefix=
– Убирает все префиксы для ретранслируемого потока.
rtmp_transponder_full_url=true
– В значении "true" игнорирует параметр "streamName" и использует RTMP адрес для ретрансляции потока в том виде, в котором его указал пользователь.
rtmp_flash_ver_subscriber=LNX 76.219.189.0
- для согласования версий RTMP клиента между WCS и YouTube.
Теперь, когда все подготовительные действия выполнены, перейдем к программированию. Разместим в HTML файле минимально необходимые элементы:
Подключаем скрипты основного API и JS скрипт для работы live трансляции, который мы создадим чуть позже:
<script type="text/javascript" src="../../../../flashphoner.js"></script>
<script type="text/javascript" src="rtsp-to-rtmp-min.js"></script>
Инициализируем API на загрузку web-страницы:
<body onload="init_page()">
Добавляем нужные элементы и кнопки – поля для ввода уникальных кодов потоков для YouTube и Facebook, кнопку для републикации RTSP потока, div элемент для вывода текущего статуса работы программы и кнопку для остановки републикации:
<input id="streamKeyYT" type="text" placeholder="YouTube Stream key"/>
<input id="streamKeyFB" type="text" placeholder="FaceBook Stream key"/>
<button id="repubBtn">Start republish</button>
<div id="republishStatus"></div>
<br>
<button id="stopBtn">Stop republish</button>
Затем переходим к созданию JS скрипта для работы републикации RTSP. Скрипт представляет собой мини REST клиент.
Создаем константы:
Константа "url", в которую записываем адрес для запросов REST API . Замените "demo.flashphoner.com" на адрес своего WCS.
Константа "rtspStream" — указываем RTSP адрес потока с IP камеры. Мы для примера используем RTSP поток с виртуальной камеры.
var url = "https://demo.flashphoner.com:8444/rest-api";
var rtspStream = "rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov"
Функция "init_page()" инициализирует основной API при загрузке web - страницы. Так же в этой функции прописываем соответствие кнопок вызываемым функциям и вызываем функцию "getStream", которая захватывает RTSP видеопоток с IP камеры:
function init_page() {
Flashphoner.init({});
repubBtn.onclick = streamToYouTube;
stopBtn.onclick = stopStream;
getStream();
}
Функция "getStream()" отправляет на WCS REST запрос /rtsp/startup
который захватывает видеопоток RTSP адрес которого был записан в константу rtspStream
function getStream() {
fetchUrl = url + "/rtsp/startup";
const options = {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
"uri": rtspStream
}),
}
fetch(fetchUrl, options);
console.log("Stream Captured");
}
Функция "streamToYouTube()" републикует захваченный видеопоток в Live трансляцию на YouTube:
function streamToYouTube() {
fetchUrl = url + "/push/startup";
const options = {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
"streamName": rtspStream,
"rtmpUrl": "rtmp://a.rtmp.youtube.com/live2/"+document.getElementById("streamKeyYT").value
}),
}
fetch(fetchUrl, options);
streamToFB()
}
Эта функция отправляет на WCS REST вызов /push/startup
в параметрах которого передаются следующие значения:
"streamName" - имя потока, который мы захватили с IP камеры. Имя потока соответствует его RTSP адресу, который мы записали в константу "rtspStream"
"rtmpUrl" - URL сервера + уникальный код потока. Эти данные выдаются при создании Live трансляции в YouTube Studio. В нашем примере мы жестко закрепили URL в коде, вы можете добавить для него еще одно поле на свою web страницу. Уникальный код потока указывается в поле "streamKeyYT" на нашей Web странице.
Функция "streamToFB" републикует захваченный видеопоток в Live трансляцию на Facebook:
function streamToFB() {
fetchUrl = url + "/push/startup";
const options = {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
"streamName": rtspStream,
"rtmpUrl": "rtmps://live-api-s.facebook.com:443/rtmp/"+document.getElementById("streamKeyFB").value
}),
}
fetch(fetchUrl, options);
document.getElementById("republishStatus").textContent = "Stream republished";
}
Эта функция так же отправляет на WCS REST вызов "/push/startup" в параметрах которого передаются значения:
"streamName" - имя потока, который мы захватили с IP камеры. Имя потока соответствует его RTSP адресу, который мы записали в константу "rtspStream"
"rtmpUrl" - URL сервера + уникальный код потока. Эти данные можно найти на странице Live трансляции в Facebook в секции Live API. Url сервера в этой функции мы указали в коде, как и для функции републикации на YouTube. Уникальный код потока берем из поля "streamKeyFB" на Web странице.
Функция "stopStream()" отправляет RTSP запрос "/rtsp/terminate" который прекращает захват потока с IP камеры на WCS и соответственно прекращает публикации на YouTube и Facebook:
function stopStream() {
fetchUrl = url + "/rtsp/terminate";
const options = {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
"uri": document.getElementById("rtspLink").value
}),
}
fetch(fetchUrl, options);
document.getElementById("captureStatus").textContent = null;
document.getElementById("republishStatus").textContent = null;
document.getElementById("stopStatus").textContent = "Stream stopped";
}
Полные коды HTML и JS файлов рассмотрим немного ниже.
Итак. Сохраняем файлы и пробуем запустить.
Последовательность действий для тестирования
Создаем Live трансляцию в YouTube Studio. Копируем уникальный код видеопотока:
Открываем созданную ранее HTML страницу. Указываем в первом поле уникальный код видеопотока, который мы скопировали на YouTube:
Создаем Live трансляцию в своем аккаунте на Facebook. Копируем уникальный код видеопотока.
Возвращаемся на нашу web страничку, вставляем скопированный код во второе поле и нажимаем кнопку "Start republish
Теперь проверяем работу нашей републикации. Снова переходим в YouTube Studio и на Facebook, ждем несколько секунд и получаем превью потока.
Для завершения републикации нажмите кнопку "Stop"
Теперь, как и обещали, исходные коды примера полностью:
Листинг HTML файла "rtsp-to-rtmp-min.html"
<!DOCTYPE html>
<html lang="en">
<head>
<script type="text/javascript" src="../../../../flashphoner.js"></script>
<script type="text/javascript" src="rtsp-to-rtmp-min.js"></script>
</head>
<body onload="init_page()">
<input id="streamKeyYT" type="text" placeholder="YouTube Stream key" /> <input id="streamKeyFB" type="text" placeholder="Facebook Stream key" /> <button id="repubBtn">Start republish</button>
<div id="republishStatus"></div>
<br />
<button id="stopBtn">Stop republish</button>
</body>
</html>
Листинг JS файла "rtsp-to-rtmp-min.js":
var url = "https://demo.flashphoner.com:8444/rest-api";
var rtspStream = "rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov"?
function init_page() {
Flashphoner.init({});
repubBtn.onclick = streamToYouTube;
stopBtn.onclick = stopStream;
getStream();
}
function getStream() {
fetchUrl = url + "/rtsp/startup";
const options = {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
"uri": rtspStream
}),
}
fetch(fetchUrl, options);
console.log("Stream Captured");
}
function streamToYouTube() {
fetchUrl = url + "/push/startup";
const options = {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
"streamName": rtspStream,
"rtmpUrl": "rtmp://a.rtmp.youtube.com/live2/" + document.getElementById("streamKeyYT").value
}),
}
fetch(fetchUrl, options);
streamToFB()
}
function streamToFB() {
fetchUrl = url + "/push/startup";
const options = {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
"streamName": rtspStream,
"rtmpUrl": "rtmps://live-api-s.facebook.com:443/rtmp/" + document.getElementById("streamKeyFB").value
}),
}
fetch(fetchUrl, options);
document.getElementById("republishStatus").textContent = "Stream republished";
}
function stopStream() {
fetchUrl = url + "/rtsp/terminate";
const options = {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
"uri": rtspStream
}),
}
fetch(fetchUrl, options);
document.getElementById("republishStatus").textContent = "Stream stopped";
}
Для минимальной реализации требуется совсем немного кода. Конечно для итогового внедрения функционала еще потребуется небольшая доработка напильником - добавить стили на web страницу и разные проверки на валидность данных в код JS скрипта. Но это работает.
Удачного стриминга!
Ссылки
WCS на Amazon EC2 - Быстрое развертывание WCS на базе Amazon
WCS на DigitalOcean - Быстрое развертывание WCS на базе DigitalOcean
WCS в Docker - Запуск WCS как Docker контейнера
Трансляция WebRTC видеопотока с конвертацией в RTMP - Функции сервера по конвертации WebRTC аудио видео потока в RTMP
Трансляция потокового видео с профессионального устройства видеозахвата (Live Encoder) по протоколу RTMP - Функции сервера по конвертации видеопотоков от Live Encoder в RTMP
HTML5-трансляции с RTSP-IP камер - Функции сервера по воспроизведению RTSP видеопотоков
token
Просто оставлю это здесь:
ffmpeg -nostats -hide_banner -nostdin -loglevel error -f lavfi -i anullsrc -rtsp_transport tcp -r 25 -re -analyzeduration 0 -probesize 1024 -i rtsp://127.0.0.1:12345/9832 -tune zerolatency -c:v copy -c:a aac -strict experimental -f flv rtmp://x.rtmp.youtube.com/live2/key
К сожалению ввиду своей кармы я не могу вам ответить ха ха. Но о каких таких проблемах с китайским RTSP вы говорите? У меня только китайские камеры причем самые голимые, стримы висят по полгода без каких бы то ни было проблем.
Lemko
к сожаленья проблема с «китайским» RTSP :(
sacai
а можете оставить здесь такой же однострочник, но на YT, FB и Twitch одновременно?
timka05
А можно чуть подробнее?
token
Конечно, только раз в сутки.
Lemko
Потеря пакетів при нахождении сервера далеко по сети. Например, у нас камеры в Украине а прием в Нидерландах чистый ужас. Пока не сделали рядом с камерами релей.
Lemko
Сделал по Вашему примеру с -f lavfi -i anullsrc улучшились все потоки.
Спасибо добрый человек.