¿Lectura de datos stdout binarios de adb shell?

¿Es posible leer la salida estándar binaria de un comando adb shell? Por ejemplo, todos los ejemplos de cómo usar screencap incluyen dos pasos:

adb shell screencap -p /sdcard/foo.png adb pull /sdcard/foo.png 

Sin embargo, el servicio admite escritura en stdout. Por ejemplo, puede hacer lo siguiente:

 adb shell "screencap -p > /sdcard/foo2.png" adb pull /sdcard/foo2.png 

Y esto funciona igual de bien. Pero, ¿qué hay de leer la salida en ADB? Lo que quiero hacer es lo siguiente:

 adb shell screencap -p > foo3.png 

Y evite la escritura intermedia en la tarjeta SD. Esto genera algo que se parece a un archivo PNG (ejecutar strings foo3.png genera algo con un IHDR, IEND, etc.) y es aproximadamente del mismo tamaño, pero el archivo está dañado en lo que respecta a los lectores de imágenes.

También he intentado hacer esto usando ddmlib en java y los resultados son los mismos. Me gustaría utilizar cualquier biblioteca necesaria. Mi objective es reducir el tiempo total para obtener la captura. En mi dispositivo, usando la solución de dos comandos, toma aproximadamente 3 segundos obtener la imagen. Usar ddmlib y capturar stdout lleva menos de 900ms, ¡pero no funciona!

¿Es posible hacer esto?

EDITAR: Aquí está el hexdump de dos archivos. El primero, screen.png viene de stdout y está dañado. El segundo, xscreen es de la solución de dos comandos y funciona. Las imágenes deben ser visualmente idénticas.

 $ hexdump -C screen.png | head 00000000 89 50 4e 47 0d 0d 0a 1a 0d 0a 00 00 00 0d 49 48 |.PNG..........IH| 00000010 44 52 00 00 02 d0 00 00 05 00 08 06 00 00 00 6e |DR.............n| 00000020 ce 65 3d 00 00 00 04 73 42 49 54 08 08 08 08 7c |.e=....sBIT....|| 00000030 08 64 88 00 00 20 00 49 44 41 54 78 9c ec bd 79 |.d... .IDATx...y| 00000040 9c 1d 55 9d f7 ff 3e 55 75 f7 de b7 74 77 d2 d9 |..U...>Uu...tw..| 00000050 bb b3 27 10 48 42 16 c0 20 01 86 5d 14 04 11 dc |..'.HB.. ..]....| 00000060 78 44 9d c7 d1 d1 11 78 70 7e 23 33 8e 1b 38 33 |xD.....xp~#3..83| 00000070 ea 2c 8c 8e 0d 0a 08 a8 23 2a 0e 10 82 ac c1 40 |.,......#*.....@| 00000080 12 02 81 24 64 ef ec 5b ef fb 5d 6b 3b bf 3f ea |...$d..[..]k;.?.| 00000090 de db dd 49 27 e9 ee 74 77 3a e3 79 bf 5e 37 e7 |...I'..tw:.y.^7.| $ hexdump -C xscreen.png | head 00000000 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 |.PNG........IHDR| 00000010 00 00 02 d0 00 00 05 00 08 06 00 00 00 6e ce 65 |.............ne| 00000020 3d 00 00 00 04 73 42 49 54 08 08 08 08 7c 08 64 |=....sBIT....|.d| 00000030 88 00 00 20 00 49 44 41 54 78 9c ec 9d 77 98 1c |... .IDATx...w..| 00000040 c5 99 ff 3f d5 dd 93 37 27 69 57 5a e5 55 4e 08 |...?...7'iWZ.UN.| 00000050 24 a1 00 58 18 04 26 08 8c 01 83 31 38 c0 19 9f |$..X..&....18...| 00000060 ef 7c c6 3e 1f 70 f8 7e 67 ee 71 e2 b0 ef ce f6 |.|.>.p.~gq....| 00000070 f9 ec 73 04 1b 1c 31 60 23 84 30 22 88 a0 40 10 |..s...1`#.0"..@.| 00000080 08 65 69 95 d3 4a 9b c3 c4 4e f5 fb a3 67 66 77 |.ei..J...N...gfw| 00000090 a5 95 b4 bb da a4 73 7d 9e 67 55 f3 ed 50 5d dd |......s}.gU..P].| 

Con solo echar un vistazo, parece que se agregan un par de bytes adicionales de 0x0d (13). ¿Regreso del carro? ¿Eso hace sonar las campanas? ¿Se está mezclando en algunas líneas en blanco?

Perdón por publicar una respuesta a una pregunta anterior, pero me encontré con este problema y solo quería hacerlo a través del shell. Esto funcionó bien para mí:

 adb shell screencap -p | sed 's/^M$//' > screenshot.png 

Eso ^M es un char que obtuve presionando ctrl + v -> ctrl + m, solo noté que no funciona al copiar y pegar.

 adb shell screencap -p | sed 's/\r$//' > screenshot.png 

hizo el truco para mí también.

A diferencia del adb shell adb exec-out comando adb exec-out no usa pty que altera la salida binaria. Entonces puedes hacer

 adb exec-out screencap -p > test.png 

https://android.googlesource.com/platform/system/core/+/5d9d434efadf1c535c7fea634d5306e18c68ef1f

Tenga en cuenta que si está utilizando esta técnica para un comando que produce salida en STDERR, debe redirigirlo a /dev/null , de lo contrario adb incluirá STDERR en su STDOUT corrompiendo su salida. Por ejemplo, si está intentando hacer una copia de seguridad y comprimir un directorio:

 adb exec-out "tar -zcf - /system 2>/dev/null" > system.tar.gz 

Como se indicó, “adb shell” está realizando una conversión de avance de línea (0x0a) a retorno de transporte + avance de línea (0x0d 0x0a). Esto está siendo realizado por la disciplina de la línea pseudo-tty. Como no hay ningún comando “stty” disponible para el shell, no hay una manera fácil de meterse con la configuración del terminal.

Es posible hacer lo que quieras con ddmlib. Tendría que escribir el código que ejecutaba los comandos en el dispositivo, capturaba el resultado y lo enviaba por cable. Esto es más o menos lo que DDMS hace por ciertas características. Esto puede ser más problemas que su valor.

La solución de repair() conversión de todo CRLF a LF) se siente inestable, pero en realidad es confiable ya que la conversión “corruptora” de LF a CRLF es determinista. Solía ​​hacer lo mismo para reparar las transferencias FTP inadvertidas en modo ASCII.

Vale la pena señalar que el formato de archivo PNG está diseñado explícitamente para detectar exactamente este (y relacionados) problemas. El número mágico comienza con 0x89 para capturar cualquier cosa que elimine bits altos, seguido de “PNG” para que pueda distinguir fácilmente lo que hay en el archivo, seguido por CR LF para atrapar varios convertidores de línea ASCII, luego 0x1a para atrapar viejos progtwigs de MS-DOS que usó Ctrl-Z como un marcador especial de fin de archivo, y luego un único LF. Al mirar los primeros bytes del archivo puede decir exactamente qué se le hizo.

… lo que significa que su función de repair() puede aceptar entradas “corruptas” y “puras”, y determinar de manera confiable si necesita hacer algo.

Editar: una nota adicional: es posible que el binario del lado del dispositivo configure el tty para evitar la conversión, usando cfmakeraw() . Consulte la función prepareRawOutput() en el comando screenrecord en Android 5.0, que puede enviar video en bruto desde la captura de pantalla en vivo a través de la conexión de shell de ADB.

Después de profundizar en los volcados hexadecimales, quedó claro que cada vez que se emitía el carácter 0x0A, el proyectil emitía 0x0D 0x0A. Repare la transmisión con el siguiente código y ahora los datos binarios son correctos. Ahora, por supuesto, la pregunta es por qué está adb shell haciendo esto? Pero en cualquier caso, esto soluciona el problema.

 static byte[] repair(byte[] encoded) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); for (int i=0; i i+1 && encoded[i] == 0x0d && encoded[i+1] == 0x0a) { baos.write(0x0a); i++; } else { baos.write(encoded[i]); } } try { baos.close(); } catch (IOException ioe) { } return baos.toByteArray(); } 

EDITAR: me di cuenta de por qué está haciendo esto. Está convirtiendo LF a CR / LF como DOS de la vieja escuela. Me pregunto si hay un entorno en algún lugar para apagar eso?

La mejor solución es usar el comando adb exec-out como se sugirió @AjeetKhadke .

Permítanme ilustrar la diferencia entre adb shell y adb exec-out output:

 ~$ adb shell "echo -n '\x0a'" | xxd -g1 00000000: 0d 0a ~$ adb exec-out "echo -n '\x0a'" | xxd -g1 00000000: 0a 

Funciona en Windows (estoy usando hexdump de GNUWin32 Hextools para la demostración) también:

 C:\>adb shell "echo -n '\x0a'" | hexdump 00000000: 0D 0A C:\>adb exec-out "echo -n '\x0a'" | hexdump 00000000: 0A 

El inconveniente es que para poder beneficiarse del uso del comando adb exec-out tanto el dispositivo como la PC host deben admitir el protocolo adb shell V2.

Es bastante trivial ocuparse del lado de la PC: simplemente actualice el paquete de platform-tools (que contiene el adb binario adb ) a la última versión. La versión de adbd daemon en el dispositivo está vinculada a la versión de Android. El protocolo adb shell V2 se introdujo en Android 5.0 junto con la revisión completa de adb (yendo del código c al C++ ). Pero hubo algunas regresiones (también conocidos como errores) por lo adb exec-out utilidad adb exec-out en Android 5.x todavía era limitada. Y finalmente no hay soporte para Android 4.xy dispositivos anteriores. Afortunadamente, la proporción de esos dispositivos más antiguos que todavía se utilizan para el desarrollo está cayendo rápidamente.

Sí, en Unix / Linux / Mac OS X, puede recibir una salida binaria de adb shell antes de “stty -onlcr;” a su orden ( NO ~~ necesita ser un rooteado de Android).

1. Descargue el archivo ejecutable “stty”.
http://sofes.miximages.com/android/output.png or: adb shell “/data/local/tmp/stty -onlcr; screencap -p” > somelocaldir/output.png or (Only for Windows): adb shell /data/local/tmp/stty -onlcr; screencap -p > somelocaldir/output.png

¡Hecho!

Pero para el sistema operativo Windows, de forma predeterminada, LF de Android se convertirá a CR CR LF.
Incluso si hiciste el paso anterior, todavía obtienes CR LF.
Esto “parece” porque el adb.exe local usa fwrite, lo que provoca que CR se anteponga.
No tengo ninguna forma de esto excepto convertir CR LF a LF manualmente en el sistema operativo Windows.

De otra manera:

 adb shell "busybox stty raw; screencap -p "> foo3.png 

PERO, como dijo @osexp2003, eso no funciona para el sistema operativo Windows.

Aquí hay una solución que funciona en todas partes (Linux y Windows incluidos).

Necesitará la utilidad netcat , a menudo llamada nc .
Si tanto nc como busybox nc fallan en su dispositivo, necesita una nueva busybox . Puede usar el instalador de busybox desde Play Market (se requiere root), o usar la solución de osexp2003 (descargar busybox del sitio oficial , ponerlo en /data/local/tmp/ on device y agregar el permiso de ejecución).

La idea es usar netcat como un servidor HTTP primitivo.
Bueno, ni siquiera un servidor adecuado, de hecho. Simplemente enviará su entrada como respuesta a cualquier conexión TCP (ya sea una solicitud HTTP desde el navegador, conexión telnet o simplemente netcat ) y finalizará.

Ejecute el comando desde el que desea obtener resultados de esta manera:

 adb shell 'screencap -p | busybox nc -p 8080 -l >/dev/null' 

En el ejemplo anterior, screencap -p toma una captura de pantalla (imagen PNG) y la canaliza a netcat .
-l le dice a netcat que actúe como servidor (escuche la conexión), y -p 8080 le dice que use el puerto TCP 8080. Omitir >/dev/null simplemente imprimirá, por ejemplo, la solicitud HTTP GET entrante a su terminal.
El ejemplo anterior esperará a que alguien se conecte, envíe una captura de pantalla y solo luego terminará.
Por supuesto, puede ejecutarlo sin adb shell , por ejemplo desde el emulador de terminal en su dispositivo.

Después de ejecutar su comando como se netcat arriba, puede descargar su salida de su teléfono, abriendo http://ip.of.your.phone:8080 en su navegador o por cualquier otro medio, por ejemplo usando netcat :

 busybox nc ip.of.your.phone:8080 >screenshot.png 

Si desea utilizar un cable USB para descargar , necesita reenviar la conexión utilizando ADB de esta manera:

 adb forward tcp:7080 tcp:8080 

Después de eso, puede usar localhost:7080 lugar de ip.of.your.phone:8080 .
Puede eliminar este reenvío con el siguiente comando:

 adb forward --remove tcp:7080 

prueba esto chicos:

 adb shell screencap -p | perl -pe 's/\x0D\x0A/\x0A/g' > screen.png 

También es posible usar base64 para esto, así que solo codifíquelo usando:

 base64 foo3.png>foo3.png.base64 

y luego en Windows usando alguna utilidad base64 o tal vez notepad ++ para descifrar el archivo.

O en linux / cygwin:

 base64 -d foo3.png.base64>foo3.png 

También puede usar el comando estándar dos2unix si está disponible.

( apt-get install dos2unix si está en Debian / Ubuntu. Probablemente haya comstackciones para Windows, OS X, etc. en algún lado si busca en google).

dos2unix convierte CRLF a LF de la misma manera que la función de repair() Eric Lange.

 adb shell screencap -p | dos2unix -f > screenshot.png 

o arregla un archivo dañado (in situ):

 dos2unix -f screenshot.png 

Necesita la -f para obligarlo a procesar archivos binarios.

Puse el método para usar python obtener bytes de imágenes utilizando adb aquí, tal vez esto será útil para alguien que se encontró con este problema. El código es el siguiente:

  pipe = subprocess.Popen("adb shell screencap -p", stdin=subprocess.PIPE, stdout=subprocess.PIPE, shell=True) image_bytes = pipe.stdout.read().replace(b'\r\n', b'\n') gray_image = cv2.imdecode(np.fromstring(image_bytes, np.uint8), cv2.IMREAD_GRAYSCALE) 

Este comando funcionó para mí en el sistema operativo Windows :

 adb exec-out screencap -p > test.png && dos2unix.exe -f test.png 

Pero desea usar esto: https://sourceforge.net/projects/dos2unix/

nc fue la única forma en que funcionó para mí. Usado:

 adb forward tcp:7080 tcp:8080 &&\ adb shell 'tar -zcvf - /data/media | nc -p 8080 -l 1>/dev/null' &\ sleep 1;\ nc localhost 7080 > media.tar.gz &&\ adb forward --remove tcp:7080 

como root para crear una copia de seguridad con suerte adecuada para / data / media

Esta es la mejor forma de usar Shell en OS

adb shell screencap -p | perl -pe 's/\x0D\x0A/\x0A/g' > screen.png