上一篇文章讲解了GIF格式基本内容,本文进行gif的代码分析。主要参考FFMPEG 5.0 中相关代码逻辑,一方面FFMPEG集成度比较高,通用性好,对于学习理论有更好的适用性;另一方面也是逃懒的一种表现了。
gif编码路径:libavcodecgif.c
gif解码路径:libavcodecgifdec.c
lzw解码算法:libavcodeclzw.c
lzw编码算法libavcodeclzwenc.c
本文从解码角度分别走读一下gif和lzw代码,在自己对于gif核心内容有一个大概学习,同时希望能够让大家对gif图像有一个基本了解。
GIF Decoder
FFMPEG中的定义
gif_decode_init
这段代码是GIF解码器的初始化函数,主要的作用是初始化解码器所需要的变量和数据结构。
首先,创建一个GifState结构体,并将AVCodecContext结构体中的指针赋值给s->avctx,以便于在解码过程中可以访问AVCodecContext结构体中的属性。
然后,将AVCodecContext结构体中的像素格式设置为AV_PIX_FMT_RGB32,即每个像素使用32位RGB格式表示。接着,创建一个AVFrame结构体,并将其赋值给s->frame,以便于在解码过程中可以存储解码后的像素数据。
最后,调用ff_lzw_decode_open函数,初始化LZW解压器,并将其赋值给s->lzw。如果初始化失败,则返回AVERROR(ENOMEM)错误码,表示内存分配失败。
总体来说,这段代码的作用是为GIF解码器的解码过程做好必要的准备工作,包括变量和数据结构的初始化、像素格式的设置和LZW解压器的初始化。
gif_decode_frame
“gif_decode_frame” 函数是 FFmpeg 中 GIF 解码器的一部分。FFmpeg 是一个编解码器和相关工具的库,允许开发人员编码和解码各种多媒体格式。该函数负责解码 GIF 图像的一个单独帧。
函数以 AVCodecContext 作为输入,其中包含编解码器特定的参数,以 AVFrame 作为输出帧,以 got_frame 变量作为输入,如果成功解码一帧则设置为 1,以 AVPacket 作为输入,其中包含帧的压缩数据。
函数首先检查 AVFrame 是否有效以及 AVPacket 是否包含任何数据。如果没有,则返回错误。如果 AVPacket 包含数据,则调用 gif_decode_init 函数初始化解码器,然后调用 gif_decode_next 函数解码下一个帧。如果解码成功,则将 got_frame 变量设置为 1 并返回 0。否则,返回错误。
总之,gif_decode_frame 函数是 FFmpeg 中 GIF 解码器的重要部分,允许开发人员解码 GIF 图像并从中提取单个帧。
gif_parse_next_image
这段代码实现了从 GIF 文件中读取图像的功能。它接受一个 GifState 结构体和一个 AVFrame 结构体,并返回一个整数值。
该函数首先从 GIF 文件中读取图像的帧头信息,包括图像宽度、高度、颜色深度和是否存在局部颜色表等参数。然后,该函数分配并初始化一个 LZW 上下文,用于解码图像数据。
接下来,该函数从流中读取 LZW 编码的图像数据,并使用 LZW 上下文解码它。然后,该函数根据颜色深度和颜色表信息将解码后的数据转换为像素值,并将像素值存储到 AVFrame 结构体的数据缓冲区中。
最后,该函数返回一个整数值,表示是否成功读取了图像数据。如果成功读取了图像数据,则返回 0;否则返回负数。
gif_decode_close
这段代码是GIF解码器的关闭函数,主要的作用是在解码器使用完后释放已经分配的内存,并关闭LZW解压器。
首先,获取GifState结构体指针s,该指针保存了解码器的状态信息。然后,调用ff_lzw_decode_close函数关闭LZW解压器,释放解压器所占用的内存。接着,使用av_frame_free函数释放s->frame指向的AVFrame结构体所占用的内存。最后,使用av_freep函数释放s->idx_line和s->stored_img指向的内存块,这些内存块分别用于存储解码器的中间结果和已经解码的图像数据。
最后,返回0表示函数执行成功。
总体来说,这段代码的作用是在GIF解码器使用完后,释放已经分配的内存,并关闭LZW解压器,以避免内存泄露和资源浪费。
LZW算法
LZWState
ff_lzw_decode_init
ff_lzw_decode 函数使用 LZW 压缩算法来解码给定数量的字节。该函数接受一个 LZW 上下文、一个输出缓冲区和要解码的字节数。它返回已解码的字节数。
该函数中使用的算法受 Steven A. Bennett 在 1987 年编写的 LZW GIF 解码器的启发。函数开始初始化一些变量,包括输出缓冲区的长度、当前和旧的编码值以及第一个和旧的字符。
然后函数进入一个循环,直到遇到结束码为止。在循环中,函数检查是否还有字符留在堆栈中。如果有,它会将字符从堆栈中弹出并添加到输出缓冲区中。
然后函数从 LZW 上下文中获取下一个编码。如果编码是结束码,则退出循环。如果编码是清除码,则重置 LZW 上下文,并继续循环。如果编码是有效的编码,则函数通过遍历前缀和后缀数组来解码编码,直到找到不是前缀编码的字符。然后将该字符添加到堆栈中,并更新前缀和后缀数组。
如果后缀数组变满,则函数将编码大小增加1。然后循环继续,直到遇到结束码为止。
最后,函数使用新状态更新 LZW 上下文,并返回已解码的字节数。
这段代码实现了从流中获取一个编码的功能。该函数接受一个 LZW 状态结构体,返回从流中读取的编码。
函数首先检查当前位于缓冲区的位数是否小于当前编码的位数,并且是否还有剩余的字节可以读取。如果缓冲区的位数比当前编码的位数小且没有剩余的字节可以读取,则返回结束码。
然后根据当前的 LZW 模式(GIF 或 TIFF),从流中读取字节并将其转换为编码。函数使用一个缓冲区来存储读取的字节,直到缓冲区的位数等于当前编码的位数。
最后,函数将缓冲区的位数减去当前编码的位数,并返回读取的编码。
ff_lzw_decode_tail
用于解码LZW压缩格式的数据。该函数的主要作用是解码剩余的数据(也称为“尾巴”),并返回解码后的数据的字节数。
ff_lzw_decode_open
该函数用于LZW解码器初始化。
ff_lzw_decode_close
该函数用于LZW解码器释放内存
而在Google也在skia模块中给出相关代码:
https://skia.googlesource.com/libgifcodec/+/refs/heads/main
作者简介:我是一枚爱跑步的程序猿,维护公众号和知乎专栏《MediaStack》,有兴趣可以关注,一起学习音视频知识,时不时分享实战经验。
版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。