前言
这是大一写过的一个小项目,现在大三,重新实现了一下。这是原来的链接,可以看一下效果,思路和现在的一样。 扩展性、健壮性比原来更好,思路也更清晰了。当时只想花里胡哨,这次重心放在质量、功能上。 后续会不断改进它,直到它贴近实际。
项目分析
项目围绕实现压缩、解压缩。 我们遵循模块间低耦合、模块内高内聚的原则来分析;代码尽量保证可移植性。
模块划分
-
压缩/解压缩均通过哈夫曼编码算法来实现,所以我们的第一个模块为算法模块。 -
实际的任务流程需要一个模块来控制,负责流程的控制,提供简单可用的接口,划分为接口模块。 -
在进行编码时,我们需要进行字节和位串的转换,这就需要位的操作,C语言没有提供这样的函数,需要自己实现,所以划分为位操作模块。 -
对于任一个模块,我们希望给它一个输入,产生一个结果,而不用它考虑输入来自哪里、输出到了哪里。所以我们需要一个流处理模块来集中解决这个问题,这样也可以降低各模块的耦合程度。 -
为了方便, 我们需要一个打印错误信息的模块,就把它作为错误处理模块。 -
为了出现错误时更方便排查,我们增加一个测试模块, 实际上是将各个模块节点的IO进行记录,方便对比排查。
将项目划分为以下几个模块:
- 算法模块
- 接口模块
- 流处理模块
- 错误处理模块
- 位操作模块
- 测试模块
实测中需要频繁地使用缓冲区,包括字符串缓冲区、字节缓冲区、位缓冲区 ,每次都要实现一遍非常不方便,好在数量不多,后续会增加缓冲区模块。
模块功能分析
1.算法模块
算法模块的任务是通过哈夫曼编码得到编码表,输入和输出进行功能分析。这个模块聚合程度很高,我们尽可能不去动它。
- 统计字节频率
- 编码需要构造哈夫曼树, 而构造树需要有weight(权值),而权值需要扫描输入流来进行统计,显然这一工作与编码独立,所以我们将它作为单独功能。
- 构造哈夫曼树
- 哈夫曼编码
- 得到哈夫曼编码表。
- 这里的表是抽象类型,实际上用二级指针数组存储编码字符串指针。
- 编码的结果写入输出流,流均由流处理模块指定。
2.接口模块
这个模块负责将各个模块连接,提供压缩、解压缩的接口。
3.流处理模块
这个模块负责压缩文件写入的格式、解析压缩文件、错误打印格式等。
4.错误处理模块
较为简单,但注意打印的消息要写入标准错误流(STDERR )而非标准输出流(STDOUT )。
5. 位操作模块
提供位操作。
6.测试模块
这个模块虽然名义上为模块,但实际上却渗透到各个模块中,我们通过宏定义开关来降低它与其他模块的耦合度。
流程图
这里主要描述压缩、解压、压缩文件格式。
压缩逻辑流程图
读取原文件
统计字节频率
字节权值
哈夫曼树
构造哈夫曼树
编码
按格式构造压缩文件
解压逻辑流程图
按格式解析压缩文件
得到
编码表
余码
原文件大小
根据编码还原原文件
计算压缩率
名词/行为解释
压缩文件格式
压缩文件包括:
例子
假设有个压缩文件1.hfm,那么在文件中的数据是这样的:
10000
1824
00 11111111011010110000
01 11111111011010110001
02 11111111011010110010
03 11111111011010110011
04 11111111011010110100
05 11111111011010110101
......
FE 1111111101101010110
FF 1111111101101010111
�fn�?�R_p�[/�n��`_�����]W�CI{z��<���P����kK��O������!&��,t?���p������
��a`nP
......
第一行是余码;第二行是原文件字节数;接下来的256行([0, 255])是编码表;最后的N行是压缩后的数据,没错,它是乱七八糟的。
代码解读
实际实现时,我们按照由易到难、由具体到抽象的顺序来实现。 很明显,错误处理模块、位操作模块、算法就很具体,而流格式化的模块就相对抽象。
错误处理模块
用到了变参函数。 err.c
#include "err.h"
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
void errPrint(const char *format, va_list arg_list)
{
char buf[ERR_BUF_SIZE];
vsnprintf(buf, sizeof(buf), format, arg_list);
fprintf(stderr, "%s", buf);
}
void errExit(const char *format, va_list arg_list)
{
errPrint(format, arg_list);
exit(EXIT_FAILURE);
}
void errCaller(void (*errFunc)(const char *, va_list), const char *format, ...)
{
va_list arg_list;
va_start(arg_list, format);
errFunc(format, arg_list);
va_end(arg_list);
}
err.h
#ifndef ERR_H
#define ERR_H
#define ERR_BUF_SIZE 4096
#include <stdarg.h>
void errPrint(const char *format, va_list arg_list);
void errExit(const char *format, va_list arg_list);
void errCaller(void (*errFunc)(const char *, va_list), const char *format, ...);
#endif
位操作模块
bit.c
#include "bit.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include "err.h"
void setbit(Byte *pbyte, size_t ordinal, bool value)
{
Byte mask_AND_to_0[8] = {
0xFE,
0xFD,
0xFB,
0xF7,
0xEF,
0xDF,
0xBF,
0x7F
};
Byte mask_OR_to_1[8] = {
0x01,
0x02,
0x04,
0x08,
0x10,
0x20,
0x40,
0x80
};
if (value == 0)
(*pbyte) &= mask_AND_to_0[ordinal];
else if (value == 1)
(*pbyte) |= mask_OR_to_1[ordinal];
}
int getbit(Byte byte, size_t oridinal)
{
return (byte >> (oridinal - 1)) | 0x00;
}
static size_t rest_bit_quantity(Byte *bytebit_buf, size_t buf_size, size_t byte_end, size_t bit_end)
{
return (buf_size - byte_end) * 8 - bit_end;
}
bool bitcat(Byte *bytebit_buf, size_t buf_size, size_t *byte_end, size_t *bit_end, const char *_01_str)
{
size_t i;
if (strlen(_01_str) > rest_bit_quantity(bytebit_buf, buf_size, *byte_end, *bit_end))
return false;
for (i = 0; _01_str[i] != '\0'; ++i) {
setbit(&(bytebit_buf[*byte_end]), 7 - *bit_end, _01_str[i] - '0');
++(*bit_end);
if (*bit_end > 7) {
(*bit_end) %= 8;
++(*byte_end);
}
}
return true;
}
bit.h
#ifndef BIT_H
#define BIT_H
#include <stdbool.h>
#include <stddef.h>
typedef unsigned char Byte;
#define BYTE_NUM 256
#define BYTE_MIN 0
#define BYTE_MAX 255
void setbit(Byte *pbyte, size_t ordinal, bool value);
int getbit(Byte byte, size_t oridinal);
bool bitcat(Byte *bytebit_buf, size_t buf_size, size_t *byte_end, size_t *bit_end, const char *_01_str);
#endif
包裹函数
在实现剩下的模块之前,我们将一些函数封装成包裹函数,这样不用频繁验证返回值,减少分散我们的注意力。
除了包裹函数还有以下两个自定义函数:
mFclose() ,即many Fclose,可以一次性关闭多个文件(注意最后一个参数必须是NULL ,否则会运行时错误而中止程序)。itoa_() ,由于linux下没有itoa() 函数,便自己实现了一下,并且扩展了它的功能,可选补齐前导0 (通过min_length 参数)。在字节转位串的时候非常方便。加一条_是为了在其他系统编译时不与标准库的itoa() 冲突。
由于FILE* 无法得知打开的文件名,在报错误处理时很不方便,为了保留文件名我们将FILE 封装为File ,并为它适配了相关的包裹函数。
pkg.c
#include "pkg.h"
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "err.h"
File *Fopen(const char *filename, const char *mode)
{
File *file = (File *)malloc(sizeof(File));
file->pfile = fopen(filename, mode);
if (file->pfile == NULL) errCaller(errExit, ERR_MSG_FOPEN(file));
snprintf(file->filename, sizeof(file->filename), "%s", filename);
snprintf(file->mode, sizeof(file->mode), "%s", mode);
return file;
}
int Feof(File *stream) { return feof(stream->pfile); }
void Rewind(File *stream) { rewind(stream->pfile); }
void Fclose(File *stream)
{
if (stream == NULL) return;
if (fclose(stream->pfile) != 0) errCaller(errExit, ERR_MSG_FCLOSE(stream));
free(stream);
}
void mFclose(File *stream, ...)
{
va_list arg_list;
va_start(arg_list, stream);
Fclose(stream);
for (;;) {
stream = va_arg(arg_list, File *);
if (stream == NULL) break;
Fclose(stream);
}
va_end(arg_list);
}
ssize_t Fread(void *mem, size_t elem_size, size_t elem_count, File *istream)
{
return fread(mem, elem_size, elem_count, istream->pfile);
}
ssize_t Fwrite(void *mem, size_t elem_size, size_t elem_count, File *ostream)
{
ssize_t count = fwrite(mem, elem_size, elem_count, ostream->pfile);
if (count < elem_count) errCaller(errExit, ERR_MSG_FWRITE(ostream));
return count;
}
int Fscanf(File *istream, const char *format, ...)
{
va_list arg_list;
va_start(arg_list, format);
int ret = vfscanf(istream->pfile, format, arg_list);
va_end(arg_list);
return ret;
}
int Fprintf(File *ostream, const char *format, ...)
{
va_list arg_list;
va_start(arg_list, format);
int ret = vfprintf(ostream->pfile, format, arg_list);
va_end(arg_list);
return ret;
}
char *itoa_(size_t value, char *result, size_t radix, size_t min_length)
{
const char digits[] = "0123456789abcdef";
char buf[66];
const size_t end = sizeof(buf) - 1;
const size_t prefix_0_beg = end - min_length;
size_t beg = end;
buf[end] = '\0';
size_t digit, i;
for (; value > 0;) {
digit = value % radix;
buf[--beg] = digits[digit];
value /= radix;
}
for (i = prefix_0_beg; i < beg; ++i)
buf[i] = '0';
if (beg > prefix_0_beg) beg = prefix_0_beg;
strcpy(result, &buf[beg]);
strcat(result, "\0");
return result;
}
pkg.h
#ifndef PKG_H
#define PKG_H
#include <stdio.h>
#define countof(array) (sizeof(array) / sizeof(array[0]))
#define FILENAME_SIZE 512
#define MODE_SIZE 4
typedef struct File {
FILE *pfile;
char filename[FILENAME_SIZE];
char mode[MODE_SIZE];
} File;
#define ERR_MSG_FOPEN(pfile) "Failed to open file %s.", pfile->filename
#define ERR_MSG_FCLOSE(pfile) "Failed to close file %s.", pfile->filename
#define ERR_MSG_FWRITE(pfile) "Failed to write the whole memory to file %s.", pfile->filename
int Feof(File *stream);
void Rewind(File *stream);
File *Fopen(const char *file, const char *mode);
void Fclose(File *stream);
int Fscanf(File *istream, const char *format, ...);
int Fprintf(File *ostream, const char *format, ...);
void mFclose(File *stream, ...);
ssize_t Fread(void *mem, size_t elem_size, size_t elem_count, File *istream);
ssize_t Fwrite(void *mem, size_t elem_size, size_t elem_count, File *ostream);
char *itoa_(size_t value, char *result, size_t radix, size_t min_length);
#endif
算法模块
到这里有两问题一直很纠结,
- 变量名是越长越好,还是越短越好?太长读代码就像阅读题,太短则像文言文,我宁愿它长一点。
- 用驼峰还是下划线?考虑到变量名长,下划线法单词辨识度应该更高。
huffman.c
#include "huffman.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "bit.h"
#include "err.h"
#include "huffman.h"
static void select_min(huffman_tree tree, const size_t curr_node_index, size_t *min_1, size_t *min_2)
{
size_t i, min_weight = SIZE_MAX;
for (i = 0; i < curr_node_index; ++i)
if (tree[i].parent == HAVE_NO_PARENT && tree[i].weight < min_weight) {
min_weight = tree[i].weight;
*min_1 = i;
}
for (i = 0, min_weight = SIZE_MAX; i < curr_node_index; ++i)
if (tree[i].parent == HAVE_NO_PARENT && tree[i].weight < min_weight && i != *min_1) {
min_weight = tree[i].weight;
*min_2 = i;
}
}
void create_huffman_tree(huffman_tree *pTree, const size_t *weight, size_t weight_elem_num)
{
size_t i, min_1, min_2;
const size_t num_leafNode = weight_elem_num;
const size_t num_allNode = 2 * num_leafNode - 1;
*pTree = (huffman_tree)malloc(num_allNode * sizeof(huffman_tree_node));
huffman_tree tree = *pTree;
for (i = 0; i < num_allNode; ++i) tree[i].parent = HAVE_NO_PARENT;
for (i = 0; i < num_leafNode; ++i) tree[i].weight = weight[i];
for (i = num_leafNode; i < num_allNode; ++i) {
select_min(tree, i, &min_1, &min_2);
tree[min_1].parent = i;
tree[min_2].parent = i;
tree[i].lchild = min_1;
tree[i].rchild = min_2;
tree[i].weight = tree[min_1].weight + tree[min_2].weight;
}
}
char **huffman_encode(const huffman_tree tree, size_t num_leafNode)
{
size_t parent, curr, i, start;
const size_t n = num_leafNode;
char **encode_result = (char **)malloc(n * sizeof(char *));
char *one_code = (char *)alloca(n * sizeof(char));
one_code[n - 1] = '\0';
for (i = 0; i < n; ++i) {
curr = i;
start = n - 1;
parent = tree[curr].parent;
while (parent != HAVE_NO_PARENT) {
if (tree[parent].lchild == curr)
one_code[--start] = '0';
else
one_code[--start] = '1';
curr = parent;
parent = tree[parent].parent;
}
encode_result[i] = (char *)malloc((n - start) * sizeof(char));
strcpy(encode_result[i], &one_code[start]);
}
return encode_result;
}
void huffman_decode(char *_01_str, size_t *_01_str_end, const char **encode_result, Byte *result,
size_t *result_end)
{
size_t beg, i;
*result_end = 0;
bool find;
for (beg = 0; _01_str[beg] != '\0';) {
for (i = 0, find = false; i < BYTE_NUM; ++i) {
size_t len = strlen(encode_result[i]);
if (strncmp(encode_result[i], &_01_str[beg], len) == 0) {
result[*result_end] = i;
++(*result_end);
beg += len;
find = true;
break;
}
}
if (!find) break;
}
char tmp_str[9];
strcpy(tmp_str, &_01_str[beg]);
strcpy(_01_str, tmp_str);
*_01_str_end = strlen(_01_str);
}
huffman.h
#ifndef HUFFMAN_H
#define HUFFMAN_H
#define HAVE_NO_PARENT -1
#include "bit.h"
typedef struct huffman_node_data_type {
Byte data;
} huffman_node_data_type;
typedef struct huffman_tree_node {
huffman_node_data_type data;
size_t weight;
int lchild, rchild, parent;
} huffman_tree_node, *huffman_tree;
void create_huffman_tree(huffman_tree *pTree, const size_t *weight, size_t weight_elem_num);
char **huffman_encode(const huffman_tree tree, size_t num_leafNode);
void huffman_decode(char *_01_str, size_t *_01_str_end, const char **encode_result, Byte *result,
size_t *result_end);
#endif
接口模块
interface.c
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include "bit.h"
#include "huffman.h"
#include "pkg.h"
#include "stream_manager.h"
void compress(File *istream, File *ostream, const char **encode_result)
{
Byte read_buf[IO_BUF_SIZE];
Byte bytebit_buf[BYTE_BIT_BUF_SIZE];
ssize_t count;
size_t i, byte_end, bit_end, origin_size;
reserve_header(ostream);
output_huffmanCode(ostream, encode_result);
origin_size = 0;
for (byte_end = bit_end = 0; !Feof(istream);) {
count = Fread(read_buf, sizeof(read_buf[0]), countof(read_buf), istream);
if (count <= 0) continue;
origin_size += count * sizeof(read_buf[0]);
for (i = 0; i < count; ++i) {
const char *one_code = encode_result[read_buf[i]];
if (bitcat(bytebit_buf, sizeof(bytebit_buf), &byte_end, &bit_end, one_code))
continue;
else {
fflush_bytebit_buffer(bytebit_buf, &byte_end, ostream);
--i;
}
}
}
fflush_bytebit_buffer(bytebit_buf, &byte_end, ostream);
char surplus[9];
itoa_(bytebit_buf[byte_end], surplus, 2, 0);
surplus[bit_end] = '\0';
fill_header(ostream, surplus, origin_size);
}
void decompress(File *istream, File *ostream)
{
char surplus[9];
size_t origin_size;
const char **encode_result = (const char **)parse_compress_header(istream, surplus, &origin_size);
Byte read_buf[IO_BUF_SIZE], decode_buf[IO_BUF_SIZE];
char _01_str[_01_STR_BUF_SIZE];
size_t i, _01_str_end, decode_buf_end;
ssize_t count;
for (decode_buf_end = _01_str_end = 0; !Feof(istream);) {
count = Fread(read_buf, sizeof(read_buf[0]), countof(read_buf), istream);
if (count <= 0) continue;
for (i = 0; i < count;) {
if (sizeof(_01_str) - 1 - _01_str_end >= 8) {
itoa_(read_buf[i], &_01_str[_01_str_end], 2, 8);
_01_str_end += 8;
++i;
} else {
huffman_decode(_01_str, &_01_str_end, encode_result, decode_buf, &decode_buf_end);
Fwrite(decode_buf, sizeof(decode_buf[0]), decode_buf_end, ostream);
}
}
}
strcat(_01_str, surplus);
huffman_decode(_01_str, &_01_str_end, encode_result, decode_buf, &decode_buf_end);
Fwrite(decode_buf, sizeof(decode_buf[0]), decode_buf_end, ostream);
}
interface.h
#ifndef INTERFACE_H
#define INTERFACE_H
#include "pkg.h"
void compress(File *istream, File *ostream, const char **encode_result);
void decompress(File *istream, File *ostream);
#endif
流处理模块
stream_manager.c
#include "stream_manager.h"
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "bit.h"
#include "err.h"
#include "pkg.h"
void count_byte_weight(File *istream, size_t *byte_times, size_t byte_times_size)
{
size_t i;
memset(byte_times, 0, byte_times_size);
Byte bytes[IO_BUF_SIZE];
ssize_t count;
for (; !Feof(istream);) {
count = Fread(bytes, sizeof(bytes[0]), countof(bytes), istream);
if (count <= 0) continue;
for (i = 0; i < count; ++i) {
++byte_times[bytes[i]];
}
}
}
void output_huffmanCode(File *ostream, const char **encode_result)
{
size_t i;
const size_t num_leafNode = BYTE_NUM;
for (i = 0; i < num_leafNode; ++i) {
Fprintf(ostream, O_FORMAT_BODY_HUFFMAN_CODE, i, encode_result[i]);
}
}
char **parse_compress_header(File *istream, char *surplus, size_t *origin_size)
{
Fscanf(istream, I_FORMAT_HEADER_SURPLUS, surplus);
Fscanf(istream, I_FORMAT_HEADER_ORIGIN_SIZE, origin_size);
char **encode_result = (char **)malloc(sizeof(char *) * BYTE_MAX);
char one_code[HUFFMAN_CODE_MAX_LEN];
size_t i, unused;
for (i = 0; i <= BYTE_MAX; ++i) {
Fscanf(istream, I_FORMAT_BODY_HUFFMAN_CODE, &unused, one_code);
encode_result[i] = (char *)malloc(strlen(one_code) * sizeof(char));
strcpy(encode_result[i], one_code);
}
return encode_result;
}
ssize_t fflush_bytebit_buffer(Byte *bytebit_buf, size_t *byte_end, File *ostream)
{
ssize_t count = Fwrite(bytebit_buf, sizeof(bytebit_buf[0]), *byte_end, ostream);
bytebit_buf[0] = bytebit_buf[*byte_end];
*byte_end = 0;
return count;
}
void reserve_header(File *ostream)
{
Fprintf(ostream, O_FORMAT_HEADER_SURPLUS, "");
Fprintf(ostream, O_FORMAT_HEADER_ORIGIN_SIZE, (long)0);
}
static void rewind_header(File *ostream) { rewind(ostream->pfile); }
void fill_header(File *ostream, const char *surplus, size_t origin_size)
{
rewind_header(ostream);
Fprintf(ostream, O_FORMAT_HEADER_SURPLUS, surplus);
Fprintf(ostream, O_FORMAT_HEADER_ORIGIN_SIZE, origin_size);
}
stream_manager.h
#ifndef STREAM_MANAGER_H
#define STREAM_MANAGER_H
#include "bit.h"
#include "pkg.h"
#define HUFFMAN_CODE_MAX_LEN BYTE_NUM
#define IO_BUF_SIZE 4096
#define BYTE_BIT_BUF_SIZE 4096
#define _01_STR_BUF_SIZE 4096
#define I_FORMAT_HEADER_SURPLUS "%8s\n"
#define O_FORMAT_HEADER_SURPLUS "%-8s\n"
#define I_FORMAT_HEADER_ORIGIN_SIZE "%20ld\n"
#define O_FORMAT_HEADER_ORIGIN_SIZE "%-20ld\n"
#define I_FORMAT_BODY_HUFFMAN_CODE "%02lX\t%s\n"
#define O_FORMAT_BODY_HUFFMAN_CODE "%02lX\t%s\n"
void count_byte_weight(File *istream, size_t *byte_times, size_t byte_times_size);
void output_huffmanCode(File *ostream, const char **encode_result);
char **parse_compress_header(File *istream, char *surplus, size_t *origin_size);
ssize_t fflush_bytebit_buffer(Byte *bytebit_buf, size_t *byte_end, File *ostream);
void reserve_header(File *ostream);
void fill_header(File *ostream, const char *surplus, size_t origin_size);
#endif
main函数
main函数给了一个使用的例子。 main.c
#include <stdlib.h>
#include "bit.h"
#include "err.h"
#include "huffman.h"
#include "interface.h"
#include "pkg.h"
#include "stream_manager.h"
int main(int argc, char **argv)
{
File *istream, *ostream;
const char *src_file = argv[1], *dest_file = argv[2];
istream = Fopen(src_file, "r");
size_t byte_times[BYTE_NUM];
count_byte_weight(istream, byte_times, sizeof(byte_times));
huffman_tree tree;
create_huffman_tree(&tree, byte_times, countof(byte_times));
const char **encode_result_1 = (const char **)huffman_encode(tree, BYTE_NUM);
Rewind(istream);
ostream = Fopen(dest_file, "w");
compress(istream, ostream, encode_result_1);
free(encode_result_1);
mFclose(istream, ostream, NULL);
const char new_file[] = "decompress.txt";
istream = Fopen(dest_file, "r");
ostream = Fopen(new_file, "w");
decompress(istream, ostream);
mFclose(istream, ostream, NULL);
return 0;
}
makefile
CC = gcc
BIN = main
OBJ = bit.o err.o huffman.o interface.o main.o pkg.o stream_manager.o
LIB =
INC =
FLAGS = $(INC) -Wall
main: $(OBJ)
@$(CC) $(OBJ) -o $(BIN) $(LIB)
bit.o: bit.c bit.h err.h
@$(CC) -c bit.c -o bit.o $(FLAGS)
err.o: err.c err.h
@$(CC) -c err.c -o err.o $(FLAGS)
huffman.o: huffman.c huffman.h bit.h err.h
@$(CC) -c huffman.c -o huffman.o $(FLAGS)
interface.o: interface.c bit.h pkg.h stream_manager.h
@$(CC) -c interface.c -o interface.o $(FLAGS)
main.o: main.c bit.h huffman.h interface.h pkg.h stream_manager.h
@$(CC) -c main.c -o main.o $(FLAGS)
pkg.o: pkg.c pkg.h err.h
@$(CC) -c pkg.c -o pkg.o $(FLAGS)
stream_manager.o: stream_manager.c stream_manager.h pkg.h bit.h err.h
@$(CC) -c stream_manager.c -o stream_manager.o $(FLAGS)
.PHONY: clean
clean:
@rm $(OBJ) $(BIN)
测试模块
这个部分我还没有实现,但是测试了一个618M的mkv视频文件。
可以看到,100%地还原了。但是压缩后的文件更大了
这是因为视频文件本身就是压缩格式的,再压缩它也没有效果。 上次做的压缩率在70%~75%,这次的理论上要高一点,因为为了赶工减少了一些优化步骤,后面会再改进。
后续
上面的代码在我的64位 debian11上编译0报错0警告,gcc版本为10.2.1。 windows上没有再测试了,应该也能通过。
同样也能看到,压缩+解压速度慢得惊人,23min,这是我们不可能接受的。 后续会进行效率提升,应该会用到多线程/多进程。再进一步的,字符串处理可以用内联汇编改写。无论是哪种措施,无疑会降低我们程序的可移植性。 为了提高移植性,后面或许会选择C++,虽然我不怎么用C++。
|