Pixel Streaming - Audio has bad quality

Hi,

we have a Pixel Streaming Build up and running an are so far quite happy with the results but we have a big problem getting a good audio signal using pixel streaming. It is really low quality, mono only and we have no idea where to look, to change the settings to something better. Has someone experienced something similar and has any hints where to look, because we skimmed through the source code but couldn’t find the files, where this settings are configured.

Any suggestions are highly appreciated!

I will run some tests to try and reproduce it with 4.25, and I’ll come back to you.

Cool, thanks a lot! If you need any additional information on our side - let us know!

Hi Rui.Figueira,

did you have any success in reproducing the issue?

Hi Tyrus86,
Yes, I had a chance to try it today with a sample project, and indeed it seems it’s mono and rather low bitrate.
I’ll have to to chase some colleague experienced with WebRTC internals to try and debug this, since I can’t find anything obvious on our side.

Hi Rui,

do you have any updates on this issue?

Hi Tyrus,
Yes, we have someone experienced with WebRTC actively looking into that, but no ETA yet I’m afraid, due to other high priority work.

It is nice to hear that someone is looking into it, event though it’s not the highest priority. Thanks a lot! :slight_smile:

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") });
        }

Thank you very much for the solution!
I will report back as soon as we test it :slight_smile:

Hey dude, did you test this? I know its been 5 years but wondering as we have bad audio as well.