Skip to content
ck edited this page Jun 30, 2017 · 6 revisions

Welcome to the ffjpeg wiki!

终于完成了 ffjpeg 的全部代码,暂时不考虑性能和优化,用最简单易懂的方式,给出了 jpeg 编码和解码的实现。

对源代码做下简单的解释说明:

  • bitstr
  • bmp
  • color
  • dct
  • huffman
  • jfif
  • quant
  • zigzag
  • ffjpeg

bitstr

定义并实现了一个 bit 流对象,接口仿照 c 标准库的文件读写函数设计:

void* bitstr_open (int type, char *file, char *mode);
int   bitstr_close(void *stream);
int   bitstr_getc (void *stream);
int   bitstr_putc (int c, void *stream);
int   bitstr_seek (void *stream, long offset, int origin);
long  bitstr_tell (void *stream);
int   bitstr_getb (void *stream);        // 读取一个 bit
int   bitstr_putb (int b, void *stream); // 输出一个 bit
int   bitstr_get_bits(void *stream, int n);           // 读取 n 个 bits
int   bitstr_put_bits(void *stream, int bits, int n); // 输出 n 个 bits
int   bitstr_flush(void *stream, int flag);

支持 BITSTR_MEM 和 BITSTR_FILE 两种类型的 bitstr,分别对应内存和文件。

bmp

bmp 定义并实现了 bitmap 对象,接口设计如下:

int  bmp_load  (BMP *pb, char *file);   // 从文件加载并创建一个 BMP 对象
int  bmp_create(BMP *pb, int w, int h); // 根据宽高创建一个 BMP 对象
int  bmp_save(BMP *pb, char *file);     // 保存 BMP 对象为 .bmp 文件
void bmp_free(BMP *pb);                 // 销毁 BMP 对象

bmp 对象实现了对 bmp 文件的读取和保存,注意当前仅支持 24bit bmp 文件。

color

color 模块实现了 RGB -> YUV 和 YUV -> RGB 的色彩转换,接口定义如下:

void yuv_to_rgb(int y, int u, int v, BYTE *r, BYTE *g, BYTE *b);
void rgb_to_yuv(BYTE r, BYTE g, BYTE b, int *y, int *u, int *v);

其中 RGB 的颜色分量的范围为 [0, 255],YUV 颜色分量的范围为 [-128, 127]. 代码实现时,采用乘法和移位运算取代浮点运算,算是做了简单的优化。

dct

dct 模块实现了 8x8 二维 dct 正变换和逆变换,接口定义如下:

void fdct2d8x8(int *du); // 正变换
void idct2d8x8(int *du); // 逆变换

代码实现时,采用了 dct 的快速算法,并采用了整数运算。

huffman

Huffman 模块实现了 jpeg 标准的范式哈夫曼编解码:

void huffman_stat_freq(HUFCODEITEM codelist[256], void *stream);
void huffman_encode_init(HUFCODEC *phc, int flag);
void huffman_encode_done(HUFCODEC *phc);
BOOL huffman_encode_run (HUFCODEC *phc);
BOOL huffman_encode_step(HUFCODEC *phc, int symbol);
void huffman_decode_init(HUFCODEC *phc);
void huffman_decode_done(HUFCODEC *phc);
BOOL huffman_decode_run (HUFCODEC *phc);
int  huffman_decode_step(HUFCODEC *phc);

huffman_stat_freq 为频率统计函数,可以从 stream 流中统计出一个 codelist。codelist 包含了全部符号的出现频率。

huffman_encode_init 可以从 codelist 或者 huftab 进行初始化,传入参数 flag 为 0 时,从 codelist 初始化;为 1 时,从 huftab 初始化。因为 jpeg 标准给出了推荐的 huffman 编码表(总共四个 lumin ac/dc, chrom ac/dc),可以不用统计符号表和生成 huftab。

huffman 编码的难点在于构造 huffman 树和 huffman 编码表。解码的难点在于判断 codesize,以及根据 code 值查找出 symbol。

quant

quant 模块是对量化的实现,并保存了 jpeg 标准推荐的两个量化表(lumin + chrom)。量化的原理非常简单,就是乘除法。

zigzag

zigzag 模块实现了 zigzag 的编解码。原理也很简单,利用一张索引表进行索引即可。

ffjpeg

ffjpeg 是一个简单的测试程序的实现,main 函数和命令行参数的处理,就在这里面了。

jfif

重点和难点都在 jfif 这个模块里面了。这个模块实现了对 jfif 文件的解析、加载、保存,等等功能,接口定义如下:

void* jfif_load(char *file); // 加载一个 .jpg 文件到 jfif 对象
int   jfif_save(void *ctxt, char *file); // 保存 jfif 对象到 .jpg 文件
void  jfif_free(void *ctxt); // 销毁 jfif 对象

这三个函数,仅仅实现了对 jfif 文件的解析操作,并没有做编码或解码的动作。

int   jfif_decode(void *ctxt, BMP *pb); // 解码
void* jfif_encode(BMP *pb); // 编码

jfif_decode 从一个 jfif 对象解码,并创建出一个 bmp 对象。 jfif_encode 从一个 bmp 对象编码,并创建出一个 jfif 对象。

jfif 中 DQT 这个定义的 quant 表,是按 zigzag 编码后的顺序存放,这个需要注意。

huffman 编解码的时候,还需要涉及到 category_encode / category_decode,这个可以参考 jpeg 文档,搞清楚是什么意思。

解码的流程:

  1. jfif_load 加载 .jpg 文件为 jfif 对象到内存

  2. 创建一个 bmp 对象,宽高跟 jfif 对象一致

  3. 申请 yuv buffer,宽高对齐到 16x16 方便处理

  4. 计算并维护好 yuv buffer、采样系数等相关变量

  5. 初始化 bitstr 流作为输入流

  6. 初始化 huffman 解码器,总共四个但输入流是同一个

  7. 依次处理每个 mcu 的解码,将解码数据放入 yuv buffer

---(每个 du:解码 dc -> 解码 ac -> 反 zigzag -> 反量化 -> dct 逆变换 -> 放入 yuv buffer)

  1. 转换 yuv 到 rgb,数保存到 bmp 对象的 buffer 里

  2. 保存 bmp 对象到 .bmp 文件

编码的流程

编码流程基本上是解码的逆过程,请自行研究文档和分析代码。 编码时需要注意的是,RLE 编码后,EOB 的处理,多了不行,少了也不行。这个很关键,处理不好的话,编码出来的图像就是有问题的。

如何优化

优化的事情,大家想想办法吧,我个人不太擅长。可以想到的有:

  • 减少内存拷贝,比如 du 到 yuv buffer 的拷贝是可以省的
  • yuv <-> rgb 转换有优化空间(饱和运算可以用汇编优化)
  • zigzag 可以优化,用索引去找数据,减少内存拷贝
  • huffman 解码和 bitstr 操作看能否优化下了
  • quant 操作可以和 dct 做到一起(dct 的快速算法会乘上一个系数矩阵)

rockcarry

2017-6-30