Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support YUV 4:4:4 streaming #1282

Merged
merged 1 commit into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion app/cli/commandlineparser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@ void StreamCommandLineParser::parse(const QStringList &args, StreamingPreference
parser.addToggleOption("keep-awake", "prevent display sleep while streaming");
parser.addToggleOption("performance-overlay", "show performance overlay");
parser.addToggleOption("hdr", "HDR streaming");
parser.addToggleOption("yuv444", "YUV 4:4:4 sampling, if supported");
parser.addChoiceOption("capture-system-keys", "capture system key combos", m_CaptureSysKeysModeMap.keys());
parser.addChoiceOption("video-codec", "video codec", m_VideoCodecMap.keys());
parser.addChoiceOption("video-decoder", "video decoder", m_VideoDecoderMap.keys());
Expand Down Expand Up @@ -425,7 +426,7 @@ void StreamCommandLineParser::parse(const QStringList &args, StreamingPreference
}
} else if (displaySet || parser.isSet("fps")) {
preferences->bitrateKbps = preferences->getDefaultBitrate(
preferences->width, preferences->height, preferences->fps);
preferences->width, preferences->height, preferences->fps, preferences->enableYUV444);
}

// Resolve --packet-size option
Expand Down Expand Up @@ -493,6 +494,9 @@ void StreamCommandLineParser::parse(const QStringList &args, StreamingPreference

// Resolve --hdr and --no-hdr options
preferences->enableHdr = parser.getToggleOptionValue("hdr", preferences->enableHdr);

// Resolve --yuv444 and --no-yuv444 options
preferences->enableYUV444 = parser.getToggleOptionValue("yuv444", preferences->enableYUV444);

// Resolve --capture-system-keys option
if (parser.isSet("capture-system-keys")) {
Expand Down
31 changes: 29 additions & 2 deletions app/gui/SettingsView.qml
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,8 @@ Flickable {

StreamingPreferences.bitrateKbps = StreamingPreferences.getDefaultBitrate(StreamingPreferences.width,
StreamingPreferences.height,
StreamingPreferences.fps);
StreamingPreferences.fps,
StreamingPreferences.enableYUV444);
slider.value = StreamingPreferences.bitrateKbps
}

Expand Down Expand Up @@ -448,7 +449,8 @@ Flickable {

StreamingPreferences.bitrateKbps = StreamingPreferences.getDefaultBitrate(StreamingPreferences.width,
StreamingPreferences.height,
StreamingPreferences.fps);
StreamingPreferences.fps,
StreamingPreferences.enableYUV444);
slider.value = StreamingPreferences.bitrateKbps
}

Expand Down Expand Up @@ -1615,6 +1617,31 @@ Flickable {
qsTr("HDR streaming is not supported on this PC.")
}

CheckBox {
id: enableYUV444
width: parent.width
text: qsTr("Enable YUV 4:4:4 (Experimental)")
font.pointSize: 12

checked: StreamingPreferences.enableYUV444
onCheckedChanged: {
StreamingPreferences.enableYUV444 = checked
StreamingPreferences.bitrateKbps = StreamingPreferences.getDefaultBitrate(StreamingPreferences.width,
StreamingPreferences.height,
StreamingPreferences.fps,
StreamingPreferences.enableYUV444);
slider.value = StreamingPreferences.bitrateKbps
}

ToolTip.delay: 1000
ToolTip.timeout: 5000
ToolTip.visible: hovered
ToolTip.text: enabled ?
qsTr("Good for streaming desktop and text-heavy games, not very good for fast-paced games.")
:
qsTr("YUV 4:4:4 is not supported on this PC.")
}

CheckBox {
id: enableMdns
width: parent.width
Expand Down
12 changes: 10 additions & 2 deletions app/settings/streamingpreferences.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#define SER_AUDIOCFG "audiocfg"
#define SER_VIDEOCFG "videocfg"
#define SER_HDR "hdr"
#define SER_YUV444 "yuv444"
#define SER_VIDEODEC "videodec"
#define SER_WINDOWMODE "windowmode"
#define SER_MDNS "mdns"
Expand Down Expand Up @@ -117,7 +118,8 @@ void StreamingPreferences::reload()
width = settings.value(SER_WIDTH, 1280).toInt();
height = settings.value(SER_HEIGHT, 720).toInt();
fps = settings.value(SER_FPS, 60).toInt();
bitrateKbps = settings.value(SER_BITRATE, getDefaultBitrate(width, height, fps)).toInt();
enableYUV444 = settings.value(SER_YUV444, false).toBool();
bitrateKbps = settings.value(SER_BITRATE, getDefaultBitrate(width, height, fps, enableYUV444)).toInt();
enableVsync = settings.value(SER_VSYNC, true).toBool();
gameOptimizations = settings.value(SER_GAMEOPTS, true).toBool();
playAudioOnHost = settings.value(SER_HOSTAUDIO, false).toBool();
Expand Down Expand Up @@ -322,6 +324,7 @@ void StreamingPreferences::save()
settings.setValue(SER_SHOWPERFOVERLAY, showPerformanceOverlay);
settings.setValue(SER_AUDIOCFG, static_cast<int>(audioConfig));
settings.setValue(SER_HDR, enableHdr);
settings.setValue(SER_YUV444, enableYUV444);
settings.setValue(SER_VIDEOCFG, static_cast<int>(videoCodecConfig));
settings.setValue(SER_VIDEODEC, static_cast<int>(videoDecoderSelection));
settings.setValue(SER_WINDOWMODE, static_cast<int>(windowMode));
Expand All @@ -337,7 +340,7 @@ void StreamingPreferences::save()
settings.setValue(SER_KEEPAWAKE, keepAwake);
}

int StreamingPreferences::getDefaultBitrate(int width, int height, int fps)
int StreamingPreferences::getDefaultBitrate(int width, int height, int fps, bool yuv444)
{
// Don't scale bitrate linearly beyond 60 FPS. It's definitely not a linear
// bitrate increase for frame rate once we get to values that high.
Expand Down Expand Up @@ -385,5 +388,10 @@ int StreamingPreferences::getDefaultBitrate(int width, int height, int fps)
}
}

if (yuv444) {
// This is rough estimation based on the fact that 4:4:4 doubles the amount of raw YUV data compared to 4:2:0
resolutionFactor *= 2;
}

return qRound(resolutionFactor * frameRateFactor) * 1000;
}
5 changes: 4 additions & 1 deletion app/settings/streamingpreferences.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class StreamingPreferences : public QObject
static StreamingPreferences* get(QQmlEngine *qmlEngine = nullptr);

Q_INVOKABLE static int
getDefaultBitrate(int width, int height, int fps);
getDefaultBitrate(int width, int height, int fps, bool yuv444);

Q_INVOKABLE void save();

Expand Down Expand Up @@ -125,6 +125,7 @@ class StreamingPreferences : public QObject
Q_PROPERTY(AudioConfig audioConfig MEMBER audioConfig NOTIFY audioConfigChanged)
Q_PROPERTY(VideoCodecConfig videoCodecConfig MEMBER videoCodecConfig NOTIFY videoCodecConfigChanged)
Q_PROPERTY(bool enableHdr MEMBER enableHdr NOTIFY enableHdrChanged)
Q_PROPERTY(bool enableYUV444 MEMBER enableYUV444 NOTIFY enableYUV444Changed)
Q_PROPERTY(VideoDecoderSelection videoDecoderSelection MEMBER videoDecoderSelection NOTIFY videoDecoderSelectionChanged)
Q_PROPERTY(WindowMode windowMode MEMBER windowMode NOTIFY windowModeChanged)
Q_PROPERTY(WindowMode recommendedFullScreenMode MEMBER recommendedFullScreenMode CONSTANT)
Expand Down Expand Up @@ -169,6 +170,7 @@ class StreamingPreferences : public QObject
AudioConfig audioConfig;
VideoCodecConfig videoCodecConfig;
bool enableHdr;
bool enableYUV444;
VideoDecoderSelection videoDecoderSelection;
WindowMode windowMode;
WindowMode recommendedFullScreenMode;
Expand All @@ -191,6 +193,7 @@ class StreamingPreferences : public QObject
void audioConfigChanged();
void videoCodecConfigChanged();
void enableHdrChanged();
void enableYUV444Changed();
void videoDecoderSelectionChanged();
void uiDisplayModeChanged();
void windowModeChanged();
Expand Down
55 changes: 50 additions & 5 deletions app/streaming/session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -597,15 +597,22 @@ bool Session::initialize()
return false;
}

LiInitializeStreamConfiguration(&m_StreamConfig);
m_StreamConfig.width = m_Preferences->width;
m_StreamConfig.height = m_Preferences->height;

int x, y, width, height;
getWindowDimensions(x, y, width, height);

// Create a hidden window to use for decoder initialization tests
SDL_Window* testWindow = SDL_CreateWindow("", 0, 0, 1280, 720,
SDL_Window* testWindow = SDL_CreateWindow("", x, y, width, height,
SDL_WINDOW_HIDDEN | StreamUtils::getPlatformWindowFlags());
if (!testWindow) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Failed to create test window with platform flags: %s",
SDL_GetError());

testWindow = SDL_CreateWindow("", 0, 0, 1280, 720, SDL_WINDOW_HIDDEN);
testWindow = SDL_CreateWindow("", x, y, width, height, SDL_WINDOW_HIDDEN);
if (!testWindow) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to create window for hardware decode test: %s",
Expand All @@ -621,9 +628,6 @@ bool Session::initialize()
LiInitializeVideoCallbacks(&m_VideoCallbacks);
m_VideoCallbacks.setup = drSetup;

LiInitializeStreamConfiguration(&m_StreamConfig);
m_StreamConfig.width = m_Preferences->width;
m_StreamConfig.height = m_Preferences->height;
m_StreamConfig.fps = m_Preferences->fps;
m_StreamConfig.bitrate = m_Preferences->bitrateKbps;

Expand Down Expand Up @@ -677,17 +681,27 @@ bool Session::initialize()
CHANNEL_MASK_FROM_AUDIO_CONFIGURATION(m_StreamConfig.audioConfiguration));

// Start with all codecs and profiles in priority order
m_SupportedVideoFormats.append(VIDEO_FORMAT_AV1_HIGH10_444);
m_SupportedVideoFormats.append(VIDEO_FORMAT_AV1_MAIN10);
m_SupportedVideoFormats.append(VIDEO_FORMAT_H265_REXT10_444);
m_SupportedVideoFormats.append(VIDEO_FORMAT_H265_MAIN10);
m_SupportedVideoFormats.append(VIDEO_FORMAT_AV1_HIGH8_444);
m_SupportedVideoFormats.append(VIDEO_FORMAT_AV1_MAIN8);
m_SupportedVideoFormats.append(VIDEO_FORMAT_H265_REXT8_444);
m_SupportedVideoFormats.append(VIDEO_FORMAT_H265);
m_SupportedVideoFormats.append(VIDEO_FORMAT_H264_HIGH8_444);
m_SupportedVideoFormats.append(VIDEO_FORMAT_H264);

// Mask off 10-bit codecs right away if HDR is not enabled
if (!m_Preferences->enableHdr) {
m_SupportedVideoFormats.removeByMask(VIDEO_FORMAT_MASK_10BIT);
}

// Mask off YUV 4:4:4 codecs right away if the option is not enabled
if (!m_Preferences->enableYUV444) {
m_SupportedVideoFormats.removeByMask(VIDEO_FORMAT_MASK_YUV444);
}

switch (m_Preferences->videoCodecConfig)
{
case StreamingPreferences::VCC_AUTO:
Expand Down Expand Up @@ -969,6 +983,37 @@ bool Session::validateLaunch(SDL_Window* testWindow)
}
}

if (m_Preferences->enableYUV444) {
if (!(m_Computer->serverCodecModeSupport & SCM_MASK_YUV444)) {
emitLaunchWarning(tr("Your host PC doesn't support YUV 4:4:4 streaming."));
m_SupportedVideoFormats.removeByMask(VIDEO_FORMAT_MASK_YUV444);
}
else if (m_Preferences->videoDecoderSelection != StreamingPreferences::VDS_FORCE_SOFTWARE) {
m_SupportedVideoFormats.removeByMask(~m_SupportedVideoFormats.maskByServerCodecModes(m_Computer->serverCodecModeSupport));

if (!m_SupportedVideoFormats.isEmpty() &&
!(m_SupportedVideoFormats.front() & VIDEO_FORMAT_MASK_YUV444)) {
emitLaunchWarning(tr("Your host PC doesn't support YUV 4:4:4 streaming for selected video codec."));
}
else {
while (!m_SupportedVideoFormats.isEmpty() &&
(m_SupportedVideoFormats.front() & VIDEO_FORMAT_MASK_YUV444) &&
!isHardwareDecodeAvailable(testWindow,
m_Preferences->videoDecoderSelection,
m_SupportedVideoFormats.front(),
m_StreamConfig.width,
m_StreamConfig.height,
m_StreamConfig.fps)) {
m_SupportedVideoFormats.removeFirst();
}
if (!m_SupportedVideoFormats.isEmpty() &&
!(m_SupportedVideoFormats.front() & VIDEO_FORMAT_MASK_YUV444)) {
emitLaunchWarning(tr("This PC's GPU doesn't support YUV 4:4:4 decoding for selected video codec."));
}
}
}
}

if (m_StreamConfig.width >= 3840) {
// Only allow 4K on GFE 3.x+
if (m_Computer->gfeVersion.isEmpty() || m_Computer->gfeVersion.startsWith("2.")) {
Expand Down
51 changes: 24 additions & 27 deletions app/streaming/session.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,36 +41,33 @@ class SupportedVideoFormatList : public QList<int>

int maskByServerCodecModes(int serverCodecModes)
{
int val = *this;

// Make sure nobody forgets to update this for new SCM values
SDL_assert((serverCodecModes & ~(SCM_MASK_H264 | SCM_MASK_HEVC | SCM_MASK_AV1)) == 0);

// H.264 SCM masks
if (!(serverCodecModes & SCM_H264)) {
val &= ~VIDEO_FORMAT_H264;
}
SDL_assert((serverCodecModes & SCM_MASK_H264 & ~SCM_H264) == 0);

// HEVC SCM masks
if (!(serverCodecModes & SCM_HEVC)) {
val &= ~VIDEO_FORMAT_H265;
}
if (!(serverCodecModes & SCM_HEVC_MAIN10)) {
val &= ~VIDEO_FORMAT_H265_MAIN10;
int mask = 0;

const QMap<int, int> mapping = {
{SCM_H264, VIDEO_FORMAT_H264},
{SCM_H264_HIGH8_444, VIDEO_FORMAT_H264_HIGH8_444},
{SCM_HEVC, VIDEO_FORMAT_H265},
{SCM_HEVC_MAIN10, VIDEO_FORMAT_H265_MAIN10},
{SCM_HEVC_REXT8_444, VIDEO_FORMAT_H265_REXT8_444},
{SCM_HEVC_REXT10_444, VIDEO_FORMAT_H265_REXT10_444},
{SCM_AV1_MAIN8, VIDEO_FORMAT_AV1_MAIN8},
{SCM_AV1_MAIN10, VIDEO_FORMAT_AV1_MAIN10},
{SCM_AV1_HIGH8_444, VIDEO_FORMAT_AV1_HIGH8_444},
{SCM_AV1_HIGH10_444, VIDEO_FORMAT_AV1_HIGH10_444},
};

for (QMap<int, int>::const_iterator it = mapping.cbegin(); it != mapping.cend(); ++it) {
if (serverCodecModes & it.key()) {
mask |= it.value();
serverCodecModes &= ~it.key();
}
}
SDL_assert((serverCodecModes & SCM_MASK_HEVC & ~(SCM_HEVC | SCM_HEVC_MAIN10)) == 0);

// AV1 SCM masks
if (!(serverCodecModes & SCM_AV1_MAIN8)) {
val &= ~VIDEO_FORMAT_AV1_MAIN8;
}
if (!(serverCodecModes & SCM_AV1_MAIN10)) {
val &= ~VIDEO_FORMAT_AV1_MAIN10;
}
SDL_assert((serverCodecModes & SCM_MASK_AV1 & ~(SCM_AV1_MAIN8 | SCM_AV1_MAIN10)) == 0);
// Make sure nobody forgets to update this for new SCM values
SDL_assert(serverCodecModes == 0);

return val;
int val = *this;
return val & mask;
}
};

Expand Down
18 changes: 17 additions & 1 deletion app/streaming/video/ffmpeg-renderers/plvk.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -917,9 +917,16 @@ int PlVkRenderer::getRendererAttributes()
return RENDERER_ATTRIBUTE_HDR_SUPPORT;
}

int PlVkRenderer::getDecoderColorspace()
{
// We rely on libplacebo for color conversion, pick colorspace with the same primaries as sRGB
return COLORSPACE_REC_709;
}

int PlVkRenderer::getDecoderColorRange()
{
// Explicitly set the color range to full to fix raised black levels on OLED displays
// Explicitly set the color range to full to fix raised black levels on OLED displays,
// should also reduce banding artifacts in all situations
return COLOR_RANGE_FULL;
}

Expand Down Expand Up @@ -948,6 +955,15 @@ bool PlVkRenderer::isPixelFormatSupported(int videoFormat, AVPixelFormat pixelFo
// Vulkan frames are always supported
return true;
}
else if (videoFormat & VIDEO_FORMAT_MASK_YUV444) {
if (videoFormat & VIDEO_FORMAT_MASK_10BIT) {
return pixelFormat == AV_PIX_FMT_YUV444P10;
}
else {
return pixelFormat == AV_PIX_FMT_YUV444P ||
pixelFormat == AV_PIX_FMT_YUVJ444P;
}
}
else if (videoFormat & VIDEO_FORMAT_MASK_10BIT) {
switch (pixelFormat) {
case AV_PIX_FMT_P010:
Expand Down
1 change: 1 addition & 0 deletions app/streaming/video/ffmpeg-renderers/plvk.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class PlVkRenderer : public IFFmpegRenderer {
virtual void notifyOverlayUpdated(Overlay::OverlayType) override;
virtual bool notifyWindowChanged(PWINDOW_STATE_CHANGE_INFO) override;
virtual int getRendererAttributes() override;
virtual int getDecoderColorspace() override;
virtual int getDecoderColorRange() override;
virtual int getDecoderCapabilities() override;
virtual bool needsTestFrame() override;
Expand Down
Loading