Video: Add mastering display metadata to HDR videos

For most players this appears to made no difference. But one case it fixes
is that with this metadata, on Youtube with SDR, 10bit PQ looks the same as
12bit PQ, where it was previously too desaturated.

10bit and 12bit HLG was already consistent without this metadata and there
appears to be no impact, though it remains inconsistent with PQ.

It seems good practice to be explicit about the mastering display regardless.

The way we get the max luminance is not ideal though. We get it from the
view transform name containing "HDR XXXX nits" as used in the Blender and
ACES configs. There is currently no way to query this information from
OpenColorIO.

In the future we can add a user control for this. For now if we can not
determine the max luminance this way, we don't write mastering display
metadata. The video player will then make some default assumption for tone
mapping, often 1000 nits.

Pull Request: https://projects.blender.org/blender/blender/pulls/144378
This commit is contained in:
Brecht Van Lommel
2025-09-08 11:47:48 +02:00
parent aeb103fb50
commit a599ce0908
2 changed files with 73 additions and 0 deletions

View File

@@ -144,6 +144,76 @@ static const char **get_file_extensions(int format)
}
}
static void add_hdr_mastering_display_metadata(AVCodecParameters *codecpar,
AVCodecContext *c,
const ImageFormatData *imf)
{
if (c->color_primaries != AVCOL_PRI_BT2020) {
return;
}
int max_luminance = 0;
if (c->color_trc == AVCOL_TRC_ARIB_STD_B67) {
/* HLG is always 1000 nits. */
max_luminance = 1000;
}
else if (c->color_trc == AVCOL_TRC_SMPTEST2084) {
/* PQ uses heuristic based on view transform name. In the future this could become
* a user control, but this solves the common cases. */
blender::StringRefNull view_name = imf->view_settings.view_transform;
if (view_name.find("HDR 500 nits")) {
max_luminance = 500;
}
else if (view_name.find("HDR 1000 nits")) {
max_luminance = 1000;
}
else if (view_name.find("HDR 2000 nits")) {
max_luminance = 2000;
}
else if (view_name.find("HDR 4000 nits")) {
max_luminance = 4000;
}
else if (view_name.find("HDR 10000 nits")) {
max_luminance = 10000;
}
}
/* If we don't know anything, don't write metadata. The video player will make some
* default assumption, often 1000 nits. */
if (max_luminance == 0) {
return;
}
AVPacketSideData *side_data = av_packet_side_data_new(&codecpar->coded_side_data,
&codecpar->nb_coded_side_data,
AV_PKT_DATA_MASTERING_DISPLAY_METADATA,
sizeof(AVMasteringDisplayMetadata),
0);
if (side_data == nullptr) {
CLOG_ERROR(&LOG, "Failed to attached mastering display metadata to stream");
return;
}
AVMasteringDisplayMetadata *mastering_metadata = reinterpret_cast<AVMasteringDisplayMetadata *>(
side_data->data);
/* Rec.2020 primaries and D65 white point. */
mastering_metadata->has_primaries = 1;
mastering_metadata->display_primaries[0][0] = av_make_q(34000, 50000);
mastering_metadata->display_primaries[0][1] = av_make_q(16000, 50000);
mastering_metadata->display_primaries[1][0] = av_make_q(13250, 50000);
mastering_metadata->display_primaries[1][1] = av_make_q(34500, 50000);
mastering_metadata->display_primaries[2][0] = av_make_q(7500, 50000);
mastering_metadata->display_primaries[2][1] = av_make_q(3000, 50000);
mastering_metadata->white_point[0] = av_make_q(15635, 50000);
mastering_metadata->white_point[1] = av_make_q(16450, 50000);
mastering_metadata->has_luminance = 1;
mastering_metadata->min_luminance = av_make_q(1, 10000);
mastering_metadata->max_luminance = av_make_q(max_luminance, 1);
}
/* Write a frame to the output file */
static bool write_video_frame(MovieWriter *context, AVFrame *frame, ReportList *reports)
{
@@ -1141,6 +1211,8 @@ static AVStream *alloc_video_stream(MovieWriter *context,
avcodec_parameters_from_context(st->codecpar, c);
add_hdr_mastering_display_metadata(st->codecpar, c, imf);
context->video_time = 0.0f;
return st;

View File

@@ -25,6 +25,7 @@ extern "C" {
# include <libavutil/buffer.h>
# include <libavutil/channel_layout.h>
# include <libavutil/imgutils.h>
# include <libavutil/mastering_display_metadata.h>
# include <libavutil/opt.h>
# include <libavutil/rational.h>
# include <libavutil/samplefmt.h>