PR #21678 opened by Marton Balint (cus)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/21678
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/21678.patch

This patch series started as an experiment, because I was suprised that there 
is no proper support for RGB / YUV444 videos on the web with transparency.

I could not find any documentation which disallows the use of transparency in 
WEBM files with VP9 codec for pixel formats other than YUV 4:2:0, so this patch 
series add support for creating and reading such files: 4:2:2, 4:4:4, RGB, and 
their high bit depth variants.

It is not quite clear if the alpha channel should be encoded using the same 
pixel format as the normal channels, or it should always be YUV 4:2:0. I 
sticked to the same format as the normal color components.

Unfortunately the browsers I tested (chrome, firefox) only support 4:2:0 
properly, so let's require an experimental flag to encode VP9 with the new, 
more exotic pixel formats. Maybe browsers will pick up support if something at 
least can generate such files...


>From d55a1694beed59c0f337366b56b1f2f9d5b4dff0 Mon Sep 17 00:00:00 2001
From: Marton Balint <[email protected]>
Date: Sat, 7 Feb 2026 00:19:47 +0100
Subject: [PATCH 1/4] avformat/matroskaenc: only write AlphaMode for VP8 and
 VP9

AlphaMode should indicate whether the BlockAdditional Element with BlockAddID
of "1" contains Alpha data, as defined by to the Codec Mapping for the 
`CodecID`.

Only VP8 and VP9 Codec Mappings define this, so writing it for all codecs with
a pixel format of YUVA420P was wrong.

Signed-off-by: Marton Balint <[email protected]>
---
 libavformat/matroskaenc.c | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c
index 50188c396c..ef5f3d0259 100644
--- a/libavformat/matroskaenc.c
+++ b/libavformat/matroskaenc.c
@@ -19,6 +19,7 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  */
 
+#include <stdbool.h>
 #include <stdint.h>
 
 #include "config_components.h"
@@ -1757,6 +1758,14 @@ static void 
mkv_write_blockadditionmapping(AVFormatContext *s, const MatroskaMux
 #endif
 }
 
+static bool codec_has_blockadditional_alpha(const AVCodecParameters *par)
+{
+    if (par->codec_id != AV_CODEC_ID_VP8 &&
+        par->codec_id != AV_CODEC_ID_VP9)
+        return false;
+    return (par->format == AV_PIX_FMT_YUVA420P);
+}
+
 static int mkv_write_track_video(AVFormatContext *s, MatroskaMuxContext *mkv,
                                  const AVStream *st, const AVCodecParameters 
*par,
                                  AVIOContext *pb)
@@ -1785,7 +1794,7 @@ static int mkv_write_track_video(AVFormatContext *s, 
MatroskaMuxContext *mkv,
     if (ret < 0)
         return ret;
 
-    if (par->format == AV_PIX_FMT_YUVA420P ||
+    if (codec_has_blockadditional_alpha(par) ||
         ((tag = av_dict_get(st->metadata, "alpha_mode", NULL, 0)) ||
          (tag = av_dict_get( s->metadata, "alpha_mode", NULL, 0))) && 
strtol(tag->value, NULL, 0))
         ebml_writer_add_uint(&writer, MATROSKA_ID_VIDEOALPHAMODE, 1);
-- 
2.52.0


>From e2e08682059c5aab9a68ff019c392ddc11bc47e6 Mon Sep 17 00:00:00 2001
From: Marton Balint <[email protected]>
Date: Sat, 7 Feb 2026 00:36:07 +0100
Subject: [PATCH 2/4] avformat/matroskaenc: write AlphaMode flag for all VP8
 and VP9 pixel formats with alpha

Let's check the pixel format descriptor flags instead of hardcoding a specific
pixel format.

Signed-off-by: Marton Balint <[email protected]>
---
 libavformat/matroskaenc.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c
index ef5f3d0259..2eec6cfb7f 100644
--- a/libavformat/matroskaenc.c
+++ b/libavformat/matroskaenc.c
@@ -1760,10 +1760,11 @@ static void 
mkv_write_blockadditionmapping(AVFormatContext *s, const MatroskaMux
 
 static bool codec_has_blockadditional_alpha(const AVCodecParameters *par)
 {
+    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(par->format);
     if (par->codec_id != AV_CODEC_ID_VP8 &&
         par->codec_id != AV_CODEC_ID_VP9)
         return false;
-    return (par->format == AV_PIX_FMT_YUVA420P);
+    return desc && (desc->flags & AV_PIX_FMT_FLAG_ALPHA);
 }
 
 static int mkv_write_track_video(AVFormatContext *s, MatroskaMuxContext *mkv,
-- 
2.52.0


>From 80b04671d0bb0721f5966edc6f939e48c9c13bf0 Mon Sep 17 00:00:00 2001
From: Marton Balint <[email protected]>
Date: Sat, 7 Feb 2026 00:58:37 +0100
Subject: [PATCH 3/4] avcodec/libvpxdec: add support for decoding pixel formats
 other than YUV420 with alpha

Signed-off-by: Marton Balint <[email protected]>
---
 libavcodec/libvpxdec.c | 22 ++++++++++++++++------
 1 file changed, 16 insertions(+), 6 deletions(-)

diff --git a/libavcodec/libvpxdec.c b/libavcodec/libvpxdec.c
index 1257a3607c..317725bf31 100644
--- a/libavcodec/libvpxdec.c
+++ b/libavcodec/libvpxdec.c
@@ -140,23 +140,28 @@ static int set_pix_fmt(AVCodecContext *avctx, struct 
vpx_image *img,
 #if CONFIG_LIBVPX_VP9_DECODER
     case VPX_IMG_FMT_I422:
         avctx->profile = AV_PROFILE_VP9_1;
-        avctx->pix_fmt = AV_PIX_FMT_YUV422P;
+        avctx->pix_fmt =
+            has_alpha_channel ? AV_PIX_FMT_YUVA422P : AV_PIX_FMT_YUV422P;
         return 0;
     case VPX_IMG_FMT_I440:
+        //TODO: Add alpha support once the pixel format becomes available
         avctx->profile = AV_PROFILE_VP9_1;
         avctx->pix_fmt = AV_PIX_FMT_YUV440P;
         return 0;
     case VPX_IMG_FMT_I444:
         avctx->profile = AV_PROFILE_VP9_1;
         avctx->pix_fmt = avctx->colorspace == AVCOL_SPC_RGB ?
-                         AV_PIX_FMT_GBRP : AV_PIX_FMT_YUV444P;
+                         (has_alpha_channel ? AV_PIX_FMT_GBRAP : 
AV_PIX_FMT_GBRP) :
+                         (has_alpha_channel ? AV_PIX_FMT_YUVA444P : 
AV_PIX_FMT_YUV444P);
         return 0;
     case VPX_IMG_FMT_I42016:
         avctx->profile = AV_PROFILE_VP9_2;
         if (img->bit_depth == 10) {
-            avctx->pix_fmt = AV_PIX_FMT_YUV420P10;
+            avctx->pix_fmt =
+                has_alpha_channel ? AV_PIX_FMT_YUVA420P10 : 
AV_PIX_FMT_YUV420P10;
             return 0;
         } else if (img->bit_depth == 12) {
+            //TODO: Add alpha support once the pixel format becomes available
             avctx->pix_fmt = AV_PIX_FMT_YUV420P12;
             return 0;
         } else {
@@ -165,15 +170,18 @@ static int set_pix_fmt(AVCodecContext *avctx, struct 
vpx_image *img,
     case VPX_IMG_FMT_I42216:
         avctx->profile = AV_PROFILE_VP9_3;
         if (img->bit_depth == 10) {
-            avctx->pix_fmt = AV_PIX_FMT_YUV422P10;
+            avctx->pix_fmt =
+                has_alpha_channel ? AV_PIX_FMT_YUVA422P10 : 
AV_PIX_FMT_YUV422P10;
             return 0;
         } else if (img->bit_depth == 12) {
+            //TODO: Add alpha support once the pixel format becomes available
             avctx->pix_fmt = AV_PIX_FMT_YUV422P12;
             return 0;
         } else {
             return AVERROR_INVALIDDATA;
         }
     case VPX_IMG_FMT_I44016:
+        //TODO: Add alpha support once the pixel format becomes available
         avctx->profile = AV_PROFILE_VP9_3;
         if (img->bit_depth == 10) {
             avctx->pix_fmt = AV_PIX_FMT_YUV440P10;
@@ -188,11 +196,13 @@ static int set_pix_fmt(AVCodecContext *avctx, struct 
vpx_image *img,
         avctx->profile = AV_PROFILE_VP9_3;
         if (img->bit_depth == 10) {
             avctx->pix_fmt = avctx->colorspace == AVCOL_SPC_RGB ?
-                             AV_PIX_FMT_GBRP10 : AV_PIX_FMT_YUV444P10;
+                             (has_alpha_channel ? AV_PIX_FMT_GBRAP10 : 
AV_PIX_FMT_GBRP10) :
+                             (has_alpha_channel ? AV_PIX_FMT_YUVA444P10 : 
AV_PIX_FMT_YUV444P10);
             return 0;
         } else if (img->bit_depth == 12) {
             avctx->pix_fmt = avctx->colorspace == AVCOL_SPC_RGB ?
-                             AV_PIX_FMT_GBRP12 : AV_PIX_FMT_YUV444P12;
+                             (has_alpha_channel ? AV_PIX_FMT_GBRAP12 : 
AV_PIX_FMT_GBRP12) :
+                             (has_alpha_channel ? AV_PIX_FMT_YUVA444P12 : 
AV_PIX_FMT_YUV444P12);
             return 0;
         } else {
             return AVERROR_INVALIDDATA;
-- 
2.52.0


>From e8aac616fcc815adae46758999f42ea38c0a453c Mon Sep 17 00:00:00 2001
From: Marton Balint <[email protected]>
Date: Sat, 7 Feb 2026 18:17:41 +0100
Subject: [PATCH 4/4] avcodec/libvpxenc: add experimental support for alpha
 pixel formats other than YUV 4:2:0.

I could not find any documentation which disallows the use of transparency for
pixel formats other than YUV 4:2:0, so this patch adds support for transparency
using 4:2:2, 4:4:4, RGB, and their high bit depth variants.

It is not quite clear if the alpha channel should be encoded using the
same pixel format as the normal channels, or it should be always YUV 4:2:0. I
sticked to the same format as the normal color components.

Unfortunately the browsers I tested (chrome, firefox) only support 4:2:0
properly, so let's require an experimental flag to generate files with the new,
more exotic pixel formats.

Signed-off-by: Marton Balint <[email protected]>
---
 libavcodec/libvpxenc.c | 47 ++++++++++++++++++++++++++++++++++++++----
 1 file changed, 43 insertions(+), 4 deletions(-)

diff --git a/libavcodec/libvpxenc.c b/libavcodec/libvpxenc.c
index 082709c41f..e54fb1b673 100644
--- a/libavcodec/libvpxenc.c
+++ b/libavcodec/libvpxenc.c
@@ -825,6 +825,7 @@ static int set_pix_fmt(AVCodecContext *avctx, 
vpx_codec_caps_t codec_caps,
         *img_fmt = VPX_IMG_FMT_I420;
         return 0;
     case AV_PIX_FMT_YUV422P:
+    case AV_PIX_FMT_YUVA422P:
         enccfg->g_profile = 1;
         *img_fmt = VPX_IMG_FMT_I422;
         return 0;
@@ -833,12 +834,15 @@ static int set_pix_fmt(AVCodecContext *avctx, 
vpx_codec_caps_t codec_caps,
         *img_fmt = VPX_IMG_FMT_I440;
         return 0;
     case AV_PIX_FMT_GBRP:
+    case AV_PIX_FMT_GBRAP:
         ctx->vpx_cs = VPX_CS_SRGB;
     case AV_PIX_FMT_YUV444P:
+    case AV_PIX_FMT_YUVA444P:
         enccfg->g_profile = 1;
         *img_fmt = VPX_IMG_FMT_I444;
         return 0;
     case AV_PIX_FMT_YUV420P10:
+    case AV_PIX_FMT_YUVA420P10:
     case AV_PIX_FMT_YUV420P12:
         if (codec_caps & VPX_CODEC_CAP_HIGHBITDEPTH) {
             enccfg->g_profile = 2;
@@ -848,6 +852,7 @@ static int set_pix_fmt(AVCodecContext *avctx, 
vpx_codec_caps_t codec_caps,
         }
         break;
     case AV_PIX_FMT_YUV422P10:
+    case AV_PIX_FMT_YUVA422P10:
     case AV_PIX_FMT_YUV422P12:
         if (codec_caps & VPX_CODEC_CAP_HIGHBITDEPTH) {
             enccfg->g_profile = 3;
@@ -866,10 +871,14 @@ static int set_pix_fmt(AVCodecContext *avctx, 
vpx_codec_caps_t codec_caps,
         }
         break;
     case AV_PIX_FMT_GBRP10:
+    case AV_PIX_FMT_GBRAP10:
     case AV_PIX_FMT_GBRP12:
+    case AV_PIX_FMT_GBRAP12:
         ctx->vpx_cs = VPX_CS_SRGB;
     case AV_PIX_FMT_YUV444P10:
+    case AV_PIX_FMT_YUVA444P10:
     case AV_PIX_FMT_YUV444P12:
+    case AV_PIX_FMT_YUVA444P12:
         if (codec_caps & VPX_CODEC_CAP_HIGHBITDEPTH) {
             enccfg->g_profile = 3;
             *img_fmt = VPX_IMG_FMT_I44416;
@@ -1004,12 +1013,21 @@ static av_cold int vpx_init(AVCodecContext *avctx,
     vpx_svc_extra_cfg_t svc_params;
 #endif
     const AVDictionaryEntry* en = NULL;
+    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(avctx->pix_fmt);
 
     av_log(avctx, AV_LOG_INFO, "%s\n", vpx_codec_version_str());
     av_log(avctx, AV_LOG_VERBOSE, "%s\n", vpx_codec_build_config());
 
-    if (avctx->pix_fmt == AV_PIX_FMT_YUVA420P)
+    if (desc && (desc->flags & AV_PIX_FMT_FLAG_ALPHA)) {
         ctx->is_alpha = 1;
+        if (avctx->pix_fmt != AV_PIX_FMT_YUVA420P && 
avctx->strict_std_compliance > FF_COMPLIANCE_EXPERIMENTAL) {
+            av_log(avctx, AV_LOG_ERROR,
+                   "Pixel format '%s' is not widely supported. "
+                   "Use -strict experimental to use it anyway, or use 
'yuva420p' pixel format instead.\n",
+                   av_get_pix_fmt_name(avctx->pix_fmt));
+            return AVERROR(EINVAL);
+        }
+    }
 
     if ((res = vpx_codec_enc_config_default(iface, &enccfg, 0)) != 
VPX_CODEC_OK) {
         av_log(avctx, AV_LOG_ERROR, "Failed to get config: %s\n",
@@ -1695,15 +1713,24 @@ static int realloc_alpha_uv(AVCodecContext *avctx, int 
width, int height)
         av_freep(&planes[VPX_PLANE_U]);
         av_freep(&planes[VPX_PLANE_V]);
 
-        vpx_img_wrap(rawimg_alpha, VPX_IMG_FMT_I420, width, height, 1,
+        vpx_img_wrap(rawimg_alpha, ctx->rawimg.fmt, width, height, 1,
                      (unsigned char*)1);
         planes[VPX_PLANE_U] = av_malloc_array(stride[VPX_PLANE_U], height);
         planes[VPX_PLANE_V] = av_malloc_array(stride[VPX_PLANE_V], height);
         if (!planes[VPX_PLANE_U] || !planes[VPX_PLANE_V])
             return AVERROR(ENOMEM);
 
-        memset(planes[VPX_PLANE_U], 0x80, stride[VPX_PLANE_U] * height);
-        memset(planes[VPX_PLANE_V], 0x80, stride[VPX_PLANE_V] * height);
+        if (ctx->rawimg.bit_depth > 8 && ctx->vpx_cs != VPX_CS_SRGB) {
+            int val = 0x80 << (ctx->rawimg.bit_depth - 8);
+            AV_WN16(planes[VPX_PLANE_U], val);
+            AV_WN16(planes[VPX_PLANE_V], val);
+            av_memcpy_backptr(planes[VPX_PLANE_U] + 2, 2, stride[VPX_PLANE_U] 
* height - 2);
+            av_memcpy_backptr(planes[VPX_PLANE_V] + 2, 2, stride[VPX_PLANE_V] 
* height - 2);
+        } else {
+            int val = (ctx->vpx_cs == VPX_CS_SRGB) ? 0x00 : 0x80;
+            memset(planes[VPX_PLANE_U], val, stride[VPX_PLANE_U] * height);
+            memset(planes[VPX_PLANE_V], val, stride[VPX_PLANE_V] * height);
+        }
     }
 
     return 0;
@@ -2085,9 +2112,12 @@ static const enum AVPixelFormat vp9_pix_fmts_highcol[] = 
{
     AV_PIX_FMT_YUV420P,
     AV_PIX_FMT_YUVA420P,
     AV_PIX_FMT_YUV422P,
+    AV_PIX_FMT_YUVA422P,
     AV_PIX_FMT_YUV440P,
     AV_PIX_FMT_YUV444P,
+    AV_PIX_FMT_YUVA444P,
     AV_PIX_FMT_GBRP,
+    AV_PIX_FMT_GBRAP,
     AV_PIX_FMT_NONE
 };
 
@@ -2095,19 +2125,28 @@ static const enum AVPixelFormat vp9_pix_fmts_highbd[] = 
{
     AV_PIX_FMT_YUV420P,
     AV_PIX_FMT_YUVA420P,
     AV_PIX_FMT_YUV422P,
+    AV_PIX_FMT_YUVA422P,
     AV_PIX_FMT_YUV440P,
     AV_PIX_FMT_YUV444P,
+    AV_PIX_FMT_YUVA444P,
     AV_PIX_FMT_YUV420P10,
+    AV_PIX_FMT_YUVA420P10,
     AV_PIX_FMT_YUV422P10,
+    AV_PIX_FMT_YUVA422P10,
     AV_PIX_FMT_YUV440P10,
     AV_PIX_FMT_YUV444P10,
+    AV_PIX_FMT_YUVA444P10,
     AV_PIX_FMT_YUV420P12,
     AV_PIX_FMT_YUV422P12,
     AV_PIX_FMT_YUV440P12,
     AV_PIX_FMT_YUV444P12,
+    AV_PIX_FMT_YUVA444P12,
     AV_PIX_FMT_GBRP,
+    AV_PIX_FMT_GBRAP,
     AV_PIX_FMT_GBRP10,
+    AV_PIX_FMT_GBRAP10,
     AV_PIX_FMT_GBRP12,
+    AV_PIX_FMT_GBRAP12,
     AV_PIX_FMT_NONE
 };
 
-- 
2.52.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to