将二进制文件转换为 C 语言数组的 100 种方法
develop c
How can I convert a binary file to the text declaring a C/C++ array with that content?
背景
标题党(英语:clickbait,又称钓鱼式标题、诱饵式标题),指网络中故意用较为夸张、耸动的文章标题以及缩略图以吸引网友点击观看文章、视频或帖子的人。
– 维基百科
有一个很大的 yuv 文件,如果直接 fread()
需要花费大量时间,特别是文件读写速度很慢的场景
(例如交叉编译时调试代码从一块开发板上读取电脑端的文件)。
以下内容绝对不与 Stackoverflow 同名问题 重复。
可以通过以下代码获取测试用的 yuv 文件:
ffmpeg -f rawvideo -pix_fmt yuv420p -s 1080x720 -i /dev/urandom -vframes 10 -f rawvideo 1080x720.yuv
可以通过以下代码检测生成的 yuv 文件和原始的 yuv 文件是否一致:
sha256sum out.yuv | cut -d' ' -f1 | diff <(sha256sum 1280x720.yuv | cut -d' ' -f1) -
方案
shell
Sharp shell programmers should take note of the following…
– Larry Wall Perl
需要安装一个软件把二进制文件转换为 8/10/16 进制编码。可以选择的选项包括:
- od: 这是 POSIX 系统预装的软件之一,由以下软件包提供:
- xxd: 由 vim 提供
- hexdump: 由 util-linux 提供
- hexyl: 这是 RIIR 派的软件之一,推荐日常使用
以下用 od
和 16 进制编码作示范。
scripts/generate-yuv.h.sh
:
#!/usr/bin/env sh
# dump yuv file to a C array to fasten fread()
echo "// Don't edit this file!
// generated by $0
#include <stdlib.h>
char yuv[] = {"
od -vAn -tx1 ${1:-1280x720.yuv} | sed 's/ /, 0x/g' | sed 's/$/,/g' | cut -c3-
echo '};
const size_t yuv_len = sizeof(yuv);
'
scripts/yuv.c
:
#if 0
gcc "$0" -o a && exec a "$@"
#endif
#include <stdio.h>
#include <stdlib.h>
#include "yuv.h"
/**
* compare hashes of output and original yuv to check if yuv.h is correct
*/
int main(int argc, char *argv[])
{
fwrite(yuv, sizeof(yuv[0]), yuv_len, stdout);
return EXIT_SUCCESS;
}
chmod +x scripts/generate-yuv.h.sh scripts/yuv.c
scripts/generate-yuv.h.sh 1280x720.yuv > yuv.h
scripts/yuv.c > out.yuv
点评:超级简单的方案,绝大部分 Linux 开发者在几分钟之内就能一边看着 od
的输出一边写出代码。不过 shell 脚本里的 4 次进程分叉使得性能差强人意。
perl
Practicing Perl Programmers should take note of the following…
– Larry Wall Perl
完全可以用专门的数据驱动编程语言把文本处理的进程分叉去掉。
scripts/generate-yuv.h.pl
:
#!/usr/bin/env perl
exit if $#ARGV < 0;
$_ = `od -vAn -tx1 $ARGV[0]`;
exit 1 if $? != 0;
chomp;
s/ /, 0x/g;
s/$/,/gm;
s/^, /\t/gm;
$template = <<"EOF";
/**
* Don't edit this file!
* generated by $0
*/
#include <stdlib.h>
char yuv[] = {
$_
};
const size_t yuv_len = sizeof(yuv);
EOF
print $template;
__END__
=head1 NAME
generate-yuv.h.pl - dump yuv file to a C array to fasten fread()
点评:也是很简单的方案,做过数据驱动编程的开发者对这样的需求已经见怪不怪了。
同样几分钟就可以写好代码。性能瓶颈在 od
上。
C
Cerebral C and C++ programmers should take note of the following…
– Larry Wall Perl
这里直接推荐 Adobe 公司的 bin2c:
$ bin2c yuv < /the/path/of/352x288.yuv > yuv.h
$ cat yuv.h
#include <stdlib.h>
const char yuv[] = "\
...
";
const size_t yuv_len = sizeof(yuv) - 1;
官方给出了速度比较:
xxd 13.9045 Mb/s
bin2c 547.613 Mb/s
类似的实现网上还有好几个。不一一点评了。
ld
As a result, you have many choices to control its behavior.
– ld
要是只有以上几个普通方案,笔者倒真不至于浪费时间写这篇博文—— @harieamjari 在 一个帖子 里给了一个令笔者高呼 awesome 的回答:
scripts/yuv.c
#if 0
gcc "$0" yuv.o -o a && exec a "$@"
#endif
#include <stdio.h>
#include <stdlib.h>
extern char _binary_out_yuv_start[];
extern char _binary_out_yuv_end[];
/**
* compare hashes of output and original yuv to check if yuv.h is correct
*/
int main(){
char *s = _binary_out_yuv_start, *e =_binary_out_yuv_end;
fwrite(s, sizeof(s[0]), (size_t)(e - s), stdout);
return EXIT_SUCCESS;
}
$ chmod +x scripts/yuv.c
$ ld -r -bbinary 1280x720.yuv -oyuv.o
$ nm yuv.o
XXXXXXXXXXXXXXXX D _binary_yuv_end
XXXXXXXXXXXXXXXX A _binary_yuv_size
0000000000000000 D _binary_yuv_start
$ scripts/yuv.c > out.yuv
总结
不止一种方法去做一件事。
– Larry Wall Perl
如果你愿意你真的可以找到 100 种方法——把 od
换成其它工具,使用 其他进制而非 16
进制,使用别的语言来实现这一功能。
当然这样的方法就算数量再多也远不如最后一种给笔者带来的震撼。
对开发工具的熟练掌握永远不是一件坏事。感谢阅读。