/* * This file is part of mpv. * * mpv 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. * * mpv 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 mpv. If not, see . */ #include #include #include #include #include #include "config.h" #include "video/img_format.h" #include "video/mp_image.h" #include "video/fmt-conversion.h" struct mp_imgfmt_entry { const char *name; // valid if desc.id is set struct mp_imgfmt_desc desc; // valid if reg_desc.component_size is set struct mp_regular_imgfmt reg_desc; // valid if non-0 and no reg_desc enum mp_csp forced_csp; enum mp_component_type ctype; }; #define FRINGE_GBRP(def, dname, bits) \ [def - IMGFMT_CUST_BASE] = { \ .name = dname, \ .reg_desc = { .component_type = MP_COMPONENT_TYPE_UINT, \ .component_size = 1, .component_pad = bits - 8, \ .num_planes = 3, .forced_csp = MP_CSP_RGB, \ .planes = { {1, {2}}, {1, {3}}, {1, {1}} }, }, } #define FLOAT_YUV(def, dname, xs, ys, a_planes) \ [def - IMGFMT_CUST_BASE] = { \ .name = dname, \ .reg_desc = { .component_type = MP_COMPONENT_TYPE_FLOAT, \ .component_size = 4, .num_planes = a_planes, \ .planes = { {1, {1}}, {1, {2}}, {1, {3}}, {1, {4}} }, \ .chroma_xs = xs, .chroma_ys = ys, }} static const struct mp_imgfmt_entry mp_imgfmt_list[] = { // not in ffmpeg [IMGFMT_VDPAU_OUTPUT - IMGFMT_CUST_BASE] = { .name = "vdpau_output", .desc = { .id = IMGFMT_VDPAU_OUTPUT, .avformat = AV_PIX_FMT_NONE, .flags = MP_IMGFLAG_BE | MP_IMGFLAG_LE | MP_IMGFLAG_RGB | MP_IMGFLAG_HWACCEL, }, }, [IMGFMT_RGB30 - IMGFMT_CUST_BASE] = { .name = "rgb30", .desc = { .id = IMGFMT_RGB30, .avformat = AV_PIX_FMT_NONE, .flags = MP_IMGFLAG_BYTE_ALIGNED | MP_IMGFLAG_NE | MP_IMGFLAG_RGB, .num_planes = 1, .align_x = 1, .align_y = 1, .bytes = {4}, .bpp = {32}, .plane_bits = 30, .component_bits = 10, }, .forced_csp = MP_CSP_RGB, .ctype = MP_COMPONENT_TYPE_UINT, }, [IMGFMT_YAP8 - IMGFMT_CUST_BASE] = { .name = "yap8", .reg_desc = { .component_type = MP_COMPONENT_TYPE_UINT, .component_size = 1, .num_planes = 2, .planes = { {1, {1}}, {1, {4}} }, }, }, [IMGFMT_YAP16 - IMGFMT_CUST_BASE] = { .name = "yap16", .reg_desc = { .component_type = MP_COMPONENT_TYPE_UINT, .component_size = 2, .num_planes = 2, .planes = { {1, {1}}, {1, {4}} }, }, }, [IMGFMT_Y1 - IMGFMT_CUST_BASE] = { .name = "y1", .reg_desc = { .component_type = MP_COMPONENT_TYPE_UINT, .component_size = 1, .component_pad = -7, .num_planes = 1, .forced_csp = MP_CSP_RGB, .planes = { {1, {1}} }, }, }, [IMGFMT_YAPF - IMGFMT_CUST_BASE] = { .name = "grayaf32", // try to mimic ffmpeg naming convention .reg_desc = { .component_type = MP_COMPONENT_TYPE_FLOAT, .component_size = 4, .num_planes = 2, .planes = { {1, {1}}, {1, {4}} }, }, }, FLOAT_YUV(IMGFMT_444PF, "yuv444pf", 0, 0, 3), FLOAT_YUV(IMGFMT_444APF, "yuva444pf", 0, 0, 4), FLOAT_YUV(IMGFMT_420PF, "yuv420pf", 1, 1, 3), FLOAT_YUV(IMGFMT_420APF, "yuva420pf", 1, 1, 4), FLOAT_YUV(IMGFMT_422PF, "yuv422pf", 1, 0, 3), FLOAT_YUV(IMGFMT_422APF, "yuva422pf", 1, 0, 4), FLOAT_YUV(IMGFMT_440PF, "yuv440pf", 0, 1, 3), FLOAT_YUV(IMGFMT_440APF, "yuva440pf", 0, 1, 4), FLOAT_YUV(IMGFMT_410PF, "yuv410pf", 2, 2, 3), FLOAT_YUV(IMGFMT_410APF, "yuva410pf", 2, 2, 4), FLOAT_YUV(IMGFMT_411PF, "yuv411pf", 2, 0, 3), FLOAT_YUV(IMGFMT_411APF, "yuva411pf", 2, 0, 4), FRINGE_GBRP(IMGFMT_GBRP1, "gbrp1", 1), FRINGE_GBRP(IMGFMT_GBRP2, "gbrp2", 2), FRINGE_GBRP(IMGFMT_GBRP3, "gbrp3", 3), FRINGE_GBRP(IMGFMT_GBRP4, "gbrp4", 4), FRINGE_GBRP(IMGFMT_GBRP5, "gbrp5", 5), FRINGE_GBRP(IMGFMT_GBRP6, "gbrp6", 6), // in FFmpeg, but FFmpeg names have an annoying "_vld" suffix [IMGFMT_VIDEOTOOLBOX - IMGFMT_CUST_BASE] = { .name = "videotoolbox", }, [IMGFMT_VAAPI - IMGFMT_CUST_BASE] = { .name = "vaapi", }, }; static const struct mp_imgfmt_entry *get_mp_desc(int imgfmt) { if (imgfmt < IMGFMT_CUST_BASE) return NULL; int index = imgfmt - IMGFMT_CUST_BASE; if (index >= MP_ARRAY_SIZE(mp_imgfmt_list)) return NULL; const struct mp_imgfmt_entry *e = &mp_imgfmt_list[index]; return e->name ? e : NULL; } char **mp_imgfmt_name_list(void) { int count = IMGFMT_END - IMGFMT_START; char **list = talloc_zero_array(NULL, char *, count + 1); int num = 0; for (int n = IMGFMT_START; n < IMGFMT_END; n++) { const char *name = mp_imgfmt_to_name(n); if (strcmp(name, "unknown") != 0) list[num++] = talloc_strdup(list, name); } return list; } int mp_imgfmt_from_name(bstr name) { if (bstr_equals0(name, "none")) return 0; for (int n = 0; n < MP_ARRAY_SIZE(mp_imgfmt_list); n++) { const struct mp_imgfmt_entry *p = &mp_imgfmt_list[n]; if (p->name && bstr_equals0(name, p->name)) return IMGFMT_CUST_BASE + n; } return pixfmt2imgfmt(av_get_pix_fmt(mp_tprintf(80, "%.*s", BSTR_P(name)))); } char *mp_imgfmt_to_name_buf(char *buf, size_t buf_size, int fmt) { const struct mp_imgfmt_entry *p = get_mp_desc(fmt); const char *name = p ? p->name : NULL; if (!name) { const AVPixFmtDescriptor *pixdesc = av_pix_fmt_desc_get(imgfmt2pixfmt(fmt)); if (pixdesc) name = pixdesc->name; } if (!name) name = "unknown"; snprintf(buf, buf_size, "%s", name); int len = strlen(buf); if (len > 2 && buf[len - 2] == MP_SELECT_LE_BE('l', 'b') && buf[len - 1] == 'e') buf[len - 2] = '\0'; return buf; } static struct mp_imgfmt_desc to_legacy_desc(int fmt, struct mp_regular_imgfmt reg) { struct mp_imgfmt_desc desc = { .id = fmt, .avformat = AV_PIX_FMT_NONE, .flags = MP_IMGFLAG_BYTE_ALIGNED | MP_IMGFLAG_NE | (reg.forced_csp ? MP_IMGFLAG_RGB | MP_IMGFLAG_RGB_P : MP_IMGFLAG_YUV | MP_IMGFLAG_YUV_P), .num_planes = reg.num_planes, .chroma_xs = reg.chroma_xs, .chroma_ys = reg.chroma_ys, .component_bits = reg.component_size * 8 - abs(reg.component_pad), }; desc.align_x = 1 << reg.chroma_xs; desc.align_y = 1 << reg.chroma_ys; desc.plane_bits = desc.component_bits; for (int p = 0; p < reg.num_planes; p++) { desc.bytes[p] = reg.component_size; desc.bpp[p] = desc.bytes[p] * 8; desc.xs[p] = p == 1 || p == 2 ? desc.chroma_xs : 0; desc.ys[p] = p == 1 || p == 2 ? desc.chroma_ys : 0; for (int c = 0; c < reg.planes[p].num_components; c++) { if (reg.planes[p].components[c] == 4) desc.flags |= MP_IMGFLAG_ALPHA; } } return desc; } struct mp_imgfmt_desc mp_imgfmt_get_desc(int mpfmt) { const struct mp_imgfmt_entry *mpdesc = get_mp_desc(mpfmt); if (mpdesc && mpdesc->desc.id) return mpdesc->desc; if (mpdesc && mpdesc->reg_desc.component_size) return to_legacy_desc(mpfmt, mpdesc->reg_desc); enum AVPixelFormat fmt = imgfmt2pixfmt(mpfmt); const AVPixFmtDescriptor *pd = av_pix_fmt_desc_get(fmt); if (!pd || pd->nb_components > 4 || fmt == AV_PIX_FMT_NONE || fmt == AV_PIX_FMT_UYYVYY411) return (struct mp_imgfmt_desc) {0}; enum mp_component_type is_uint = mp_imgfmt_get_component_type(mpfmt) == MP_COMPONENT_TYPE_UINT; struct mp_imgfmt_desc desc = { .id = mpfmt, .avformat = fmt, .chroma_xs = pd->log2_chroma_w, .chroma_ys = pd->log2_chroma_h, }; int planedepth[4] = {0}; int el_size = (pd->flags & AV_PIX_FMT_FLAG_BITSTREAM) ? 1 : 8; bool need_endian = false; // single component is spread over >1 bytes int shift = -1; // shift for all components, or -1 if not uniform for (int c = 0; c < pd->nb_components; c++) { AVComponentDescriptor d = pd->comp[c]; // multiple components per plane -> Y is definitive, ignore chroma if (!desc.bpp[d.plane]) desc.bpp[d.plane] = d.step * el_size; planedepth[d.plane] += d.depth; need_endian |= (d.depth + d.shift) > 8; if (c == 0) desc.component_bits = d.depth; if (d.depth != desc.component_bits) desc.component_bits = 0; if (c == 0) shift = d.shift; if (shift != d.shift) shift = -1; } for (int p = 0; p < 4; p++) { if (desc.bpp[p]) desc.num_planes++; } desc.plane_bits = planedepth[0]; // Check whether any components overlap other components (per plane). // We're cheating/simplifying here: we assume that this happens if a shift // is set - which is wrong in general (could be needed for padding, instead // of overlapping bits of another component - use the "< 8" test to exclude // "normal" formats which use this for padding, like p010). // Needed for rgb444le/be. bool component_byte_overlap = false; for (int c = 0; c < pd->nb_components; c++) { AVComponentDescriptor d = pd->comp[c]; component_byte_overlap |= d.shift > 0 && planedepth[d.plane] > 8 && desc.component_bits < 8; } // If every component sits in its own byte, or all components are within // a single byte, no endian-dependent access is needed. If components // stride bytes (like with packed 2 byte RGB formats), endian-dependent // access is needed. need_endian |= component_byte_overlap; if (!need_endian) { desc.flags |= MP_IMGFLAG_LE | MP_IMGFLAG_BE; } else { desc.flags |= (pd->flags & AV_PIX_FMT_FLAG_BE) ? MP_IMGFLAG_BE : MP_IMGFLAG_LE; } enum mp_csp csp = mp_imgfmt_get_forced_csp(mpfmt); if ((pd->flags & AV_PIX_FMT_FLAG_HWACCEL)) { desc.flags |= MP_IMGFLAG_HWACCEL; } else if (csp == MP_CSP_XYZ) { /* nothing */ } else if (csp == MP_CSP_RGB) { desc.flags |= MP_IMGFLAG_RGB; } else { desc.flags |= MP_IMGFLAG_YUV; } if (pd->flags & AV_PIX_FMT_FLAG_ALPHA) desc.flags |= MP_IMGFLAG_ALPHA; if (!(pd->flags & AV_PIX_FMT_FLAG_HWACCEL) && !(pd->flags & AV_PIX_FMT_FLAG_BITSTREAM)) { desc.flags |= MP_IMGFLAG_BYTE_ALIGNED; for (int p = 0; p < desc.num_planes; p++) desc.bytes[p] = desc.bpp[p] / 8; } if (pd->flags & AV_PIX_FMT_FLAG_PAL) desc.flags |= MP_IMGFLAG_PAL; if ((desc.flags & (MP_IMGFLAG_YUV | MP_IMGFLAG_RGB)) && (desc.flags & MP_IMGFLAG_BYTE_ALIGNED) && !(pd->flags & AV_PIX_FMT_FLAG_PAL) && !component_byte_overlap && shift >= 0 && is_uint) { bool same_depth = true; for (int p = 0; p < desc.num_planes; p++) { same_depth &= planedepth[p] == planedepth[0] && desc.bpp[p] == desc.bpp[0]; } if (same_depth && pd->nb_components == desc.num_planes) { if (desc.flags & MP_IMGFLAG_YUV) { desc.flags |= MP_IMGFLAG_YUV_P; } else { desc.flags |= MP_IMGFLAG_RGB_P; } } if (pd->nb_components == 3 && desc.num_planes == 2 && planedepth[1] == planedepth[0] * 2 && desc.bpp[1] == desc.bpp[0] * 2 && (desc.flags & MP_IMGFLAG_YUV)) { desc.flags |= MP_IMGFLAG_YUV_NV; } if (desc.flags & (MP_IMGFLAG_YUV_P | MP_IMGFLAG_RGB_P | MP_IMGFLAG_YUV_NV)) desc.component_bits += shift; } for (int p = 0; p < desc.num_planes; p++) { desc.xs[p] = (p == 1 || p == 2) ? desc.chroma_xs : 0; desc.ys[p] = (p == 1 || p == 2) ? desc.chroma_ys : 0; } desc.align_x = 1 << desc.chroma_xs; desc.align_y = 1 << desc.chroma_ys; if ((desc.bpp[0] % 8) != 0) desc.align_x = 8 / desc.bpp[0]; // expect power of 2 if (desc.flags & MP_IMGFLAG_HWACCEL) { desc.component_bits = 0; desc.plane_bits = 0; } return desc; } static bool validate_regular_imgfmt(const struct mp_regular_imgfmt *fmt) { bool present[MP_NUM_COMPONENTS] = {0}; int n_comp = 0; for (int n = 0; n < fmt->num_planes; n++) { const struct mp_regular_imgfmt_plane *plane = &fmt->planes[n]; n_comp += plane->num_components; if (n_comp > MP_NUM_COMPONENTS) return false; if (!plane->num_components) return false; // no empty planes in between allowed bool pad_only = true; int chroma_luma = 0; // luma: 1, chroma: 2, both: 3 for (int i = 0; i < plane->num_components; i++) { int comp = plane->components[i]; if (comp > MP_NUM_COMPONENTS) return false; if (comp == 0) continue; pad_only = false; if (present[comp - 1]) return false; // no duplicates present[comp - 1] = true; chroma_luma |= (comp == 2 || comp == 3) ? 2 : 1; } if (pad_only) return false; // no planes with only padding allowed if ((fmt->chroma_xs > 0 || fmt->chroma_ys > 0) && chroma_luma == 3) return false; // separate chroma/luma planes required } if (!(present[0] || present[3]) || // at least component 1 or alpha needed (present[1] && !present[0]) || // component 2 requires component 1 (present[2] && !present[1])) // component 3 requires component 2 return false; return true; } enum mp_csp mp_imgfmt_get_forced_csp(int imgfmt) { const struct mp_imgfmt_entry *p = get_mp_desc(imgfmt); if (p && p->reg_desc.component_size) return p->reg_desc.forced_csp; if (p && p->forced_csp) return p->forced_csp; enum AVPixelFormat pixfmt = imgfmt2pixfmt(imgfmt); const AVPixFmtDescriptor *pixdesc = av_pix_fmt_desc_get(pixfmt); if (pixdesc && (pixdesc->flags & AV_PIX_FMT_FLAG_HWACCEL)) return MP_CSP_AUTO; // FFmpeg does not provide a flag for XYZ, so this is the best we can do. if (pixdesc && strncmp(pixdesc->name, "xyz", 3) == 0) return MP_CSP_XYZ; if (pixdesc && (pixdesc->flags & AV_PIX_FMT_FLAG_RGB)) return MP_CSP_RGB; if (pixfmt == AV_PIX_FMT_PAL8 || pixfmt == AV_PIX_FMT_MONOBLACK || pixfmt == AV_PIX_FMT_MONOWHITE) return MP_CSP_RGB; return MP_CSP_AUTO; } enum mp_component_type mp_imgfmt_get_component_type(int imgfmt) { const struct mp_imgfmt_entry *p = get_mp_desc(imgfmt); if (p && p->reg_desc.component_size) return p->reg_desc.component_type; if (p && p->ctype) return p->ctype; const AVPixFmtDescriptor *pixdesc = av_pix_fmt_desc_get(imgfmt2pixfmt(imgfmt)); if (!pixdesc || (pixdesc->flags & AV_PIX_FMT_FLAG_HWACCEL)) return MP_COMPONENT_TYPE_UNKNOWN; if (pixdesc->flags & AV_PIX_FMT_FLAG_FLOAT) return MP_COMPONENT_TYPE_FLOAT; return MP_COMPONENT_TYPE_UINT; } int mp_find_other_endian(int imgfmt) { return pixfmt2imgfmt(av_pix_fmt_swap_endianness(imgfmt2pixfmt(imgfmt))); } static bool is_native_endian(const AVPixFmtDescriptor *pixdesc) { enum AVPixelFormat pixfmt = av_pix_fmt_desc_get_id(pixdesc); enum AVPixelFormat other = av_pix_fmt_swap_endianness(pixfmt); if (other == AV_PIX_FMT_NONE || other == pixfmt) return true; // no endian nonsense bool is_le = *(char *)&(uint32_t){1}; return pixdesc && (is_le != !!(pixdesc->flags & AV_PIX_FMT_FLAG_BE)); } bool mp_get_regular_imgfmt(struct mp_regular_imgfmt *dst, int imgfmt) { struct mp_regular_imgfmt res = {0}; const AVPixFmtDescriptor *pixdesc = av_pix_fmt_desc_get(imgfmt2pixfmt(imgfmt)); if (!pixdesc) { const struct mp_imgfmt_entry *p = get_mp_desc(imgfmt); if (p && p->reg_desc.component_size) { *dst = p->reg_desc; return true; } return false; } if ((pixdesc->flags & AV_PIX_FMT_FLAG_BITSTREAM) || (pixdesc->flags & AV_PIX_FMT_FLAG_HWACCEL) || (pixdesc->flags & AV_PIX_FMT_FLAG_PAL) || pixdesc->nb_components < 1 || pixdesc->nb_components > MP_NUM_COMPONENTS || !is_native_endian(pixdesc)) return false; res.component_type = mp_imgfmt_get_component_type(imgfmt); if (!res.component_type) return false; const AVComponentDescriptor *comp0 = &pixdesc->comp[0]; int depth = comp0->depth + comp0->shift; if (depth < 1 || depth > 64) return false; res.component_size = (depth + 7) / 8; for (int n = 0; n < pixdesc->nb_components; n++) { const AVComponentDescriptor *comp = &pixdesc->comp[n]; if (comp->plane < 0 || comp->plane >= MP_MAX_PLANES) return false; res.num_planes = MPMAX(res.num_planes, comp->plane + 1); // We support uniform depth only. if (comp->depth != comp0->depth || comp->shift != comp0->shift) return false; // Uniform component size; even the padding must have same size. int ncomp = comp->step / res.component_size; if (!ncomp || ncomp * res.component_size != comp->step) return false; struct mp_regular_imgfmt_plane *plane = &res.planes[comp->plane]; if (plane->num_components && plane->num_components != ncomp) return false; plane->num_components = ncomp; int pos = comp->offset / res.component_size; if (pos < 0 || pos >= ncomp || ncomp > MP_NUM_COMPONENTS) return false; if (plane->components[pos]) return false; plane->components[pos] = n + 1; } // Make sure alpha is always component 4. if (pixdesc->nb_components == 2 && (pixdesc->flags & AV_PIX_FMT_FLAG_ALPHA)) { for (int n = 0; n < res.num_planes; n++) { for (int i = 0; i < res.planes[n].num_components; i++) { if (res.planes[n].components[i] == 2) res.planes[n].components[i] = 4; } } } res.component_pad = comp0->depth - res.component_size * 8; if (comp0->shift) { // We support padding only on 1 side. if (comp0->shift + comp0->depth != res.component_size * 8) return false; res.component_pad = -res.component_pad; } res.chroma_xs = pixdesc->log2_chroma_w; res.chroma_ys = pixdesc->log2_chroma_h; if (pixdesc->flags & AV_PIX_FMT_FLAG_BAYER) return false; // it's satan himself res.forced_csp = mp_imgfmt_get_forced_csp(imgfmt); if (!validate_regular_imgfmt(&res)) return false; *dst = res; return true; } static bool regular_imgfmt_equals(struct mp_regular_imgfmt *a, struct mp_regular_imgfmt *b) { if (a->component_type != b->component_type || a->component_size != b->component_size || a->num_planes != b->num_planes || a->component_pad != b->component_pad || a->forced_csp != b->forced_csp || a->chroma_xs != b->chroma_xs || a->chroma_ys != b->chroma_ys) return false; for (int n = 0; n < a->num_planes; n++) { int num_comps = a->planes[n].num_components; if (num_comps != b->planes[n].num_components) return false; for (int i = 0; i < num_comps; i++) { if (a->planes[n].components[i] != b->planes[n].components[i]) return false; } } return true; } // Find a format that matches this one exactly. int mp_find_regular_imgfmt(struct mp_regular_imgfmt *src) { for (int n = IMGFMT_START + 1; n < IMGFMT_END; n++) { struct mp_regular_imgfmt f; if (mp_get_regular_imgfmt(&f, n) && regular_imgfmt_equals(src, &f)) return n; } return 0; } // Find a format that has the given flags set with the following configuration. int mp_imgfmt_find(int xs, int ys, int planes, int component_bits, int flags) { for (int n = IMGFMT_START + 1; n < IMGFMT_END; n++) { struct mp_imgfmt_desc desc = mp_imgfmt_get_desc(n); if (desc.id && ((desc.flags & flags) == flags)) { if (desc.num_planes == planes && desc.chroma_xs == xs && desc.chroma_ys == ys && desc.plane_bits == component_bits && (desc.flags & MP_IMGFLAG_NE)) return desc.id; } } return 0; } // Compare the dst image formats, and return the one which can carry more data // (e.g. higher depth, more color components, lower chroma subsampling, etc.), // with respect to what is required to keep most of the src format. // Returns the imgfmt, or 0 on error. int mp_imgfmt_select_best(int dst1, int dst2, int src) { enum AVPixelFormat dst1pxf = imgfmt2pixfmt(dst1); enum AVPixelFormat dst2pxf = imgfmt2pixfmt(dst2); enum AVPixelFormat srcpxf = imgfmt2pixfmt(src); enum AVPixelFormat dstlist[] = {dst1pxf, dst2pxf, AV_PIX_FMT_NONE}; return pixfmt2imgfmt(avcodec_find_best_pix_fmt_of_list(dstlist, srcpxf, 1, 0)); } // Same as mp_imgfmt_select_best(), but with a list of dst formats. int mp_imgfmt_select_best_list(int *dst, int num_dst, int src) { int best = 0; for (int n = 0; n < num_dst; n++) best = best ? mp_imgfmt_select_best(best, dst[n], src) : dst[n]; return best; }