diff mbox

[FFmpeg-devel] Support HDR dynamic metadata (HDR10+) in HEVC decoder.

Message ID 20190122175210.131018-1-moh.izadi@gmail.com
State Superseded
Headers show

Commit Message

Mohammad Izadi Jan. 22, 2019, 5:52 p.m. UTC
---
 libavcodec/hevc_sei.c | 211 ++++++++++++++++++++++++++++++++++++++++--
 libavcodec/hevc_sei.h |   6 ++
 libavcodec/hevcdec.c  |  22 +++++
 3 files changed, 233 insertions(+), 6 deletions(-)

Comments

Rostislav Pehlivanov Jan. 22, 2019, 7:50 p.m. UTC | #1
On Tue, 22 Jan 2019 at 18:01, Mohammad Izadi <moh.izadi@gmail.com> wrote:

> ---
>  libavcodec/hevc_sei.c | 211 ++++++++++++++++++++++++++++++++++++++++--
>  libavcodec/hevc_sei.h |   6 ++
>  libavcodec/hevcdec.c  |  22 +++++
>  3 files changed, 233 insertions(+), 6 deletions(-)
>
> diff --git a/libavcodec/hevc_sei.c b/libavcodec/hevc_sei.c
> index c59bd4321e..6edec9f0db 100644
> --- a/libavcodec/hevc_sei.c
> +++ b/libavcodec/hevc_sei.c
> @@ -25,6 +25,7 @@
>  #include "golomb.h"
>  #include "hevc_ps.h"
>  #include "hevc_sei.h"
> +#include "libavutil/hdr_dynamic_metadata.h"
>
>  static int decode_nal_sei_decoded_picture_hash(HEVCSEIPictureHash *s,
> GetBitContext *gb)
>  {
> @@ -206,10 +207,179 @@ static int
> decode_registered_user_data_closed_caption(HEVCSEIA53Caption *s, GetB
>      return 0;
>  }
>
> -static int decode_nal_sei_user_data_registered_itu_t_t35(HEVCSEI *s,
> GetBitContext *gb,
> +static int decode_registered_user_data_dynamic_hdr_plus(AVDynamicHDRPlus
> *s, GetBitContext *gb,
> +                                                        void *logctx, int
> size)
> +{
> +    const int luminance_den = 10000;
> +    const int peak_luminance_den = 15;
> +    const int rgb_den = 100000;
> +    const int fraction_pixel_den = 1000;
> +    const int knee_point_den = 4095;
> +    const int bezier_anchor_den = 1023;
> +    const int saturation_weight_den = 8;
> +
> +    int w, i, j;
> +
> +    if (get_bits_left(gb) < size * 8)
> +        return AVERROR_INVALIDDATA;
> +
> +    if (get_bits_left(gb) < 2)
> +        return AVERROR_INVALIDDATA;
> +    s->num_windows = get_bits(gb, 2);
> +    if (s->num_windows < 1 || s->num_windows > 3) {
> +        av_log(logctx, AV_LOG_ERROR, "num_windows=%d, must be in [1,
> 3]\n",
> +               s->num_windows);
> +        return AVERROR_INVALIDDATA;
> +    }
> +
> +    if (get_bits_left(gb) < ((19 * 8 + 1) * (s->num_windows - 1)))
> +        return AVERROR_INVALIDDATA;
>

Put a newline here?


+    for (w = 1; w < s->num_windows; w++) {
> +        s->params[w].window_upper_left_corner_x.num = get_bits(gb, 16);
> +        s->params[w].window_upper_left_corner_y.num = get_bits(gb, 16);
> +        s->params[w].window_lower_right_corner_x.num = get_bits(gb, 16);
> +        s->params[w].window_lower_right_corner_y.num = get_bits(gb, 16);
> +        // The corners are set to absolute coordinates here. They should
> be
> +        // converted to the relative coordinates (in [0, 1]) in the
> decoder.
> +        s->params[w].window_upper_left_corner_x.den = 1;
> +        s->params[w].window_upper_left_corner_y.den = 1;
> +        s->params[w].window_lower_right_corner_x.den = 1;
> +        s->params[w].window_lower_right_corner_y.den = 1;
> +
> +        s->params[w].center_of_ellipse_x = get_bits(gb, 16);
> +        s->params[w].center_of_ellipse_y = get_bits(gb, 16);
> +        s->params[w].rotation_angle = get_bits(gb, 8);
> +        s->params[w].semimajor_axis_internal_ellipse = get_bits(gb, 16);
> +        s->params[w].semimajor_axis_external_ellipse = get_bits(gb, 16);
> +        s->params[w].semiminor_axis_external_ellipse = get_bits(gb, 16);
> +        s->params[w].overlap_process_option = get_bits(gb, 1);
> +    }
> +
> +    if (get_bits_left(gb) < 28)
> +        return AVERROR(EINVAL);
>

And here.


+    s->targeted_system_display_maximum_luminance.num = get_bits(gb, 27);
> +    s->targeted_system_display_maximum_luminance.den = luminance_den;
> +    s->targeted_system_display_actual_peak_luminance_flag = get_bits(gb,
> 1);
> +
> +    if (s->targeted_system_display_actual_peak_luminance_flag) {
> +        int rows, cols;
> +        if (get_bits_left(gb) < 10)
> +            return AVERROR(EINVAL);
> +        rows = get_bits(gb, 5);
> +        cols = get_bits(gb, 5);
> +        if (((rows < 2) || (rows > 25)) || ((cols < 2) || (cols > 25))) {
> +            av_log(logctx, AV_LOG_ERROR, "num_rows=%d, num_cols=%d, they
> must [2, 25] for targeted_system_display_actual_peak_luminance\n", rows,
> cols);
> +            return AVERROR_INVALIDDATA;
> +        }
> +        s->num_rows_targeted_system_display_actual_peak_luminance = rows;
> +        s->num_cols_targeted_system_display_actual_peak_luminance = cols;
> +
> +        if (get_bits_left(gb) < (rows * cols * 4))
> +            return AVERROR(EINVAL);
> +
> +        for (i = 0; i < rows; i++) {
> +            for (j = 0; j < cols; j++) {
> +
> s->targeted_system_display_actual_peak_luminance[i][j].num = get_bits(gb,
> 4);
> +
> s->targeted_system_display_actual_peak_luminance[i][j].den =
> peak_luminance_den;
> +            }
> +        }
> +    }
>

Same.



> +    for (w = 0; w < s->num_windows; w++) {
> +        if (get_bits_left(gb) < (3 * 17 + 17 + 4))
> +            return AVERROR(EINVAL);
> +        for (i = 0; i < 3; i++) {
> +            s->params[w].maxscl[i].num = get_bits(gb, 17);
> +            s->params[w].maxscl[i].den = rgb_den;
> +        }
> +        s->params[w].average_maxrgb.num = get_bits(gb, 17);
> +        s->params[w].average_maxrgb.den = rgb_den;
> +        s->params[w].num_distribution_maxrgb_percentiles = get_bits(gb,
> 4);
> +
> +        if (get_bits_left(gb) <
> +            (s->params[w].num_distribution_maxrgb_percentiles * 24))
> +            return AVERROR(EINVAL);
> +        for (i = 0; i < s->params[w].num_distribution_maxrgb_percentiles;
> i++) {
> +            s->params[w].distribution_maxrgb[i].percentage = get_bits(gb,
> 7);
> +            s->params[w].distribution_maxrgb[i].percentile.num =
> get_bits(gb, 17);
> +            s->params[w].distribution_maxrgb[i].percentile.den = rgb_den;
> +        }
> +
> +        if (get_bits_left(gb) < 10)
> +            return AVERROR(EINVAL);
> +        s->params[w].fraction_bright_pixels.num = get_bits(gb, 10);
> +        s->params[w].fraction_bright_pixels.den = fraction_pixel_den;
> +    }
>

Same.


+    if (get_bits_left(gb) < 1)
> +        return AVERROR(EINVAL);
>

Same.


+    s->mastering_display_actual_peak_luminance_flag = get_bits(gb, 1);
> +    if (s->mastering_display_actual_peak_luminance_flag) {
> +        int rows, cols;
> +        if (get_bits_left(gb) < 10)
> +            return AVERROR(EINVAL);
> +        rows = get_bits(gb, 5);
> +        cols = get_bits(gb, 5);
> +        if (((rows < 2) || (rows > 25)) || ((cols < 2) || (cols > 25))) {
> +            av_log(logctx, AV_LOG_ERROR, "num_rows=%d, num_cols=%d, they
> must be in [2, 25] for mastering_display_actual_peak_luminance\n", rows,
> cols);
> +            return AVERROR_INVALIDDATA;
> +        }
> +        s->num_rows_mastering_display_actual_peak_luminance = rows;
> +        s->num_cols_mastering_display_actual_peak_luminance = cols;
> +
> +        if (get_bits_left(gb) < (rows * cols * 4))
> +            return AVERROR(EINVAL);
> +
> +        for (i = 0; i < rows; i++) {
> +            for (j = 0; j < cols; j++) {
> +                s->mastering_display_actual_peak_luminance[i][j].num =
> get_bits(gb, 4);
> +                s->mastering_display_actual_peak_luminance[i][j].den =
> peak_luminance_den;
> +            }
> +        }
> +    }
> +
> +    for (w = 0; w < s->num_windows; w++) {
> +        if (get_bits_left(gb) < 1)
> +            return AVERROR(EINVAL);
> +        s->params[w].tone_mapping_flag = get_bits(gb, 1);
> +        if (s->params[w].tone_mapping_flag) {
> +            if (get_bits_left(gb) < 28)
> +                return AVERROR(EINVAL);
> +            s->params[w].knee_point_x.num = get_bits(gb, 12);
> +            s->params[w].knee_point_x.den = knee_point_den;
> +            s->params[w].knee_point_y.num = get_bits(gb, 12);
> +            s->params[w].knee_point_y.den = knee_point_den;
> +            s->params[w].num_bezier_curve_anchors = get_bits(gb, 4);
> +
> +            if (get_bits_left(gb) <
> (s->params[w].num_bezier_curve_anchors * 10))
> +                return AVERROR(EINVAL);
> +            for (i = 0; i < s->params[w].num_bezier_curve_anchors; i++) {
> +                s->params[w].bezier_curve_anchors[i].num = get_bits(gb,
> 10);
> +                s->params[w].bezier_curve_anchors[i].den =
> bezier_anchor_den;
> +            }
> +        }
> +
> +        if (get_bits_left(gb) < 1)
> +            return AVERROR(EINVAL);
> +        s->params[w].color_saturation_mapping_flag = get_bits(gb, 1);
> +        if (s->params[w].color_saturation_mapping_flag) {
> +            if (get_bits_left(gb) < 6)
> +                return AVERROR(EINVAL);
> +            s->params[w].color_saturation_weight.num = get_bits(gb, 6);
> +            s->params[w].color_saturation_weight.den =
> saturation_weight_den;
> +        }
> +    }
> +
> +    skip_bits(gb, get_bits_left(gb));
> +
> +    return 0;
> +}
> +
> +static int decode_nal_sei_user_data_registered_itu_t_t35(HEVCSEI *s,
> +                                                         GetBitContext
> *gb,
> +                                                         void *logctx,
>                                                           int size)
>  {
> -    uint32_t country_code;
> +    uint8_t country_code;
> +    uint16_t provider_code;
>      uint32_t user_identifier;
>
>      if (size < 7)
> @@ -222,11 +392,39 @@ static int
> decode_nal_sei_user_data_registered_itu_t_t35(HEVCSEI *s, GetBitConte
>          size--;
>      }
>
> -    skip_bits(gb, 8);
> -    skip_bits(gb, 8);
> -
> +    provider_code = get_bits(gb, 16);
>      user_identifier = get_bits_long(gb, 32);
>
> +    // Check for dynamic metadata - HDR10+(SMPTE 2094-40).
> +    if ((provider_code == 0x003C) &&
> +        ((user_identifier & 0xFFFFFF00) == 0x00010400)) {
> +        int err;
> +        size_t size;
> +        AVDynamicHDRPlus *hdr_plus = av_dynamic_hdr_plus_alloc(&size);
> +        if (!hdr_plus) {
> +            return AVERROR(ENOMEM);
> +        }
>

No need for brackets here.



> +        if (s->dynamic_hdr_plus.info){
> +            av_buffer_unref(&s->dynamic_hdr_plus.info);
> +        }
>

Same.


+        s->dynamic_hdr_plus.info =
> +            av_buffer_create((uint8_t*)hdr_plus, size,
> +                             av_buffer_default_free, NULL, 0);
> +        if (!s->dynamic_hdr_plus.info) {
> +            av_freep(&hdr_plus);
> +            return AVERROR(ENOMEM);
> +        }
> +
> +        hdr_plus->itu_t_t35_country_code = country_code;
> +        hdr_plus->application_version =
> +            (uint8_t)((user_identifier & 0x000000FF));
>

No need to split this in two lines.


+
> +        err = decode_registered_user_data_dynamic_hdr_plus(hdr_plus, gb,
> logctx, size);
> +        if (!err)
> +            av_buffer_unref(&s->dynamic_hdr_plus.info);
> +        return err;
> +    }
> +
>      switch (user_identifier) {
>          case MKBETAG('G', 'A', '9', '4'):
>              return
> decode_registered_user_data_closed_caption(&s->a53_caption, gb, size);
> @@ -292,7 +490,7 @@ static int decode_nal_sei_prefix(GetBitContext *gb,
> void *logctx, HEVCSEI *s,
>      case HEVC_SEI_TYPE_ACTIVE_PARAMETER_SETS:
>          return decode_nal_sei_active_parameter_sets(s, gb, logctx);
>      case HEVC_SEI_TYPE_USER_DATA_REGISTERED_ITU_T_T35:
> -        return decode_nal_sei_user_data_registered_itu_t_t35(s, gb, size);
> +        return decode_nal_sei_user_data_registered_itu_t_t35(s, gb,
> logctx, size);
>      case HEVC_SEI_TYPE_ALTERNATIVE_TRANSFER_CHARACTERISTICS:
>          return
> decode_nal_sei_alternative_transfer(&s->alternative_transfer, gb);
>      default:
> @@ -365,4 +563,5 @@ void ff_hevc_reset_sei(HEVCSEI *s)
>  {
>      s->a53_caption.a53_caption_size = 0;
>      av_freep(&s->a53_caption.a53_caption);
> +    av_buffer_unref(&s->dynamic_hdr_plus.info);
>  }
> diff --git a/libavcodec/hevc_sei.h b/libavcodec/hevc_sei.h
> index 2fec00ace0..80c56b10bb 100644
> --- a/libavcodec/hevc_sei.h
> +++ b/libavcodec/hevc_sei.h
> @@ -23,6 +23,7 @@
>
>  #include <stdint.h>
>
> +#include "libavutil/buffer.h"
>  #include "get_bits.h"
>
>  /**
> @@ -94,6 +95,10 @@ typedef struct HEVCSEIMasteringDisplay {
>      uint32_t min_luminance;
>  } HEVCSEIMasteringDisplay;
>
> +typedef struct HEVCSEIDynamicHDRPlus{
> +    AVBufferRef *info;
> +} HEVCSEIDynamicHDRPlus;
> +
>  typedef struct HEVCSEIContentLight {
>      int present;
>      uint16_t max_content_light_level;
> @@ -109,6 +114,7 @@ typedef struct HEVCSEI {
>      HEVCSEIPictureHash picture_hash;
>      HEVCSEIFramePacking frame_packing;
>      HEVCSEIDisplayOrientation display_orientation;
> +    HEVCSEIDynamicHDRPlus dynamic_hdr_plus;
>      HEVCSEIPictureTiming picture_timing;
>      HEVCSEIA53Caption a53_caption;
>      HEVCSEIMasteringDisplay mastering_display;
> diff --git a/libavcodec/hevcdec.c b/libavcodec/hevcdec.c
> index 10bf2563c0..a7ed26b9d9 100644
> --- a/libavcodec/hevcdec.c
> +++ b/libavcodec/hevcdec.c
> @@ -28,6 +28,7 @@
>  #include "libavutil/display.h"
>  #include "libavutil/internal.h"
>  #include "libavutil/mastering_display_metadata.h"
> +#include "libavutil/hdr_dynamic_metadata.h"
>  #include "libavutil/md5.h"
>  #include "libavutil/opt.h"
>  #include "libavutil/pixdesc.h"
> @@ -2769,6 +2770,26 @@ static int set_side_data(HEVCContext *s)
>          s->avctx->color_trc = out->color_trc =
> s->sei.alternative_transfer.preferred_transfer_characteristics;
>      }
>
> +    if (s->sei.dynamic_hdr_plus.info){
> +        AVDynamicHDRPlus *metadata =
> (AVDynamicHDRPlus*)s->sei.dynamic_hdr_plus.info->data;
> +        // Convert coordinates to relative coordinate in [0, 1].
> +        metadata->params[0].window_upper_left_corner_x.num  = 0;
> +        metadata->params[0].window_upper_left_corner_y.num  = 0;
> +        metadata->params[0].window_lower_right_corner_x.num =
> out->width-1;
> +        metadata->params[0].window_lower_right_corner_y.num =
> out->height-1;
> +        for (int w = 0; w < metadata->num_windows; w++) {
> +            metadata->params[w].window_upper_left_corner_x.den =
> out->width-1;
> +            metadata->params[w].window_upper_left_corner_y.den =
> out->height-1;
> +            metadata->params[w].window_lower_right_corner_x.den =
> out->width-1;
> +            metadata->params[w].window_lower_right_corner_y.den =
> out->height-1;
> +        }
>

Put spaces between all the variables you're subtracting one out of?



> +        if (!av_frame_new_side_data_from_buf(out,
> AV_FRAME_DATA_DYNAMIC_HDR_PLUS, s->sei.dynamic_hdr_plus.info)) {
>

This is wrong, this takes ownership of the buffer, so by the time you apply
it to the next frame it would have been free'd by the previous frame's
unref.
You need to call av_buffer_ref(s->sei.dynamic_hdr_plus.info) and put the
new reference in av_frame_new_side_data_from_buf().
You should also check if av_buffer_ref returned NULL in case of OOM.
In case there's an error I don't think you should unref the s->
sei.dynamic_hdr_plus.info buffer. It'll be freed during uninit, which the
user will call if they think its necessary. That way the decoder state
won't change.
You should however return whatever av_frame_new_side_data_from_buf()
returns. Just don't check it.


+            av_buffer_unref(&s->sei.dynamic_hdr_plus.info);
> +            return AVERROR(ENOMEM);
> +        }
> +        s->sei.dynamic_hdr_plus.info = NULL;
>

See above, you don't need this, also you should not set pointers to
AVBufferRefs to NULL, av_buffer_unref() will do that for you when the count
reaches 0.



> +    }
> +
>      return 0;
>  }
>
> @@ -3309,6 +3330,7 @@ static av_cold int hevc_decode_free(AVCodecContext
> *avctx)
>          s->HEVClc = NULL;
>      av_freep(&s->HEVClcList[0]);
>
> +    ff_hevc_reset_sei(&s->sei);


Shouldn't this also get called on flush? Otherwise when seeking old HDR
data will get ref'd to new frames.



>      ff_h2645_packet_uninit(&s->pkt);
>
>      return 0;
> --
> 2.20.1.321.g9e740568ce-goog
>
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
James Almer Jan. 22, 2019, 8:36 p.m. UTC | #2
On 1/22/2019 4:50 PM, Rostislav Pehlivanov wrote:
> On Tue, 22 Jan 2019 at 18:01, Mohammad Izadi <moh.izadi@gmail.com> wrote:
>> diff --git a/libavcodec/hevc_sei.h b/libavcodec/hevc_sei.h
>> index 2fec00ace0..80c56b10bb 100644
>> --- a/libavcodec/hevc_sei.h
>> +++ b/libavcodec/hevc_sei.h
>> @@ -23,6 +23,7 @@
>>
>>  #include <stdint.h>
>>
>> +#include "libavutil/buffer.h"
>>  #include "get_bits.h"
>>
>>  /**
>> @@ -94,6 +95,10 @@ typedef struct HEVCSEIMasteringDisplay {
>>      uint32_t min_luminance;
>>  } HEVCSEIMasteringDisplay;
>>
>> +typedef struct HEVCSEIDynamicHDRPlus{
>> +    AVBufferRef *info;
>> +} HEVCSEIDynamicHDRPlus;
>> +
>>  typedef struct HEVCSEIContentLight {
>>      int present;
>>      uint16_t max_content_light_level;
>> @@ -109,6 +114,7 @@ typedef struct HEVCSEI {
>>      HEVCSEIPictureHash picture_hash;
>>      HEVCSEIFramePacking frame_packing;
>>      HEVCSEIDisplayOrientation display_orientation;
>> +    HEVCSEIDynamicHDRPlus dynamic_hdr_plus;
>>      HEVCSEIPictureTiming picture_timing;
>>      HEVCSEIA53Caption a53_caption;
>>      HEVCSEIMasteringDisplay mastering_display;
>> diff --git a/libavcodec/hevcdec.c b/libavcodec/hevcdec.c
>> index 10bf2563c0..a7ed26b9d9 100644
>> --- a/libavcodec/hevcdec.c
>> +++ b/libavcodec/hevcdec.c
>> @@ -28,6 +28,7 @@
>>  #include "libavutil/display.h"
>>  #include "libavutil/internal.h"
>>  #include "libavutil/mastering_display_metadata.h"
>> +#include "libavutil/hdr_dynamic_metadata.h"
>>  #include "libavutil/md5.h"
>>  #include "libavutil/opt.h"
>>  #include "libavutil/pixdesc.h"
>> @@ -2769,6 +2770,26 @@ static int set_side_data(HEVCContext *s)
>>          s->avctx->color_trc = out->color_trc =
>> s->sei.alternative_transfer.preferred_transfer_characteristics;
>>      }
>>
>> +    if (s->sei.dynamic_hdr_plus.info){
>> +        AVDynamicHDRPlus *metadata =
>> (AVDynamicHDRPlus*)s->sei.dynamic_hdr_plus.info->data;
>> +        // Convert coordinates to relative coordinate in [0, 1].
>> +        metadata->params[0].window_upper_left_corner_x.num  = 0;
>> +        metadata->params[0].window_upper_left_corner_y.num  = 0;
>> +        metadata->params[0].window_lower_right_corner_x.num =
>> out->width-1;
>> +        metadata->params[0].window_lower_right_corner_y.num =
>> out->height-1;
>> +        for (int w = 0; w < metadata->num_windows; w++) {
>> +            metadata->params[w].window_upper_left_corner_x.den =
>> out->width-1;
>> +            metadata->params[w].window_upper_left_corner_y.den =
>> out->height-1;
>> +            metadata->params[w].window_lower_right_corner_x.den =
>> out->width-1;
>> +            metadata->params[w].window_lower_right_corner_y.den =
>> out->height-1;
>> +        }
>>
> 
> Put spaces between all the variables you're subtracting one out of?

He can't edit the AVBufferRef stored in HEVCSEI for each frame as it
becomes non writable past the first reference.

> 
> 
> 
>> +        if (!av_frame_new_side_data_from_buf(out,
>> AV_FRAME_DATA_DYNAMIC_HDR_PLUS, s->sei.dynamic_hdr_plus.info)) {
>>
> 
> This is wrong, this takes ownership of the buffer, so by the time you apply
> it to the next frame it would have been free'd by the previous frame's
> unref.
> You need to call av_buffer_ref(s->sei.dynamic_hdr_plus.info) and put the
> new reference in av_frame_new_side_data_from_buf().
> You should also check if av_buffer_ref returned NULL in case of OOM.
> In case there's an error I don't think you should unref the s->
> sei.dynamic_hdr_plus.info buffer. It'll be freed during uninit, which the
> user will call if they think its necessary. That way the decoder state
> won't change.
> You should however return whatever av_frame_new_side_data_from_buf()
> returns. Just don't check it.

If this is meant to work through resolution changes, then he'll have to
call av_buffer_make_writable() for the new reference meant to be
attached as side data, either when it differs from the previous frame
(this needs code to keep track of dimension changes), or
unconditionally, in which case the benefit of reusing buffers will be lost.
diff mbox

Patch

diff --git a/libavcodec/hevc_sei.c b/libavcodec/hevc_sei.c
index c59bd4321e..6edec9f0db 100644
--- a/libavcodec/hevc_sei.c
+++ b/libavcodec/hevc_sei.c
@@ -25,6 +25,7 @@ 
 #include "golomb.h"
 #include "hevc_ps.h"
 #include "hevc_sei.h"
+#include "libavutil/hdr_dynamic_metadata.h"
 
 static int decode_nal_sei_decoded_picture_hash(HEVCSEIPictureHash *s, GetBitContext *gb)
 {
@@ -206,10 +207,179 @@  static int decode_registered_user_data_closed_caption(HEVCSEIA53Caption *s, GetB
     return 0;
 }
 
-static int decode_nal_sei_user_data_registered_itu_t_t35(HEVCSEI *s, GetBitContext *gb,
+static int decode_registered_user_data_dynamic_hdr_plus(AVDynamicHDRPlus *s, GetBitContext *gb,
+                                                        void *logctx, int size)
+{
+    const int luminance_den = 10000;
+    const int peak_luminance_den = 15;
+    const int rgb_den = 100000;
+    const int fraction_pixel_den = 1000;
+    const int knee_point_den = 4095;
+    const int bezier_anchor_den = 1023;
+    const int saturation_weight_den = 8;
+
+    int w, i, j;
+
+    if (get_bits_left(gb) < size * 8)
+        return AVERROR_INVALIDDATA;
+
+    if (get_bits_left(gb) < 2)
+        return AVERROR_INVALIDDATA;
+    s->num_windows = get_bits(gb, 2);
+    if (s->num_windows < 1 || s->num_windows > 3) {
+        av_log(logctx, AV_LOG_ERROR, "num_windows=%d, must be in [1, 3]\n",
+               s->num_windows);
+        return AVERROR_INVALIDDATA;
+    }
+
+    if (get_bits_left(gb) < ((19 * 8 + 1) * (s->num_windows - 1)))
+        return AVERROR_INVALIDDATA;
+    for (w = 1; w < s->num_windows; w++) {
+        s->params[w].window_upper_left_corner_x.num = get_bits(gb, 16);
+        s->params[w].window_upper_left_corner_y.num = get_bits(gb, 16);
+        s->params[w].window_lower_right_corner_x.num = get_bits(gb, 16);
+        s->params[w].window_lower_right_corner_y.num = get_bits(gb, 16);
+        // The corners are set to absolute coordinates here. They should be
+        // converted to the relative coordinates (in [0, 1]) in the decoder.
+        s->params[w].window_upper_left_corner_x.den = 1;
+        s->params[w].window_upper_left_corner_y.den = 1;
+        s->params[w].window_lower_right_corner_x.den = 1;
+        s->params[w].window_lower_right_corner_y.den = 1;
+
+        s->params[w].center_of_ellipse_x = get_bits(gb, 16);
+        s->params[w].center_of_ellipse_y = get_bits(gb, 16);
+        s->params[w].rotation_angle = get_bits(gb, 8);
+        s->params[w].semimajor_axis_internal_ellipse = get_bits(gb, 16);
+        s->params[w].semimajor_axis_external_ellipse = get_bits(gb, 16);
+        s->params[w].semiminor_axis_external_ellipse = get_bits(gb, 16);
+        s->params[w].overlap_process_option = get_bits(gb, 1);
+    }
+
+    if (get_bits_left(gb) < 28)
+        return AVERROR(EINVAL);
+    s->targeted_system_display_maximum_luminance.num = get_bits(gb, 27);
+    s->targeted_system_display_maximum_luminance.den = luminance_den;
+    s->targeted_system_display_actual_peak_luminance_flag = get_bits(gb, 1);
+
+    if (s->targeted_system_display_actual_peak_luminance_flag) {
+        int rows, cols;
+        if (get_bits_left(gb) < 10)
+            return AVERROR(EINVAL);
+        rows = get_bits(gb, 5);
+        cols = get_bits(gb, 5);
+        if (((rows < 2) || (rows > 25)) || ((cols < 2) || (cols > 25))) {
+            av_log(logctx, AV_LOG_ERROR, "num_rows=%d, num_cols=%d, they must [2, 25] for targeted_system_display_actual_peak_luminance\n", rows, cols);
+            return AVERROR_INVALIDDATA;
+        }
+        s->num_rows_targeted_system_display_actual_peak_luminance = rows;
+        s->num_cols_targeted_system_display_actual_peak_luminance = cols;
+
+        if (get_bits_left(gb) < (rows * cols * 4))
+            return AVERROR(EINVAL);
+
+        for (i = 0; i < rows; i++) {
+            for (j = 0; j < cols; j++) {
+                s->targeted_system_display_actual_peak_luminance[i][j].num = get_bits(gb, 4);
+                s->targeted_system_display_actual_peak_luminance[i][j].den = peak_luminance_den;
+            }
+        }
+    }
+    for (w = 0; w < s->num_windows; w++) {
+        if (get_bits_left(gb) < (3 * 17 + 17 + 4))
+            return AVERROR(EINVAL);
+        for (i = 0; i < 3; i++) {
+            s->params[w].maxscl[i].num = get_bits(gb, 17);
+            s->params[w].maxscl[i].den = rgb_den;
+        }
+        s->params[w].average_maxrgb.num = get_bits(gb, 17);
+        s->params[w].average_maxrgb.den = rgb_den;
+        s->params[w].num_distribution_maxrgb_percentiles = get_bits(gb, 4);
+
+        if (get_bits_left(gb) <
+            (s->params[w].num_distribution_maxrgb_percentiles * 24))
+            return AVERROR(EINVAL);
+        for (i = 0; i < s->params[w].num_distribution_maxrgb_percentiles; i++) {
+            s->params[w].distribution_maxrgb[i].percentage = get_bits(gb, 7);
+            s->params[w].distribution_maxrgb[i].percentile.num = get_bits(gb, 17);
+            s->params[w].distribution_maxrgb[i].percentile.den = rgb_den;
+        }
+
+        if (get_bits_left(gb) < 10)
+            return AVERROR(EINVAL);
+        s->params[w].fraction_bright_pixels.num = get_bits(gb, 10);
+        s->params[w].fraction_bright_pixels.den = fraction_pixel_den;
+    }
+    if (get_bits_left(gb) < 1)
+        return AVERROR(EINVAL);
+    s->mastering_display_actual_peak_luminance_flag = get_bits(gb, 1);
+    if (s->mastering_display_actual_peak_luminance_flag) {
+        int rows, cols;
+        if (get_bits_left(gb) < 10)
+            return AVERROR(EINVAL);
+        rows = get_bits(gb, 5);
+        cols = get_bits(gb, 5);
+        if (((rows < 2) || (rows > 25)) || ((cols < 2) || (cols > 25))) {
+            av_log(logctx, AV_LOG_ERROR, "num_rows=%d, num_cols=%d, they must be in [2, 25] for mastering_display_actual_peak_luminance\n", rows, cols);
+            return AVERROR_INVALIDDATA;
+        }
+        s->num_rows_mastering_display_actual_peak_luminance = rows;
+        s->num_cols_mastering_display_actual_peak_luminance = cols;
+
+        if (get_bits_left(gb) < (rows * cols * 4))
+            return AVERROR(EINVAL);
+
+        for (i = 0; i < rows; i++) {
+            for (j = 0; j < cols; j++) {
+                s->mastering_display_actual_peak_luminance[i][j].num = get_bits(gb, 4);
+                s->mastering_display_actual_peak_luminance[i][j].den = peak_luminance_den;
+            }
+        }
+    }
+
+    for (w = 0; w < s->num_windows; w++) {
+        if (get_bits_left(gb) < 1)
+            return AVERROR(EINVAL);
+        s->params[w].tone_mapping_flag = get_bits(gb, 1);
+        if (s->params[w].tone_mapping_flag) {
+            if (get_bits_left(gb) < 28)
+                return AVERROR(EINVAL);
+            s->params[w].knee_point_x.num = get_bits(gb, 12);
+            s->params[w].knee_point_x.den = knee_point_den;
+            s->params[w].knee_point_y.num = get_bits(gb, 12);
+            s->params[w].knee_point_y.den = knee_point_den;
+            s->params[w].num_bezier_curve_anchors = get_bits(gb, 4);
+
+            if (get_bits_left(gb) < (s->params[w].num_bezier_curve_anchors * 10))
+                return AVERROR(EINVAL);
+            for (i = 0; i < s->params[w].num_bezier_curve_anchors; i++) {
+                s->params[w].bezier_curve_anchors[i].num = get_bits(gb, 10);
+                s->params[w].bezier_curve_anchors[i].den = bezier_anchor_den;
+            }
+        }
+
+        if (get_bits_left(gb) < 1)
+            return AVERROR(EINVAL);
+        s->params[w].color_saturation_mapping_flag = get_bits(gb, 1);
+        if (s->params[w].color_saturation_mapping_flag) {
+            if (get_bits_left(gb) < 6)
+                return AVERROR(EINVAL);
+            s->params[w].color_saturation_weight.num = get_bits(gb, 6);
+            s->params[w].color_saturation_weight.den = saturation_weight_den;
+        }
+    }
+
+    skip_bits(gb, get_bits_left(gb));
+
+    return 0;
+}
+
+static int decode_nal_sei_user_data_registered_itu_t_t35(HEVCSEI *s,
+                                                         GetBitContext *gb,
+                                                         void *logctx,
                                                          int size)
 {
-    uint32_t country_code;
+    uint8_t country_code;
+    uint16_t provider_code;
     uint32_t user_identifier;
 
     if (size < 7)
@@ -222,11 +392,39 @@  static int decode_nal_sei_user_data_registered_itu_t_t35(HEVCSEI *s, GetBitConte
         size--;
     }
 
-    skip_bits(gb, 8);
-    skip_bits(gb, 8);
-
+    provider_code = get_bits(gb, 16);
     user_identifier = get_bits_long(gb, 32);
 
+    // Check for dynamic metadata - HDR10+(SMPTE 2094-40).
+    if ((provider_code == 0x003C) &&
+        ((user_identifier & 0xFFFFFF00) == 0x00010400)) {
+        int err;
+        size_t size;
+        AVDynamicHDRPlus *hdr_plus = av_dynamic_hdr_plus_alloc(&size);
+        if (!hdr_plus) {
+            return AVERROR(ENOMEM);
+        }
+        if (s->dynamic_hdr_plus.info){
+            av_buffer_unref(&s->dynamic_hdr_plus.info);
+        }
+        s->dynamic_hdr_plus.info =
+            av_buffer_create((uint8_t*)hdr_plus, size,
+                             av_buffer_default_free, NULL, 0);
+        if (!s->dynamic_hdr_plus.info) {
+            av_freep(&hdr_plus);
+            return AVERROR(ENOMEM);
+        }
+
+        hdr_plus->itu_t_t35_country_code = country_code;
+        hdr_plus->application_version =
+            (uint8_t)((user_identifier & 0x000000FF));
+
+        err = decode_registered_user_data_dynamic_hdr_plus(hdr_plus, gb, logctx, size);
+        if (!err)
+            av_buffer_unref(&s->dynamic_hdr_plus.info);
+        return err;
+    }
+
     switch (user_identifier) {
         case MKBETAG('G', 'A', '9', '4'):
             return decode_registered_user_data_closed_caption(&s->a53_caption, gb, size);
@@ -292,7 +490,7 @@  static int decode_nal_sei_prefix(GetBitContext *gb, void *logctx, HEVCSEI *s,
     case HEVC_SEI_TYPE_ACTIVE_PARAMETER_SETS:
         return decode_nal_sei_active_parameter_sets(s, gb, logctx);
     case HEVC_SEI_TYPE_USER_DATA_REGISTERED_ITU_T_T35:
-        return decode_nal_sei_user_data_registered_itu_t_t35(s, gb, size);
+        return decode_nal_sei_user_data_registered_itu_t_t35(s, gb, logctx, size);
     case HEVC_SEI_TYPE_ALTERNATIVE_TRANSFER_CHARACTERISTICS:
         return decode_nal_sei_alternative_transfer(&s->alternative_transfer, gb);
     default:
@@ -365,4 +563,5 @@  void ff_hevc_reset_sei(HEVCSEI *s)
 {
     s->a53_caption.a53_caption_size = 0;
     av_freep(&s->a53_caption.a53_caption);
+    av_buffer_unref(&s->dynamic_hdr_plus.info);
 }
diff --git a/libavcodec/hevc_sei.h b/libavcodec/hevc_sei.h
index 2fec00ace0..80c56b10bb 100644
--- a/libavcodec/hevc_sei.h
+++ b/libavcodec/hevc_sei.h
@@ -23,6 +23,7 @@ 
 
 #include <stdint.h>
 
+#include "libavutil/buffer.h"
 #include "get_bits.h"
 
 /**
@@ -94,6 +95,10 @@  typedef struct HEVCSEIMasteringDisplay {
     uint32_t min_luminance;
 } HEVCSEIMasteringDisplay;
 
+typedef struct HEVCSEIDynamicHDRPlus{
+    AVBufferRef *info;
+} HEVCSEIDynamicHDRPlus;
+
 typedef struct HEVCSEIContentLight {
     int present;
     uint16_t max_content_light_level;
@@ -109,6 +114,7 @@  typedef struct HEVCSEI {
     HEVCSEIPictureHash picture_hash;
     HEVCSEIFramePacking frame_packing;
     HEVCSEIDisplayOrientation display_orientation;
+    HEVCSEIDynamicHDRPlus dynamic_hdr_plus;
     HEVCSEIPictureTiming picture_timing;
     HEVCSEIA53Caption a53_caption;
     HEVCSEIMasteringDisplay mastering_display;
diff --git a/libavcodec/hevcdec.c b/libavcodec/hevcdec.c
index 10bf2563c0..a7ed26b9d9 100644
--- a/libavcodec/hevcdec.c
+++ b/libavcodec/hevcdec.c
@@ -28,6 +28,7 @@ 
 #include "libavutil/display.h"
 #include "libavutil/internal.h"
 #include "libavutil/mastering_display_metadata.h"
+#include "libavutil/hdr_dynamic_metadata.h"
 #include "libavutil/md5.h"
 #include "libavutil/opt.h"
 #include "libavutil/pixdesc.h"
@@ -2769,6 +2770,26 @@  static int set_side_data(HEVCContext *s)
         s->avctx->color_trc = out->color_trc = s->sei.alternative_transfer.preferred_transfer_characteristics;
     }
 
+    if (s->sei.dynamic_hdr_plus.info){
+        AVDynamicHDRPlus *metadata = (AVDynamicHDRPlus*)s->sei.dynamic_hdr_plus.info->data;
+        // Convert coordinates to relative coordinate in [0, 1].
+        metadata->params[0].window_upper_left_corner_x.num  = 0;
+        metadata->params[0].window_upper_left_corner_y.num  = 0;
+        metadata->params[0].window_lower_right_corner_x.num = out->width-1;
+        metadata->params[0].window_lower_right_corner_y.num = out->height-1;
+        for (int w = 0; w < metadata->num_windows; w++) {
+            metadata->params[w].window_upper_left_corner_x.den = out->width-1;
+            metadata->params[w].window_upper_left_corner_y.den = out->height-1;
+            metadata->params[w].window_lower_right_corner_x.den = out->width-1;
+            metadata->params[w].window_lower_right_corner_y.den = out->height-1;
+        }
+        if (!av_frame_new_side_data_from_buf(out, AV_FRAME_DATA_DYNAMIC_HDR_PLUS, s->sei.dynamic_hdr_plus.info)) {
+            av_buffer_unref(&s->sei.dynamic_hdr_plus.info);
+            return AVERROR(ENOMEM);
+        }
+        s->sei.dynamic_hdr_plus.info = NULL;
+    }
+
     return 0;
 }
 
@@ -3309,6 +3330,7 @@  static av_cold int hevc_decode_free(AVCodecContext *avctx)
         s->HEVClc = NULL;
     av_freep(&s->HEVClcList[0]);
 
+    ff_hevc_reset_sei(&s->sei);
     ff_h2645_packet_uninit(&s->pkt);
 
     return 0;