AviSynth / AviSynthPlus

AviSynth with improvements
http://avs-plus.net
930 stars 74 forks source link

Levels() messes up colorspace for yuv420p10le video #352

Closed dmak closed 1 year ago

dmak commented 1 year ago

Video was created by Samsung mobile running Android 13.

AVS file demonstrating the issue:

LoadPlugin("C:\Applications\AviSynthPlus\plugins64+\ffms2.dll")

file="20230407_160742.mp4"
FFIndex(file)
AudioDub(FFVideoSource(file), FFAudioSource(file))

Levels(10, 1, 240, 0, 255)

Workaround:

The issue does not occur if ConvertBits(8) is called before Levels(). Also when I remove Levels(), the playback is just fine.

Video description by MediaInfo:

Video
ID                                       : 1
Format                                   : HEVC
Format/Info                              : High Efficiency Video Coding
Format profile                           : Main 10@L4@Main
HDR format                               : SMPTE ST 2094 App 4, Version 1, HDR10+ Profile B compatible
Codec ID                                 : hvc1
Codec ID/Info                            : High Efficiency Video Coding
Duration                                 : 44 s 479 ms
Source duration                          : 44 s 468 ms
Bit rate                                 : 24.9 Mb/s
Width                                    : 1 920 pixels
Height                                   : 1 080 pixels
Display aspect ratio                     : 16:9
Frame rate mode                          : Variable
Frame rate                               : 29.970 (29970/1000) FPS
Minimum frame rate                       : 14.998 FPS
Maximum frame rate                       : 30.171 FPS
Real frame rate                          : 30.000 FPS
Color space                              : YUV
Chroma subsampling                       : 4:2:0
Bit depth                                : 10 bits
Bits/(Pixel*Frame)                       : 0.401
Stream size                              : 132 MiB (99%)
Source stream size                       : 132 MiB (99%)
Title                                    : VideoHandle
Color range                              : Limited
Color primaries                          : BT.2020
Transfer characteristics                 : PQ
Matrix coefficients                      : BT.2020 non-constant
Mastering display color primaries        : Display P3
Mastering display luminance              : min: 0.0050 cd/m2, max: 1000 cd/m2
mdhd_Duration                            : 44479
Codec configuration box                  : hvcC

ffmpeg report:

Stream #0:0(eng): Video: h264 (avc1 / 0x31637661), yuv420p10le(tv, bt2020nc/bt2020/smpte2084, progressive), 1920x1080, q=2-31, 30 fps, 15360 tbn (default)

Side-by-side result (left is AVS, right is original MP4): example AviSynth+ v3.7.2-7290a6f ffms2 v3.0.1.0 1325+16 6ad7738

DTL2020 commented 1 year ago

As documentation says - http://avisynth.nl/index.php/Levels Note in AVS+, the parameters input_low, input_high, output_low and output_high

are float instead of int. are not autoscaling ā€“ they are relative to the current bit depth:

So for 10bit you need to use Levels(10_x_4, 1, 240_x_4, 0, 255_x_4)

where 10x4=40, 240x4=960, 255x4=1020

dmak commented 1 year ago

are not autoscaling ā€“ they are relative to the current bit depth

Thanks, that indeed explains the behavior. Basically your explanation renders the issue invalid, however I still cannot get why the video passed through AVS looks more dim / pale (on the left) than original video (on the right). I didn't observe the issue with 8-bit video. Is it an issue with ffms2.dll? I tried DirectShowSource() instead of FFmpegSource2() but it results the same negative effect. So I wonder if there is an alternative / better way to feed 10-bit HEVC video to AviSynth?

image

DTL2020 commented 1 year ago

You work with PQ transfer, BT.2020 primaries and matrix - at any conversion stage you can fail with SDR/SCG display (also it is not known which display do you use for converting digital domain encoding into physical image). So it looks you simply fall into new-age HDR/WCG nighmare. Also all your attempt to show such files via internet may be invalid because internet may still run on 'old' sRGB colour image data encoding.

Better start to switch your video camera to SDR and 601/709 colour primaries and matrix and look for result. Next start to switch each param from 'old standard' and see where what happens.

AVS typically not change transfer/matrix/primaries but it is responsibility of enduser to track image encoding domain and use correct display or conversion tools. For example your software player may make auto-conversion for you at .mp4 file playback and AVS output do not have required metadata or your display/player do not understand it and so on. Lots of points to fail.

The only visible via internet - your player render to OS display surface .mp4 file and AVS file somehow differently. There may be tons of different reasons. Also you may ask player developer. Also that player-wrapper may use tons of internal conversion tools from other developers.

As some possible quick solution you may try to encode AVS output into .mp4 file with ffmpeg and provide manually all required HDR/WCG metadata (so MediaInfo of your source and encoded file at least in the fields of Color range : Limited Color primaries : BT.2020 Transfer characteristics : PQ Matrix coefficients : BT.2020 non-constant Mastering display color primaries : Display P3 Mastering display luminance : min: 0.0050 cd/m2, max: 1000 cd/m2

must match). And try to render encoded file with same player software.

dmak commented 1 year ago

You're right, the simplest way is to disable HDR10+ video in the Camera app settings: image The player I use is MPC-HC v1.9.8.65 (ee997206c), let me know if there is a better player that can take AVS as input and is also feature-rich. What about downgrading the 10-bit video using ConvertBits(8)? Should AviSynth adapt video stream and meta information in that case? Well, I am not sure I can correctly translate HDR metadata from MediaInfo input into ffmpeg arguments, but I found this article which describes how to encode HDR10+ video using H.265 codec. Let's hope it works with AVS input šŸ¤ž

DTL2020 commented 1 year ago

"What about downgrading the 10-bit video using ConvertBits(8)?"

It not save from using different digital domain data encoding. If you need to run at SDR/SCG displays you need to convert transfer/matrix/primaries to the SDR/SCG system. There are several tools available in AVS. You can look into thread https://forum.doom9.org/showthread.php?t=183224&page=3 about possible conversion tools and issues.

"Should AviSynth adapt video stream and meta information in that case?"

No - AVS do not have internal tools for transfer/matrix/primaries conversions.

" if there is a better player that can take AVS as input and is also feature-rich."

I read ffmpeg can read AVS metadata (if you set it manually or your source plugin do ?). So may be ffplay can display somehow good (with proper conversion to your display). But you may not like simple command-line player software.

dmak commented 1 year ago

Well, you're right with that I need to pass the metadata to ffmpeg and it generally worked fine:

ffmpeg.exe -i 20230407_160742.avs -c:v libx265 -x265-params "colorprim=bt2020:transfer=smpte2084:colormatrix=bt2020nc" -crf 28 -preset medium "output.mkv"

I also had to ask ChatGPT for help so somebody could use it for providing more explanations the same way: image P.S. Returning to original trap: would be great if AviSynth provides the function e.g. Levels2() which is bit-aware (and more difficult to mess up).

DTL2020 commented 1 year ago

AVS have completely different levels mapping to number values in different samples values modes. For example float32 samples are not simply 16.0f..235.0f but 0.0f to 1.0f. That is very non-natural but programmers take this way. So Levels for floats have 0 to 1 black and nominal white levels. Also it is not single mapping rule - the black may be shifted and so on. I also suggest to add clip/frame property for levels mapping for floats :) .

qyot27 commented 1 year ago

FFplay does not tonemap HDR content on playback, for that you need mpv. VLC can probably do it too, but I wouldn't know how its settings might differ.

The remaining part of the problem is that the frame properties support in FFmpeg's AviSynth demuxer does not pass through dynamic metadata such as Dolby Vision or whatever other sorts of HDR+ formats there are, it only looks at the kinds of metadata that remain static throughout the clip (which is enough for 'standard' HDR10). For [most(?) types of] Dolby Vision you need to use a plugin like DoViBaker.

If/when the FFmpeg AviSynth demuxer can read RPU-related frame properties and set them for output, then all it would take is something that uses libplacebo (again, mpv) to detect and correctly reintegrate it on playback.

DTL2020 commented 1 year ago

Also do documentation at http://avisynth.nl/index.php/Levels really correct for float32:

Nominal black and white 'limited/narrow' (from 8bits) mapped to 16/256 and 235/256 or to 0.0f and 1.0f ? Or depend on plugin and/or may be switched of ConvertBits() ? Or 'full' 0..255 8bit mapped to 0.0f and 255/256 ?