-
Notifications
You must be signed in to change notification settings - Fork 46
Home
终于完成了 ffjpeg 的全部代码,暂时不考虑性能和优化,用最简单易懂的方式,给出了 jpeg 编码和解码的实现。
- bitstr
- bmp
- color
- dct
- huffman
- jfif
- quant
- zigzag
- ffjpeg
定义并实现了一个 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 定义并实现了 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 模块实现了 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 模块实现了 8x8 二维 dct 正变换和逆变换,接口定义如下:
void fdct2d8x8(int *du); // 正变换
void idct2d8x8(int *du); // 逆变换
代码实现时,采用了 dct 的快速算法,并采用了整数运算。
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 模块是对量化的实现,并保存了 jpeg 标准推荐的两个量化表(lumin + chrom)。量化的原理非常简单,就是乘除法。
zigzag 模块实现了 zigzag 的编解码。原理也很简单,利用一张索引表进行索引即可。
ffjpeg 是一个简单的测试程序的实现,main 函数和命令行参数的处理,就在这里面了。
重点和难点都在 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 文档,搞清楚是什么意思。
-
jfif_load 加载 .jpg 文件为 jfif 对象到内存
-
创建一个 bmp 对象,宽高跟 jfif 对象一致
-
申请 yuv buffer,宽高对齐到 16x16 方便处理
-
计算并维护好 yuv buffer、采样系数等相关变量
-
初始化 bitstr 流作为输入流
-
初始化 huffman 解码器,总共四个但输入流是同一个
-
依次处理每个 mcu 的解码,将解码数据放入 yuv buffer
---(每个 du:解码 dc -> 解码 ac -> 反 zigzag -> 反量化 -> dct 逆变换 -> 放入 yuv buffer)
-
转换 yuv 到 rgb,数保存到 bmp 对象的 buffer 里
-
保存 bmp 对象到 .bmp 文件
编码流程基本上是解码的逆过程,请自行研究文档和分析代码。 编码时需要注意的是,RLE 编码后,EOB 的处理,多了不行,少了也不行。这个很关键,处理不好的话,编码出来的图像就是有问题的。
优化的事情,大家想想办法吧,我个人不太擅长。可以想到的有:
- 减少内存拷贝,比如 du 到 yuv buffer 的拷贝是可以省的
- yuv <-> rgb 转换有优化空间(饱和运算可以用汇编优化)
- zigzag 可以优化,用索引去找数据,减少内存拷贝
- huffman 解码和 bitstr 操作看能否优化下了
- quant 操作可以和 dct 做到一起(dct 的快速算法会乘上一个系数矩阵)
rockcarry
2017-6-30