Pixel Streaming - Audio has bad quality

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