首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >ffmpeg对pal8进行scale时丢失透明通道问题分析

ffmpeg对pal8进行scale时丢失透明通道问题分析

原创
作者头像
xiaoxia
修改2024-11-29 21:26:02
修改2024-11-29 21:26:02
3920
举报
文章被收录于专栏:ffmpegffmpeg

1、问题

通过 ffmpeg 对pal8格式的 png 图片进行缩放时,原始图像的透明通道丢失了,被替换为黑色底。

原始图像:

ffmpeg 命令:

代码语言:shell
复制
ffmpeg -i 0_1.png -filter_complex "scale=w=100:h=100"  -y o.png

结果:

2、问题定位分析

首选看下 ffmpeg 对 AVPixelFormat 说明,从注释部分清楚可以看到,AV_PIX_FMT_PAL8 格式比较特殊,在 ffmpeg 的 frame 中的存储和其他格式不一样。AV_PIX_FMT_PAL8 时,它的透明通道是存放在AVFrame.data[1]中的。

include/libavutil/pixfmt.h

代码语言:txt
复制
* @par
* When the pixel format is palettized RGB32 (AV_PIX_FMT_PAL8), the palettized
* image data is stored in AVFrame.data[0]. The palette is transported in
* AVFrame.data[1], is 1024 bytes long (256 4-byte entries) and is
* formatted the same as in AV_PIX_FMT_RGB32 described above (i.e., it is
* also endian-specific). Note also that the individual RGB32 palette
* components stored in AVFrame.data[1] should be in the range 0..255.
* This is important as many custom PAL8 video codecs that were designed
* to run on the IBM VGA graphics adapter use 6-bit palette components.

继续跟进到 scale 滤镜,通过源文件发现,scale 滤镜在实现时,对 AV_PIX_FMT_PAL8 做了很多特殊逻辑。

libavfilter/vf_scale.c

A、AVPixelFormat 协商时,输出的 AVPixelFormat 对 AV_PIX_FMT_PAL8 做了特殊判断,取了 sws_isSupportedOutput 和 AV_PIX_FMT_PAL8 的并集,也就是强制指定支持了 AV_PIX_FMT_PAL8。

这里补充下,因为 scale 滤镜,底层是使用 ffmpeg 的 swscale 来实现的,所以 scale 在协商时需要通过 sws_isSupportedInput、sws_isSupportedOutput 分别来判断输入输出的 AVPixelFormat 支持情况。

B、帧处理时,遇到输出的 AVPixelFormat 为 AV_PIX_FMT_PAL8 时,也会做一些特殊处理。

一方面在送入 swscale 处理前,会强行指定 swscale 的输出 AVPixelFormat 为 AV_PIX_FMT_BGR8。

另一方面前面也提到,AV_PIX_FMT_PAL8 时 AVFrame.data[1]存放的是透明通道。通过代码发现,scale 滤镜直接将对AVFrame.data[1]进行指定,这源于 AV_PIX_FMT_PAL8 的特殊存储结构。

通过对 scale 滤镜分析,发现虽然其底层调用的是 swscale,为什么又要对 AV_PIX_FMT_PAL8 做这么多特殊逻辑呢?是不是 swscale 对 PAL8 有什么特殊限制呢?我们继续深入到 swscale 内部进行分析。我们先看下 sws_isSupportedInput、sws_isSupportedOutput 的实现。通过代码发现,这两个函数其实就是两个查表函数,依赖于一个 FormatEntry 表 format_entries。

libswscale/utils.c

代码语言:c
复制
typedef struct FormatEntry {
    uint8_t is_supported_in         :1;
    uint8_t is_supported_out        :1;
    uint8_t is_supported_endianness :1;
} FormatEntry;

static const FormatEntry format_entries[AV_PIX_FMT_NB] = {
    [AV_PIX_FMT_YUV420P]     = { 1, 1 },
    [AV_PIX_FMT_YUYV422]     = { 1, 1 },
    // ...
    [AV_PIX_FMT_PAL8]        = { 1, 0 },
    // ...
};

可以清晰看到,swscale 只支持 AV_PIX_FMT_PAL8 的输入,不支持 AV_PIX_FMT_PAL8 的输出。可以简单的类比为 swscale 支持 AV_PIX_FMT_PAL8 的解码,但是不支持 AV_PIX_FMT_PAL8 的编码。

现在整个问题的原因基本就清晰了。因为 scale 滤镜到 swscale 滤镜中间做了一次 AV_PIX_FMT_BGR8 转换,所以透明通道丢失了。

可能有疑问,上面说 scale 滤镜会直接指定 AVFrame.data[1](也就是透明通道),那岂不是透明通道还存在?答案是否定的,我们看代码发现,这个指定位于 sws_scale 调用前,而此时并没有透明通道信息,而且 swscale 的输出被强制指定为 AV_PIX_FMT_BGR8 ,而 AV_PIX_FMT_BGR8 没有数据在 AVFrame.data[1],所以 swscale 并没有处理AVFrame.data[1],最终输出的帧里面也不会有透明通道。

代码语言:txt
复制
/*
                       +--------------+                         +---------+
  ---------PAL8------> in             | ---------PAL8---------> in        |
                       | scale filter |                         | swscale |
  <--PAL8 loss alpha-- out            | <--BGR8 without alpha-- out       |
                       +--------------+                         +---------+
*/

3、问题解决

既然根本原因是 ffmpeg swscale 不支持 AV_PIX_FMT_PAL8 的输出,那我们支持上不就可以了? 这里并不简单,我们要支持的话,需要去修改 ffmepg 代码,实现 swscale 内部对 AV_PIX_FMT_PAL8 的处理,也需要调整链路上的 scale 滤镜实现。加上提交到社区合并,整个流程耗时会比较长。

这里我们采取了另一种牺牲效率的方式,即在帧送入 scale 滤镜前,将其 AVPixelFormat 转换为 AV_PIX_FMT_RGBA,swscale 完全支持 AV_PIX_FMT_RGBA。这样就解决了透明通道丢失的问题。

代码语言:shell
复制
ffmpeg -i 0_1.png -pix_fmt rgba -filter_complex "scale=w=100:h=100"  -y o.png

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、问题
  • 2、问题定位分析
  • 3、问题解决
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档