Bug 476186

Summary: Screen recording quality is terrible
Product: [Frameworks and Libraries] KPipeWire Reporter: Hector Martin <marcan>
Component: generalAssignee: Plasma Bugs List <plasma-bugs>
Status: CONFIRMED ---    
Severity: major CC: adam.m.fontenot+kde, aleixpol, daanturo, el, m.kurz, natalie_clarius, nate, ngompa13, noahadvs
Priority: NOR    
Version: unspecified   
Target Milestone: ---   
Platform: Other   
OS: Linux   
Latest Commit: Version Fixed In:
Sentry Crash Report:
Attachments: screencast illustrating poor recording quality
illustration of crf 20 vs 31 in a fullscreen recording

Description Hector Martin 2023-10-28 08:09:48 UTC
Screen recording quality in Spectacle is bad to the point of being unusable for anything more than casual use.

Selecting quality is a codec, specific issue, but for x264 in particular, the current code is very questionable:

https://invent.kde.org/plasma/kpipewire/-/blob/master/src/libx264encoder.cpp

This sets `m_avCodecContext->global_quality` from some weird formula, but it's unclear how that maps to encoder settings in the end within ffmpeg. What I see in the resulting files is that Average Bitrate mode is being used (rc=abr), with bitrate somehow varying based on dimensions. This is a very bad choice for x264.

The correct "just give me a given quality please" mode in x264 is CRF (constant rate factor) mode, which does not force any given bitrate but rather targets a specific visual quality. If the quality settings are not configurable (or not configurable beyond a simple quality slider), then that mode should be the default to give decent output without more fuss. CRF mode is resolution-independent, and will automatically scale bitrate depending on the requirements (video size, motion complexity, etc.). It's the best option to default to for most users.
Comment 1 Noah Davis 2023-10-30 21:32:53 UTC
> Screen recording quality in Spectacle is bad to the point of being unusable for anything more than casual use.

Is this the case for all codecs pr just x264? I personally thought VP9 quality was generally OK, but it might vary based on hardware and usecase.
Comment 2 Noah Davis 2023-10-30 23:36:23 UTC
Are you using a screen with a scale factor other than 100%? I'm wondering if that could be a source of the bug. I noticed that rectangle recording has abysmally bad quality when recording my 200% scale screen (my other screen has 100% scale). However, if I do a screen recording of the 200% scale screen (I select the screen instead of making a rectangle), then the quality is fine.
Comment 3 Hector Martin 2023-10-31 03:49:25 UTC
I am in fact (150%), but the bad quality looks like compression artifacts, so it shouldn't be related to scaling/resolution, but rather a codec issue. It also happens when recording the full screen.

@Noah yes, with fine detail like lots of text (especially colored) it falls apart. Looking at the printed config, `rc_end_usage` is 0 which means VBR, which is the same problem as libx264: you are telling the decoder to target a specific bitrate regardless of picture complexity, so if things get too complex, the whole thing becomes a blockfest since it's physically impossible to encode in that few bits. Screen recording *really* needs constant quality mode to be useful for offline recording (not streaming where you have constraints).
Comment 4 Noah Davis 2023-12-14 14:18:50 UTC
Do you have this issue with the beta version of Spectacle? If you do, can you post a short video demonstrating the bad quality? Also, do you you get better or worse quality with larger resolution videos or different recording types (e.g., screen recording vs region recording)? We've fixed the issue with high DPI screens being scaled down, so we can rule out that issue for now, although I'm not sure if the latest beta release has that change.
Comment 5 Nate Graham 2023-12-14 17:03:40 UTC
I can't reproduce this with today's git master, FWIW. The resource usage while recording goes bananas, but the final video has reasonable quality and a very small file size.
Comment 6 Adam Fontenot 2024-02-18 16:14:47 UTC
Created attachment 165924 [details]
screencast illustrating poor recording quality

I see this on the latest beta (kpipewire 5.92.3, spectacle 24.01.95) with VP9 recording. As requested I'm attaching a video, which is a short recording I made of Crosswords [1] to illustrate a bug with that program.

These are clearly encoding artifacts stemming from the very low CRF used by default (31) for VP9. I was able to mostly fix the issue by changing the default quality to 20, which seems like a much more reasonable default.

[1] https://gitlab.gnome.org/jrb/crosswords
Comment 7 Noah Davis 2024-02-20 02:13:34 UTC
(In reply to Adam Fontenot from comment #6)
> Created attachment 165924 [details]
> screencast illustrating poor recording quality
> 
> I see this on the latest beta (kpipewire 5.92.3, spectacle 24.01.95) with
> VP9 recording. As requested I'm attaching a video, which is a short
> recording I made of Crosswords [1] to illustrate a bug with that program.
> 
> These are clearly encoding artifacts stemming from the very low CRF used by
> default (31) for VP9. I was able to mostly fix the issue by changing the
> default quality to 20, which seems like a much more reasonable default.
> 
> [1] https://gitlab.gnome.org/jrb/crosswords

From all the reading of encoder documentation I've done in the past, 31 is not low, it's average and actually looks decent when doing fullscreen recording. In my own tests, there was not a significant difference in quality between 20 CRF and 31 CRF when doing fullscreen recording either. For me, fullscreen is 1080p@60Hz or 4K@60Hz. For some reason, recording a smaller region has a strong negative effect on recording quality, so we may need to scale quality with region size inversely.
Comment 8 Adam Fontenot 2024-02-20 05:13:00 UTC
(In reply to Noah Davis from comment #7)
> From all the reading of encoder documentation I've done in the past, 31 is
> not low, it's average and actually looks decent when doing fullscreen
> recording. In my own tests, there was not a significant difference in
> quality between 20 CRF and 31 CRF when doing fullscreen recording either.
> For me, fullscreen is 1080p@60Hz or 4K@60Hz. For some reason, recording a
> smaller region has a strong negative effect on recording quality, so we may
> need to scale quality with region size inversely.

I think the issue is more likely to lie elsewhere. There are very few actual frames in my screencast, which is an issue not only for seeking, but also for the efficiency of the encoder.

This is probably not as noticeable with full motion 24 fps or 60 fps video, but in the case of my screencast, there are only 90 actual frames in the file, because KPipeWire seems to be dropping duplicate frames rather than sending them to the encoder. In a 15.35 second clip with a nominal framerate of 60 fps, you expect to see 921 frames. So ffmpeg has only encoded 1 in 10 frames, due to the missing duplicates.

The low number of frames means that the quality of the video depends on the quality of the individual frames much more. At a relatively low quality CRF like 31, the encoder is probably cheating too much on these frames. This is combined with the fact that video encoders are optimized for full motion video, not screencasts, so their quality metrics cheat heavily on flat fields (regions of nearly solid color) because they can be smoothed out and encoded at a very small size. That's clearly visible in my screencast.

The recommendation for CRF 31 seems to come from Google, and I'm skeptical of their figures. They consistently underestimate bitrates needed for reasonable quality, as is readily apparent if you watch their encodes for YouTube. Interestingly, they recommend a CRF of 15 for 4K video, so VP9's CRF metric does not seem particularly consistent. For what it's worth, their example encode of "Tears of Steel" (the open source Blender film) at CRF 31 and 1080p strikes me as rather poor, and that has the advantage of being a two-pass encode. [1]

I'm not trying to be overly debate-y here; there's not going to be a single one-size-fits-all value for all resolutions and all use cases. The correct approach is probably to allow setting the CRF directly in the Spectacle settings and passing the value to KPipeWire.

[1] https://developers.google.com/media/vp9/settings/vod/
Comment 9 Adam Fontenot 2024-02-20 05:36:11 UTC
Created attachment 165948 [details]
illustration of crf 20 vs 31 in a fullscreen recording

As a quick illustration, I've taken two fullscreen (60 fps 1080p) screencasts with comparable details (I scrolled up and down this page in my browser for a bit), at both CRF 31 (the default) and CRF 20 (with my patch).

The result illustrates the way that VP9 cheats on flat fields quite well. This area of the screen shown in the image is basically static in the video, and contains large regions of a single color. The encoder cheats a lot here, with a result that is visibly much worse with CRF 31.

For common screencast use cases, like recording bugs and features, this is a worst case scenario of sorts, since you'd like to see relatively crisp looking text and icons.
Comment 10 Ellie 2024-02-21 07:12:37 UTC
Wouldn't it be possible to just add some slider to Spectacle to allow to specify the bitrate? After all this is possible for the jpeg quality as well. I've also just run into this with a higher DPI UI setting, and the resulting recording is both extremely blurry and so full of artifacts that it's hard to use it for anything. On top of things it's not even that small, but if the initial quality is already so bad this also prevents re-encoding for a more reasonable file size. It therefore seems to me like it would be a better idea to err on the side of too high quality rather than too low.
Comment 11 Adam Fontenot 2024-02-23 16:09:58 UTC
My discussion above was about VP9, I've been investigating quality issues with H.264 today. Rather, the lack thereof, because the results are pretty okay for me.

`m_avCodecContext->global_quality` is equivalent to setting the `-q` option on the ffmpeg command line. Unfortunately, as is clear from the documentation [1], libx264 ignores the global quality setting *entirely*. So on most ffmpeg versions it's equivalent to the default of CRF 23, which is pretty tolerable quality for screen recordings.

As evidence of this, when recording the screen with libx264, Spectacle prints the following to the terminal:

[libx264 @ 0x77274430b640] -qscale is ignored, -crf is recommended.

As a result the file I get is encoded with `rc=crf`, since that's the default. I'm not sure how Hector got `rc=abr` in their files, my understanding is that this is only applicable to two pass encodes, not for streaming encoding as is used here.

At any rate, what's clear is that the current H.264 code needs to be replaced. At present the quality settings are a complete noop.

See also: https://trac.ffmpeg.org/ticket/3238

[1] https://ffmpeg.org/ffmpeg-codecs.html#Options-35