Looks like we found the issue.
A fix will be available in a future version of the engine, but if you want to try it out, you need to change two files:
Engine\Plugins\Media\PixelStreaming\Source\PixelStreaming\Private\Streamer.cpp, change the FStreamer::AddStreams method to make use of a cricket::AudioOptions struct. Full function follows:
void FStreamer::AddStreams(FPlayerId PlayerId)
{
const FString StreamId = TEXT("stream_id");
const char AudioLabel[] = "audio_label";
const char VideoLabel[] = "video_label";
FPlayerSession* Session = GetPlayerSession(PlayerId);
check(Session);
cricket::AudioOptions AudioSourceOptions;
AudioSourceOptions.echo_cancellation = false;
AudioSourceOptions.auto_gain_control = false;
AudioSourceOptions.noise_suppression = false;
if (bPlanB)
{
rtc::scoped_refptr<webrtc::MediaStreamInterface> Stream;
if (auto* StreamPtr = Streams.Find(StreamId))
{
Stream = *StreamPtr;
}
else
{
Stream = PeerConnectionFactory->CreateLocalMediaStream(TCHAR_TO_ANSI(*StreamId));
rtc::scoped_refptr<webrtc::AudioTrackInterface> AudioTrackLocal(
PeerConnectionFactory->CreateAudioTrack(AudioLabel, PeerConnectionFactory->CreateAudioSource(AudioSourceOptions)));
Stream->AddTrack(AudioTrackLocal);
auto VideoCapturerStrong = std::make_unique<FVideoCapturer>(HWEncoderDetails);
VideoCapturer = VideoCapturerStrong.get();
rtc::scoped_refptr<webrtc::VideoTrackInterface> VideoTrackLocal(PeerConnectionFactory->CreateVideoTrack(
VideoLabel, PeerConnectionFactory->CreateVideoSource(std::move(VideoCapturerStrong))));
Stream->AddTrack(VideoTrackLocal);
Streams[StreamId] = Stream;
}
verifyf(Session->GetPeerConnection().AddStream(Stream), TEXT("Failed to add stream for player %u"), PlayerId);
}
else
{
if (!Session->GetPeerConnection().GetSenders().empty())
{
return; // Already added tracks
}
if (!AudioTrack)
{
AudioTrack =
PeerConnectionFactory->CreateAudioTrack(AudioLabel, PeerConnectionFactory->CreateAudioSource(AudioSourceOptions));
}
if (!VideoTrack)
{
auto VideoCapturerStrong = std::make_unique<FVideoCapturer>(HWEncoderDetails);
VideoCapturer = VideoCapturerStrong.get();
VideoTrack = PeerConnectionFactory->CreateVideoTrack(
VideoLabel, PeerConnectionFactory->CreateVideoSource(std::move(VideoCapturerStrong)));
}
auto Res = Session->GetPeerConnection().AddTrack(AudioTrack, { TCHAR_TO_ANSI(*StreamId) });
if (!Res.ok())
{
UE_LOG(PixelStreamer, Error, TEXT("Failed to add AudioTrack to PeerConnection of player %u. Msg=%s"), Session->GetPlayerId(), ANSI_TO_TCHAR(Res.error().message()));
}
Res = Session->GetPeerConnection().AddTrack(VideoTrack, { TCHAR_TO_ANSI(*StreamId) });
if (!Res.ok())
{
UE_LOG(PixelStreamer, Error, TEXT("Failed to add VideoTrack to PeerConnection of player %u. Msg=%s"), Session->GetPlayerId(), ANSI_TO_TCHAR(Res.error().message()));
}
}
}
Engine/Source/Programs/PixelStreaming/WebServers/SignallingWebServer/scripts/webRtcPlayer.js, change the handleCreateOffer function to:
handleCreateOffer = function (pc) {
pc.createOffer(self.sdpConstraints).then(function (offer) {
offer.sdp = offer.sdp.replace("useinbandfec=1", "useinbandfec=1;stereo=1;maxaveragebitrate=128000");
pc.setLocalDescription(offer);
if (self.onWebRtcOffer) {
// (andriy): increase start bitrate from 300 kbps to 20 mbps and max bitrate from 2.5 mbps to 100 mbps
// (100 mbps means we don't restrict encoder at all)
// after we `setLocalDescription` because other browsers are not c happy to see google-specific config
offer.sdp = offer.sdp.replace(/(a=fmtp:\d+ .*level-asymmetry-allowed=.*)\r\n/gm, "$1;x-google-start-bitrate=10000;x-google-max-bitrate=20000\r\n");
self.onWebRtcOffer(offer);
}
},
function () { console.warn("Couldn't create offer") });
}