【问题标题】:Is there a fast way to read alternate bytes in dd有没有一种快速的方法来读取 dd 中的备用字节
【发布时间】:2019-04-24 18:58:12
【问题描述】:

我正在尝试在循环中使用 dd 读取二进制文件中的所有其他字节对,但速度非常慢。

我在 BusyBox 嵌入式设备上有一个二进制文件,其中包含 rgb565 格式的数据。每个像素是 2 个字节,我正在尝试读取每个其他像素以进行非常基本的图像缩放以减小文件大小。

整体大小为 640x480,通过循环 dd 与 960 字节块大小,我已经能够读取每隔“行”的像素。但是,即使在我的本地系统上,通过循环使用 2 字节块大小来对剩余的所有其他“列”执行相同的操作也是非常慢

i=1
while [[ $i -le 307200 ]]
do
        dd bs=2 skip=$((i-1)) seek=$((i-1)) count=1 if=./tmpfile >> ./outfile 2>/dev/null
        let i=i+2
done

虽然我得到了我期望的输出,但这个方法是不可用的。

是否有一些不太明显的方法可以让 dd 快速复制每隔一对字节?

遗憾的是,我无法控制编译到 BusyBox 中的内容。我对其他可能的方法持开放态度,但我可以使用 dd/sh 解决方案。例如,一个构建省略了 head -c...

感谢所有反馈。我会逐一检查各种建议并查看结果。

【问题讨论】:

  • 为什么是seek=$((i-2))?为什么不简单地 seek=2 每次都跳过两个字节?你根本不应该增加i;默认情况下,dd 会在它停止的地方继续阅读。 (另外,我个人的偏好是使用of=./outfile,而不是重定向标准输出。)
  • 你能用perl吗?
  • 另外,你能用python吗?
  • 您知道系统上命令 (argmax) 的最大参数长度吗?通常你可以通过sysctl -a | grep -i arg找到它
  • 没有 perl,没有 python # sysctl -a | grep -i arg sysctl: error reading key 'net.ipv4.route.flush': Permission denied 我可以尝试 seek=2 - 知道它会从停止的地方继续,这肯定会导致更优化的操作。在这种情况下,重定向标准输出只是一个示例。

标签: shell sh busybox dd


【解决方案1】:

只要您不需要处理换行符和空字节,跳过所有其他字符对于 sed 或 awk 等工具来说是微不足道的。但是 Busybox 对 sed 和 awk 中空字节的支持很差,我认为您根本无法应对它们。可以处理换行符,但这是一个巨大的痛苦,因为有 16 种不同的组合需要处理,具体取决于 4 字节块中的每个位置是否是换行符。

由于任意二进制数据很麻烦,让我们转换为十六进制或八进制!我将从bin2hex and hex2bin scripts by Stéphane Chazelas 中汲取灵感。由于我们不关心中间格式,所以我将使用八进制,它处理起来要简单得多,因为最后一步使用了仅支持八进制的printf。 Stéphane 的hex2bin 使用 awk 进行十六进制到八进制的转换; oct2bin 可以使用 sed。所以最后你需要shodsedprintf。 我认为您无法避免printf:输出空字节至关重要。虽然od 是必不可少的,但它的大多数选项都不是,因此应该可以调整此代码以支持非常精简的 od,并进行更多的后处理。

od -An -v -t o1 -w4 |
sed 's/^ \([0-7]*\) \([0-7]*\).*/printf \\\\\1\\\\\2/' |
sh

与基于 dd 的方法相比,它如此快的原因是 BusyBox 在父进程中运行 printf,而 dd 需要自己的进程。分叉很慢。如果我没记错的话,有一个编译选项可以让 BusyBox fork 用于所有实用程序。在这种情况下,我的方法可能会和你的一样慢。这是使用dd 的中间方法,它无法避免分叉,但至少可以避免每次打开和关闭文件。它应该比你的快一点。

i=$(($(wc -c <"$1") / 4))
exec <"$1"
dd ibs=2 count=1 conv=notrunc 2>/dev/null
while [ $i -gt 1 ]; do
  dd ibs=2 count=1 skip=1 conv=notrunc 2>/dev/null
  i=$((i - 1))
done

【讨论】:

  • 由于缺少 base64 转换选项,我实际上已经使用可用工具对十六进制进行了相关操作。我将不得不进一步调查这条线。
  • @anti_climax 十六进制通常比八进制更容易使用,但是八进制与二进制之间的转换要容易得多。 Base64(或 uuencode,即使 base64 不可用,也可能可用,我已经使用 uuencode 构建了没有 -m 选项的 BB)在这里并没有真正帮助,因为您希望字节按 2 或 4 分组,但是 base64 和 uuencode 按 3 分组。
  • 我的意思是从更一般的意义上说,由于缺少 base64 编码选项,我使用有限的工具集将二进制数据操作为其他形式,所以如果不是的话,我至少熟悉整体机制这里需要的细节。
【解决方案2】:

不知道使用 BusyBox 是否会更快甚至可能,但这是一个想法......

#!/bin/bash

# Empty result file
> result

exec 3< datafile
while true; do
    # Read 2 bytes into file "short"
    dd bs=2 count=1 <&3 > short 2> /dev/null
    [ ! -s short ] && break
    # Accumulate result file
    cat short >> result
    # Read two bytes and discard
    dd bs=2 count=1 <&3 > short 2> /dev/null
    [ ! -s short ] && break
done

或者这样应该更有效:

#!/bin/bash

exec 3< datafile
for ((i=0;i<76800;i++)) ; do
    # Skip 2 bytes then read 2 bytes
    dd bs=2 count=1 skip=1 <&3 2> /dev/null
done > result

或者,也许您可​​以使用netcatssh 将文件发送到具有适当工具的合理(更强大)计算机来处理并返回它。例如,如果远程计算机有 ImageMagick,它可以非常简单地缩小图像。

【讨论】:

  • 最终目标是减小文件大小,特别是避免通过慢速计量连接传输过多的数据。我发现四分之一比例的图像在被压缩和传输出去之前被进一步裁剪到特定区域仍然是可读的。
【解决方案3】:

另一种选择可能是使用 Lua,它以小巧、快速且非常适合嵌入式系统而著称 - 请参阅 Lua website。那里也有预建的、可下载的二进制文件。也建议在Busybox website here上。

我以前从未编写过任何 Lua,因此可能会效率低下,但这似乎工作得很好,并且在我的桌面上可以在几毫秒内处理 640x480 RGB565 图像。

-- scale.lua
-- Usage: lua scale.lua input.bin output.bin
-- Scale an image by skipping alternate lines and alternate columns

-- Set up width, height and bytes per pixel
w   = 640
h   = 480
bpp = 2    

-- Open first argument for input, second for output
inp = assert(io.open(arg[1], "rb"))
out = assert(io.open(arg[2], "wb"))

-- Read image, one line at a time
for i = 0, h-1, 1 do
   -- Read a whole line
   line = inp:read(w*bpp)

   -- Only use every second line
   if (i % 2) == 0 then
      io.write("DEBUG: Processing row: ",i,"\n")
      -- Build up new, reduced line by picking substrings
      reduced=""
      for p = 1, w*bpp, bpp*2 do
         reduced = reduced .. string.sub(line,p,p+bpp-1)
      end
      io.write("DEBUG: New line length in bytes: ",#reduced,"\n")
      out:write(reduced)
   end
end

assert(out:close())

我用 ImageMagick 创建了一个灰度测试图像,如下所示:

magick -depth 16 -size 640x480 gradient: gray:image.bin

然后我运行上面的 Lua 脚本:

lua scale.lua image.bin smaller.bin

然后我制作了一个可以查看的 JPEG 文件以供测试:

magick -depth 16 -size 320x240 gray:smaller.bin smaller.jpg

【讨论】:

  • 我有 Lua 可用,但我不确定它是否会更快。我使用了一个已发布的 base64 转换脚本——因为在 shell 中缺少 that 功能或真正的位运算符——而且它也非常慢。
  • @anti_climax 您能否说明您正在运行的实际设备是什么?还有,磁盘是什么?
  • 它是一个使用闪存运行的嵌入式设备,我相信它是一个 ARM 单核处理器。想想 10 年前运行的无线路由器在拨号连接的另一端剥离了 BusyBox。我绝对会试一试,由于系统本身,我并不抱太大希望。
  • 也许在它旁边放一个 5 美元的 Raspberry Pi Zero W 和内置 wifi(运行 Raspbian OS,就像 Debian),然后快速免费发送图像,用于 JPEG 或任何其他压缩使用ImageMagick。或者,如果您的嵌入式设备没有 wifi,请使用带有有线以太网的 Raspberry Pi 3。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多