android native screen capture tool screen It is possible to write a native C application, that opens the framebuffer (/dev/graphics/fb0) to extract bitmap data representing the screen surface image.

It is also possible to modify the framebuffer data and by doing so to do a low level drawing on screen (that is also very fast).

The data must be then converted (aligned/reversed) to standard BMP format, and saved to disc.

The framebuffer grabber:

  1.  
  2. static int get_framebuffer(GGLSurface *fb)
  3. {
  4. int fd;
  5. void *bits;
  6.  
  7. fd = open("/dev/graphics/fb0", O_RDWR);
  8. if(fd < 0) {
  9. perror("cannot open fb0");
  10. return -1;
  11. }
  12.  
  13. if(ioctl(fd, FBIOGET_FSCREENINFO, &fi) < 0) {
  14. perror("failed to get fb0 info");
  15. return -1;
  16. }
  17.  
  18. if(ioctl(fd, FBIOGET_VSCREENINFO, &vi) < 0) {
  19. perror("failed to get fb0 info");
  20. return -1;
  21. }
  22.  
  23. //dumpinfo(&fi, &vi);
  24.  
  25. bits = mmap(0, fi.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
  26. if(bits == MAP_FAILED) {
  27. perror("failed to mmap framebuffer");
  28. return -1;
  29. }
  30.  
  31. fb->version = sizeof(*fb);
  32. fb->width = vi.xres;
  33. fb->height = vi.yres;
  34. fb->stride = fi.line_length / (vi.bits_per_pixel >> 3);
  35. fb->data = bits;
  36. fb->format = GGL_PIXEL_FORMAT_RGB_565;
  37.  
  38. fb++;
  39.  
  40. fb->version = sizeof(*fb);
  41. fb->width = vi.xres;
  42. fb->height = vi.yres;
  43. fb->stride = fi.line_length / (vi.bits_per_pixel >> 3);
  44. fb->data = (void*) (((unsigned) bits) + vi.yres * vi.xres * 2);
  45. fb->format = GGL_PIXEL_FORMAT_RGB_565;
  46.  
  47. return fd;
  48. }
  49.  

The BITMAP former:
The first thing to do is to convert the Framebuffer's 16 bit data into 24 bit. We're not gaining extra-quality here, we're just "stretching" the colors from:
5 bits blue : 2^5=32 blue color nuances
6 bits green : 2^6=64 green color nuances (in 16bit colors, green has more color space since the human eye is more sensitive to green color)
5 bits red : 2^5=32 red color nuances
32x64x32 possible 16bits colors (2^16)
To:
8 bits blue : 2^8 = 256 color nuances
8 bits green : 2^8 = 256 color nuances
8 bits red : 2^8 = 256 color nuances
A total of 2^24 colors, but as I said we're just "stretching" the color intervals without any information gain.

  1.  
  2. //convert pixel data
  3. uint8_t *rgb24;
  4. if (depth == 16)
  5. {
  6. rgb24 = (uint8_t *)malloc(w * h * 3);
  7. int i = 0;
  8. for (;i<w*h;i++)
  9. {
  10. uint16_t pixel16 = ((uint16_t *)gr_framebuffer[0].data)[i];
  11. // RRRRRGGGGGGBBBBBB -> RRRRRRRRGGGGGGGGBBBBBBBB
  12. // in rgb24 color max is 2^8 per channel (*255/32 *255/64 *255/32)
  13. rgb24[3*i+2] = (255*(pixel16 & 0x001F))/ 32; //Blue
  14. rgb24[3*i+1] = (255*((pixel16 & 0x07E0) >> 5))/64; //Green
  15. rgb24[3*i] = (255*((pixel16 & 0xF800) >> 11))/32; //Red
  16. }
  17. }
  18. else
  19. if (depth == 24)
  20. {
  21. rgb24 = (uint8_t *) gr_framebuffer[0].data;
  22. }
  23. else
  24. {
  25. //free
  26. close(gr_fb_fd);
  27. exit(2);
  28. };
  29. //save RGB 24 Bitmap
  30. int bytes_per_pixel = 3;
  31. BMPHEAD bh;
  32. memset ((char *)&bh,0,sizeof(BMPHEAD)); // sets everything to 0
  33. //bh.filesize = calculated size of your file (see below)
  34. //bh.reserved = two zero bytes
  35. bh.headersize = 54L; // for 24 bit images
  36. bh.infoSize = 0x28L; // for 24 bit images
  37. bh.width = w; // width of image in pixels
  38. bh.depth = h; // height of image in pixels
  39. bh.biPlanes = 1; // for 24 bit images
  40. bh.bits = 8 * bytes_per_pixel; // for 24 bit images
  41. bh.biCompression = 0L; // no compression
  42. int bytesPerLine;
  43. bytesPerLine = w * bytes_per_pixel; // for 24 bit images
  44. //round up to a dword boundary
  45. if (bytesPerLine & 0x0003)
  46. {
  47. bytesPerLine |= 0x0003;
  48. ++bytesPerLine;
  49. }
  50. bh.filesize = bh.headersize + (long)bytesPerLine * bh.depth;
  51. FILE * bmpfile;
  52. //printf("Bytes per line : %d\n", bytesPerLine);
  53. bmpfile = fopen("screen.bmp", "wb");
  54. if (bmpfile == NULL)
  55. {
  56. close(gr_fb_fd);
  57. exit(3);
  58. }
  59. fwrite("BM",1,2,bmpfile);
  60. fwrite((char *)&bh, 1, sizeof (bh), bmpfile);
  61. //fwrite(rgb24,1,w*h*3,bmpfile);
  62. char *linebuf;
  63. linebuf = (char *) calloc(1, bytesPerLine);
  64. if (linebuf == NULL)
  65. {
  66. fclose(bmpfile);
  67. close(gr_fb_fd);
  68. exit(4);
  69. }
  70.  

Now the next thing to do is to align the BGR triplets and to switch order , to match the BMP RGB24 color format.

  1.  
  2. int line,x;
  3. for (line = h-1; line >= 0; line --)
  4. {
  5. // fill line linebuf with the image data for that line
  6. for( x =0 ; x < w; x++ )
  7. {
  8. *(linebuf+x*bytes_per_pixel) = *(rgb24 + (x+line*w)*bytes_per_pixel+2);
  9. *(linebuf+x*bytes_per_pixel+1) = *(rgb24 + (x+line*w)*bytes_per_pixel+1);
  10. *(linebuf+x*bytes_per_pixel+2) = *(rgb24 + (x+line*w)*bytes_per_pixel+0);
  11. }
  12. // remember that the order is BGR and if width is not a multiple
  13. // of 4 then the last few bytes may be unused
  14. fwrite(linebuf, 1, bytesPerLine, bmpfile);
  15. }
  16. fclose(bmpfile);
  17. close(gr_fb_fd);
  18.  

Download the code: (old code removed, see update below)
Note that you are not allowed to distribute this code without visibly indicating it's author (me) and the source (this page) and You may not embedded it in commercial applications.
To compile the code, use the NDK, as presented in this article.
Running the code produces a screen.bmp file:
android native screen capture tool

screen.bmp
android native screen capture tool screen

Hope this helps.

Update, April 03, 2012

By popular demand, I have modified the code to support RGB32 as well. In this case, we have an extra alpha channel, that we need to ignore when creating the final bitmap. So I have added a RGB32->RGB24 converter, but it is only a alpha channel remover, since we are not talking about true RGB32. The Red,Green,Blue channel are still 8bits each, as in the case of RGB24. Peter's solution below works, but I felt the need to write a simpler code:

  1.  
  2. if (depth == 32) //skip transparency channel
  3. {
  4. rgb24 = (uint8_t *) malloc(w * h * 3);
  5. int i=0;
  6. for (;i<w*h;i++)
  7. {
  8. uint32_t pixel32 = ((uint32_t *)gr_framebuffer[0].data)[i];
  9. // in rgb24 color max is 2^8 per channel
  10. rgb24[3*i+0] = pixel32 & 0x000000FF; //Blue
  11. rgb24[3*i+1] = (pixel32 & 0x0000FF00) >> 8; //Green
  12. rgb24[3*i+2] = (pixel32 & 0x00FF0000) >> 16; //Red
  13. }
  14. }
  15.  

Here is the complete source code, and the native ELF binary file:
Native
And a BMP result of my Android using an RGB32 screen:

When using capturescr ELF file in ADB shell, you can get the returned error code using:

  1.  
  2. echo $?
  3.  
(Visited 3,772 times, 7 visits today)
facebooktwittergoogle_plusredditpinterestlinkedinmailfacebooktwittergoogle_plusredditpinterestlinkedinmail
Tagged on:                         

116 thoughts on “Android Native Screen capture application using the Framebuffer

  • May 20, 2013 at 7:57 am
    Permalink

    Hello Radu – I am able to use this code and capture screenshots from Samsung phones. But it captures a black screen shot for Nexus 4. Do you know how to correct it?

  • June 29, 2013 at 10:43 am
    Permalink

    How to resolve this problem :
    root@android:/data # ./capturescr
    ./capturescr
    failed to mmap framebuffer: Invalid argument
    Segmentation fault
    -rwxrwxrwx root root 7603 2013-06-25 16:32 capturescr

  • June 29, 2013 at 7:31 pm
    Permalink

    did you run chmod on fb0?

  • July 13, 2013 at 2:04 pm
    Permalink

    Hi Radu,

    Please can you post a full code example? I actually wanted to take a screenshot of my android device programatically. And this has to happen as a background service.

    I tried “os.write((“/system/bin/screencap -p ” + “/sdcard/img.png”).getBytes(“ASCII”));” => This doesnt seem to work.

    Any idea?

    Regards, Hari

  • July 13, 2013 at 2:21 pm
    Permalink

    hi Hari,

    os.write(… doesn’t seem right. you need to launch the screencap code as a native executable.

  • July 13, 2013 at 2:27 pm
    Permalink

    Hi Radu.. Thanks a lot for the quick reply. please can you give a hint on how to do that? Sorry- am a dump newbie. :(

  • July 13, 2013 at 4:47 pm
    Permalink

    @Radu:

    I guess, I am closer to it now. Please can you check if I have done some mistake here?

    String run = “cmd /c start ” + Environment.getExternalStorageDirectory().getPath() + “/copyImage.bat”;
    Runtime rn = Runtime.getRuntime();
    Process pp = rn.exec(run);

    I have copied copyImage.bat in /mnt/sdcard/

    and the copyImage.bat has the following code:

    adb shell /system/bin/screencap -p /mnt/sdcard/Hari.png

    is there something wrong here?

  • July 14, 2013 at 5:26 am
    Permalink

    Hi Radu..I also tried the following:

    String run = “/system/bin/screencap -p /mnt/sdcard/Hari.png”;
    Runtime rn = Runtime.getRuntime();
    Process pp = rn.exec(run);

    No use for this also. I am not getting the result.

  • July 14, 2013 at 10:27 am
    Permalink

    Ok Hari, this is getting better, the previous code was… well.. strange.

    If you look at the C code you’ll see capturescr doesn’t use any parameters, and outputs the screen capture image to screen.bmp . So the name is capturescr (not screencap), there are no PNGs here, no path, and no -p (where did you get that from??).

    You also need to learn about permissions, as capturescr must be set to executable, while fb0 set as readable.

    Probably your progress would be faster if you would test all these in the terminal (Adb sheel) before jumping to Runtime exec. Good luck!

  • September 19, 2013 at 3:15 pm
    Permalink

    Yep!
    i am facing same problem which is already discussed above. fd=open(“/dev/graphics/fb0″); is failing
    pls help me, I was strucked here…

  • October 13, 2013 at 8:53 am
    Permalink

    Hi
    I try to build this project.But I can’t find where to download souce code.
    Can you send a copy to gyr89@126.com.
    Thanks a lot.

  • February 10, 2014 at 11:07 pm
    Permalink

    Hi,

    On java level I am able to do a screenshot between specified “surface layers”. This particularly allows me to make a screenshot of a screen “below my app” – for example if my app has transparent background and some buttons on it then when you run it from home screen you will see the home screen under my app. With a “normal” screenshot you will get the home screen and my buttons, but if you make a screenshot specifying minLayer 0 and maxLayer 21010 with my app having layer 21015, then you will get only the home screen. An example of such “hidden” API is SurfaceControl.screenshot(int width, int height, int minLayer, int maxLayer).

    The question is – are those levels somehow stored in the frame buffer which allows you to extract/overwrite/modify them or the frame buffer file already contains the merged and flattened bitmap?

    Thanks

  • August 8, 2014 at 12:20 pm
    Permalink

    hello Radu
    I get the same picture every time when i use the code to shot the screen
    My phone is HTC one

  • Pingback: Programmatic screenshot of a webview with flash elements | DL-UAT

  • June 11, 2015 at 2:10 pm
    Permalink

    Hello I want to create a screen capture video with atlist 5 FPS inside app. Will you please provide any example. I just want to capture a video on single activity and its contents. Your help will be greatly appreciated sir.
    Email:prash.jadhav777@gmail.com
    Thank you,

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>