diff mbox series

[FFmpeg-devel,2/2] avutil: add HDR10+ dynamic metadata serialization function

Message ID 3bd88490-9f9f-39e0-756e-9d15d2e78d88@vimeo.com
State New
Headers show
Series None | expand

Commit Message

Raphaël Zumer March 2, 2023, 9:43 p.m. UTC
Fixed brace style and moved inline buffer size calculation comments to a single block at the top.


Signed-off-by: Raphaël Zumer <rzumer@tebako.net>
---
 libavutil/hdr_dynamic_metadata.c | 142 +++++++++++++++++++++++++++++++
 libavutil/hdr_dynamic_metadata.h |  11 +++
 libavutil/version.h              |   2 +-
 3 files changed, 154 insertions(+), 1 deletion(-)

Comments

Raphaël Zumer March 9, 2023, 2:18 p.m. UTC | #1
Hi,

While I omitted adding v2/v3 here, I believe all comments on this set of patches have been addressed so far, unless anyone strongly disagrees with the rationale for moving dynamic HDR parsing and serialization to libavutil or with the function signature.

Please let me know if I missed anything.

Thanks,
Raphaël Zumer

On 3/2/23 16:43, Raphaël Zumer wrote:
> Fixed brace style and moved inline buffer size calculation comments to a single block at the top.
>
>
> Signed-off-by: Raphaël Zumer <rzumer@tebako.net>
> ---
>  libavutil/hdr_dynamic_metadata.c | 142 +++++++++++++++++++++++++++++++
>  libavutil/hdr_dynamic_metadata.h |  11 +++
>  libavutil/version.h              |   2 +-
>  3 files changed, 154 insertions(+), 1 deletion(-)
>
> diff --git a/libavutil/hdr_dynamic_metadata.c b/libavutil/hdr_dynamic_metadata.c
> index 98f399b032..24dd3dab2d 100644
> --- a/libavutil/hdr_dynamic_metadata.c
> +++ b/libavutil/hdr_dynamic_metadata.c
> @@ -225,3 +225,145 @@ int av_dynamic_hdr_plus_from_t35(AVDynamicHDRPlus *s, const uint8_t *data,
>  
>      return 0;
>  }
> +
> +AVBufferRef *av_dynamic_hdr_plus_to_t35(AVDynamicHDRPlus *s)
> +{
> +    AVBufferRef *buf;
> +    size_t size_bits, size_bytes;
> +    PutBitContext pbc, *pb = &pbc;
> +
> +    if (!s)
> +        return NULL;
> +
> +    /**
> +     * Buffer size per CTA-861-H p.253-254:
> +     * 48 bits for the header (56 minus excluded 8-bit country code)
> +     * 2 bits for num_windows
> +     * 937 bits for window geometry, for each window above 1
> +     * 27 bits for targeted_system_display_maximum_luminance
> +     * 1-3855 bits for targeted system display peak luminance information
> +     * 0-442 bits for intra-window pixel distribution information
> +     * 1-3855 bits for mastering display peak luminance information
> +     * 0-537 bits for per-window tonemapping information
> +     * 0-21 bits for per-window color saturation mapping information
> +     */
> +    size_bits = 48 +
> +        2 +
> +        FFMAX((s->num_windows - 1), 0) * 937 +
> +        27 +
> +        1 + (s->targeted_system_display_actual_peak_luminance_flag ? 10 +
> +            s->num_rows_targeted_system_display_actual_peak_luminance *
> +            s->num_cols_targeted_system_display_actual_peak_luminance * 4 : 0) +
> +        s->num_windows * 82;
> +
> +    for (int w = 0; w < s->num_windows; w++)
> +        size_bits += s->params[w].num_distribution_maxrgb_percentiles * 24;
> +
> +    size_bits += 1 + (s->mastering_display_actual_peak_luminance_flag ? 10 +
> +        s->num_rows_mastering_display_actual_peak_luminance *
> +        s->num_cols_mastering_display_actual_peak_luminance * 4 : 0) +
> +        s->num_windows * 1;
> +
> +    for (int w = 0; w < s->num_windows; w++) {
> +        if (s->params[w].tone_mapping_flag)
> +            size_bits += 28 + s->params[w].num_bezier_curve_anchors * 10;
> +    }
> +
> +    size_bits += s->num_windows * 1;
> +    for (int w = 0; w < s->num_windows; w++) {
> +        if (s->params[w].color_saturation_mapping_flag)
> +            size_bits += 6;
> +    }
> +
> +    size_bytes = (size_bits + 7) / 8;
> +
> +    buf = av_buffer_alloc(size_bytes);
> +    if (!buf)
> +        return NULL;
> +
> +    init_put_bits(pb, buf->data, size_bytes);
> +
> +    // itu_t_t35_country_code shall be 0xB5 (USA) (excluded from the payload)
> +    // itu_t_t35_terminal_provider_code shall be 0x003C
> +    put_bits(pb, 16, 0x003C);
> +    // itu_t_t35_terminal_provider_oriented_code is set to ST 2094-40
> +    put_bits(pb, 16, 0x0001);
> +    // application_identifier shall be set to 4
> +    put_bits(pb, 8, 4);
> +    // application_mode is set to Application Version 1
> +    put_bits(pb, 8, 1);
> +
> +    // Payload as per CTA-861-H p.253-254
> +    put_bits(pb, 2, s->num_windows);
> +
> +    for (int w = 1; w < s->num_windows; w++) {
> +        put_bits(pb, 16, s->params[w].window_upper_left_corner_x.num / s->params[w].window_upper_left_corner_x.den);
> +        put_bits(pb, 16, s->params[w].window_upper_left_corner_y.num / s->params[w].window_upper_left_corner_y.den);
> +        put_bits(pb, 16, s->params[w].window_lower_right_corner_x.num / s->params[w].window_lower_right_corner_x.den);
> +        put_bits(pb, 16, s->params[w].window_lower_right_corner_y.num / s->params[w].window_lower_right_corner_y.den);
> +        put_bits(pb, 16, s->params[w].center_of_ellipse_x);
> +        put_bits(pb, 16, s->params[w].center_of_ellipse_y);
> +        put_bits(pb, 8, s->params[w].rotation_angle);
> +        put_bits(pb, 16, s->params[w].semimajor_axis_internal_ellipse);
> +        put_bits(pb, 16, s->params[w].semimajor_axis_external_ellipse);
> +        put_bits(pb, 16, s->params[w].semiminor_axis_external_ellipse);
> +        put_bits(pb, 1, s->params[w].overlap_process_option);
> +    }
> +
> +    put_bits(pb, 27, s->targeted_system_display_maximum_luminance.num * luminance_den /
> +        s->targeted_system_display_maximum_luminance.den);
> +    put_bits(pb, 1, s->targeted_system_display_actual_peak_luminance_flag);
> +    if (s->targeted_system_display_actual_peak_luminance_flag) {
> +        put_bits(pb, 5, s->num_rows_targeted_system_display_actual_peak_luminance);
> +        put_bits(pb, 5, s->num_cols_targeted_system_display_actual_peak_luminance);
> +        for (int i = 0; i < s->num_rows_targeted_system_display_actual_peak_luminance; i++) {
> +            for (int j = 0; j < s->num_cols_targeted_system_display_actual_peak_luminance; j++)
> +                put_bits(pb, 4, s->targeted_system_display_actual_peak_luminance[i][j].num * peak_luminance_den /
> +                    s->targeted_system_display_actual_peak_luminance[i][j].den);
> +        }
> +    }
> +
> +    for (int w = 0; w < s->num_windows; w++) {
> +        for (int i = 0; i < 3; i++)
> +            put_bits(pb, 17, s->params[w].maxscl[i].num * rgb_den / s->params[w].maxscl[i].den);
> +        put_bits(pb, 17, s->params[w].average_maxrgb.num * rgb_den / s->params[w].average_maxrgb.den);
> +        put_bits(pb, 4, s->params[w].num_distribution_maxrgb_percentiles);
> +        for (int i = 0; i < s->params[w].num_distribution_maxrgb_percentiles; i++) {
> +            put_bits(pb, 7, s->params[w].distribution_maxrgb[i].percentage);
> +            put_bits(pb, 17, s->params[w].distribution_maxrgb[i].percentile.num * rgb_den /
> +                s->params[w].distribution_maxrgb[i].percentile.den);
> +        }
> +        put_bits(pb, 10, s->params[w].fraction_bright_pixels.num * fraction_pixel_den /
> +            s->params[w].fraction_bright_pixels.den);
> +    }
> +
> +    put_bits(pb, 1, s->mastering_display_actual_peak_luminance_flag);
> +    if (s->mastering_display_actual_peak_luminance_flag) {
> +        put_bits(pb, 5, s->num_rows_mastering_display_actual_peak_luminance);
> +        put_bits(pb, 5, s->num_cols_mastering_display_actual_peak_luminance);
> +        for (int i = 0; i < s->num_rows_mastering_display_actual_peak_luminance; i++) {
> +            for (int j = 0; j < s->num_cols_mastering_display_actual_peak_luminance; j++)
> +                put_bits(pb, 4, s->mastering_display_actual_peak_luminance[i][j].num * peak_luminance_den /
> +                    s->mastering_display_actual_peak_luminance[i][j].den);
> +        }
> +    }
> +
> +    for (int w = 0; w < s->num_windows; w++) {
> +        put_bits(pb, 1, s->params[w].tone_mapping_flag);
> +        if (s->params[w].tone_mapping_flag) {
> +            put_bits(pb, 12, s->params[w].knee_point_x.num * knee_point_den / s->params[w].knee_point_x.den);
> +            put_bits(pb, 12, s->params[w].knee_point_y.num * knee_point_den / s->params[w].knee_point_y.den);
> +            put_bits(pb, 4, s->params[w].num_bezier_curve_anchors);
> +            for (int i = 0; i < s->params[w].num_bezier_curve_anchors; i++)
> +                put_bits(pb, 10, s->params[w].bezier_curve_anchors[i].num * bezier_anchor_den /
> +                    s->params[w].bezier_curve_anchors[i].den);
> +            put_bits(pb, 1, s->params[w].color_saturation_mapping_flag);
> +            if (s->params[w].color_saturation_mapping_flag)
> +                put_bits(pb, 6, s->params[w].color_saturation_weight.num * saturation_weight_den /
> +                    s->params[w].color_saturation_weight.den);
> +        }
> +    }
> +
> +    flush_put_bits(pb);
> +    return buf;
> +}
> diff --git a/libavutil/hdr_dynamic_metadata.h b/libavutil/hdr_dynamic_metadata.h
> index 1f953ef1f5..797a5c64ae 100644
> --- a/libavutil/hdr_dynamic_metadata.h
> +++ b/libavutil/hdr_dynamic_metadata.h
> @@ -21,6 +21,7 @@
>  #ifndef AVUTIL_HDR_DYNAMIC_METADATA_H
>  #define AVUTIL_HDR_DYNAMIC_METADATA_H
>  
> +#include "buffer.h"
>  #include "frame.h"
>  #include "rational.h"
>  
> @@ -351,4 +352,14 @@ AVDynamicHDRPlus *av_dynamic_hdr_plus_create_side_data(AVFrame *frame);
>  int av_dynamic_hdr_plus_from_t35(AVDynamicHDRPlus *s, const uint8_t *data,
>                                   int size);
>  
> +/**
> + * Serialize dynamic HDR10+ metadata to a user data registered ITU-T T.35 buffer,
> + * excluding the country code and beginning with the terminal provider code.
> + * @param s A pointer containing the decoded AVDynamicHDRPlus structure.
> + *
> + * @return Pointer to an AVBufferRef containing the raw ITU-T T.35 representation
> + *         of the HDR10+ metadata if succeed, or NULL if buffer allocation fails.
> + */
> +AVBufferRef *av_dynamic_hdr_plus_to_t35(AVDynamicHDRPlus *s);
> +
>  #endif /* AVUTIL_HDR_DYNAMIC_METADATA_H */
> diff --git a/libavutil/version.h b/libavutil/version.h
> index 900b798971..7635672985 100644
> --- a/libavutil/version.h
> +++ b/libavutil/version.h
> @@ -79,7 +79,7 @@
>   */
>  
>  #define LIBAVUTIL_VERSION_MAJOR  58
> -#define LIBAVUTIL_VERSION_MINOR   3
> +#define LIBAVUTIL_VERSION_MINOR   4
>  #define LIBAVUTIL_VERSION_MICRO 100
>  
>  #define LIBAVUTIL_VERSION_INT   AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \
James Almer March 12, 2023, 3:21 p.m. UTC | #2
On 3/9/2023 11:18 AM, Raphaël Zumer wrote:
> Hi,
> 
> While I omitted adding v2/v3 here, I believe all comments on this set of patches have been addressed so far, unless anyone strongly disagrees with the rationale for moving dynamic HDR parsing and serialization to libavutil or with the function signature.
> 
> Please let me know if I missed anything.
> 
> Thanks,
> Raphaël Zumer

I'll apply this (and patch 1/1) in a few days if nobody comments.
The code needs to be in lavu if we want muxers and demuxers to use this 
functionality, so moving the existing lavc functions is fine unless we 
add more avpriv_ functions that people tend to dislike.
Zhao Zhili March 12, 2023, 4:25 p.m. UTC | #3
> From: ffmpeg-devel <ffmpeg-devel-bounces@ffmpeg.org> On Behalf Of Raphaël Zumer
> Sent: 2023年3月3日 5:43
> To: ffmpeg-devel@ffmpeg.org
> Subject: [FFmpeg-devel] [PATCH 2/2] avutil: add HDR10+ dynamic metadata serialization function
> 
> Fixed brace style and moved inline buffer size calculation comments to a single block at the top.
> 
> 
> Signed-off-by: Raphaël Zumer <rzumer@tebako.net>
> ---
>  libavutil/hdr_dynamic_metadata.c | 142 +++++++++++++++++++++++++++++++
>  libavutil/hdr_dynamic_metadata.h |  11 +++
>  libavutil/version.h              |   2 +-
>  3 files changed, 154 insertions(+), 1 deletion(-)
> 
> diff --git a/libavutil/hdr_dynamic_metadata.c b/libavutil/hdr_dynamic_metadata.c
> index 98f399b032..24dd3dab2d 100644
> --- a/libavutil/hdr_dynamic_metadata.c
> +++ b/libavutil/hdr_dynamic_metadata.c
> @@ -225,3 +225,145 @@ int av_dynamic_hdr_plus_from_t35(AVDynamicHDRPlus *s, const uint8_t *data,
> 
>      return 0;
>  }
> +
> +AVBufferRef *av_dynamic_hdr_plus_to_t35(AVDynamicHDRPlus *s)
> +{

How about add 'const' modifier to AVDynamicHDRPlus *s ?

> +    AVBufferRef *buf;
> +    size_t size_bits, size_bytes;
> +    PutBitContext pbc, *pb = &pbc;
> +
> +    if (!s)
> +        return NULL;
> +
> +    /**
> +     * Buffer size per CTA-861-H p.253-254:
> +     * 48 bits for the header (56 minus excluded 8-bit country code)
> +     * 2 bits for num_windows
> +     * 937 bits for window geometry, for each window above 1
> +     * 27 bits for targeted_system_display_maximum_luminance
> +     * 1-3855 bits for targeted system display peak luminance information
> +     * 0-442 bits for intra-window pixel distribution information
> +     * 1-3855 bits for mastering display peak luminance information
> +     * 0-537 bits for per-window tonemapping information
> +     * 0-21 bits for per-window color saturation mapping information
> +     */
> +    size_bits = 48 +
> +        2 +
> +        FFMAX((s->num_windows - 1), 0) * 937 +
> +        27 +
> +        1 + (s->targeted_system_display_actual_peak_luminance_flag ? 10 +
> +            s->num_rows_targeted_system_display_actual_peak_luminance *
> +            s->num_cols_targeted_system_display_actual_peak_luminance * 4 : 0) +
> +        s->num_windows * 82;
> +
> +    for (int w = 0; w < s->num_windows; w++)
> +        size_bits += s->params[w].num_distribution_maxrgb_percentiles * 24;
> +
> +    size_bits += 1 + (s->mastering_display_actual_peak_luminance_flag ? 10 +
> +        s->num_rows_mastering_display_actual_peak_luminance *
> +        s->num_cols_mastering_display_actual_peak_luminance * 4 : 0) +
> +        s->num_windows * 1;
> +
> +    for (int w = 0; w < s->num_windows; w++) {
> +        if (s->params[w].tone_mapping_flag)
> +            size_bits += 28 + s->params[w].num_bezier_curve_anchors * 10;
> +    }
> +
> +    size_bits += s->num_windows * 1;
> +    for (int w = 0; w < s->num_windows; w++) {
> +        if (s->params[w].color_saturation_mapping_flag)
> +            size_bits += 6;
> +    }
> +
> +    size_bytes = (size_bits + 7) / 8;
> +
> +    buf = av_buffer_alloc(size_bytes);
> +    if (!buf)
> +        return NULL;
> +
> +    init_put_bits(pb, buf->data, size_bytes);
> +
> +    // itu_t_t35_country_code shall be 0xB5 (USA) (excluded from the payload)
> +    // itu_t_t35_terminal_provider_code shall be 0x003C
> +    put_bits(pb, 16, 0x003C);
> +    // itu_t_t35_terminal_provider_oriented_code is set to ST 2094-40
> +    put_bits(pb, 16, 0x0001);
> +    // application_identifier shall be set to 4
> +    put_bits(pb, 8, 4);
> +    // application_mode is set to Application Version 1
> +    put_bits(pb, 8, 1);
> +
> +    // Payload as per CTA-861-H p.253-254
> +    put_bits(pb, 2, s->num_windows);
> +
> +    for (int w = 1; w < s->num_windows; w++) {
> +        put_bits(pb, 16, s->params[w].window_upper_left_corner_x.num / s->params[w].window_upper_left_corner_x.den);
> +        put_bits(pb, 16, s->params[w].window_upper_left_corner_y.num / s->params[w].window_upper_left_corner_y.den);
> +        put_bits(pb, 16, s->params[w].window_lower_right_corner_x.num / s->params[w].window_lower_right_corner_x.den);
> +        put_bits(pb, 16, s->params[w].window_lower_right_corner_y.num / s->params[w].window_lower_right_corner_y.den);
> +        put_bits(pb, 16, s->params[w].center_of_ellipse_x);
> +        put_bits(pb, 16, s->params[w].center_of_ellipse_y);
> +        put_bits(pb, 8, s->params[w].rotation_angle);
> +        put_bits(pb, 16, s->params[w].semimajor_axis_internal_ellipse);
> +        put_bits(pb, 16, s->params[w].semimajor_axis_external_ellipse);
> +        put_bits(pb, 16, s->params[w].semiminor_axis_external_ellipse);
> +        put_bits(pb, 1, s->params[w].overlap_process_option);
> +    }
> +
> +    put_bits(pb, 27, s->targeted_system_display_maximum_luminance.num * luminance_den /
> +        s->targeted_system_display_maximum_luminance.den);
> +    put_bits(pb, 1, s->targeted_system_display_actual_peak_luminance_flag);
> +    if (s->targeted_system_display_actual_peak_luminance_flag) {
> +        put_bits(pb, 5, s->num_rows_targeted_system_display_actual_peak_luminance);
> +        put_bits(pb, 5, s->num_cols_targeted_system_display_actual_peak_luminance);
> +        for (int i = 0; i < s->num_rows_targeted_system_display_actual_peak_luminance; i++) {
> +            for (int j = 0; j < s->num_cols_targeted_system_display_actual_peak_luminance; j++)
> +                put_bits(pb, 4, s->targeted_system_display_actual_peak_luminance[i][j].num * peak_luminance_den /
> +                    s->targeted_system_display_actual_peak_luminance[i][j].den);
> +        }
> +    }
> +
> +    for (int w = 0; w < s->num_windows; w++) {
> +        for (int i = 0; i < 3; i++)
> +            put_bits(pb, 17, s->params[w].maxscl[i].num * rgb_den / s->params[w].maxscl[i].den);
> +        put_bits(pb, 17, s->params[w].average_maxrgb.num * rgb_den / s->params[w].average_maxrgb.den);
> +        put_bits(pb, 4, s->params[w].num_distribution_maxrgb_percentiles);
> +        for (int i = 0; i < s->params[w].num_distribution_maxrgb_percentiles; i++) {
> +            put_bits(pb, 7, s->params[w].distribution_maxrgb[i].percentage);
> +            put_bits(pb, 17, s->params[w].distribution_maxrgb[i].percentile.num * rgb_den /
> +                s->params[w].distribution_maxrgb[i].percentile.den);
> +        }
> +        put_bits(pb, 10, s->params[w].fraction_bright_pixels.num * fraction_pixel_den /
> +            s->params[w].fraction_bright_pixels.den);
> +    }
> +
> +    put_bits(pb, 1, s->mastering_display_actual_peak_luminance_flag);
> +    if (s->mastering_display_actual_peak_luminance_flag) {
> +        put_bits(pb, 5, s->num_rows_mastering_display_actual_peak_luminance);
> +        put_bits(pb, 5, s->num_cols_mastering_display_actual_peak_luminance);
> +        for (int i = 0; i < s->num_rows_mastering_display_actual_peak_luminance; i++) {
> +            for (int j = 0; j < s->num_cols_mastering_display_actual_peak_luminance; j++)
> +                put_bits(pb, 4, s->mastering_display_actual_peak_luminance[i][j].num * peak_luminance_den /
> +                    s->mastering_display_actual_peak_luminance[i][j].den);
> +        }
> +    }
> +
> +    for (int w = 0; w < s->num_windows; w++) {
> +        put_bits(pb, 1, s->params[w].tone_mapping_flag);
> +        if (s->params[w].tone_mapping_flag) {
> +            put_bits(pb, 12, s->params[w].knee_point_x.num * knee_point_den / s->params[w].knee_point_x.den);
> +            put_bits(pb, 12, s->params[w].knee_point_y.num * knee_point_den / s->params[w].knee_point_y.den);
> +            put_bits(pb, 4, s->params[w].num_bezier_curve_anchors);
> +            for (int i = 0; i < s->params[w].num_bezier_curve_anchors; i++)
> +                put_bits(pb, 10, s->params[w].bezier_curve_anchors[i].num * bezier_anchor_den /
> +                    s->params[w].bezier_curve_anchors[i].den);
> +            put_bits(pb, 1, s->params[w].color_saturation_mapping_flag);
> +            if (s->params[w].color_saturation_mapping_flag)
> +                put_bits(pb, 6, s->params[w].color_saturation_weight.num * saturation_weight_den /
> +                    s->params[w].color_saturation_weight.den);
> +        }
> +    }
> +
> +    flush_put_bits(pb);
> +    return buf;
> +}
> diff --git a/libavutil/hdr_dynamic_metadata.h b/libavutil/hdr_dynamic_metadata.h
> index 1f953ef1f5..797a5c64ae 100644
> --- a/libavutil/hdr_dynamic_metadata.h
> +++ b/libavutil/hdr_dynamic_metadata.h
> @@ -21,6 +21,7 @@
>  #ifndef AVUTIL_HDR_DYNAMIC_METADATA_H
>  #define AVUTIL_HDR_DYNAMIC_METADATA_H
> 
> +#include "buffer.h"
>  #include "frame.h"
>  #include "rational.h"
> 
> @@ -351,4 +352,14 @@ AVDynamicHDRPlus *av_dynamic_hdr_plus_create_side_data(AVFrame *frame);
>  int av_dynamic_hdr_plus_from_t35(AVDynamicHDRPlus *s, const uint8_t *data,
>                                   int size);
> 
> +/**
> + * Serialize dynamic HDR10+ metadata to a user data registered ITU-T T.35 buffer,
> + * excluding the country code and beginning with the terminal provider code.
> + * @param s A pointer containing the decoded AVDynamicHDRPlus structure.
> + *
> + * @return Pointer to an AVBufferRef containing the raw ITU-T T.35 representation
> + *         of the HDR10+ metadata if succeed, or NULL if buffer allocation fails.
> + */
> +AVBufferRef *av_dynamic_hdr_plus_to_t35(AVDynamicHDRPlus *s);
> +
>  #endif /* AVUTIL_HDR_DYNAMIC_METADATA_H */
> diff --git a/libavutil/version.h b/libavutil/version.h
> index 900b798971..7635672985 100644
> --- a/libavutil/version.h
> +++ b/libavutil/version.h
> @@ -79,7 +79,7 @@
>   */
> 
>  #define LIBAVUTIL_VERSION_MAJOR  58
> -#define LIBAVUTIL_VERSION_MINOR   3
> +#define LIBAVUTIL_VERSION_MINOR   4
>  #define LIBAVUTIL_VERSION_MICRO 100
> 
>  #define LIBAVUTIL_VERSION_INT   AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \
> --
> 2.39.1
> 
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
> 
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
Anton Khirnov March 12, 2023, 7:48 p.m. UTC | #4
Quoting Raphaël Zumer (2023-03-02 22:43:29)
> +/**
> + * Serialize dynamic HDR10+ metadata to a user data registered ITU-T T.35 buffer,
> + * excluding the country code and beginning with the terminal provider code.
> + * @param s A pointer containing the decoded AVDynamicHDRPlus structure.
> + *
> + * @return Pointer to an AVBufferRef containing the raw ITU-T T.35 representation
> + *         of the HDR10+ metadata if succeed, or NULL if buffer allocation fails.
> + */
> +AVBufferRef *av_dynamic_hdr_plus_to_t35(AVDynamicHDRPlus *s);

Why is this an AVBufferRef rather than a plain av_malloced() uint8_t
array? You can very easily turn the latter into the former, but the
reverse is a lot more annoying.
Raphaël Zumer March 12, 2023, 9:50 p.m. UTC | #5
I expanded on this in another email in the chain, but the buffer size needs to be communicated to the user, as it is not embedded in the payload. It seems needlessly convoluted to me to create a separate function solely to calculate the size of the buffer so that it can be allocated by the user and passed to the serialization function, and I cannot think of another solution that would not be even more convoluted and awkward for the user.

I don't understand how going from AVBufferRef to uint8_t* is more complicated than the reverse. The buffer in the AVBufferRef is allocated via av_malloc() and is directly accessible through the data field. Am I missing some detail?

Raphaël Zumer

On 3/12/23 15:48, Anton Khirnov wrote:
> Quoting Raphaël Zumer (2023-03-02 22:43:29)
>> +/**
>> + * Serialize dynamic HDR10+ metadata to a user data registered ITU-T T.35 buffer,
>> + * excluding the country code and beginning with the terminal provider code.
>> + * @param s A pointer containing the decoded AVDynamicHDRPlus structure.
>> + *
>> + * @return Pointer to an AVBufferRef containing the raw ITU-T T.35 representation
>> + *         of the HDR10+ metadata if succeed, or NULL if buffer allocation fails.
>> + */
>> +AVBufferRef *av_dynamic_hdr_plus_to_t35(AVDynamicHDRPlus *s);
> Why is this an AVBufferRef rather than a plain av_malloced() uint8_t
> array? You can very easily turn the latter into the former, but the
> reverse is a lot more annoying.
>
James Almer March 12, 2023, 9:52 p.m. UTC | #6
On 3/12/2023 6:50 PM, Raphaël Zumer wrote:
> I expanded on this in another email in the chain, but the buffer size needs to be communicated to the user, as it is not embedded in the payload. It seems needlessly convoluted to me to create a separate function solely to calculate the size of the buffer so that it can be allocated by the user and passed to the serialization function, and I cannot think of another solution that would not be even more convoluted and awkward for the user.
> 
> I don't understand how going from AVBufferRef to uint8_t* is more complicated than the reverse. The buffer in the AVBufferRef is allocated via av_malloc() and is directly accessible through the data field. Am I missing some detail?
> 
> Raphaël Zumer

You can do it like how the AVDynamicHDRPlus struct is allocated and its 
size returned to the user in av_dynamic_hdr_plus_alloc(). That is

uint8_t *av_dynamic_hdr_plus_to_t35(AVDynamicHDRPlus *s, size_t *size);

The function would then store the calculated size of the array on *size.

> 
> On 3/12/23 15:48, Anton Khirnov wrote:
>> Quoting Raphaël Zumer (2023-03-02 22:43:29)
>>> +/**
>>> + * Serialize dynamic HDR10+ metadata to a user data registered ITU-T T.35 buffer,
>>> + * excluding the country code and beginning with the terminal provider code.
>>> + * @param s A pointer containing the decoded AVDynamicHDRPlus structure.
>>> + *
>>> + * @return Pointer to an AVBufferRef containing the raw ITU-T T.35 representation
>>> + *         of the HDR10+ metadata if succeed, or NULL if buffer allocation fails.
>>> + */
>>> +AVBufferRef *av_dynamic_hdr_plus_to_t35(AVDynamicHDRPlus *s);
>> Why is this an AVBufferRef rather than a plain av_malloced() uint8_t
>> array? You can very easily turn the latter into the former, but the
>> reverse is a lot more annoying.
>>
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
> 
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
Raphaël Zumer March 12, 2023, 9:56 p.m. UTC | #7
On 3/12/23 17:52, James Almer wrote:
> On 3/12/2023 6:50 PM, Raphaël Zumer wrote:
>> I expanded on this in another email in the chain, but the buffer size needs to be communicated to the user, as it is not embedded in the payload. It seems needlessly convoluted to me to create a separate function solely to calculate the size of the buffer so that it can be allocated by the user and passed to the serialization function, and I cannot think of another solution that would not be even more convoluted and awkward for the user.
>>
>> I don't understand how going from AVBufferRef to uint8_t* is more complicated than the reverse. The buffer in the AVBufferRef is allocated via av_malloc() and is directly accessible through the data field. Am I missing some detail?
>>
>> Raphaël Zumer
> You can do it like how the AVDynamicHDRPlus struct is allocated and its 
> size returned to the user in av_dynamic_hdr_plus_alloc(). That is
>
> uint8_t *av_dynamic_hdr_plus_to_t35(AVDynamicHDRPlus *s, size_t *size);
>
> The function would then store the calculated size of the array on *size.

OK great, I will do that and address the remaining comments tomorrow.

Thanks

>> On 3/12/23 15:48, Anton Khirnov wrote:
>>> Quoting Raphaël Zumer (2023-03-02 22:43:29)
>>>> +/**
>>>> + * Serialize dynamic HDR10+ metadata to a user data registered ITU-T T.35 buffer,
>>>> + * excluding the country code and beginning with the terminal provider code.
>>>> + * @param s A pointer containing the decoded AVDynamicHDRPlus structure.
>>>> + *
>>>> + * @return Pointer to an AVBufferRef containing the raw ITU-T T.35 representation
>>>> + *         of the HDR10+ metadata if succeed, or NULL if buffer allocation fails.
>>>> + */
>>>> +AVBufferRef *av_dynamic_hdr_plus_to_t35(AVDynamicHDRPlus *s);
>>> Why is this an AVBufferRef rather than a plain av_malloced() uint8_t
>>> array? You can very easily turn the latter into the former, but the
>>> reverse is a lot more annoying.
>>>
>> _______________________________________________
>> ffmpeg-devel mailing list
>> ffmpeg-devel@ffmpeg.org
>> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>>
>> To unsubscribe, visit link above, or email
>> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
Anton Khirnov March 13, 2023, 1:36 p.m. UTC | #8
Quoting Raphaël Zumer (2023-03-12 22:50:27)
> I expanded on this in another email in the chain, but the buffer size needs to be communicated to the user, as it is not embedded in the payload. It seems needlessly convoluted to me to create a separate function solely to calculate the size of the buffer so that it can be allocated by the user and passed to the serialization function, and I cannot think of another solution that would not be even more convoluted and awkward for the user.
> 
> I don't understand how going from AVBufferRef to uint8_t* is more complicated than the reverse. The buffer in the AVBufferRef is allocated via av_malloc() and is directly accessible through the data field. Am I missing some detail?

What I mean is that once data is wrapped in an AVBufer, it is "locked
in", you always have to keep an AVBufferRef around.
Andreas Rheinhardt March 13, 2023, 10:25 p.m. UTC | #9
James Almer:
> On 3/9/2023 11:18 AM, Raphaël Zumer wrote:
>> Hi,
>>
>> While I omitted adding v2/v3 here, I believe all comments on this set
>> of patches have been addressed so far, unless anyone strongly
>> disagrees with the rationale for moving dynamic HDR parsing and
>> serialization to libavutil or with the function signature.
>>
>> Please let me know if I missed anything.
>>
>> Thanks,
>> Raphaël Zumer
> 
> I'll apply this (and patch 1/1) in a few days if nobody comments.
> The code needs to be in lavu if we want muxers and demuxers to use this
> functionality, so moving the existing lavc functions is fine unless we
> add more avpriv_ functions that people tend to dislike.

Can we wait with this until we have the actual patches that make use of it?
(And is lavfi actually supposed to make use of this? Or why is it moved
to lavu at all? lavf can also use it in lavc.)

- Andreas
James Almer March 13, 2023, 10:32 p.m. UTC | #10
On 3/13/2023 7:25 PM, Andreas Rheinhardt wrote:
> James Almer:
>> On 3/9/2023 11:18 AM, Raphaël Zumer wrote:
>>> Hi,
>>>
>>> While I omitted adding v2/v3 here, I believe all comments on this set
>>> of patches have been addressed so far, unless anyone strongly
>>> disagrees with the rationale for moving dynamic HDR parsing and
>>> serialization to libavutil or with the function signature.
>>>
>>> Please let me know if I missed anything.
>>>
>>> Thanks,
>>> Raphaël Zumer
>>
>> I'll apply this (and patch 1/1) in a few days if nobody comments.
>> The code needs to be in lavu if we want muxers and demuxers to use this
>> functionality, so moving the existing lavc functions is fine unless we
>> add more avpriv_ functions that people tend to dislike.
> 
> Can we wait with this until we have the actual patches that make use of it?
> (And is lavfi actually supposed to make use of this? Or why is it moved
> to lavu at all? lavf can also use it in lavc.)

I have patches ready to make use of one of the two functions in the 
matroska demuxer. Will send them after this is pushed. And I'll attempt 
to write one to make use of the serialization function in the matroska 
muxer too.

And it makes sense for this to be in lavu even if only lavf uses these, 
given there's a public header for it already. Installing two headers for 
HDR10+ functions in two separate libraries seems overkill when they use 
the same struct.
diff mbox series

Patch

diff --git a/libavutil/hdr_dynamic_metadata.c b/libavutil/hdr_dynamic_metadata.c
index 98f399b032..24dd3dab2d 100644
--- a/libavutil/hdr_dynamic_metadata.c
+++ b/libavutil/hdr_dynamic_metadata.c
@@ -225,3 +225,145 @@  int av_dynamic_hdr_plus_from_t35(AVDynamicHDRPlus *s, const uint8_t *data,
 
     return 0;
 }
+
+AVBufferRef *av_dynamic_hdr_plus_to_t35(AVDynamicHDRPlus *s)
+{
+    AVBufferRef *buf;
+    size_t size_bits, size_bytes;
+    PutBitContext pbc, *pb = &pbc;
+
+    if (!s)
+        return NULL;
+
+    /**
+     * Buffer size per CTA-861-H p.253-254:
+     * 48 bits for the header (56 minus excluded 8-bit country code)
+     * 2 bits for num_windows
+     * 937 bits for window geometry, for each window above 1
+     * 27 bits for targeted_system_display_maximum_luminance
+     * 1-3855 bits for targeted system display peak luminance information
+     * 0-442 bits for intra-window pixel distribution information
+     * 1-3855 bits for mastering display peak luminance information
+     * 0-537 bits for per-window tonemapping information
+     * 0-21 bits for per-window color saturation mapping information
+     */
+    size_bits = 48 +
+        2 +
+        FFMAX((s->num_windows - 1), 0) * 937 +
+        27 +
+        1 + (s->targeted_system_display_actual_peak_luminance_flag ? 10 +
+            s->num_rows_targeted_system_display_actual_peak_luminance *
+            s->num_cols_targeted_system_display_actual_peak_luminance * 4 : 0) +
+        s->num_windows * 82;
+
+    for (int w = 0; w < s->num_windows; w++)
+        size_bits += s->params[w].num_distribution_maxrgb_percentiles * 24;
+
+    size_bits += 1 + (s->mastering_display_actual_peak_luminance_flag ? 10 +
+        s->num_rows_mastering_display_actual_peak_luminance *
+        s->num_cols_mastering_display_actual_peak_luminance * 4 : 0) +
+        s->num_windows * 1;
+
+    for (int w = 0; w < s->num_windows; w++) {
+        if (s->params[w].tone_mapping_flag)
+            size_bits += 28 + s->params[w].num_bezier_curve_anchors * 10;
+    }
+
+    size_bits += s->num_windows * 1;
+    for (int w = 0; w < s->num_windows; w++) {
+        if (s->params[w].color_saturation_mapping_flag)
+            size_bits += 6;
+    }
+
+    size_bytes = (size_bits + 7) / 8;
+
+    buf = av_buffer_alloc(size_bytes);
+    if (!buf)
+        return NULL;
+
+    init_put_bits(pb, buf->data, size_bytes);
+
+    // itu_t_t35_country_code shall be 0xB5 (USA) (excluded from the payload)
+    // itu_t_t35_terminal_provider_code shall be 0x003C
+    put_bits(pb, 16, 0x003C);
+    // itu_t_t35_terminal_provider_oriented_code is set to ST 2094-40
+    put_bits(pb, 16, 0x0001);
+    // application_identifier shall be set to 4
+    put_bits(pb, 8, 4);
+    // application_mode is set to Application Version 1
+    put_bits(pb, 8, 1);
+
+    // Payload as per CTA-861-H p.253-254
+    put_bits(pb, 2, s->num_windows);
+
+    for (int w = 1; w < s->num_windows; w++) {
+        put_bits(pb, 16, s->params[w].window_upper_left_corner_x.num / s->params[w].window_upper_left_corner_x.den);
+        put_bits(pb, 16, s->params[w].window_upper_left_corner_y.num / s->params[w].window_upper_left_corner_y.den);
+        put_bits(pb, 16, s->params[w].window_lower_right_corner_x.num / s->params[w].window_lower_right_corner_x.den);
+        put_bits(pb, 16, s->params[w].window_lower_right_corner_y.num / s->params[w].window_lower_right_corner_y.den);
+        put_bits(pb, 16, s->params[w].center_of_ellipse_x);
+        put_bits(pb, 16, s->params[w].center_of_ellipse_y);
+        put_bits(pb, 8, s->params[w].rotation_angle);
+        put_bits(pb, 16, s->params[w].semimajor_axis_internal_ellipse);
+        put_bits(pb, 16, s->params[w].semimajor_axis_external_ellipse);
+        put_bits(pb, 16, s->params[w].semiminor_axis_external_ellipse);
+        put_bits(pb, 1, s->params[w].overlap_process_option);
+    }
+
+    put_bits(pb, 27, s->targeted_system_display_maximum_luminance.num * luminance_den /
+        s->targeted_system_display_maximum_luminance.den);
+    put_bits(pb, 1, s->targeted_system_display_actual_peak_luminance_flag);
+    if (s->targeted_system_display_actual_peak_luminance_flag) {
+        put_bits(pb, 5, s->num_rows_targeted_system_display_actual_peak_luminance);
+        put_bits(pb, 5, s->num_cols_targeted_system_display_actual_peak_luminance);
+        for (int i = 0; i < s->num_rows_targeted_system_display_actual_peak_luminance; i++) {
+            for (int j = 0; j < s->num_cols_targeted_system_display_actual_peak_luminance; j++)
+                put_bits(pb, 4, s->targeted_system_display_actual_peak_luminance[i][j].num * peak_luminance_den /
+                    s->targeted_system_display_actual_peak_luminance[i][j].den);
+        }
+    }
+
+    for (int w = 0; w < s->num_windows; w++) {
+        for (int i = 0; i < 3; i++)
+            put_bits(pb, 17, s->params[w].maxscl[i].num * rgb_den / s->params[w].maxscl[i].den);
+        put_bits(pb, 17, s->params[w].average_maxrgb.num * rgb_den / s->params[w].average_maxrgb.den);
+        put_bits(pb, 4, s->params[w].num_distribution_maxrgb_percentiles);
+        for (int i = 0; i < s->params[w].num_distribution_maxrgb_percentiles; i++) {
+            put_bits(pb, 7, s->params[w].distribution_maxrgb[i].percentage);
+            put_bits(pb, 17, s->params[w].distribution_maxrgb[i].percentile.num * rgb_den /
+                s->params[w].distribution_maxrgb[i].percentile.den);
+        }
+        put_bits(pb, 10, s->params[w].fraction_bright_pixels.num * fraction_pixel_den /
+            s->params[w].fraction_bright_pixels.den);
+    }
+
+    put_bits(pb, 1, s->mastering_display_actual_peak_luminance_flag);
+    if (s->mastering_display_actual_peak_luminance_flag) {
+        put_bits(pb, 5, s->num_rows_mastering_display_actual_peak_luminance);
+        put_bits(pb, 5, s->num_cols_mastering_display_actual_peak_luminance);
+        for (int i = 0; i < s->num_rows_mastering_display_actual_peak_luminance; i++) {
+            for (int j = 0; j < s->num_cols_mastering_display_actual_peak_luminance; j++)
+                put_bits(pb, 4, s->mastering_display_actual_peak_luminance[i][j].num * peak_luminance_den /
+                    s->mastering_display_actual_peak_luminance[i][j].den);
+        }
+    }
+
+    for (int w = 0; w < s->num_windows; w++) {
+        put_bits(pb, 1, s->params[w].tone_mapping_flag);
+        if (s->params[w].tone_mapping_flag) {
+            put_bits(pb, 12, s->params[w].knee_point_x.num * knee_point_den / s->params[w].knee_point_x.den);
+            put_bits(pb, 12, s->params[w].knee_point_y.num * knee_point_den / s->params[w].knee_point_y.den);
+            put_bits(pb, 4, s->params[w].num_bezier_curve_anchors);
+            for (int i = 0; i < s->params[w].num_bezier_curve_anchors; i++)
+                put_bits(pb, 10, s->params[w].bezier_curve_anchors[i].num * bezier_anchor_den /
+                    s->params[w].bezier_curve_anchors[i].den);
+            put_bits(pb, 1, s->params[w].color_saturation_mapping_flag);
+            if (s->params[w].color_saturation_mapping_flag)
+                put_bits(pb, 6, s->params[w].color_saturation_weight.num * saturation_weight_den /
+                    s->params[w].color_saturation_weight.den);
+        }
+    }
+
+    flush_put_bits(pb);
+    return buf;
+}
diff --git a/libavutil/hdr_dynamic_metadata.h b/libavutil/hdr_dynamic_metadata.h
index 1f953ef1f5..797a5c64ae 100644
--- a/libavutil/hdr_dynamic_metadata.h
+++ b/libavutil/hdr_dynamic_metadata.h
@@ -21,6 +21,7 @@ 
 #ifndef AVUTIL_HDR_DYNAMIC_METADATA_H
 #define AVUTIL_HDR_DYNAMIC_METADATA_H
 
+#include "buffer.h"
 #include "frame.h"
 #include "rational.h"
 
@@ -351,4 +352,14 @@  AVDynamicHDRPlus *av_dynamic_hdr_plus_create_side_data(AVFrame *frame);
 int av_dynamic_hdr_plus_from_t35(AVDynamicHDRPlus *s, const uint8_t *data,
                                  int size);
 
+/**
+ * Serialize dynamic HDR10+ metadata to a user data registered ITU-T T.35 buffer,
+ * excluding the country code and beginning with the terminal provider code.
+ * @param s A pointer containing the decoded AVDynamicHDRPlus structure.
+ *
+ * @return Pointer to an AVBufferRef containing the raw ITU-T T.35 representation
+ *         of the HDR10+ metadata if succeed, or NULL if buffer allocation fails.
+ */
+AVBufferRef *av_dynamic_hdr_plus_to_t35(AVDynamicHDRPlus *s);
+
 #endif /* AVUTIL_HDR_DYNAMIC_METADATA_H */
diff --git a/libavutil/version.h b/libavutil/version.h
index 900b798971..7635672985 100644
--- a/libavutil/version.h
+++ b/libavutil/version.h
@@ -79,7 +79,7 @@ 
  */
 
 #define LIBAVUTIL_VERSION_MAJOR  58
-#define LIBAVUTIL_VERSION_MINOR   3
+#define LIBAVUTIL_VERSION_MINOR   4
 #define LIBAVUTIL_VERSION_MICRO 100
 
 #define LIBAVUTIL_VERSION_INT   AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \