/*
* This file is part of libplacebo.
*
* libplacebo is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* libplacebo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with libplacebo. If not, see .
*/
#ifndef LIBPLACEBO_LIBAV_H_
#error This header should be included as part of
#elif defined(__cplusplus)
#error This header cannot be included from C++ define PL_LIBAV_IMPLEMENTATION appropriately
#else
#include
#include
#include
#include
#include
#include
#include
#include
#include
// Try importing dynamically if it wasn't already
#if !defined(VK_API_VERSION_1_2) && defined(__has_include)
# if __has_include()
# include
# endif
#endif
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(58, 11, 100) && \
defined(PL_HAVE_VULKAN) && defined(VK_API_VERSION_1_2) && \
VK_HEADER_VERSION >= 175
# define PL_HAVE_LAV_VULKAN
# include
# include
#endif
PL_LIBAV_API enum pl_color_system pl_system_from_av(enum AVColorSpace spc)
{
switch (spc) {
case AVCOL_SPC_RGB: return PL_COLOR_SYSTEM_RGB;
case AVCOL_SPC_BT709: return PL_COLOR_SYSTEM_BT_709;
case AVCOL_SPC_UNSPECIFIED: return PL_COLOR_SYSTEM_UNKNOWN;
case AVCOL_SPC_RESERVED: return PL_COLOR_SYSTEM_UNKNOWN;
case AVCOL_SPC_FCC: return PL_COLOR_SYSTEM_UNKNOWN; // missing
case AVCOL_SPC_BT470BG: return PL_COLOR_SYSTEM_BT_601;
case AVCOL_SPC_SMPTE170M: return PL_COLOR_SYSTEM_BT_601;
case AVCOL_SPC_SMPTE240M: return PL_COLOR_SYSTEM_SMPTE_240M;
case AVCOL_SPC_YCGCO: return PL_COLOR_SYSTEM_YCGCO;
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(59, 13, 100)
case AVCOL_SPC_IPT_C2: return PL_COLOR_SYSTEM_UNKNOWN; // missing
case AVCOL_SPC_YCGCO_RE: return PL_COLOR_SYSTEM_YCGCO_RE;
case AVCOL_SPC_YCGCO_RO: return PL_COLOR_SYSTEM_YCGCO_RO;
#endif
case AVCOL_SPC_BT2020_NCL: return PL_COLOR_SYSTEM_BT_2020_NC;
case AVCOL_SPC_BT2020_CL: return PL_COLOR_SYSTEM_BT_2020_C;
case AVCOL_SPC_SMPTE2085: return PL_COLOR_SYSTEM_UNKNOWN; // missing
case AVCOL_SPC_CHROMA_DERIVED_NCL: return PL_COLOR_SYSTEM_UNKNOWN; // missing
case AVCOL_SPC_CHROMA_DERIVED_CL: return PL_COLOR_SYSTEM_UNKNOWN; // missing
// Note: this colorspace is confused between PQ and HLG, which libav*
// requires inferring from other sources, but libplacebo makes explicit.
// Default to PQ as it's the more common scenario.
case AVCOL_SPC_ICTCP: return PL_COLOR_SYSTEM_BT_2100_PQ;
case AVCOL_SPC_NB: return PL_COLOR_SYSTEM_UNKNOWN;
}
return PL_COLOR_SYSTEM_UNKNOWN;
}
PL_LIBAV_API enum AVColorSpace pl_system_to_av(enum pl_color_system sys)
{
switch (sys) {
case PL_COLOR_SYSTEM_UNKNOWN: return AVCOL_SPC_UNSPECIFIED;
case PL_COLOR_SYSTEM_BT_601: return AVCOL_SPC_SMPTE170M;
case PL_COLOR_SYSTEM_BT_709: return AVCOL_SPC_BT709;
case PL_COLOR_SYSTEM_SMPTE_240M: return AVCOL_SPC_SMPTE240M;
case PL_COLOR_SYSTEM_BT_2020_NC: return AVCOL_SPC_BT2020_NCL;
case PL_COLOR_SYSTEM_BT_2020_C: return AVCOL_SPC_BT2020_CL;
case PL_COLOR_SYSTEM_BT_2100_PQ: return AVCOL_SPC_ICTCP;
case PL_COLOR_SYSTEM_BT_2100_HLG: return AVCOL_SPC_ICTCP;
case PL_COLOR_SYSTEM_DOLBYVISION: return AVCOL_SPC_UNSPECIFIED; // missing
case PL_COLOR_SYSTEM_YCGCO: return AVCOL_SPC_YCGCO;
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(59, 13, 100)
case PL_COLOR_SYSTEM_YCGCO_RE: return AVCOL_SPC_YCGCO_RE;
case PL_COLOR_SYSTEM_YCGCO_RO: return AVCOL_SPC_YCGCO_RO;
#else
case PL_COLOR_SYSTEM_YCGCO_RE: return AVCOL_SPC_UNSPECIFIED;
case PL_COLOR_SYSTEM_YCGCO_RO: return AVCOL_SPC_UNSPECIFIED;
#endif
case PL_COLOR_SYSTEM_RGB: return AVCOL_SPC_RGB;
case PL_COLOR_SYSTEM_XYZ: return AVCOL_SPC_UNSPECIFIED; // handled differently
case PL_COLOR_SYSTEM_COUNT: return AVCOL_SPC_UNSPECIFIED;
}
return AVCOL_SPC_UNSPECIFIED;
}
PL_LIBAV_API enum pl_color_levels pl_levels_from_av(enum AVColorRange range)
{
switch (range) {
case AVCOL_RANGE_UNSPECIFIED: return PL_COLOR_LEVELS_UNKNOWN;
case AVCOL_RANGE_MPEG: return PL_COLOR_LEVELS_LIMITED;
case AVCOL_RANGE_JPEG: return PL_COLOR_LEVELS_FULL;
case AVCOL_RANGE_NB: return PL_COLOR_LEVELS_UNKNOWN;
}
return PL_COLOR_LEVELS_UNKNOWN;
}
PL_LIBAV_API enum AVColorRange pl_levels_to_av(enum pl_color_levels levels)
{
switch (levels) {
case PL_COLOR_LEVELS_UNKNOWN: return AVCOL_RANGE_UNSPECIFIED;
case PL_COLOR_LEVELS_LIMITED: return AVCOL_RANGE_MPEG;
case PL_COLOR_LEVELS_FULL: return AVCOL_RANGE_JPEG;
case PL_COLOR_LEVELS_COUNT: return AVCOL_RANGE_UNSPECIFIED;
}
return AVCOL_RANGE_UNSPECIFIED;
}
PL_LIBAV_API enum pl_color_primaries pl_primaries_from_av(enum AVColorPrimaries prim)
{
switch (prim) {
case AVCOL_PRI_RESERVED0: return PL_COLOR_PRIM_UNKNOWN;
case AVCOL_PRI_BT709: return PL_COLOR_PRIM_BT_709;
case AVCOL_PRI_UNSPECIFIED: return PL_COLOR_PRIM_UNKNOWN;
case AVCOL_PRI_RESERVED: return PL_COLOR_PRIM_UNKNOWN;
case AVCOL_PRI_BT470M: return PL_COLOR_PRIM_BT_470M;
case AVCOL_PRI_BT470BG: return PL_COLOR_PRIM_BT_601_625;
case AVCOL_PRI_SMPTE170M: return PL_COLOR_PRIM_BT_601_525;
case AVCOL_PRI_SMPTE240M: return PL_COLOR_PRIM_BT_601_525;
case AVCOL_PRI_FILM: return PL_COLOR_PRIM_FILM_C;
case AVCOL_PRI_BT2020: return PL_COLOR_PRIM_BT_2020;
case AVCOL_PRI_SMPTE428: return PL_COLOR_PRIM_CIE_1931;
case AVCOL_PRI_SMPTE431: return PL_COLOR_PRIM_DCI_P3;
case AVCOL_PRI_SMPTE432: return PL_COLOR_PRIM_DISPLAY_P3;
case AVCOL_PRI_JEDEC_P22: return PL_COLOR_PRIM_EBU_3213;
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(60, 16, 100)
case AVCOL_PRI_V_GAMUT: return PL_COLOR_PRIM_V_GAMUT;
case AVCOL_PRI_EXT_NB: return PL_COLOR_PRIM_UNKNOWN;
#endif
case AVCOL_PRI_NB: return PL_COLOR_PRIM_UNKNOWN;
}
return PL_COLOR_PRIM_UNKNOWN;
}
PL_LIBAV_API enum AVColorPrimaries pl_primaries_to_av(enum pl_color_primaries prim)
{
switch (prim) {
case PL_COLOR_PRIM_UNKNOWN: return AVCOL_PRI_UNSPECIFIED;
case PL_COLOR_PRIM_BT_601_525: return AVCOL_PRI_SMPTE170M;
case PL_COLOR_PRIM_BT_601_625: return AVCOL_PRI_BT470BG;
case PL_COLOR_PRIM_BT_709: return AVCOL_PRI_BT709;
case PL_COLOR_PRIM_BT_470M: return AVCOL_PRI_BT470M;
case PL_COLOR_PRIM_EBU_3213: return AVCOL_PRI_JEDEC_P22;
case PL_COLOR_PRIM_BT_2020: return AVCOL_PRI_BT2020;
case PL_COLOR_PRIM_APPLE: return AVCOL_PRI_UNSPECIFIED; // missing
case PL_COLOR_PRIM_ADOBE: return AVCOL_PRI_UNSPECIFIED; // missing
case PL_COLOR_PRIM_PRO_PHOTO: return AVCOL_PRI_UNSPECIFIED; // missing
case PL_COLOR_PRIM_CIE_1931: return AVCOL_PRI_SMPTE428;
case PL_COLOR_PRIM_DCI_P3: return AVCOL_PRI_SMPTE431;
case PL_COLOR_PRIM_DISPLAY_P3: return AVCOL_PRI_SMPTE432;
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(60, 16, 100)
case PL_COLOR_PRIM_V_GAMUT: return AVCOL_PRI_V_GAMUT;
#else
case PL_COLOR_PRIM_V_GAMUT: return AVCOL_PRI_UNSPECIFIED; // missing
#endif
case PL_COLOR_PRIM_S_GAMUT: return AVCOL_PRI_UNSPECIFIED; // missing
case PL_COLOR_PRIM_FILM_C: return AVCOL_PRI_FILM;
case PL_COLOR_PRIM_ACES_AP0: return AVCOL_PRI_UNSPECIFIED; // missing
case PL_COLOR_PRIM_ACES_AP1: return AVCOL_PRI_UNSPECIFIED; // missing
case PL_COLOR_PRIM_COUNT: return AVCOL_PRI_UNSPECIFIED;
}
return AVCOL_PRI_UNSPECIFIED;
}
PL_LIBAV_API enum pl_color_transfer pl_transfer_from_av(enum AVColorTransferCharacteristic trc)
{
switch (trc) {
case AVCOL_TRC_RESERVED0: return PL_COLOR_TRC_UNKNOWN;
case AVCOL_TRC_BT709: return PL_COLOR_TRC_BT_1886; // EOTF != OETF
case AVCOL_TRC_UNSPECIFIED: return PL_COLOR_TRC_UNKNOWN;
case AVCOL_TRC_RESERVED: return PL_COLOR_TRC_UNKNOWN;
case AVCOL_TRC_GAMMA22: return PL_COLOR_TRC_GAMMA22;
case AVCOL_TRC_GAMMA28: return PL_COLOR_TRC_GAMMA28;
case AVCOL_TRC_SMPTE170M: return PL_COLOR_TRC_BT_1886; // EOTF != OETF
case AVCOL_TRC_SMPTE240M: return PL_COLOR_TRC_BT_1886; // EOTF != OETF
case AVCOL_TRC_LINEAR: return PL_COLOR_TRC_LINEAR;
case AVCOL_TRC_LOG: return PL_COLOR_TRC_UNKNOWN; // missing
case AVCOL_TRC_LOG_SQRT: return PL_COLOR_TRC_UNKNOWN; // missing
case AVCOL_TRC_IEC61966_2_4: return PL_COLOR_TRC_BT_1886; // EOTF != OETF
case AVCOL_TRC_BT1361_ECG: return PL_COLOR_TRC_BT_1886; // ETOF != OETF
case AVCOL_TRC_IEC61966_2_1: return PL_COLOR_TRC_SRGB;
case AVCOL_TRC_BT2020_10: return PL_COLOR_TRC_BT_1886; // EOTF != OETF
case AVCOL_TRC_BT2020_12: return PL_COLOR_TRC_BT_1886; // EOTF != OETF
case AVCOL_TRC_SMPTE2084: return PL_COLOR_TRC_PQ;
case AVCOL_TRC_SMPTE428: return PL_COLOR_TRC_ST428;
case AVCOL_TRC_ARIB_STD_B67: return PL_COLOR_TRC_HLG;
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(60, 16, 100)
case AVCOL_TRC_V_LOG: return PL_COLOR_TRC_V_LOG;
case AVCOL_TRC_EXT_NB: return PL_COLOR_TRC_UNKNOWN;
#endif
case AVCOL_TRC_NB: return PL_COLOR_TRC_UNKNOWN;
}
return PL_COLOR_TRC_UNKNOWN;
}
PL_LIBAV_API enum AVColorTransferCharacteristic pl_transfer_to_av(enum pl_color_transfer trc)
{
switch (trc) {
case PL_COLOR_TRC_UNKNOWN: return AVCOL_TRC_UNSPECIFIED;
case PL_COLOR_TRC_BT_1886: return AVCOL_TRC_BT709; // EOTF != OETF
case PL_COLOR_TRC_SRGB: return AVCOL_TRC_IEC61966_2_1;
case PL_COLOR_TRC_LINEAR: return AVCOL_TRC_LINEAR;
case PL_COLOR_TRC_GAMMA18: return AVCOL_TRC_UNSPECIFIED; // missing
case PL_COLOR_TRC_GAMMA20: return AVCOL_TRC_UNSPECIFIED; // missing
case PL_COLOR_TRC_GAMMA22: return AVCOL_TRC_GAMMA22;
case PL_COLOR_TRC_GAMMA24: return AVCOL_TRC_UNSPECIFIED; // missing
case PL_COLOR_TRC_GAMMA26: return AVCOL_TRC_UNSPECIFIED; // missing
case PL_COLOR_TRC_GAMMA28: return AVCOL_TRC_GAMMA28;
case PL_COLOR_TRC_ST428: return AVCOL_TRC_SMPTE428;
case PL_COLOR_TRC_PRO_PHOTO: return AVCOL_TRC_UNSPECIFIED; // missing
case PL_COLOR_TRC_PQ: return AVCOL_TRC_SMPTE2084;
case PL_COLOR_TRC_HLG: return AVCOL_TRC_ARIB_STD_B67;
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(60, 16, 100)
case PL_COLOR_TRC_V_LOG: return AVCOL_TRC_V_LOG;
#else
case PL_COLOR_TRC_V_LOG: return AVCOL_TRC_UNSPECIFIED; // missing
#endif
case PL_COLOR_TRC_S_LOG1: return AVCOL_TRC_UNSPECIFIED; // missing
case PL_COLOR_TRC_S_LOG2: return AVCOL_TRC_UNSPECIFIED; // missing
case PL_COLOR_TRC_COUNT: return AVCOL_TRC_UNSPECIFIED;
}
return AVCOL_TRC_UNSPECIFIED;
}
PL_LIBAV_API enum pl_chroma_location pl_chroma_from_av(enum AVChromaLocation loc)
{
switch (loc) {
case AVCHROMA_LOC_UNSPECIFIED: return PL_CHROMA_UNKNOWN;
case AVCHROMA_LOC_LEFT: return PL_CHROMA_LEFT;
case AVCHROMA_LOC_CENTER: return PL_CHROMA_CENTER;
case AVCHROMA_LOC_TOPLEFT: return PL_CHROMA_TOP_LEFT;
case AVCHROMA_LOC_TOP: return PL_CHROMA_TOP_CENTER;
case AVCHROMA_LOC_BOTTOMLEFT: return PL_CHROMA_BOTTOM_LEFT;
case AVCHROMA_LOC_BOTTOM: return PL_CHROMA_BOTTOM_CENTER;
case AVCHROMA_LOC_NB: return PL_CHROMA_UNKNOWN;
}
return PL_CHROMA_UNKNOWN;
}
PL_LIBAV_API enum AVChromaLocation pl_chroma_to_av(enum pl_chroma_location loc)
{
switch (loc) {
case PL_CHROMA_UNKNOWN: return AVCHROMA_LOC_UNSPECIFIED;
case PL_CHROMA_LEFT: return AVCHROMA_LOC_LEFT;
case PL_CHROMA_CENTER: return AVCHROMA_LOC_CENTER;
case PL_CHROMA_TOP_LEFT: return AVCHROMA_LOC_TOPLEFT;
case PL_CHROMA_TOP_CENTER: return AVCHROMA_LOC_TOP;
case PL_CHROMA_BOTTOM_LEFT: return AVCHROMA_LOC_BOTTOMLEFT;
case PL_CHROMA_BOTTOM_CENTER: return AVCHROMA_LOC_BOTTOM;
case PL_CHROMA_COUNT: return AVCHROMA_LOC_UNSPECIFIED;
}
return AVCHROMA_LOC_UNSPECIFIED;
}
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(60, 11, 100)
PL_LIBAV_API enum pl_alpha_mode pl_alpha_from_av(enum AVAlphaMode mode)
{
switch (mode) {
case AVALPHA_MODE_UNSPECIFIED: return PL_ALPHA_UNKNOWN;
case AVALPHA_MODE_STRAIGHT: return PL_ALPHA_INDEPENDENT;
case AVALPHA_MODE_PREMULTIPLIED: return PL_ALPHA_PREMULTIPLIED;
case AVALPHA_MODE_NB: return PL_ALPHA_UNKNOWN;
}
return PL_ALPHA_UNKNOWN;
}
PL_LIBAV_API enum AVAlphaMode pl_alpha_to_av(enum pl_alpha_mode mode)
{
switch (mode) {
case PL_ALPHA_NONE: return AVALPHA_MODE_UNSPECIFIED; // missing
case PL_ALPHA_UNKNOWN: return AVALPHA_MODE_UNSPECIFIED;
case PL_ALPHA_INDEPENDENT: return AVALPHA_MODE_STRAIGHT;
case PL_ALPHA_PREMULTIPLIED: return AVALPHA_MODE_PREMULTIPLIED;
case PL_ALPHA_MODE_COUNT: return AVALPHA_MODE_UNSPECIFIED;
}
return AVALPHA_MODE_UNSPECIFIED;
}
#endif
#ifdef PL_HAVE_LAV_HDR
PL_LIBAV_API void pl_map_hdr_metadata(struct pl_hdr_metadata *out,
const struct pl_av_hdr_metadata *data)
{
if (data->mdm) {
if (data->mdm->has_luminance) {
out->max_luma = av_q2d(data->mdm->max_luminance);
out->min_luma = av_q2d(data->mdm->min_luminance);
if (out->max_luma < 5.0 || out->min_luma >= out->max_luma)
out->max_luma = out->min_luma = 0; /* sanity */
}
if (data->mdm->has_primaries) {
out->prim = (struct pl_raw_primaries) {
.red.x = av_q2d(data->mdm->display_primaries[0][0]),
.red.y = av_q2d(data->mdm->display_primaries[0][1]),
.green.x = av_q2d(data->mdm->display_primaries[1][0]),
.green.y = av_q2d(data->mdm->display_primaries[1][1]),
.blue.x = av_q2d(data->mdm->display_primaries[2][0]),
.blue.y = av_q2d(data->mdm->display_primaries[2][1]),
.white.x = av_q2d(data->mdm->white_point[0]),
.white.y = av_q2d(data->mdm->white_point[1]),
};
}
}
if (data->clm) {
out->max_cll = data->clm->MaxCLL;
out->max_fall = data->clm->MaxFALL;
}
if (data->dhp && data->dhp->application_version < 2) {
float hist_max = 0;
const AVHDRPlusColorTransformParams *pars = &data->dhp->params[0];
assert(data->dhp->num_windows > 0);
out->scene_max[0] = 10000 * av_q2d(pars->maxscl[0]);
out->scene_max[1] = 10000 * av_q2d(pars->maxscl[1]);
out->scene_max[2] = 10000 * av_q2d(pars->maxscl[2]);
out->scene_avg = 10000 * av_q2d(pars->average_maxrgb);
// Calculate largest value from histogram to use as fallback for clips
// with missing MaxSCL information. Note that this may end up picking
// the "reserved" value at the 5% percentile, which in practice appears
// to track the brightest pixel in the scene.
for (int i = 0; i < pars->num_distribution_maxrgb_percentiles; i++) {
float hist_val = av_q2d(pars->distribution_maxrgb[i].percentile);
if (hist_val > hist_max)
hist_max = hist_val;
}
hist_max *= 10000;
if (!out->scene_max[0])
out->scene_max[0] = hist_max;
if (!out->scene_max[1])
out->scene_max[1] = hist_max;
if (!out->scene_max[2])
out->scene_max[2] = hist_max;
if (pars->tone_mapping_flag == 1) {
out->ootf.target_luma = av_q2d(data->dhp->targeted_system_display_maximum_luminance);
out->ootf.knee_x = av_q2d(pars->knee_point_x);
out->ootf.knee_y = av_q2d(pars->knee_point_y);
assert(pars->num_bezier_curve_anchors < 16);
for (int i = 0; i < pars->num_bezier_curve_anchors; i++)
out->ootf.anchors[i] = av_q2d(pars->bezier_curve_anchors[i]);
out->ootf.num_anchors = pars->num_bezier_curve_anchors;
}
}
}
#endif // PL_HAVE_LAV_HDR
static inline void *pl_get_side_data_raw(const AVFrame *frame,
enum AVFrameSideDataType type)
{
const AVFrameSideData *sd = av_frame_get_side_data(frame, type);
return sd ? (void *) sd->data : NULL;
}
PL_LIBAV_API void pl_color_space_from_avframe(struct pl_color_space *out_csp,
const AVFrame *frame)
{
*out_csp = (struct pl_color_space) {
.primaries = pl_primaries_from_av(frame->color_primaries),
.transfer = pl_transfer_from_av(frame->color_trc),
};
#ifdef PL_HAVE_LAV_HDR
pl_map_hdr_metadata(&out_csp->hdr, &(struct pl_av_hdr_metadata) {
.mdm = pl_get_side_data_raw(frame, AV_FRAME_DATA_MASTERING_DISPLAY_METADATA),
.clm = pl_get_side_data_raw(frame, AV_FRAME_DATA_CONTENT_LIGHT_LEVEL),
.dhp = pl_get_side_data_raw(frame, AV_FRAME_DATA_DYNAMIC_HDR_PLUS),
});
#endif
}
PL_LIBAV_API enum pl_field pl_field_from_avframe(const AVFrame *frame)
{
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(58, 7, 100)
if (!frame || !(frame->flags & AV_FRAME_FLAG_INTERLACED))
return PL_FIELD_NONE;
return (frame->flags & AV_FRAME_FLAG_TOP_FIELD_FIRST)
? PL_FIELD_TOP : PL_FIELD_BOTTOM;
#else
if (!frame || !frame->interlaced_frame)
return PL_FIELD_NONE;
return frame->top_field_first ? PL_FIELD_TOP : PL_FIELD_BOTTOM;
#endif
}
#ifdef PL_HAVE_LAV_FILM_GRAIN
PL_LIBAV_API void pl_film_grain_from_av(struct pl_film_grain_data *out_data,
const AVFilmGrainParams *fgp)
{
out_data->seed = fgp->seed;
switch (fgp->type) {
case AV_FILM_GRAIN_PARAMS_NONE: break;
case AV_FILM_GRAIN_PARAMS_AV1: {
const AVFilmGrainAOMParams *src = &fgp->codec.aom;
struct pl_av1_grain_data *dst = &out_data->params.av1;
out_data->type = PL_FILM_GRAIN_AV1;
*dst = (struct pl_av1_grain_data) {
.num_points_y = src->num_y_points,
.chroma_scaling_from_luma = src->chroma_scaling_from_luma,
.num_points_uv = { src->num_uv_points[0], src->num_uv_points[1] },
.scaling_shift = src->scaling_shift,
.ar_coeff_lag = src->ar_coeff_lag,
.ar_coeff_shift = src->ar_coeff_shift,
.grain_scale_shift = src->grain_scale_shift,
.uv_mult = { src->uv_mult[0], src->uv_mult[1] },
.uv_mult_luma = { src->uv_mult_luma[0], src->uv_mult_luma[1] },
.uv_offset = { src->uv_offset[0], src->uv_offset[1] },
.overlap = src->overlap_flag,
};
assert(sizeof(dst->ar_coeffs_uv) == sizeof(src->ar_coeffs_uv));
memcpy(dst->points_y, src->y_points, sizeof(dst->points_y));
memcpy(dst->points_uv, src->uv_points, sizeof(dst->points_uv));
memcpy(dst->ar_coeffs_y, src->ar_coeffs_y, sizeof(dst->ar_coeffs_y));
memcpy(dst->ar_coeffs_uv, src->ar_coeffs_uv, sizeof(dst->ar_coeffs_uv));
break;
}
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 2, 100)
case AV_FILM_GRAIN_PARAMS_H274: {
const AVFilmGrainH274Params *src = &fgp->codec.h274;
struct pl_h274_grain_data *dst = &out_data->params.h274;
out_data->type = PL_FILM_GRAIN_H274;
*dst = (struct pl_h274_grain_data) {
.model_id = src->model_id,
.blending_mode_id = src->blending_mode_id,
.log2_scale_factor = src->log2_scale_factor,
.component_model_present = {
src->component_model_present[0],
src->component_model_present[1],
src->component_model_present[2],
},
.intensity_interval_lower_bound = {
src->intensity_interval_lower_bound[0],
src->intensity_interval_lower_bound[1],
src->intensity_interval_lower_bound[2],
},
.intensity_interval_upper_bound = {
src->intensity_interval_upper_bound[0],
src->intensity_interval_upper_bound[1],
src->intensity_interval_upper_bound[2],
},
.comp_model_value = {
src->comp_model_value[0],
src->comp_model_value[1],
src->comp_model_value[2],
},
};
memcpy(dst->num_intensity_intervals, src->num_intensity_intervals,
sizeof(dst->num_intensity_intervals));
memcpy(dst->num_model_values, src->num_model_values,
sizeof(dst->num_model_values));
break;
}
#endif
}
}
#endif // PL_HAVE_LAV_FILM_GRAIN
static inline int pl_plane_data_num_comps(const struct pl_plane_data *data)
{
for (int i = 0; i < 4; i++) {
if (data->component_size[i] == 0)
return i;
}
return 4;
}
PL_LIBAV_API int pl_plane_data_from_pixfmt(struct pl_plane_data out_data[4],
struct pl_bit_encoding *out_bits,
enum AVPixelFormat pix_fmt)
{
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pix_fmt);
int planes = av_pix_fmt_count_planes(pix_fmt);
struct pl_plane_data aligned_data[4];
struct pl_bit_encoding bits;
bool first;
if (!desc || planes < 0) // e.g. AV_PIX_FMT_NONE
return 0;
if (desc->flags & AV_PIX_FMT_FLAG_BITSTREAM) {
// Bitstream formats will most likely never be supported
return 0;
}
if (desc->flags & AV_PIX_FMT_FLAG_PAL) {
// Palette formats are (currently) not supported
return 0;
}
if (desc->flags & AV_PIX_FMT_FLAG_BAYER) {
// Bayer format don't have valid `desc->offset` values, so we can't
// use `pl_plane_data_from_mask` on them.
return 0;
}
if (desc->nb_components == 0 || desc->nb_components > 4) {
// Bogus components, possibly fake/virtual/hwaccel format?
return 0;
}
if (planes > 4)
return 0; // This shouldn't ever happen
// Fill in the details for each plane
for (int p = 0; p < planes; p++) {
struct pl_plane_data *data = &out_data[p];
int size[4] = {0};
int shift[4] = {0};
data->swapped = desc->flags & AV_PIX_FMT_FLAG_BE;
data->type = (desc->flags & AV_PIX_FMT_FLAG_FLOAT)
? PL_FMT_FLOAT
: PL_FMT_UNORM;
data->pixel_stride = 0;
for (int c = 0; c < desc->nb_components; c++) {
const AVComponentDescriptor *comp = &desc->comp[c];
if (comp->plane != p)
continue;
if (data->swapped && comp->shift) {
// We cannot naively handle packed big endian formats because
// swapping the words also swaps the component order, so just
// exit out as a stupid safety measure
return 0;
}
size[c] = comp->depth;
shift[c] = comp->shift + comp->offset * 8;
if (data->pixel_stride && (int) data->pixel_stride != comp->step) {
// Pixel format contains components with different pixel stride
// (e.g. packed YUYV), this is currently not supported
return 0;
}
data->pixel_stride = comp->step;
}
pl_plane_data_from_comps(data, size, shift);
}
if (!out_bits)
return planes;
// Attempt aligning all of the planes for optimum compatibility
first = true;
for (int p = 0; p < planes; p++) {
aligned_data[p] = out_data[p];
if (!pl_plane_data_align(&aligned_data[p], &bits))
goto misaligned;
if (first) {
*out_bits = bits;
first = false;
} else {
if (!pl_bit_encoding_equal(&bits, out_bits))
goto misaligned;
}
}
// Overwrite the planes by their aligned versions
for (int p = 0; p < planes; p++)
out_data[p] = aligned_data[p];
return planes;
misaligned:
*out_bits = (struct pl_bit_encoding) {0};
return planes;
}
PL_LIBAV_API bool pl_test_pixfmt_caps(pl_gpu gpu, enum AVPixelFormat pixfmt,
enum pl_fmt_caps caps)
{
struct pl_bit_encoding bits;
struct pl_plane_data data[4];
pl_fmt fmt;
int planes;
switch (pixfmt) {
case AV_PIX_FMT_DRM_PRIME:
case AV_PIX_FMT_VAAPI:
return gpu->import_caps.tex & PL_HANDLE_DMA_BUF;
#ifdef PL_HAVE_LAV_VULKAN
case AV_PIX_FMT_VULKAN:
return pl_vulkan_get(gpu);
#endif
default: break;
}
planes = pl_plane_data_from_pixfmt(data, &bits, pixfmt);
if (!planes)
return false;
for (int i = 0; i < planes; i++) {
data[i].row_stride = 0;
fmt = pl_plane_find_fmt(gpu, NULL, &data[i]);
if (!fmt || (fmt->caps & caps) != caps)
return false;
}
return true;
}
PL_LIBAV_API bool pl_test_pixfmt(pl_gpu gpu, enum AVPixelFormat pixfmt)
{
return pl_test_pixfmt_caps(gpu, pixfmt, 0);
}
PL_LIBAV_API void pl_avframe_set_color(AVFrame *frame, struct pl_color_space csp)
{
const AVFrameSideData *sd;
(void) sd;
frame->color_primaries = pl_primaries_to_av(csp.primaries);
frame->color_trc = pl_transfer_to_av(csp.transfer);
#ifdef PL_HAVE_LAV_HDR
if (csp.hdr.max_cll) {
sd = av_frame_get_side_data(frame, AV_FRAME_DATA_CONTENT_LIGHT_LEVEL);
if (!sd) {
sd = av_frame_new_side_data(frame, AV_FRAME_DATA_CONTENT_LIGHT_LEVEL,
sizeof(AVContentLightMetadata));
}
if (sd) {
AVContentLightMetadata *clm = (AVContentLightMetadata *) sd->data;
*clm = (AVContentLightMetadata) {
.MaxCLL = csp.hdr.max_cll,
.MaxFALL = csp.hdr.max_fall,
};
}
}
if (csp.hdr.max_luma || csp.hdr.prim.red.x) {
sd = av_frame_get_side_data(frame, AV_FRAME_DATA_MASTERING_DISPLAY_METADATA);
if (!sd) {
sd = av_frame_new_side_data(frame, AV_FRAME_DATA_MASTERING_DISPLAY_METADATA,
sizeof(AVMasteringDisplayMetadata));
}
if (sd) {
AVMasteringDisplayMetadata *mdm = (AVMasteringDisplayMetadata *) sd->data;
*mdm = (AVMasteringDisplayMetadata) {
.max_luminance = av_d2q(csp.hdr.max_luma, 1000000),
.min_luminance = av_d2q(csp.hdr.min_luma, 1000000),
.has_luminance = !!csp.hdr.max_luma,
.display_primaries = {
{
av_d2q(csp.hdr.prim.red.x, 1000000),
av_d2q(csp.hdr.prim.red.y, 1000000),
}, {
av_d2q(csp.hdr.prim.green.x, 1000000),
av_d2q(csp.hdr.prim.green.y, 1000000),
}, {
av_d2q(csp.hdr.prim.blue.x, 1000000),
av_d2q(csp.hdr.prim.blue.y, 1000000),
}
},
.white_point = {
av_d2q(csp.hdr.prim.white.x, 1000000),
av_d2q(csp.hdr.prim.white.y, 1000000),
},
.has_primaries = !!csp.hdr.prim.red.x,
};
}
}
#endif // PL_HAVE_LAV_HDR
}
PL_LIBAV_API void pl_avframe_set_repr(AVFrame *frame, struct pl_color_repr repr)
{
frame->colorspace = pl_system_to_av(repr.sys);
frame->color_range = pl_levels_to_av(repr.levels);
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(60, 11, 100)
frame->alpha_mode = pl_alpha_to_av(repr.alpha);
#endif
// No real way to map repr.bits, the image format already has to match
}
PL_LIBAV_API void pl_avframe_set_profile(AVFrame *frame, struct pl_icc_profile profile)
{
const AVFrameSideData *sd;
av_frame_remove_side_data(frame, AV_FRAME_DATA_ICC_PROFILE);
if (!profile.len)
return;
sd = av_frame_new_side_data(frame, AV_FRAME_DATA_ICC_PROFILE, profile.len);
memcpy(sd->data, profile.data, profile.len);
}
PL_LIBAV_API void pl_frame_from_avframe(struct pl_frame *out,
const AVFrame *frame)
{
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(frame->format);
int planes = av_pix_fmt_count_planes(frame->format);
const AVFrameSideData *sd;
assert(desc);
if (desc->flags & AV_PIX_FMT_FLAG_HWACCEL) {
const AVHWFramesContext *hwfc = (AVHWFramesContext *) frame->hw_frames_ctx->data;
desc = av_pix_fmt_desc_get(hwfc->sw_format);
planes = av_pix_fmt_count_planes(hwfc->sw_format);
}
// This should never fail, and there's nothing really useful we can do in
// this failure case anyway, since this is a `void` function.
assert(planes <= 4);
*out = (struct pl_frame) {
.num_planes = planes,
.crop = {
.x0 = frame->crop_left,
.y0 = frame->crop_top,
.x1 = frame->width - frame->crop_right,
.y1 = frame->height - frame->crop_bottom,
},
.repr = {
.sys = pl_system_from_av(frame->colorspace),
.levels = pl_levels_from_av(frame->color_range),
.alpha = (desc->flags & AV_PIX_FMT_FLAG_ALPHA)
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(60, 11, 100)
? pl_alpha_from_av(frame->alpha_mode)
#else
? PL_ALPHA_INDEPENDENT
#endif
: PL_ALPHA_NONE,
// For sake of simplicity, just use the first component's depth as
// the authoritative color depth for the whole image. Usually, this
// will be overwritten by more specific information when using e.g.
// `pl_map_avframe`, but for the sake of e.g. users wishing to map
// hwaccel frames manually, this is a good default.
.bits.color_depth = desc->comp[0].depth,
},
};
pl_color_space_from_avframe(&out->color, frame);
if (frame->colorspace == AVCOL_SPC_ICTCP &&
frame->color_trc == AVCOL_TRC_ARIB_STD_B67)
{
// libav* makes no distinction between PQ and HLG ICtCp, so we need
// to manually fix it in the case that we have HLG ICtCp data.
out->repr.sys = PL_COLOR_SYSTEM_BT_2100_HLG;
} else if (strncmp(desc->name, "xyz", 3) == 0) {
// libav* handles this as a special case, but doesn't provide an
// explicit flag for it either, so we have to resort to this ugly
// hack...
out->repr.sys = PL_COLOR_SYSTEM_XYZ;
} else if (desc->flags & AV_PIX_FMT_FLAG_RGB) {
out->repr.sys = PL_COLOR_SYSTEM_RGB;
out->repr.levels = PL_COLOR_LEVELS_FULL; // libav* ignores levels for RGB
} else if (!pl_color_system_is_ycbcr_like(out->repr.sys)) {
// libav* likes leaving this as UNKNOWN (or even RGB) for YCbCr frames,
// which confuses libplacebo since we infer UNKNOWN as RGB. To get
// around this, explicitly infer a suitable colorspace.
out->repr.sys = pl_color_system_guess_ycbcr(frame->width, frame->height);
}
if ((sd = av_frame_get_side_data(frame, AV_FRAME_DATA_ICC_PROFILE))) {
out->profile = (struct pl_icc_profile) {
.data = sd->data,
.len = sd->size,
};
// Needed to ensure profile uniqueness
pl_icc_profile_compute_signature(&out->profile);
}
if ((sd = av_frame_get_side_data(frame, AV_FRAME_DATA_DISPLAYMATRIX))) {
double rot = av_display_rotation_get((const int32_t *) sd->data);
out->rotation = pl_rotation_normalize(4.5 - rot / 90.0);
}
#ifdef PL_HAVE_LAV_FILM_GRAIN
if ((sd = av_frame_get_side_data(frame, AV_FRAME_DATA_FILM_GRAIN_PARAMS)))
pl_film_grain_from_av(&out->film_grain, (AVFilmGrainParams *) sd->data);
#endif // HAVE_LAV_FILM_GRAIN
for (int p = 0; p < out->num_planes; p++) {
struct pl_plane *plane = &out->planes[p];
// Fill in the component mapping array
for (int c = 0; c < desc->nb_components; c++) {
if (desc->comp[c].plane == p)
plane->component_mapping[plane->components++] = c;
}
// Clear the superfluous components
for (int c = plane->components; c < 4; c++)
plane->component_mapping[c] = PL_CHANNEL_NONE;
}
// Only set the chroma location for definitely subsampled images, makes no
// sense otherwise
if (desc->log2_chroma_w || desc->log2_chroma_h) {
enum pl_chroma_location loc = pl_chroma_from_av(frame->chroma_location);
pl_frame_set_chroma_location(out, loc);
}
}
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(60, 15, 100)
PL_LIBAV_API const uint8_t *pl_av_stream_get_side_data(const AVStream *st,
enum AVPacketSideDataType type)
{
const AVPacketSideData *sd;
sd = av_packet_side_data_get(st->codecpar->coded_side_data,
st->codecpar->nb_coded_side_data,
type);
return sd ? sd->data : NULL;
}
#else
# define pl_av_stream_get_side_data(st, type) av_stream_get_side_data(st, type, NULL)
#endif
PL_LIBAV_API void pl_frame_copy_stream_props(struct pl_frame *out,
const AVStream *stream)
{
const uint8_t *sd;
if ((sd = pl_av_stream_get_side_data(stream, AV_PKT_DATA_DISPLAYMATRIX))) {
double rot = av_display_rotation_get((const int32_t *) sd);
out->rotation = pl_rotation_normalize(4.5 - rot / 90.0);
}
#ifdef PL_HAVE_LAV_HDR
pl_map_hdr_metadata(&out->color.hdr, &(struct pl_av_hdr_metadata) {
.mdm = (void *) pl_av_stream_get_side_data(stream,
AV_PKT_DATA_MASTERING_DISPLAY_METADATA),
.clm = (void *) pl_av_stream_get_side_data(stream,
AV_PKT_DATA_CONTENT_LIGHT_LEVEL),
# if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 2, 100)
.dhp = (void *) pl_av_stream_get_side_data(stream,
AV_PKT_DATA_DYNAMIC_HDR10_PLUS),
# endif
});
#endif
}
#undef pl_av_stream_get_side_data
#ifdef PL_HAVE_LAV_DOLBY_VISION
PL_LIBAV_API void pl_map_dovi_metadata(struct pl_dovi_metadata *out,
const AVDOVIMetadata *data)
{
const AVDOVIRpuDataHeader *header;
const AVDOVIDataMapping *mapping;
const AVDOVIColorMetadata *color;
if (!data)
return;
header = av_dovi_get_header(data);
mapping = av_dovi_get_mapping(data);
color = av_dovi_get_color(data);
for (int i = 0; i < 3; i++)
out->nonlinear_offset[i] = av_q2d(color->ycc_to_rgb_offset[i]);
for (int i = 0; i < 9; i++) {
float *nonlinear = &out->nonlinear.m[0][0];
float *linear = &out->linear.m[0][0];
nonlinear[i] = av_q2d(color->ycc_to_rgb_matrix[i]);
linear[i] = av_q2d(color->rgb_to_lms_matrix[i]);
}
for (int c = 0; c < 3; c++) {
const AVDOVIReshapingCurve *csrc = &mapping->curves[c];
struct pl_reshape_data *cdst = &out->comp[c];
cdst->num_pivots = csrc->num_pivots;
for (int i = 0; i < csrc->num_pivots; i++) {
const float scale = 1.0f / ((1 << header->bl_bit_depth) - 1);
cdst->pivots[i] = scale * csrc->pivots[i];
}
for (int i = 0; i < csrc->num_pivots - 1; i++) {
const float scale = 1.0f / (1 << header->coef_log2_denom);
cdst->method[i] = csrc->mapping_idc[i];
switch (csrc->mapping_idc[i]) {
case AV_DOVI_MAPPING_POLYNOMIAL:
for (int k = 0; k < 3; k++) {
cdst->poly_coeffs[i][k] = (k <= csrc->poly_order[i])
? scale * csrc->poly_coef[i][k]
: 0.0f;
}
break;
case AV_DOVI_MAPPING_MMR:
cdst->mmr_order[i] = csrc->mmr_order[i];
cdst->mmr_constant[i] = scale * csrc->mmr_constant[i];
for (int j = 0; j < csrc->mmr_order[i]; j++) {
for (int k = 0; k < 7; k++)
cdst->mmr_coeffs[i][j][k] = scale * csrc->mmr_coef[i][j][k];
}
break;
}
}
}
}
PL_LIBAV_API void pl_map_avdovi_metadata(struct pl_color_space *color,
struct pl_color_repr *repr,
struct pl_dovi_metadata *dovi,
const AVDOVIMetadata *metadata)
{
const AVDOVIRpuDataHeader *header;
const AVDOVIColorMetadata *dovi_color;
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(59, 12, 100)
const AVDOVIDmData *dovi_ext;
#endif
if (!color || !repr || !dovi)
return;
header = av_dovi_get_header(metadata);
dovi_color = av_dovi_get_color(metadata);
if (header->disable_residual_flag) {
pl_map_dovi_metadata(dovi, metadata);
repr->dovi = dovi;
repr->sys = PL_COLOR_SYSTEM_DOLBYVISION;
color->primaries = PL_COLOR_PRIM_BT_2020;
color->transfer = PL_COLOR_TRC_PQ;
color->hdr.min_luma =
pl_hdr_rescale(PL_HDR_PQ, PL_HDR_NITS, dovi_color->source_min_pq / 4095.0f);
color->hdr.max_luma =
pl_hdr_rescale(PL_HDR_PQ, PL_HDR_NITS, dovi_color->source_max_pq / 4095.0f);
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(59, 12, 100)
if ((dovi_ext = av_dovi_find_level(metadata, 1))) {
color->hdr.max_pq_y = dovi_ext->l1.max_pq / 4095.0f;
color->hdr.avg_pq_y = dovi_ext->l1.avg_pq / 4095.0f;
}
#endif
}
}
PL_LIBAV_API void pl_frame_map_avdovi_metadata(struct pl_frame *out_frame,
struct pl_dovi_metadata *dovi,
const AVDOVIMetadata *metadata)
{
if (!out_frame)
return;
pl_map_avdovi_metadata(&out_frame->color, &out_frame->repr, dovi, metadata);
}
#endif // PL_HAVE_LAV_DOLBY_VISION
PL_LIBAV_API bool pl_frame_recreate_from_avframe(pl_gpu gpu,
struct pl_frame *out,
pl_tex tex[4],
const AVFrame *frame)
{
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(frame->format);
struct pl_plane_data data[4] = {0};
int planes;
pl_frame_from_avframe(out, frame);
planes = pl_plane_data_from_pixfmt(data, &out->repr.bits, frame->format);
if (!planes)
return false;
for (int p = 0; p < planes; p++) {
bool is_chroma = p == 1 || p == 2; // matches lavu logic
data[p].width = AV_CEIL_RSHIFT(frame->width, is_chroma ? desc->log2_chroma_w : 0);
data[p].height = AV_CEIL_RSHIFT(frame->height, is_chroma ? desc->log2_chroma_h : 0);
if (!pl_recreate_plane(gpu, &out->planes[p], &tex[p], &data[p]))
return false;
}
return true;
}
static void pl_avframe_free_cb(void *priv)
{
AVFrame *frame = priv;
av_frame_free(&frame);
}
#define PL_MAGIC0 0xfb5b3b8b
#define PL_MAGIC1 0xee659f6d
struct pl_avalloc {
uint32_t magic[2];
pl_gpu gpu;
pl_buf buf;
};
// Attached to `pl_frame.user_data` for mapped AVFrames
struct pl_avframe_priv {
AVFrame *avframe;
struct pl_dovi_metadata dovi; // backing storage for per-frame dovi metadata
pl_tex planar; // for planar vulkan textures
};
static void pl_fix_hwframe_sample_depth(struct pl_frame *out)
{
pl_fmt fmt = out->planes[0].texture->params.format;
struct pl_bit_encoding *bits = &out->repr.bits;
bits->sample_depth = fmt->component_depth[0];
}
static bool pl_map_avframe_drm(pl_gpu gpu, struct pl_frame *out,
const AVFrame *frame)
{
const AVHWFramesContext *hwfc = (AVHWFramesContext *) frame->hw_frames_ctx->data;
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(hwfc->sw_format);
const AVDRMFrameDescriptor *drm = (AVDRMFrameDescriptor *) frame->data[0];
assert(frame->format == AV_PIX_FMT_DRM_PRIME);
if (!(gpu->import_caps.tex & PL_HANDLE_DMA_BUF) || !out->num_planes)
return false;
assert(drm->nb_layers >= out->num_planes);
for (int n = 0; n < out->num_planes; n++) {
const AVDRMLayerDescriptor *layer = &drm->layers[n];
const AVDRMPlaneDescriptor *plane = &layer->planes[0];
const AVDRMObjectDescriptor *object = &drm->objects[plane->object_index];
pl_fmt fmt = pl_find_fourcc(gpu, layer->format);
bool is_chroma = n == 1 || n == 2;
if (!fmt || !pl_fmt_has_modifier(fmt, object->format_modifier))
return false;
assert(layer->nb_planes == 1); // we only support planar formats
assert(plane->pitch >= 0); // definitely requires special handling
out->planes[n].texture = pl_tex_create(gpu, pl_tex_params(
.w = AV_CEIL_RSHIFT(frame->width, is_chroma ? desc->log2_chroma_w : 0),
.h = AV_CEIL_RSHIFT(frame->height, is_chroma ? desc->log2_chroma_h : 0),
.format = fmt,
.sampleable = true,
.blit_src = fmt->caps & PL_FMT_CAP_BLITTABLE,
.import_handle = PL_HANDLE_DMA_BUF,
.shared_mem = {
.handle.fd = object->fd,
.size = object->size,
.offset = plane->offset,
.drm_format_mod = object->format_modifier,
.stride_w = plane->pitch,
},
));
if (!out->planes[n].texture)
return false;
}
pl_fix_hwframe_sample_depth(out);
switch (hwfc->sw_format) {
case AV_PIX_FMT_P010: out->repr.bits.bit_shift = 6; break;
default: break;
}
return true;
}
// Derive a DMABUF from any other hwaccel format, and map that instead
static bool pl_map_avframe_derived(pl_gpu gpu, struct pl_frame *out,
const AVFrame *frame)
{
const int flags = AV_HWFRAME_MAP_READ | AV_HWFRAME_MAP_DIRECT;
struct pl_avframe_priv *priv = out->user_data;
AVFrame *derived = av_frame_alloc();
derived->width = frame->width;
derived->height = frame->height;
derived->format = AV_PIX_FMT_DRM_PRIME;
derived->hw_frames_ctx = av_buffer_ref(frame->hw_frames_ctx);
if (av_hwframe_map(derived, frame, flags) < 0)
goto error;
if (av_frame_copy_props(derived, frame) < 0)
goto error;
if (!pl_map_avframe_drm(gpu, out, derived))
goto error;
av_frame_free(&priv->avframe);
priv->avframe = derived;
return true;
error:
av_frame_free(&derived);
return false;
}
#ifdef PL_HAVE_LAV_VULKAN
static bool pl_acquire_avframe(pl_gpu gpu, struct pl_frame *frame)
{
const struct pl_avframe_priv *priv = frame->user_data;
AVHWFramesContext *hwfc = (void *) priv->avframe->hw_frames_ctx->data;
AVVulkanFramesContext *vkfc = hwfc->hwctx;
AVVkFrame *vkf = (AVVkFrame *) priv->avframe->data[0];
vkfc->lock_frame(hwfc, vkf);
for (int n = 0; n < frame->num_planes; n++) {
pl_vulkan_release_ex(gpu, pl_vulkan_release_params(
.tex = priv->planar ? priv->planar : frame->planes[n].texture,
.layout = vkf->layout[n],
.qf = VK_QUEUE_FAMILY_IGNORED,
.semaphore = {
.sem = vkf->sem[n],
.value = vkf->sem_value[n],
},
));
if (priv->planar)
break;
}
return true;
}
static void pl_release_avframe(pl_gpu gpu, struct pl_frame *frame)
{
const struct pl_avframe_priv *priv = frame->user_data;
AVHWFramesContext *hwfc = (void *) priv->avframe->hw_frames_ctx->data;
AVVulkanFramesContext *vkfc = hwfc->hwctx;
AVVkFrame *vkf = (AVVkFrame *) priv->avframe->data[0];
for (int n = 0; n < frame->num_planes; n++) {
int ok = pl_vulkan_hold_ex(gpu, pl_vulkan_hold_params(
.tex = priv->planar ? priv->planar : frame->planes[n].texture,
.out_layout = &vkf->layout[n],
.qf = VK_QUEUE_FAMILY_IGNORED,
.semaphore = {
.sem = vkf->sem[n],
.value = vkf->sem_value[n] + 1,
},
));
vkf->access[n] = 0;
vkf->sem_value[n] += !!ok;
if (priv->planar)
break;
}
vkfc->unlock_frame(hwfc, vkf);
}
static VkFormat map_vk_fmt(const AVVulkanFramesContext *vkfc, VkFormat fmt,
struct pl_bit_encoding *bits)
{
if (!(vkfc->img_flags & VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT))
return fmt;
switch (fmt) {
#define VK_FMT_CASE(SHIFT, OLDFMT, NEWFMT) \
case VK_FORMAT_##OLDFMT: bits->bit_shift = SHIFT; return VK_FORMAT_##NEWFMT
VK_FMT_CASE(6, R10X6_UNORM_PACK16, R16_UNORM);
VK_FMT_CASE(6, R10X6G10X6_UNORM_2PACK16, R16G16_UNORM);
VK_FMT_CASE(6, R10X6G10X6B10X6A10X6_UNORM_4PACK16, R16G16B16A16_UNORM);
VK_FMT_CASE(6, G10X6B10X6G10X6R10X6_422_UNORM_4PACK16, G16B16G16R16_422_UNORM);
VK_FMT_CASE(6, B10X6G10X6R10X6G10X6_422_UNORM_4PACK16, B16G16R16G16_422_UNORM);
VK_FMT_CASE(6, G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16, G16_B16_R16_3PLANE_420_UNORM);
VK_FMT_CASE(6, G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16, G16_B16R16_2PLANE_420_UNORM);
VK_FMT_CASE(6, G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16, G16_B16_R16_3PLANE_422_UNORM);
VK_FMT_CASE(6, G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16, G16_B16R16_2PLANE_422_UNORM);
VK_FMT_CASE(6, G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16, G16_B16_R16_3PLANE_444_UNORM);
VK_FMT_CASE(6, G10X6_B10X6R10X6_2PLANE_444_UNORM_3PACK16, G16_B16R16_2PLANE_444_UNORM);
VK_FMT_CASE(4, R12X4_UNORM_PACK16, R16_UNORM);
VK_FMT_CASE(4, R12X4G12X4_UNORM_2PACK16, R16G16_UNORM);
VK_FMT_CASE(4, R12X4G12X4B12X4A12X4_UNORM_4PACK16, R16G16B16A16_UNORM);
VK_FMT_CASE(4, G12X4B12X4G12X4R12X4_422_UNORM_4PACK16, G16B16G16R16_422_UNORM);
VK_FMT_CASE(4, B12X4G12X4R12X4G12X4_422_UNORM_4PACK16, B16G16R16G16_422_UNORM);
VK_FMT_CASE(4, G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16, G16_B16_R16_3PLANE_420_UNORM);
VK_FMT_CASE(4, G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16, G16_B16R16_2PLANE_420_UNORM);
VK_FMT_CASE(4, G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16, G16_B16_R16_3PLANE_422_UNORM);
VK_FMT_CASE(4, G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16, G16_B16R16_2PLANE_422_UNORM);
VK_FMT_CASE(4, G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16, G16_B16_R16_3PLANE_444_UNORM);
VK_FMT_CASE(4, G12X4_B12X4R12X4_2PLANE_444_UNORM_3PACK16, G16_B16R16_2PLANE_444_UNORM);
default: return fmt;
#undef VK_FMT_CASE
}
}
static bool pl_map_avframe_vulkan(pl_gpu gpu, struct pl_frame *out,
const AVFrame *frame)
{
const AVHWFramesContext *hwfc = (AVHWFramesContext *) frame->hw_frames_ctx->data;
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(hwfc->sw_format);
const AVVulkanFramesContext *vkfc = hwfc->hwctx;
AVVkFrame *vkf = (AVVkFrame *) frame->data[0];
const VkFormat *vk_fmt = vkfc->format;
struct pl_avframe_priv *priv = out->user_data;
pl_vulkan vk = pl_vulkan_get(gpu);
assert(frame->format == AV_PIX_FMT_VULKAN);
priv->planar = NULL;
if (!vk)
return false;
for (int n = 0; n < out->num_planes; n++) {
struct pl_plane *plane = &out->planes[n];
bool chroma = n == 1 || n == 2;
int num_subplanes;
assert(vk_fmt[n]);
plane->texture = pl_vulkan_wrap(gpu, pl_vulkan_wrap_params(
.image = vkf->img[n],
.width = AV_CEIL_RSHIFT(hwfc->width, chroma ? desc->log2_chroma_w : 0),
.height = AV_CEIL_RSHIFT(hwfc->height, chroma ? desc->log2_chroma_h : 0),
.format = map_vk_fmt(vkfc, vk_fmt[n], &out->repr.bits),
.usage = vkfc->usage,
));
if (!plane->texture)
return false;
num_subplanes = plane->texture->params.format->num_planes;
if (num_subplanes) {
assert(num_subplanes == out->num_planes);
priv->planar = plane->texture;
for (int i = 0; i < num_subplanes; i++)
out->planes[i].texture = priv->planar->planes[i];
break;
}
}
out->acquire = pl_acquire_avframe;
out->release = pl_release_avframe;
pl_fix_hwframe_sample_depth(out);
return true;
}
static void pl_unmap_avframe_vulkan(pl_gpu gpu, struct pl_frame *frame)
{
struct pl_avframe_priv *priv = frame->user_data;
if (priv->planar) {
pl_tex_destroy(gpu, &priv->planar);
for (int n = 0; n < frame->num_planes; n++)
frame->planes[n].texture = NULL;
}
}
#endif
PL_LIBAV_API bool pl_map_avframe_ex(pl_gpu gpu, struct pl_frame *out,
const struct pl_avframe_params *params)
{
const AVFrame *frame = params->frame;
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(frame->format);
struct pl_plane_data data[4] = {0};
pl_tex *tex = params->tex;
int planes;
struct pl_avframe_priv *priv = malloc(sizeof(*priv));
if (!priv)
goto error;
pl_frame_from_avframe(out, frame);
priv->avframe = av_frame_clone(frame);
out->user_data = priv;
#ifdef PL_HAVE_LAV_DOLBY_VISION
if (params->map_dovi) {
AVFrameSideData *sd = av_frame_get_side_data(frame, AV_FRAME_DATA_DOVI_METADATA);
if (sd) {
const AVDOVIMetadata *metadata = (AVDOVIMetadata *) sd->data;
const AVDOVIRpuDataHeader *header = av_dovi_get_header(metadata);
// Only automatically map DoVi RPUs that don't require an EL
if (header->disable_residual_flag)
pl_map_avdovi_metadata(&out->color, &out->repr, &priv->dovi, metadata);
}
#ifdef PL_HAVE_LIBDOVI
sd = av_frame_get_side_data(frame, AV_FRAME_DATA_DOVI_RPU_BUFFER);
if (sd)
pl_hdr_metadata_from_dovi_rpu(&out->color.hdr, sd->buf->data, sd->buf->size);
#endif // PL_HAVE_LIBDOVI
}
#endif // PL_HAVE_LAV_DOLBY_VISION
switch (frame->format) {
case AV_PIX_FMT_DRM_PRIME:
if (!pl_map_avframe_drm(gpu, out, frame))
goto error;
return true;
case AV_PIX_FMT_VAAPI:
if (!pl_map_avframe_derived(gpu, out, frame))
goto error;
return true;
#ifdef PL_HAVE_LAV_VULKAN
case AV_PIX_FMT_VULKAN:
if (!pl_map_avframe_vulkan(gpu, out, frame))
goto error;
return true;
#endif
default: break;
}
// Backing textures are required from this point onwards
if (!tex)
goto error;
planes = pl_plane_data_from_pixfmt(data, &out->repr.bits, frame->format);
if (!planes)
goto error;
for (int p = 0; p < planes; p++) {
AVBufferRef *buf = av_frame_get_plane_buffer((AVFrame *) frame, p);
struct pl_avalloc *alloc = buf ? av_buffer_get_opaque(buf) : NULL;
bool is_chroma = p == 1 || p == 2; // matches lavu logic
data[p].width = AV_CEIL_RSHIFT(frame->width, is_chroma ? desc->log2_chroma_w : 0);
data[p].height = AV_CEIL_RSHIFT(frame->height, is_chroma ? desc->log2_chroma_h : 0);
if (frame->linesize[p] < 0) {
data[p].pixels = frame->data[p] + frame->linesize[p] * (data[p].height - 1);
data[p].row_stride = -frame->linesize[p];
out->planes[p].flipped = true;
} else {
data[p].pixels = frame->data[p];
data[p].row_stride = frame->linesize[p];
}
// Probe for frames allocated by pl_get_buffer2
if (alloc && alloc->magic[0] == PL_MAGIC0 && alloc->magic[1] == PL_MAGIC1) {
data[p].buf = alloc->buf;
data[p].buf_offset = (uintptr_t) data[p].pixels - (uintptr_t) alloc->buf->data;
data[p].pixels = NULL;
} else if (gpu->limits.callbacks) {
// Use asynchronous upload if possible
data[p].callback = pl_avframe_free_cb;
data[p].priv = av_frame_clone(frame);
}
if (!pl_upload_plane(gpu, &out->planes[p], &tex[p], &data[p])) {
av_frame_free((AVFrame **) &data[p].priv);
goto error;
}
out->planes[p].texture = tex[p];
}
return true;
error:
pl_unmap_avframe(gpu, out);
return false;
}
// Backwards compatibility with previous versions of this API.
PL_LIBAV_API bool pl_map_avframe(pl_gpu gpu, struct pl_frame *out_frame,
pl_tex tex[4], const AVFrame *avframe)
{
return pl_map_avframe_ex(gpu, out_frame, &(struct pl_avframe_params) {
.frame = avframe,
.tex = tex,
});
}
PL_LIBAV_API void pl_unmap_avframe(pl_gpu gpu, struct pl_frame *frame)
{
struct pl_avframe_priv *priv = frame->user_data;
const AVPixFmtDescriptor *desc;
if (!priv)
goto done;
#ifdef PL_HAVE_LAV_VULKAN
if (priv->avframe->format == AV_PIX_FMT_VULKAN)
pl_unmap_avframe_vulkan(gpu, frame);
#endif
desc = av_pix_fmt_desc_get(priv->avframe->format);
if (desc->flags & AV_PIX_FMT_FLAG_HWACCEL) {
for (int i = 0; i < 4; i++)
pl_tex_destroy(gpu, &frame->planes[i].texture);
}
av_frame_free(&priv->avframe);
free(priv);
done:
memset(frame, 0, sizeof(*frame)); // sanity
}
PL_LIBAV_API AVFrame *pl_get_mapped_avframe(const struct pl_frame *frame)
{
struct pl_avframe_priv *priv = frame->user_data;
return priv->avframe;
}
static void pl_done_cb(void *priv)
{
bool *status = priv;
*status = true;
}
PL_LIBAV_API bool pl_download_avframe(pl_gpu gpu,
const struct pl_frame *frame,
AVFrame *out_frame)
{
bool done[4] = {0};
if (frame->num_planes != av_pix_fmt_count_planes(out_frame->format))
return false;
for (int p = 0; p < frame->num_planes; p++) {
bool ok = pl_tex_download(gpu, pl_tex_transfer_params(
.tex = frame->planes[p].texture,
.row_pitch = out_frame->linesize[p],
.ptr = out_frame->data[p],
// Use synchronous transfer for the last plane
.callback = (p+1) < frame->num_planes ? pl_done_cb : NULL,
.priv = &done[p],
));
if (!ok)
return false;
}
for (int p = 0; p < frame->num_planes - 1; p++) {
while (!done[p])
pl_tex_poll(gpu, frame->planes[p].texture, UINT64_MAX);
}
return true;
}
#define PL_DIV_UP(x, y) (((x) + (y) - 1) / (y))
#define PL_ALIGN(x, align) ((align) ? PL_DIV_UP(x, align) * (align) : (x))
#define PL_MAX(x, y) ((x) > (y) ? (x) : (y))
#define PL_LCM(x, y) ((x) * ((y) / av_gcd(x, y)))
static inline void pl_avalloc_free(void *opaque, uint8_t *data)
{
struct pl_avalloc *alloc = opaque;
assert(alloc->magic[0] == PL_MAGIC0);
assert(alloc->magic[1] == PL_MAGIC1);
assert(alloc->buf->data == data);
pl_buf_destroy(alloc->gpu, &alloc->buf);
free(alloc);
}
PL_LIBAV_API int pl_get_buffer2(AVCodecContext *avctx, AVFrame *pic, int flags)
{
int alignment[AV_NUM_DATA_POINTERS];
int width = pic->width;
int height = pic->height;
size_t planesize[4];
int ret = 0;
pl_gpu *pgpu = avctx->opaque;
pl_gpu gpu = pgpu ? *pgpu : NULL;
struct pl_plane_data data[4];
struct pl_avalloc *alloc;
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pic->format);
int planes = pl_plane_data_from_pixfmt(data, NULL, pic->format);
// Sanitize frame structs
memset(pic->data, 0, sizeof(pic->data));
memset(pic->linesize, 0, sizeof(pic->linesize));
memset(pic->buf, 0, sizeof(pic->buf));
pic->extended_data = pic->data;
pic->extended_buf = NULL;
if (!(avctx->codec->capabilities & AV_CODEC_CAP_DR1) || !planes)
goto fallback;
if (!gpu || !gpu->limits.thread_safe || !gpu->limits.max_mapped_size ||
!gpu->limits.host_cached)
{
goto fallback;
}
avcodec_align_dimensions2(avctx, &width, &height, alignment);
if ((ret = av_image_fill_linesizes(pic->linesize, pic->format, width)))
return ret;
for (int p = 0; p < planes; p++) {
alignment[p] = PL_LCM(alignment[p], gpu->limits.align_tex_xfer_pitch);
alignment[p] = PL_LCM(alignment[p], gpu->limits.align_tex_xfer_offset);
alignment[p] = PL_LCM(alignment[p], data[p].pixel_stride);
pic->linesize[p] = PL_ALIGN(pic->linesize[p], alignment[p]);
}
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(56, 56, 100)
ret = av_image_fill_plane_sizes(planesize, pic->format, height, (ptrdiff_t[4]) {
pic->linesize[0], pic->linesize[1], pic->linesize[2], pic->linesize[3],
});
if (ret < 0)
return ret;
#else
uint8_t *ptrs[4], * const base = (uint8_t *) 0x10000;
ret = av_image_fill_pointers(ptrs, pic->format, height, base, pic->linesize);
if (ret < 0)
return ret;
for (int p = 0; p < 4; p++)
planesize[p] = (uintptr_t) ptrs[p] - (uintptr_t) base;
#endif
for (int p = 0; p < planes; p++) {
const size_t buf_size = planesize[p] + alignment[p];
if (buf_size > gpu->limits.max_mapped_size) {
av_frame_unref(pic);
goto fallback;
}
alloc = malloc(sizeof(*alloc));
if (!alloc) {
av_frame_unref(pic);
return AVERROR(ENOMEM);
}
*alloc = (struct pl_avalloc) {
.magic = { PL_MAGIC0, PL_MAGIC1 },
.gpu = gpu,
.buf = pl_buf_create(gpu, pl_buf_params(
.size = buf_size,
.memory_type = PL_BUF_MEM_HOST,
.host_mapped = true,
.storable = desc->flags & AV_PIX_FMT_FLAG_BE,
)),
};
if (!alloc->buf) {
free(alloc);
av_frame_unref(pic);
return AVERROR(ENOMEM);
}
pic->data[p] = (uint8_t *) PL_ALIGN((uintptr_t) alloc->buf->data, alignment[p]);
pic->buf[p] = av_buffer_create(alloc->buf->data, buf_size, pl_avalloc_free, alloc, 0);
if (!pic->buf[p]) {
pl_buf_destroy(gpu, &alloc->buf);
free(alloc);
av_frame_unref(pic);
return AVERROR(ENOMEM);
}
}
return 0;
fallback:
return avcodec_default_get_buffer2(avctx, pic, flags);
}
#undef PL_MAGIC0
#undef PL_MAGIC1
#undef PL_ALIGN
#undef PL_MAX
#endif // LIBPLACEBO_LIBAV_H_