My goal is to be able to type a one word command and get a screenshot from a rooted Nexus One attached by USB.
So far, I can get the framebuffer which I believe is a 32bit xRGB888
raw image by pulling it like this:
adb pull /dev/graphics/fb0 fb0
From there though, I'm having a hard time getting it converted to a png. I'm trying with ffmpeg like this:
ffmpeg -vframes 1 -vcodec rawvideo -f rawvideo -pix_fmt rgb8888 -s 480x800 -i fb0 -f image2 -vcodec png image.png
That creates a lovely purple image that has parts that vaguely resemble the scree开发者_运维百科n, but it's by no means a clean screenshot.
A vastly easier solution for ICS is to use the following from the command line
adb shell /system/bin/screencap -p /sdcard/screenshot.png
adb pull /sdcard/screenshot.png screenshot.png
This'll save the screenshot.png file in the current directory.
Tested on a Samsung Galaxy SII & SII running 4.0.3.
Actually, there is another very simple ability to grab screenshot from your android device: write simple script 1.script
like this:
# Imports the monkeyrunner modules used by this program
from com.android.monkeyrunner import MonkeyRunner, MonkeyDevice
# Connects to the current device, returning a MonkeyDevice object
device = MonkeyRunner.waitForConnection()
# Takes a screenshot
result = device.takeSnapshot()
# Writes the screenshot to a file
result.writeToFile('1.png','png')
and call monkeyrunner 1.script
.
It seems that frame buffer of N1 uses RGB32 encoding (32 bits per pixel).
Here is my script using ffmpeg:
adb pull /dev/graphics/fb0 fb0
dd bs=1920 count=800 if=fb0 of=fb0b
ffmpeg -vframes 1 -vcodec rawvideo -f rawvideo -pix_fmt rgb32 -s 480x800 -i fb0b -f image2 -vcodec png fb0.png
Another way derived from ADP1 method described here http://code.lardcave.net/entries/2009/07/27/132648/
adb pull /dev/graphics/fb0 fb0
dd bs=1920 count=800 if=fb0 of=fb0b
python rgb32torgb888.py <fb0b >fb0b.888
convert -depth 8 -size 480x800 RGB:fb0b.888 fb0.png
Python script 'rgb32torgb888.py':
import sys
while 1:
colour = sys.stdin.read(4)
if not colour:
break
sys.stdout.write(colour[2])
sys.stdout.write(colour[1])
sys.stdout.write(colour[0])
Using my HTC Hero (and hence adjusting from 480x800 to 320x480), this works if I use rgb565 instead of 8888:
ffmpeg -vframes 1 -vcodec rawvideo -f rawvideo -pix_fmt rgb565 -s 320x480 -i fb0 -f image2 -vcodec png image.png
If you have dos2unix installed, then the below
adb shell screencap -p | dos2unix > screen.png
I believe all framebuffers to date are RGB 565, not 888.
Now we have got a single line command to take a screenshot. The command as follows:
adb shell screencap -p | perl -pe 's/\x0D\x0A/\x0A/g' > screen.png
Type the above command in your terminal and press enter. If you want the screenshot to be stored in any specific location, then give the path (or) directory before screen.png
.
Source.
I think rgb32torgb888.py should be
sys.stdout.write(colour[0])
sys.stdout.write(colour[1])
sys.stdout.write(colour[2])
I hope my script might be of any use. I use it on my galaxy tab and it works perfectly, but it is possible for you to change the default resolution. It requires the "zsh" shell, though:
#!/bin/zsh
# These settings are for the galaxy tab.
HRES=600
VRES=1024
usage() {
echo "Usage: $0 [ -p ] outputfile.png"
echo "-- takes screenshot off your Galaxy Tab Android phone."
echo " -p: portrait mode"
echo " -r X:Y: specify resolution, e.g. -r 480:640 specifies that your cellphone has 480x640 resolution."
exit 1
}
PORTRAIT=0 # false by default
umask 022
[[ ! -w . ]] && {
echo "*** Error: current directory not writeable."
usage
}
[[ ! -x $(which mogrify) ]] && {
echo "*** Error: ImageMagick (mogrify) is not in the PATH!"
usage
}
while getopts "pr:" myvar
do
[[ "$myvar" == "p" ]] && PORTRAIT=1
[[ "$myvar" == "r" ]] && {
testhres="${OPTARG%%:*}" # remove longest-matching :* from end
testvres="${OPTARG##*:}" # remove longest-matchung *: from beginning
if [[ $testhres == <0-> && $testvres == <0-> ]] # Interval: from 0 to infinite. Any value would be: <->
then
HRES=$testhres
VRES=$testvres
else
echo "Error! One of these values - '${testhres}' or '${testvres}' - is not numeric!"
usage
fi
}
done
shift $((OPTIND-1))
[[ $# < 1 ]] && usage
outputfile="${1}"
blocksize=$((HRES*4))
count=$((VRES))
adb pull /dev/graphics/fb0 fb0.$$
/bin/dd bs=$blocksize count=$count if=fb0.$$ of=fb0b.$$
/usr/bin/ffmpeg -vframes 1 -vcodec rawvideo -f rawvideo -pix_fmt rgb32 -s ${VRES}x${HRES} -i fb0b.$$ -f image2 -vcodec png "${outputfile}"
if (( ${PORTRAIT} ))
then
mogrify -rotate 270 "${outputfile}"
else
mogrify -flip -flop "${outputfile}"
fi
/bin/rm -f fb0.$$ fb0b.$$
On the MyTouch Slide 3G, I ended up with the red and blue channels swapped in my screenshots. Here's the correct ffmpeg incantation for anyone else in that situation: (the notable part: -pix_fmt bgr32)
ffmpeg -vframes 1 -vcodec rawvideo -f rawvideo -pix_fmt bgr32 -s 320x480 -i fb0 -f image2 -vcodec png image.png
Thanks to Patola for the handy shell script! At least for my phone, no mogrification is necessary to correctly orient for portrait mode (320x480), and so the end of his script becomes:
# assuming 'down' is towards the keyboard or usb jack
# in landscape and protrait modes respectively
(( ${PORTRAIT} )) || mogrify -rotate 270 "${outputfile}"
/bin/rm -f fb0.$$ fb0b.$$
rgb565
instead of 8888
also work on emulator
A little elaborate/excessive but it handles both screencap and framebuffer scenarios (as well as figuring out the resolution too).
#!/bin/bash
#
# adb-screenshot - simple script to take screenshots of android devices
#
# Requires: 'ffmpeg' and 'adb' to be somewhere in the PATH
#
# Author: Kevin C. Krinke <kevin@krinke.ca>
# License: Public Domain
# globals / constants
NAME=$(basename $0)
TGT=~/Desktop/${NAME}.png
SRC=/sdcard/${NAME}.png
TMP=/tmp/${NAME}.$$
RAW=/tmp/${NAME}.raw
FFMPEG=$(which ffmpeg)
ADB=$(which adb)
DD=$(which dd)
USB_DEVICE=""
# remove transitory files if exist
function cleanup () {
[ -f "${RAW}" ] && rm -f "${RAW}"
[ -f "${TMP}" ] && rm -f "${TMP}"
[ -z "$1" ] && die "aborting process now."
exit 0
}
# exit with an error
function die () {
echo "Critical Error: $@"
exit 1
}
# catch all signals and cleanup / dump
trap cleanup \
SIGHUP SIGINT SIGQUIT SIGILL SIGTRAP SIGABRT SIGEMT SIGFPE \
SIGKILL SIGBUS SIGSEGV SIGSYS SIGPIPE SIGALRM SIGTERM SIGURG \
SIGSTOP SIGTSTP SIGCONT SIGCHLD SIGTTIN SIGTTOU SIGIO SIGXCPU \
SIGXFSZ SIGVTALRM SIGPROF SIGWINCH SIGINFO SIGUSR1 SIGUSR2
# adb is absolutely required
[ -x "${ADB}" ] || die "ADB is missing!"
# cheap getopt
while [ $# -gt 0 ]
do
case "$1" in
"-h"|"--help")
echo "usage: $(basename $0) [-h|--help] [-s SERIAL] [/path/to/output.png]"
exit 1
;;
"-s")
[ -z "$2" ] && die "Missing argument for option \"-s\", try \"${NAME} --help\""
HAS_DEVICE=$(${ADB} devices | grep "$2" )
[ -z "${HAS_DEVICE}" ] && die "No device found with serial $2"
USB_DEVICE="$2"
;;
*)
[ -n "$1" -a -d "$(dirname $1)" ] && TGT="$1"
;;
esac
shift
done
# prep target with fire
[ -f "${TGT}" ] && rm -f "${TGT}"
# tweak ADB command line
if [ -n "${USB_DEVICE}" ]
then
ADB="$(which adb) -s ${USB_DEVICE}"
fi
# calculate resolution
DISPLAY_RAW=$(${ADB} shell dumpsys window)
HRES=$(echo "${DISPLAY_RAW}" | grep SurfaceWidth | head -1 | perl -pe 's/^.*\bSurfaceWidth\:\s*(\d+)px\b.*$/$1/')
VRES=$(echo "${DISPLAY_RAW}" | grep SurfaceHeight | head -1 | perl -pe 's/^.*\bSurfaceHeight\:\s*(\d+)px\b.*$/$1/')
RES=${HRES}x${VRES}
# check for screencap binary
HAS_SCREENCAP=$(${ADB} shell "[ -x /system/bin/screencap ] && echo 1 || echo 0" | perl -pe 's/\D+//g')
if [ "$HAS_SCREENCAP" == "1" ]
then # use screencap to get the image easy-peasy
echo -n "Getting ${RES} screencap... "
( ${ADB} shell /system/bin/screencap ${SRC} 2>&1 ) > /dev/null
[ "$?" != "0" ] && die "Failed to execute screencap"
( ${ADB} pull ${SRC} ${TMP} 2>&1 ) > /dev/null
[ "$?" != "0" ] && die "Failed to pull png image"
( ${ADB} shell rm ${SRC} 2>&1 ) > /dev/null
[ "$?" != "0" ] && die "Failed to remove png image"
mv ${TMP} ${TGT}
echo "wrote: ${TGT}"
else # fetch a framebuffer snapshot
# ffmpeg is only needed if device is pre-ICS
[ -x "${FFMPEG}" ] || die "FFMPEG is missing!"
[ -x "${DD}" ] || die "DD is missing!"
echo -n "Getting ${RES} framebuffer... "
( ${ADB} pull /dev/graphics/fb0 ${RAW} 2>&1 ) > /dev/null
[ "$?" != "0" ] && die "Failed to pull raw image data"
# calculate dd parameters
COUNT=$((HRES*4))
BLOCKSIZE=$((VRES))
( ${DD} bs=${BLOCKSIZE} count=${COUNT} if=${RAW} of=${TMP} 2>&1 ) > /dev/null
[ "$?" != "0" ] && die "Failed to realign raw image data"
( ${FFMPEG} -vframes 1 -vcodec rawvideo -f rawvideo -pix_fmt rgb32 -s ${RES} -i ${TMP} -f image2 -vcodec png ${TGT} 2>&1 ) > /dev/null
[ "$?" != "0" ] && die "Failed to encode PNG image"
echo "wrote: ${TGT}"
fi
# exit app normal
cleanup 1
This might be related to the issue Reading binary Data from adb shell's stdout where adb attempts to do LF to CRLF conversion for you (it's probably just the windows version of adb). I personally had trouble with it conversion \n to \r\r\n so as a way to convert this it's good to either to use the code at [1] or to to use.
for me running it with (in cygwin):
adb shell 'cat /dev/graphics/fb0' | perl -pi -e 's/\r\r\n/\n/g'
seemed to help
other than that try comparing the Width & height to the size of the file. File size should be evenly divisible by Width * height
if that's not the case then either the adb tool is automatically doing things for you or it's a more exotic format then rgb545 or rgb8888.
if it's just a color issue (ie: everything in the result image is in the right spot) then you might want to consider swapping the Red & Blue channels as some systems (in general) use byte order BGRA instead of RGBA.
A way to completely automatize this process is to create a script which adds the curent timestamp to the filename. This way, you don't have to write the filename yourself, all your screenshots have a different name, and your screenshots are sorted by time.
Example of bash script :
#! /bin/bash
filename=$(date +"_%Y-%m-%d-%H:%M")
/PATH_TO_ANDROID_SDK/platform-tools/adb -d shell screencap -p | perl -pe 's/\x0D\x0A/\x0A/g' > screenshot$filename.png
This will create a file named like screenshot_2014-01-07-10:31.png
精彩评论