Переключение трэков MediaStream при трансляции WebRTC потока как RTMP

defor

New Member
Добрый день.
Стоит довольно интересная задача, с которой пока не удалось справиться полностью.
Опишу сначала сетап, а потом вопросы. Текста многовато, заранее прошу прощения.
Пытаемся заимплеменитить своеобразную видео студию онлайн. На клиентской стороне мы получаем активные WebRTC стримы (объекты MediaStream). Это может быть как видео с нескольких камер, подключенных к данному клиентскому устройству, так и видео удаленных пиров. По факту имеем массив MediaStream. Один из стримов выбирается активным, после чего устанавливается сессия с WCS и стартуется стрим с указанной RTMP урлой. Такая схема "условно" рабочая (есть проблемы с качеством, но про это я уточню в блоке вопросов).
Главные сложности начинаются при попытке изменить активный WebRTC стрим. Стоит отметить, что при запустке Flashphoner стрима всегда создается новый объект MediaStream (назовем его Live Stream) и к нему добавляются клоны трэков выбранного WebRTC стрима. Я пробовал различные сценарии:
- самый логичный и в теории самый правильный - замена трэков. Как и при запуске нового, получаем массив клонов трэков вновь выбранного WebRTC стрима, очищаем все трэки Live Stream-a и вместо них вставляем клоны. При таком сценарии секунд через 10 Flashphoner стрим просто фейлится...
- пробовал "паузить" Flashphoner стрим. На паузе опять же пытался подменить трэки и сам сustomStream. Но судя по всему из паузы снова стартануться нельзя
- и наконец единственный рабочий вариант, который далек от совершенства: стопаю Flashphoner стрим. После на коллбэке .on(STREAM_STATUS.UNPUBLISHED) создается и стартует новый стрим с обновленным Live Stream. Главная проблема этого сценария в том, что при стопе потока RTMP стрим "падает". Он, конечно, перезапуститься через некоторое время (часто нужно перезагружать плэйер), но это недопустимо при live стриминге.

И, наконец, конкретные вопросы:
1) (и самый важный) Каким образом возможно подменять WebRTC трэки или весь WebRTC стрим на лету, без приостановки потока?
2) При обычной трансляции WebRTC-RTMP, когда сам Web SDK захватывает картинку с камеры и микрофона можно было поиграться битрэйтом, чтобы RTMP получал видео нормального качества. Когда же в constraints я передаю customStream, то ничего не могу передать в video constraints, стрим тупо не стартуется и выходное качество ужасное. Как в случае customStream настроить битрэйты, кодеки и проч?
3) Качество звука оставляет желать лучшего, подозреваю это тоже можно настроить, и скорее всего на серверной стороне. Подскажите, пожалуйста, как?
 

Max

Administrator
Staff member
Добрый день
Уточните, пожалуйста - речь идет о захвате потоков с HTML5 Canvas, публикации по WebRTC и ретрансляции как RTMP одного из потоков по выбору оператора?
Не самая простая схема. Давайте попробуем разделить задачу.
1. Захват видео
Это может быть как видео с нескольких камер, подключенных к данному клиентскому устройству, так и видео удаленных пиров.
Почему бы не захватывать видео с камер непосредственно, не прибегая к помощи элемента canvas?
Камеры с других устройств точно также можно публиковать на сервер, а не тянуть предварительно на клиента
2. Выбор потока
Для отображения всех потоков оператору можно использовать микшер на стороне сервера, для этого все публикуемые потоки надо будет добавить в него.
3. Ретрансляция как RTMP
1) (и самый важный) Каким образом возможно подменять WebRTC трэки или весь WebRTC стрим на лету, без приостановки потока?
Чтобы ретрансляция не прерывалась, можно использовать еще один микшер на сервере
- создается микшер
- выходной поток микшера ретранслируется как RTMP при помощи /rest-api/push/startup
- выбранный активным поток добавляется в микшер при помощи /rest-api/mixer/add
- перед этим предыдущий активный поток удаляется из микшера при помощи /rest-api/mixer/remove
Такая схема позволит управлять качеством публикации на клиенте.
Как в случае customStream настроить битрэйты, кодеки и проч?
Кодеки можно настроить при помощи stripCodecs, это должно работать и при публикации с элемента canvas
Code:
publishStream = session.createStream({
    ...
    stripCodecs: "h264,H264,flv,mpv"
}).on(STREAM_STATUS.PUBLISHING, function (publishStream) {
    ...
});
publishStream.publish();
Битрейтом в этом случае можно управлять на стороне сервера
Code:
webrtc_cc_min_bitrate=300000
webrtc_cc_max_bitrate=500000
3) Качество звука оставляет желать лучшего, подозреваю это тоже можно настроить, и скорее всего на серверной стороне. Подскажите, пожалуйста, как?
Для управления параметрами звука при ретрансляции потока как RTMP поместите в каталог /usr/local/FlashphonerWebCallServer/conf файл media_transponder.sdp со следующим содержимым:
Code:
v=0
o=- 1988962254 1988962254 IN IP4 0.0.0.0
c=IN IP4 0.0.0.0
t=0 0
a=sdplang:en
m=video 0 RTP/AVP 95 96
a=rtpmap:95 H264/90000
a=fmtp:95 profile-level-id=42e01f;packetization-mode=0
a=rtpmap:96 H264/90000
a=fmtp:96 profile-level-id=42e01f;packetization-mode=1
a=recvonly
m=audio 0 RTP/AVP 103 108
a=rtpmap:108 mpeg4-generic/48000/1
a=rtpmap:96 mpeg4-generic/8000/1
a=rtpmap:97 mpeg4-generic/11025/1
a=rtpmap:98 mpeg4-generic/12000/1
a=rtpmap:99 mpeg4-generic/16000/1
a=rtpmap:100 mpeg4-generic/22050/1
a=rtpmap:104 mpeg4-generic/24000/1
a=rtpmap:102 mpeg4-generic/32000/1
a=rtpmap:103 mpeg4-generic/44100/1
a=recvonly
 

defor

New Member
Уточните, пожалуйста - речь идет о захвате потоков с HTML5 Canvas, публикации по WebRTC и ретрансляции как RTMP одного из потоков по выбору оператора?
Насколько я понимаю, не так.
Пока мобильные камеры не подключались, все проверяется на вебках. Упрощенное объяснение на примере 2-х пользователей:
- на двух компьютерах (бродкастерах) захватываются камеры через
JavaScript:
navigator.mediaDevices.getUserMedia
- третья машина выступает в роли оператора/оркестратора/студии
- поднят сервер с SignalR, при помощи которого все 3 узла знают друг о друге
- как только оркестратор активируется, все бродкастеры соединяются с ним через SimplePeer и напрямую передают свои MediaStream потоки
- оркестратор отрисовывает их при помощи HTML5 video и позволяет выбрать активный поток
- после это появляется возможность ретрансляции его в RTMP
Code:
const tracks = this.selectedStream.stream.getTracks().map(t => t.clone());
this.liveStream = new MediaStream(tracks);

this.flashOptions.stream = this.flashOptions.session.createStream({
  name: 'flashphoner_broadcast',
  display: this.flashOptions.previewRef,
  cacheLocalResources: true,
  rtmpUrl: this.broadcastDef.inputUrl,
  constraints: {
     customStream: this.liveStream
  }
})
  .on(this.flashphoner.constants.STREAM_STATUS.PUBLISHING, streamStarted)
  .on(this.flashphoner.constants.STREAM_STATUS.UNPUBLISHED, () => streamStopped(this.flashphoner.constants.STREAM_STATUS.UNPUBLISHED, false))
  .on(this.flashphoner.constants.STREAM_STATUS.FAILED, () => streamStopped(this.flashphoner.constants.STREAM_STATUS.FAILED, true));
this.flashOptions.stream.publish();
}
Попытаюсь воспроизвести ваши советы, скорее всего будут вопросы) Спасибо большое.
 

Max

Administrator
Staff member
- на двух компьютерах (бродкастерах) захватываются камеры через
JavaScript:
navigator.mediaDevices.getUserMedia
- третья машина выступает в роли оператора/оркестратора/студии
- поднят сервер с SignalR, при помощи которого все 3 узла знают друг о друге
- как только оркестратор активируется, все бродкастеры соединяются с ним через SimplePeer и напрямую передают свои MediaStream потоки
- оркестратор отрисовывает их при помощи HTML5 video и позволяет выбрать активный поток
- после это появляется возможность ретрансляции его в RTMP
В таком случае, если уж Вы используете сервер для ретрансляций, серверная модель публикации потоков (с публикацией потоков бродкастеров на сервер и сигналингом через REST API и REST хуки) выглядит предпочтительнее, т.к. снимает нагрузку с браузера, собирающего все потоки, и позволяет обойти ограничения трансляции с канваса.
Сигналинг, кстати, Вы можете использовать и собственный, REST вызовы в этом случае потребуются для того, чтобы определить, что все потоки есть на сервере и готовы к ретрансляции.
 

defor

New Member
Все отлично получилось. Большое спасибо. Осталось разобраться, как убрать отступы у стрима микшера и можно в продакшн)
Да и наверное логично заюзать ваши REST хуки, вместо еще одного сигнального сервера.
 

Max

Administrator
Staff member
Осталось разобраться, как убрать отступы у стрима микшера и можно в продакшн)
Для этого нужно выставить соответствующее размещение картинок настройкой
Code:
mixer_layout_class=com.flashphoner.media.mixer.video.presentation.CenterNoPaddingGridLayout
В этом случае отступов не должно быть вообще
 
Top