Рестрим по RTMP (ошибка битрейта)

ArnoldE

New Member
Коллеги привет,
посоветуйте пожалуйста что можно сделать.

Необходимо получить видеопоток по WebRTC и рестримить его по RTMP на площадку Amazon Live Shoppable videos (https://www.amazon.com/live)
У Amazon вот такие требования к входящему потоку:

Recommended broadcast software settings for Amazon Live
Video Resolution: 1280x720
Frames per second: 30
AVC Level: 31
Average bit rate: 2800 kbits/sec
Keyframe interval: 2 (we do not support keyframe intervals greater than 4 seconds).
For other software, set the keyframe interval to every 60 seconds.
Транслируем видеопоток на сервер Flashphoner по WebRTC и после успешной публикации потока через RestAPI запускаем рестрим:

JavaScript:
function publish(session) {
  publishStream = session.createStream({
    name: streamName,
        display: localDisplay,
        videoContentHint: "motion",
        transport: "TCP",
    stripCodecs: "vp8,VP8,ALAW,G722,PCMU,PCMA,flv,mpv",
    constraints: {
      audio: true,
      video: false,
      customStream: canvas.captureStream(30)
    },
        sdpHook: this.sdpHook,
  }).on(STREAM_STATUS.PUBLISHING, function(streamEvent) { 
    addRestreamDestination("rtmp://rtmp.live.amazon.com/live" + "STREAMKEY");
  });
  publishStream.publish();
}

function sdpHook(sdp) {
    var sdpStringFind1 = "c=IN (.*)\r\n";
    var sdpStringReplace1 = "c=IN $1\r\nb=AS:10000\r\n";
    var sdpStringFind2 = "a=fmtp:(.*) (.*)";
    var sdpStringReplace2 = "a=fmtp:$1 $2;x-google-start-bitrate=2800;x-google-max-bitrate=4000;x-google-min-bitrate=2800";
    var newSDP = sdp.sdpString.toString();
        newSDP = newSDP.replace(new RegExp(sdpStringFind1, "g"), sdpStringReplace1);
        newSDP = newSDP.replace(new RegExp(sdpStringFind2, "g"), sdpStringReplace2);
    return newSDP;
}

async function addRestreamDestination(link) {
    try {
      const response = await fetch(urlFlashphonerRestApt + "/push/startup", {
        method: 'POST',
        body: JSON.stringify({
            "streamName": streamName,
            "width": 1280,
            "height": 720,
            "fps": 30,
            "keyFrameInterval": 60,
            "bitrate": 2800,
                        "rtmpUrl": link
        }),
        headers: {
          'Content-Type': 'application/json'
        }
      });
      const json = await response.json();
    } catch (error) {
      console.error('Error:', error);
    }
}
Через RestAPI запрашиваем все существующие рестримы (https://FLASHPHONERSERVER:8888/rest-api/push/find_all/):
Получаем такой ответ:

[{"mediaSessionId":"b1u1q2ag7spsvu10k75ab42ni0","streamName":"STREAMKEY","rtmpUrl":"rtmp://rtmp.live.amazon.com/live","rtmpFlashVersion":"LNX 76.219.189.0","rtmpTransponderStreamNamePrefix":"","width":1280,"height":720,"fps":30,"bitrate":2800,"keyFrameInterval":60,"rtmpTransponderKframeInterval":60,"muted":false,"soundEnabled":false,"rtmpTransponderForceKframeInterval":true,"rtmpTransponderFullUrl":true}]
Amazon, отвечает ошибкой: "Encoder setup error: Bit rate varies from the recommended settings (recommended: 2,800,000)" и не запускает трансляцию.

Если взять те же настройки (URL + STREAMKEY) и запустить OBS, указав настройки:
Снимок экрана 2022-08-30 в 13.02.26.png

То трансляция запускается без ошибок.

Помогите пожалуйста советом, куда еще посмотреть? )
 

Max

Administrator
Staff member
Добрый день.
Вы публикуете поток с констрейнтами по умолчанию
constraints: { audio: true, video: false, customStream: canvas.captureStream(30) },
Рекомендуем проставить констрейнты:
Code:
video: {
     width: 1280,
     height: 720,
     frameRate: 30
     minBitrate: 2800,
     maxBitrate: 4000
}
В последних версиях Chrome достаточно выставить минимальный битрейт в констрейнтах, без трюка с подменой параметров SDP.
Желательно также нормализовать отправку ключевых кадров при публикации исходного потока настройками сервера
Code:
periodic_fir_request=true
periodic_fir_request_interval=2000
Кроме того, на сервере должны быть выставлены настройки для RTMP републикации
Code:
rtmp_transponder_full_url=true
rtmp_transponder_stream_name_prefix=
rtmp_flash_ver_subscriber=LNX 76.219.189.0
rtmp_transponder_send_metadata=true
После этого проверьте, что хватает канала публикации от клиента до WCS: проверьте, что битрейт исходного потока близок к установленным ограничениям. Это можно сделать при помощи REST API запроса: Получение общей информации о потоке
Code:
POST /rest-api/stream/find HTTP/1.1
Host: 192.168.1.101:8081
Content-Length: 57
Content-Type: application/json
 
{
    "name":"stream1",
    "published":true,
    "display":["metrics"]
}
Проверьте, что поток, опубликованный на WCS, играется с него же плавно, с близкими к заданным битрейтом и FPS.
Также канала может не хватать и от WCS до Amazon, в этом случае Amazon будет получать более низкий битрейт.
Отметим, что при используемых Вами параметрах републикации на сервере работает транскодинг потока. Рекомендуем использовать сервер на ниже 8 CPU / 32 Gb RAM (16 Gb на Java heap: Настройка оперативной памяти). Можно обойтись без транскодинга, в этом случае при републикации нельзя задавать размеры кадра, FPS, GOP и битрейт, все это должно быть выставлено для исходного потока, как описано выше.
 

ArnoldE

New Member
Благодарю за ответ,
насколько я понимаю одновременно нельзя использовать в "constraints" "customStream" и "video"

JavaScript:
constraints: {
    audio: true,
    video: {
        width: 1280,
        height: 720,
        frameRate: 30,
        minBitrate: 2800,
        maxBitrate: 4000,
    },
    customStream: canvas.captureStream(30)
},
При исполнении такого кода я получаю ошибку:

Uncaught (in promise) DOMException: Failed to execute 'setRemoteDescription' on 'RTCPeerConnection': Failed to set remote answer sdp: The order of m-lines in answer doesn't match order in offer. Rejecting answer.
Promise.then (async)
streamRefreshHandlers.<computed> @ flashphoner.js:13190
wsConnection.onmessage @ flashphoner.js:11925
 

ArnoldE

New Member
Результат REST API запроса: Получение общей информации о потоке

Code:
{
"appKey": "defaultApp",
"sessionId": "/80.92.205.188:56740/SERVERIP:8443-70dd0fc3-077f-459c-a695-63fffe4ef814",
"mediaSessionId": "758cc270-29b2-11ed-897c-a39f8b3f72d4",
"name": "df3",
"published": true,
"hasVideo": true,
"hasAudio": true,
"status": "PUBLISHING",
"sdp": "v=0 o=- 2578674277050711178 2 IN IP4 127.0.0.1 s=- t=0 0 a=group:BUNDLE 0 1 a=extmap-allow-mixed a=msid-semantic: WMS 5654a25e-db57-4e15-b838-d5472fa5cbe3 m=audio 9 UDP/TLS/RTP/SAVPF 111 63 103 104 106 105 13 110 112 113 126 c=IN IP4 0.0.0.0 a=rtcp:9 IN IP4 0.0.0.0 a=ice-ufrag:dlsE a=ice-pwd:x8h8KDpmAXJKr/C8PbGlIggK a=ice-options:trickle a=fingerprint:sha-256 70:2C:E1:25:6B:DC:D7:05:F1:77:FC:72:EC:FB:CD:1C:CC:8B:1B:64:84:99:92:E1:BB:8E:22:3C:57:D8:D0:BA a=setup:actpass a=mid:0 a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01 a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid a=sendonly a=msid:5654a25e-db57-4e15-b838-d5472fa5cbe3 912bdf0c-94c3-41c0-9dcf-d494e15b6717 a=rtcp-mux a=rtpmap:111 opus/48000/2 a=rtcp-fb:111 transport-cc a=fmtp:111 minptime=10;useinbandfec=1 a=rtpmap:63 red/48000/2 a=fmtp:63 111/111 a=rtpmap:103 ISAC/16000 a=rtpmap:104 ISAC/32000 a=rtpmap:106 CN/32000 a=rtpmap:105 CN/16000 a=rtpmap:13 CN/8000 a=rtpmap:110 telephone-event/48000 a=rtpmap:112 telephone-event/32000 a=rtpmap:113 telephone-event/16000 a=rtpmap:126 telephone-event/8000 a=ssrc:1332875946 cname:zMq0hr6V8CFOi3bw a=ssrc:1332875946 msid:5654a25e-db57-4e15-b838-d5472fa5cbe3 912bdf0c-94c3-41c0-9dcf-d494e15b6717 m=video 9 UDP/TLS/RTP/SAVPF 97 127 121 125 107 108 109 124 120 123 119 35 36 41 42 98 99 100 101 114 115 116 117 118 c=IN IP4 0.0.0.0 a=rtcp:9 IN IP4 0.0.0.0 a=ice-ufrag:dlsE a=ice-pwd:x8h8KDpmAXJKr/C8PbGlIggK a=ice-options:trickle a=fingerprint:sha-256 70:2C:E1:25:6B:DC:D7:05:F1:77:FC:72:EC:FB:CD:1C:CC:8B:1B:64:84:99:92:E1:BB:8E:22:3C:57:D8:D0:BA a=setup:actpass a=mid:1 a=extmap:14 urn:ietf:params:rtp-hdrext:toffset a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time a=extmap:13 urn:3gpp:video-orientation a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01 a=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid a=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id a=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id a=sendonly a=msid:5654a25e-db57-4e15-b838-d5472fa5cbe3 130b4d7d-a718-4b08-b15c-ac0bc1690e97 a=rtcp-mux a=rtcp-rsize a=rtpmap:97 rtx/90000 a=rtpmap:127 H264/90000 a=rtcp-fb:127 goog-remb a=rtcp-fb:127 transport-cc a=rtcp-fb:127 ccm fir a=rtcp-fb:127 nack a=rtcp-fb:127 nack pli a=fmtp:127 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f a=rtpmap:121 rtx/90000 a=fmtp:121 apt=127 a=rtpmap:125 H264/90000 a=rtcp-fb:125 goog-remb a=rtcp-fb:125 transport-cc a=rtcp-fb:125 ccm fir a=rtcp-fb:125 nack a=rtcp-fb:125 nack pli a=fmtp:125 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f a=rtpmap:107 rtx/90000 a=fmtp:107 apt=125 a=rtpmap:108 H264/90000 a=rtcp-fb:108 goog-remb a=rtcp-fb:108 transport-cc a=rtcp-fb:108 ccm fir a=rtcp-fb:108 nack a=rtcp-fb:108 nack pli a=fmtp:108 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f a=rtpmap:109 rtx/90000 a=fmtp:109 apt=108 a=rtpmap:124 H264/90000 a=rtcp-fb:124 goog-remb a=rtcp-fb:124 transport-cc a=rtcp-fb:124 ccm fir a=rtcp-fb:124 nack a=rtcp-fb:124 nack pli a=fmtp:124 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f a=rtpmap:120 rtx/90000 a=fmtp:120 apt=124 a=rtpmap:123 H264/90000 a=rtcp-fb:123 goog-remb a=rtcp-fb:123 transport-cc a=rtcp-fb:123 ccm fir a=rtcp-fb:123 nack a=rtcp-fb:123 nack pli a=fmtp:123 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d001f a=rtpmap:119 rtx/90000 a=fmtp:119 apt=123 a=rtpmap:35 H264/90000 a=rtcp-fb:35 goog-remb a=rtcp-fb:35 transport-cc a=rtcp-fb:35 ccm fir a=rtcp-fb:35 nack a=rtcp-fb:35 nack pli a=fmtp:35 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=4d001f a=rtpmap:36 rtx/90000 a=fmtp:36 apt=35 a=rtpmap:41 AV1/90000 a=rtcp-fb:41 goog-remb a=rtcp-fb:41 transport-cc a=rtcp-fb:41 ccm fir a=rtcp-fb:41 nack a=rtcp-fb:41 nack pli a=rtpmap:42 rtx/90000 a=fmtp:42 apt=41 a=rtpmap:98 VP9/90000 a=rtpmap:99 rtx/90000 a=rtpmap:100 VP9/90000 a=rtcp-fb:100 goog-remb a=rtcp-fb:100 transport-cc a=rtcp-fb:100 ccm fir a=rtcp-fb:100 nack a=rtcp-fb:100 nack pli a=fmtp:100 profile-id=2 a=rtpmap:101 rtx/90000 a=fmtp:101 apt=100 a=rtpmap:114 H264/90000 a=rtcp-fb:114 goog-remb a=rtcp-fb:114 transport-cc a=rtcp-fb:114 ccm fir a=rtcp-fb:114 nack a=rtcp-fb:114 nack pli a=fmtp:114 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=64001f a=rtpmap:115 rtx/90000 a=fmtp:115 apt=114 a=rtpmap:116 red/90000 a=rtpmap:117 rtx/90000 a=fmtp:117 apt=116 a=rtpmap:118 ulpfec/90000 a=ssrc-group:FID 836160531 1530011211 a=ssrc:836160531 cname:zMq0hr6V8CFOi3bw a=ssrc:836160531 msid:5654a25e-db57-4e15-b838-d5472fa5cbe3 130b4d7d-a718-4b08-b15c-ac0bc1690e97 a=ssrc:1530011211 cname:zMq0hr6V8CFOi3bw a=ssrc:1530011211 msid:5654a25e-db57-4e15-b838-d5472fa5cbe3 130b4d7d-a718-4b08-b15c-ac0bc1690e97 ",
"audioCodec": "opus",
"videoCodec": "H264",
"record": false,
"width": 1280,
"height": 720,
"bitrate": 0,
"minBitrate": 0,
"maxBitrate": 0,
"quality": 0,
"history": false,
"gop": 0,
"fps": 0,
"audioBitrate": 0,
"codecImpl": "",
"transport": "TCP",
"cvoExtension": false,
"createDate": 1662008197470,
"mediaType": "publish",
"audioState": {
"muted": false
},
"videoState": {
"muted": false
},
"mediaProvider": "WebRTC",
"metrics": {
"VIDEO_SYNC": 1662008247642,
"VIDEO_K_FRAMES": 6,
"AUDIO_SYNC": 1662008247670,
"VIDEO_NACK": 146,
"AUDIO_RATE": 265608,
"AUDIO_LOST": 229,
"VIDEO_LOST": 194,
"VIDEO_CODEC": 119,
"VIDEO_B_FRAMES": 0,
"VIDEO_PLI": 0,
"AUDIO_CODEC": 111,
"VIDEO_RATE": 3293712,
"VIDEO_WIDTH": 1280,
"VIDEO_GOP_SIZE": 353,
"VIDEO_HEIGHT": 720,
"VIDEO_FPS": 29,
"VIDEO_P_FRAMES": 1027
},
"origin": "http://localhost:8080",
"constraints": {
"audio": true,
"video": false,
"customStream": {}
}
}
 
Last edited:

ArnoldE

New Member
При добавлении данных параметров в конфигурационный файл сервера:

Code:
periodic_fir_request=true
periodic_fir_request_interval=2000
появилась каждые 2 секунды "пульсация" в видеопотоке (такое ощущение что за доли секунды картинка распадается на пиксели, а потом резко собирается вновь)

Прикладываю конфигурационный файл сервера "flashphoner.properties"

# Config flashphoner.properties
# To get more settings:
# ssh -p 2001 admin@localhost
# default password: admin
# show node-settings
# show node-settings | grep port
#server ip
ip=SERVERIP
ip_local=SERVERIP

#webrtc ports range
media_port_from=31001
media_port_to=32000

#codecs
codecs=opus,alaw,ulaw,g729,speex16,g722,mpeg4-generic,telephone-event,h264,vp8,flv,mpv
codecs_exclude_sip=mpeg4-generic,flv,mpv
codecs_exclude_streaming=flv,telephone-event
codecs_exclude_sip_rtmp=opus,g729,g722,mpeg4-generic,vp8,mpv

#websocket ports
ws.port=8080
wss.port=8443

webrtc_cc_min_bitrate=750000
webrtc_cc_max_bitrate=5000000

rtmp_transponder_full_url=true
rtmp_transponder_stream_name_prefix=
rtmp_flash_ver_subscriber=LNX 76.219.189.0
rtmp_transponder_send_metadata=true

periodic_fir_request=true
periodic_fir_request_interval=2000
 

ArnoldE

New Member
Также прикладываю график утилизации ресурсов сервера (пики, это трансляция видеопотока)

Снимок экрана 2022-09-01 в 08.26.10.png
 

Max

Administrator
Staff member
насколько я понимаю одновременно нельзя использовать в "constraints" "customStream" и "video"
Действительно, если Вы захватываете поток с канваса, констрейнты должны быть выставлены следующим образом:
Code:
    constraints = {
        audio: false,
        video: false,
        customStream: stream
    };
Посмотрите также исходные тексты примера Canvas Streaming. В этом случае будут следующие ограничения:
1. Разрешение публикации всегда будет не выше, чем разрешение канваса (т.е. Вы должны рисовать картинку 1280x720 для соблюдения требований Amazon
2. Страница, на которой расположен элемент, с которого захватывается поток, должна быть всегда на переднем плане, перекрытие другим окном или переключение на другую вкладку браузера не допускаются.
3. Управление битрейтом возможно только на стороне сервера, в Вашем случае настройки должны быть примерно такими:
Code:
webrtc_cc_min_bitrate=2800000
webrtc_cc_max_bitrate=4000000
для соблюдения требований Amazon
появилась каждые 2 секунды "пульсация" в видеопотоке
Это означает, что каждые две секунды приходит ключевой фрейм от браузера. Уточните, где именно Вы смотрите картинку: играете транслируемый поток в примере Player с WCS сервера, или с того сервера, который принимает RTMP поток?
Если Вы играете поток с WCS сервера и наблюдаете пикселизацию, это означает проблемы с каналом публикации или проигрывания. Судя по приведенным метрикам
"VIDEO_K_FRAMES": 6,
...
"VIDEO_NACK": 146,
...
"AUDIO_LOST": 229,
"VIDEO_LOST": 194,
...
"VIDEO_P_FRAMES": 1027
число потерянных пакетов составляет около 10% от общего числа принятых пакетов. Кроме того, ключевые фреймы приходят редко (всего 6 за время этой публикации).
Это говорит о том, что, если Вы используете TCP для публикации, пропускной способности канала между публикующим клиентом и сервером не хватает для потока 720p с высоким битрейтом. Рекомендуем публиковать поток через чистый канал без потерь не менее 20 Мбит/с, либо снизить разрешение и битрейт потока до таких, которые будут соответствовать каналу. Соответственно, на стороне RTMP эндпойнта придется выставить более низкие настройки, чем 720p 2800 кбит/с. Youtube, например, таких ограничений не налагает. Транскодинг к требуемым параметрам в этом случае не сильно поможет, т.к. апскейлинг всегда ухудшает качество.
Также прикладываю график утилизации ресурсов сервера (пики, это трансляция видеопотока)
Все верно, Вы заказываете транскодинг, указывая явно разрешение при републикации. Транскодинг сильно нагружает сервер, поэтому мы предлагали здесь минимальную конфигурацию сервера, если нельзя обойтись без транскодинга.
Итого: рекомендуем протестировать предварительно публикацию не с канваса, а с камеры, убедиться, что нет потерь при публикации потока на WCS с параметрами, которые требуются для републикации как RTMP. После этого тестировать републикацию RTMP. Если нет возможности использовать более хороший канал публикации исходного потока, снижать его параметры и ограничивать требования на стороне, которая принимает RTMP.
 

ArnoldE

New Member
Подскажите в чем может быть еще разница между Flashphoner (Рестрим RTMP) и OBS?

Провели следующий эксперимент:

Эксперимент 1:
С помощью вебприложения из браузера отправили видеопоток по протоколу WebRTC на Flashphoner, запустили рестрим по протоколу RTMP на площадку Amazon Live.
Схема: WebRTC -> Flashphoner -> RTMP -> Amazon Live
Результат: "Encoder setup error: Bit rate varies from the recommended settings (recommended: 2,800,000)"

Эксперимент 2:
С помощью вебприложения из браузера отправили видеопоток по протоколу WebRTC на Flashphoner, запустили рестрим по протоколу RTMP на тот же Flashphoner в приложение live, далее на том же ПК (у которого в браузере запущено вебприложение) запустили OBS и в виде источника видеосигнала использовали RTMP с Flashphoner приложения live, далее с помощью OBS запустили трансляцию по RTMP на площадку Amazon Live.
Схема: WebRTC -> Flashphoner -> RTMP -> OBS -> RTMP -> Amazon Live
Результат: ОК
 

Max

Administrator
Staff member
Разница в том, что при стриминге с OBS битрейт будет стабильным, а при републикации WebRTC стрима как RTMP битрейт будет зависеть от битрейта источника.
По приведенным Вами ранее метрикам, у Вас плохой канал от ПК до WCS, которого не хватает на заданный битрейт. Либо, другой вариант. Вы отправляете страницу с канвасом в фон, что дает остановку видеотрафика и, как следствие, рост потерянных пакетов и сброс битрейта.
Попробуйте добиться того, чтобы стрим с вебкамеры (не с канваса) корректно (без потерь) заходил на WCS. В этом случае с републикацией стрима по RTMP не должно быть проблем.
Например, опубликуйте в примере Media Devices https://wcs:8444/client2/examples/demo/streaming/media_devices_manager/media_device_manager.html поток 720p с камеры, с битрейтов в диапазоне 2800-3000 кбит/с по TCP
1662946350045.png

убедитесь, что по статистике битрейт достиг желаемого, но при этом счетчик NACK для видео не растет
1662946604739.png

проверьте, что разрешение стрима соответствует желаемому, по метрикам VIDEO_WIDTH, VIDEO_HEIGHT
Code:
POST /rest-api/stream/find HTTP/1.1
Host: wcs:8081
Content-Type: application/json

{
    "name":"test",
    "published":true,
    "display":["metrics"]
}
и републикуйте этот поток по RTMP на Amazon Live при помощи REST API /push/startup
Code:
POST /rest-api/push/startup HTTP/1.1
Host: wcs:8081
Content-Type: application/json

{
    "streamName": "test",
    "rtmpUrl":"rtmp://rtmp.live.amazon.com/live/streamKey",
    "rtmpTransponderFullUrl": true
}
 
Top