-
Notifications
You must be signed in to change notification settings - Fork 82
Add color handling to VideoEncoder GPU #1125
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
base: main
Are you sure you want to change the base?
Conversation
…o gpu_pix_fmts
|
|
||
| if (videoStreamOptions.pixelFormat.has_value()) { | ||
| // TODO-VideoEncoder: Enable pixel formats to be set by user | ||
| // and handled with the appropriate NPP function on GPU. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I moved this TODO from setupHardwareFrameContextForEncoding to here in initializeEncoder to centralize pixel_format handling.
The behavior is unchanged: If pixel_format argument is used while frames are on GPU, an error is raised.
The default usage of nv12 is moved into initializeEncoder` as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
are we raising an error for pixel_format on gpu because of what nicolas mentioned below? that passing NV12 leads to yuv420?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Essentially yes. Because we do not understand the codec's behavior yet, we do not want the user to set or expect a pixel format.
…o gpu_pix_fmts
… into gpu_pix_fmts
| if ffmpeg_version == 4 and codec == "av1_nvenc": | ||
| pytest.skip("av1_nvenc is not supported on FFmpeg 4") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For this one, we might be able to use the skipif mark as part of the parametrization, which is our new preferred strategy to keep internal CI healthy.
But, I am curious: is this actually related to this PR? If av1_nvenc really isn't supported on FFmpeg, then how can our existing test (on main) pass?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These skips are examples of covariate parametrization, where we do not know both values in either parametrization line. I considered using the new pattern, but I believe it is impossible in these cases.
is this actually related to this PR?
It is not related to this PR, I likely did not locally test on FFmpeg 4 previously, because my installation of FFmpeg 4 does not support av1_nvenc.
Details
(ffmpeg4_cuda12_6) dev@gpu-dev-d63de2d5:~/torchcodec$ ffmpeg -encoders | grep nvenc
ffmpeg version 4.4.2 Copyright (c) 2000-2021 the FFmpeg developers
built with gcc 10.4.0 (conda-forge gcc 10.4.0-16)
configuration: --prefix=/home/dev/.conda/envs/ffmpeg4_cuda12_6 --cc=/home/conda/feedstock_root/build_artifacts/ffmpeg_1660334214659/_build_env/bin/x86_64-conda-linux-gnu-cc --cxx=/home/conda/feedstock_root/build_artifacts/ffmpeg_1660334214659/_build_env/bin/x86_64-conda-linux-gnu-c++ --nm=/home/conda/feedstock_root/build_artifacts/ffmpeg_1660334214659/_build_env/bin/x86_64-conda-linux-gnu-nm --ar=/home/conda/feedstock_root/build_artifacts/ffmpeg_1660334214659/_build_env/bin/x86_64-conda-linux-gnu-ar --disable-doc --disable-openssl --enable-avresample --enable-demuxer=dash --enable-hardcoded-tables --enable-libfreetype --enable-libfontconfig --enable-libopenh264 --enable-gnutls --enable-libmp3lame --enable-libvpx --enable-pthreads --enable-vaapi --enable-gpl --enable-libx264 --enable-libx265 --enable-libaom --enable-libsvtav1 --enable-libxml2 --enable-pic --enable-shared --disable-static --enable-version3 --enable-zlib --pkg-config=/home/conda/feedstock_root/build_artifacts/ffmpeg_1660334214659/_build_env/bin/pkg-config
libavutil 56. 70.100 / 56. 70.100
libavcodec 58.134.100 / 58.134.100
libavformat 58. 76.100 / 58. 76.100
libavdevice 58. 13.100 / 58. 13.100
libavfilter 7.110.100 / 7.110.100
libavresample 4. 0. 0 / 4. 0. 0
libswscale 5. 9.100 / 5. 9.100
libswresample 3. 9.100 / 3. 9.100
libpostproc 55. 9.100 / 55. 9.100
V....D h264_nvenc NVIDIA NVENC H.264 encoder (codec h264)
V..... nvenc NVIDIA NVENC H.264 encoder (codec h264)
V..... nvenc_h264 NVIDIA NVENC H.264 encoder (codec h264)
V..... nvenc_hevc NVIDIA NVENC hevc encoder (codec hevc)
V....D hevc_nvenc NVIDIA NVENC hevc encoder (codec hevc)
how can our existing test (on main) pass?
They do not!
The av1_nvenc test is skipped if IN_GITHUB_CI, so it would not have appeared there. Since this test is also marked as @needs_ffmpeg_cli, its not run internally either.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it thank you - I understand how that's relevant for this PR now.
Agreed the first skip (the one with color_space == "bt470bg" and color_range == "tv") is a covariate parametrization, but I think this one just above is not: the only thing that is parametrized over is codec, so I think we could use something like pytest.param("av1_nvenc", marks=pytest.mark.skipif(get_ffmpeg_major_version() == 4).
That's not very critical in any case, feel free to ignore.
test/test_encoders.py
Outdated
| assert_tensor_close_on_at_least(ff_frame, enc_frame, percentage=96, atol=2) | ||
| assert_tensor_close_on_at_least( | ||
| ff_frame, enc_frame, percentage=percentage, atol=2 | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Out of curiosity can you try to validate an upper bound on the absolute difference? E.g. something like
assert_close(..., rtol=0, atol=5)
would be pretty good. Might have to be higher than 5. If it's something like 50 then it's not worth it, as it's not informative. But if we can get a low upper-bound, that gives confidence that we're not too far off.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The lowest bound I tested that passed was atol=40, which is pretty high. The current test tells us that 96% of the frames are within 2, which seems to be fairly high accuracy
test/test_encoders.py
Outdated
| # Default values vary by codec, so we only assert when | ||
| # color_range and color_space are not None. | ||
| if color_range is not None: | ||
| color_range = ffmpeg_metadata["color_range"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you meant an assert here.
Also, while this is functionally equivalent (because of the assert encoder_metadata[...] == ffmpeg_metadata[...] call above), what we logically want to assert here is that the encode_metadata respected the input color_range and the input color_space. So I'd suggest to write this as `assert encode_metadata[...] == color_range.
Same below
src/torchcodec/_core/Encoder.cpp
Outdated
| deviceInterface_->registerHardwareDeviceWithCodec(avCodecContext_.get()); | ||
| deviceInterface_->setupHardwareFrameContextForEncoding( | ||
| avCodecContext_.get()); | ||
| avCodecContext_.get(), outPixelFormat_); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, we have this invariant throughout the GPU encoder that the output pixel format is always AV_PIX_FMT_NV12. it's good practice to enforce this invariant in the APIs themselves. Here for example, there is no need for deviceInterface_->setupHardwareFrameContextForEncoding() to take the pixel format as a parameter, because it's an invariant that it should always be AV_PIX_FMT_NV12.
The same is true for all the other APIs of the device interface. There are different ways to enforce this invariant in the API, one simple one is to make outputPixelFormatForEncoding_ a public constexpr member.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was aiming to follow up quickly on adding support for other pixel formats, but I see how this parameter is confusing at the moment. I will remove it from the APIs until it is needed.
| "GPU encoding expected to encode into nv12 pixel format, but got ", | ||
| av_get_pix_fmt_name(targetPixelFormat), | ||
| ". This should not happen, please report this to the TorchCodec repo"); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Above: this is related to my other comment below about enforcing targetPixelFormat to be AV_PIX_FMT_NV12 through the APIs. It's great that you are raising on this, and indeed this should never happen and this is a torchcodec bug. But we can avoid risking the bug entirely by simply just not making targetPixelFormat a parameter! That's the value of "enforcing the invariant through the API".
NicolasHug
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @Dan-Flores !
| if ffmpeg_version == 4 and codec == "av1_nvenc": | ||
| pytest.skip("av1_nvenc is not supported on FFmpeg 4") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it thank you - I understand how that's relevant for this PR now.
Agreed the first skip (the one with color_space == "bt470bg" and color_range == "tv") is a covariate parametrization, but I think this one just above is not: the only thing that is parametrized over is codec, so I think we could use something like pytest.param("av1_nvenc", marks=pytest.mark.skipif(get_ffmpeg_major_version() == 4).
That's not very critical in any case, feel free to ignore.
| # when color_range='tv' and color_space=None on FFmpeg 7/8. | ||
| # Since this failure is rare, I suspect its a bug related to these | ||
| # older container formats on newer FFmpeg versions. | ||
| if ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use if not ... rather than if: pass; else: ....
|
|
||
| std::string getDetails() override; | ||
|
|
||
| constexpr static AVPixelFormat CUDA_PIXEL_FORMAT = AV_PIX_FMT_NV12; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit, name this CUDA_ENCODING_PIXEL_FORMAT (or any variant that is explicitly about encoding), because this file and class contain a lot of stuff about decoding. We don't want to imply that this is a "decoding pixel format".
| : AV_PIX_FMT_YUV420P; | ||
| if (frames_.device().is_cuda()) { | ||
| // Default to nv12 pixel format when encoding on GPU. | ||
| outPixelFormat_ = AV_PIX_FMT_NV12; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To avoid duplication of hard-coded constants, we can use the interface's CUDA_PIXEL_FORMAT field here (if it's public, which I think it is?)
| // - In limited range, Y is offset by 16 to add the lower margin. | ||
| // - In both color ranges, U,V are offset by 128 to be centered around 0. | ||
| // | ||
| // RGB to YUV conversion matrices to use in NPP color conversion functions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the note, it will be super useful if we ever need to go back to this in the future.
This PR resolves this TODO:
// TODO-VideoEncoder: Enable configuration of color properties, similar to FFmpeg.Support is added for the following parameters:
BT.601,BT.709,BT.2020tv(limited),pc(full)As a result, 6
ColorConversionMatricesare stored and utilized for NPP color conversion functions. To my understanding, these are the most commonly used or newer color spaces parameters (docs for AVColorSpace)The testing caveats:
test_nvenc_against_ffmpeg_cli. It only passes when using non-default color range and colorspace (specifically, not limited range and BT601). Since these tests pass on FFmpeg 7 and 8, I suspect there is some issue in FFmpeg 4 and 6. I've opened Enable color parameters in NVENC test on FFmpeg 4 and 6 #1140 to track this issue.av1_nvenctest is disabled on FFmpeg4, as the codec is not implemented.