2
votes

Existe-t-il un moyen rapide de lire des octets alternatifs dans dd

J'essaie de lire toutes les autres paires d'octets dans un fichier binaire en utilisant dd dans une boucle, mais c'est anormalement lent.

J'ai un fichier binaire sur un périphérique embarqué BusyBox contenant des données au format rgb565. Chaque pixel fait 2 octets et j'essaie de lire tous les autres pixels pour faire une mise à l'échelle très basique de l'image afin de réduire la taille du fichier.

La taille globale est de 640x480 et j'ai pu lire tous les autres " row "de pixels en bouclant dd avec une taille de bloc de 960 octets. Mais faire la même chose pour toutes les autres "colonnes" qui restent en bouclant avec une taille de bloc de 2 octets est ridiculement lent même sur mon système local.

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

Bien que j'obtienne le résultat attendu, cette méthode est inutilisable.

Existe-t-il un moyen moins évident de faire copier rapidement par dd toutes les autres paires d'octets?

Malheureusement, je n'ai pas beaucoup de contrôle sur ce qui est compilé dans BusyBox. Je suis ouvert à d'autres méthodes possibles, mais une solution dd / sh peut être tout ce que je peux utiliser. Par exemple, une compilation a omis head -c ...

J'apprécie tous les commentaires. Je vais vérifier chacune des différentes suggestions et vérifier avec les résultats.


7 commentaires

Pourquoi seek = $ ((i-2)) ? Pourquoi ne pas simplement seek = 2 pour sauter exactement deux octets à chaque fois? Vous ne devriez pas du tout avoir à incrémenter i ; par défaut, dd reprend la lecture là où elle s'est arrêtée. (De plus, ma préférence personnelle serait d'utiliser of =. / Outfile plutôt que de rediriger la sortie standard.)


Pouvez-vous utiliser perl ?


Sinon, pouvez-vous utiliser python?


Connaissez-vous la longueur maximale des arguments d'une commande ( argmax ) sur votre système s'il vous plaît? Normalement, vous pouvez le trouver avec sysctl -a | grep -i arg


Pas de perl, pas de python # sysctl -a | grep -i arg sysctl: erreur de lecture de la clé 'net.ipv4.route.flush': Autorisation refusée Je peux essayer le seek = 2 - sachant qu'il reprend là où il s'est arrêté pourrait certainement conduire à un fonctionnement beaucoup plus optimisé . La redirection de la sortie standard est simplement un exemple dans ce cas.


@ B.Shefter - est-ce que vous dites que je n'ai pas du tout besoin de faire une boucle, ou simplement que je n'ai pas besoin de définir la recherche avec une valeur incrémentée de la boucle?


@ anti-climax Je pense que vous bouclez toujours (c'est-à-dire que vous continuez à lire deux octets à la fois), mais vous n'avez pas besoin d'un compteur - continuez simplement à lire jusqu'à ce qu'il ne reste plus rien à lire.


3 Réponses :


1
votes

Aucune idée si cela sera plus rapide ou même possible avec BusyBox, mais c'est une pensée ...

#!/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

Ou cela devrait être plus efficace:

#!/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

Ou, peut-être que vous pourriez utiliser netcat ou ssh pour envoyer le fichier à un ordinateur sensible (plus puissant) avec les outils appropriés pour le traiter et le renvoyer. Par exemple, si l'ordinateur distant avait ImageMagick , il pourrait très simplement réduire la taille de l'image.


1 commentaires

Le but ultime est de réduire la taille du fichier spécifiquement pour éviter de transférer plus de données que nécessaire sur une connexion lente à compteur. J'ai constaté que les images en quart d'échelle sont toujours lisibles en plus d'être recadrées dans des zones spécifiques avant d'être compressées et transférées.



3
votes

Sauter tous les autres caractères est simple pour des outils comme sed ou awk tant que vous n'avez pas besoin de gérer les nouvelles lignes et les octets nuls. Mais le support de Busybox pour les octets nuls dans sed et awk est suffisamment médiocre pour que je ne pense pas que vous puissiez les gérer du tout. Il est possible de gérer les nouvelles lignes, mais c'est une douleur énorme car il y a 16 combinaisons différentes à gérer selon que chaque position dans un bloc de 4 octets est une nouvelle ligne ou non.

Puisque les données binaires arbitraires sont pénibles, nous allons traduire en hexadécimal ou octal! Je vais m'inspirer de bin2hex et hex2bin scripts par Stéphane Chazelas . Comme nous ne nous soucions pas du format intermédiaire, j'utiliserai octal, ce qui est beaucoup plus simple à gérer car la dernière étape utilise printf qui ne prend en charge que l'octal. hex2bin de Stéphane utilise awk pour la conversion hexadécimale en octale; un oct2bin peut utiliser sed. Donc, à la fin, vous avez besoin de sh , od , sed et printf . Je ne pense pas que vous puissiez éviter printf : il est essentiel de générer des octets nuls. Bien que od soit essentiel, la plupart de ses options ne le sont pas, il devrait donc être possible de modifier ce code pour prendre en charge une od très simple avec un peu plus de post-traitement.

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

La raison pour laquelle c'est si rapide par rapport à votre approche basée sur dd est que BusyBox exécute printf dans le processus parent, alors que dd nécessite le sien processus. La fourche est lente. Si je me souviens bien, il existe une option de compilation qui fait que BusyBox se fourche pour tous les utilitaires. Dans ce cas, mon approche sera probablement aussi lente que la vôtre. Voici une approche intermédiaire utilisant dd qui ne peut pas éviter les fourchettes, mais au moins évite d'ouvrir et de fermer le fichier à chaque fois. Il devrait être un peu plus rapide que le vôtre.

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


3 commentaires

Faute d'option de conversion base64, j'ai en fait effectué des manipulations liées à hexagone avec les outils disponibles. Je vais devoir enquêter plus avant sur cette ligne.


@anti_climax Hex est généralement un peu plus facile à travailler qu'avec octal, mais la conversion vers / depuis le binaire est beaucoup plus facile avec l'octal. Base64 (ou uuencode, qui peut être disponible même si base64 ne l'est pas, j'ai eu des builds de BB avec uuencode sans l'option -m ) n'aide pas vraiment ici parce que vous voulez des octets groupés par 2 ou 4, mais base64 et uuencode groupés par 3.


Je voulais dire dans le sens plus général que j'ai manipulé des données binaires vers d'autres formes en utilisant le jeu d'outils limité par manque d'option d'encodage base64, donc je suis au moins familier avec la mécanique globale sinon les détails nécessaires ici.



0
votes

Une autre option pourrait être d'utiliser Lua qui a la réputation d'être petit, rapide et bien adapté aux systèmes embarqués - voir Site Web de Lua . Il existe également des binaires pré-construits et téléchargeables. Il est également suggéré sur le site Web Busybox ici .

Je n'ai jamais écrit de Lua auparavant, donc il peut y avoir quelques inefficacités mais cela semble fonctionner plutôt bien et traite une image 640x480 RGB565 en quelques millisecondes sur mon bureau.

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

J'ai créé une image de test en niveaux de gris avec ImageMagick comme suit:

lua scale.lua image.bin smaller.bin

Ensuite, j'ai exécuté le script Lua ci-dessus avec:

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

Ensuite, j'ai créé un JPEG que j'ai pu voir pour le tester avec:

-- 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())


4 commentaires

J'ai Lua disponible mais je ne sais pas si ce sera beaucoup plus rapide. J'ai utilisé un script de conversion base64 publié - par manque de cette fonctionnalité ou de véritables opérateurs binaires dans le shell - et il était également anormalement lent.


@anti_climax Pourriez-vous préciser quel est le périphérique réel sur lequel vous utilisez s'il vous plaît? Aussi, quel est le disque?


C'est un appareil embarqué fonctionnant en flash avec, je crois, un processeur ARM monocœur. Pensez à un routeur sans fil de 10 ans exécutant BusyBox dépouillé de l'autre côté d'une connexion commutée. Je vais absolument essayer, je n'ai tout simplement pas de grands espoirs à cause du système lui-même.


Peut-être mettre un Raspberry Pi Zero W à 5 $ avec wifi intégré (exécutant Raspbian OS qui est comme Debian) à côté, et envoyer l'image là-bas rapidement et gratuitement pour JPEG ou toute autre compression en utilisant ImageMagick . Ou si votre appareil embarqué n'a pas de wifi, utilisez un Raspberry Pi 3 avec Ethernet filaire.