Pull to refresh

Сборка бинарных файлов Android с помощью исходников и Android NDK. Прокачиваем утилиту screencap

Reading time25 min
Views6.3K
Я занимаюсь автоматизацией Android устройств и часто SDK или ОС Android не имеют нужного функционала или его работа выполняется медленно/очень медленно.

Используя возможности Native Development Kit (NDK) мы можем написать функционал, который будет выполняться быстрей, чем тот же функционал на Java. За счет данного кита мы можем добавлять в свое приложение код, написанный на C/C++ или создавать свои бинарные файлы под мобильные Android устройства.

В данной статье я расскажу каким образом мы можем настроить компиляцию бинарного файла под ОС Android, а так же покажу процесс, как мы можем дополнить функционал уже существующих бинарных файлов в этой ОС.

Для примера я «прокачаю» screencap, который сможет не только получать скриншот всего экрана или выдавать «сырые данные», но и возвращать цвет пикселя по указанной точки или получать изображение только нужной области. Итак, поехали!

Установка NDK


Скачиваем Android NDK и распаковываем архив или устанавливаем через SDK Manager.

Если еще нет, то можете добавить доп. инструменты:

sudo apt-get install android-tools-adb android-tools-fastboot

И создаем «проект» под архитектуру вашего мобильного устройства:

 ./android-ndk-r12b/build/tools/make_standalone_toolchain.py --arch <arm or arm64 or other> --install-dir ~/arm

Так как у моего HOMTOM HT16 архитектура armeabi-v7a то я буду использовать команду:

 ./android-ndk-r12b/build/tools/make_standalone_toolchain.py --arch arm --install-dir ~/arm

И ждем, пока скрипт создаст все необходимые файлы (до 5 мин. примерно).

Проверка на работоспособонсть


Создадим файл hello_world.c с простым кодом:

#include <stdio.h>
int main () {
  puts("hello world");
}

И попробуем его скомпилировать:

~/arm/bin/clang -pie hello_world.c -o hello_world

С помощью атрибута -o указываем имя файла, а с помощью ключа -pie мы указываем, что бинарный файл PIE и все его зависимости загружаются в случайные расположения в виртуальной памяти каждый раз, когда приложение выполняется.

Если компиляция прошла успешно, то заливаем файл на телефон:

adb push ./hello_world /data/local/tmp

И попробуем бинарник:

adb shell
/data/local/tmp/hello_world

Если вы увидели вывод фразы «hello world» — значит вы всё сделали правильно!

Если у вас все таки появились ошибки, возможно вы выбрали не правильную архитектуру, тогда просто удалите данную директорию и заново создайте с нужной.

Для определения архитектуры можете выполнить команду

adb shell cat /proc/cpuinfo

Исходники и библиотеки


Так как анализ скомпилированного бинарного файла очень затратно по времени, а Android является открытой системой, то почему бы не воспользоваться этим качеством!

Заходим сюда и ищем нужную версию Android. Ну а так уже — скачиваем архив и ищем нужный исходник бинарного файла. Хотя, конечно же есть вариант — Google и «правильный запрос».

В моем случае нужный мне файл находится по этой ссылке.

screencap.cpp

#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <linux/fb.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <binder/ProcessState.h>
#include <gui/SurfaceComposerClient.h>
#include <gui/ISurfaceComposer.h>
#include <ui/DisplayInfo.h>
#include <ui/PixelFormat.h>
// TODO: Fix Skia.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
#include <SkImageEncoder.h>
#include <SkData.h>
#pragma GCC diagnostic pop
using namespace android;
static uint32_t DEFAULT_DISPLAY_ID = ISurfaceComposer::eDisplayIdMain;
static void usage(const char* pname)
{
    fprintf(stderr,
            "usage: %s [-hp] [-d display-id] [FILENAME]\n"
            "   -h: this message\n"
            "   -p: save the file as a png.\n"
            "   -d: specify the display id to capture, default %d.\n"
            "If FILENAME ends with .png it will be saved as a png.\n"
            "If FILENAME is not given, the results will be printed to stdout.\n",
            pname, DEFAULT_DISPLAY_ID
    );
}
static SkColorType flinger2skia(PixelFormat f)
{
    switch (f) {
        case PIXEL_FORMAT_RGB_565:
            return kRGB_565_SkColorType;
        default:
            return kN32_SkColorType;
    }
}
static status_t vinfoToPixelFormat(const fb_var_screeninfo& vinfo,
        uint32_t* bytespp, uint32_t* f)
{
    switch (vinfo.bits_per_pixel) {
        case 16:
            *f = PIXEL_FORMAT_RGB_565;
            *bytespp = 2;
            break;
        case 24:
            *f = PIXEL_FORMAT_RGB_888;
            *bytespp = 3;
            break;
        case 32:
            // TODO: do better decoding of vinfo here
            *f = PIXEL_FORMAT_RGBX_8888;
            *bytespp = 4;
            break;
        default:
            return BAD_VALUE;
    }
    return NO_ERROR;
}
static status_t notifyMediaScanner(const char* fileName) {
    String8 cmd("am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d file://");
    String8 fileUrl("\"");
    fileUrl.append(fileName);
    fileUrl.append("\"");
    cmd.append(fileName);
    cmd.append(" > /dev/null");
    int result = system(cmd.string());
    if (result < 0) {
        fprintf(stderr, "Unable to broadcast intent for media scanner.\n");
        return UNKNOWN_ERROR;
    }
    return NO_ERROR;
}
int main(int argc, char** argv)
{
    ProcessState::self()->startThreadPool();
    const char* pname = argv[0];
    bool png = false;
    int32_t displayId = DEFAULT_DISPLAY_ID;
    int c;
    while ((c = getopt(argc, argv, "phd:")) != -1) {
        switch (c) {
            case 'p':
                png = true;
                break;
            case 'd':
                displayId = atoi(optarg);
                break;
            case '?':
            case 'h':
                usage(pname);
                return 1;
        }
    }
    argc -= optind;
    argv += optind;
    int fd = -1;
    const char* fn = NULL;
    if (argc == 0) {
        fd = dup(STDOUT_FILENO);
    } else if (argc == 1) {
        fn = argv[0];
        fd = open(fn, O_WRONLY | O_CREAT | O_TRUNC, 0664);
        if (fd == -1) {
            fprintf(stderr, "Error opening file: %s (%s)\n", fn, strerror(errno));
            return 1;
        }
        const int len = strlen(fn);
        if (len >= 4 && 0 == strcmp(fn+len-4, ".png")) {
            png = true;
        }
    }
    
    if (fd == -1) {
        usage(pname);
        return 1;
    }
    void const* mapbase = MAP_FAILED;
    ssize_t mapsize = -1;
    void const* base = NULL;
    uint32_t w, s, h, f;
    size_t size = 0;
    // Maps orientations from DisplayInfo to ISurfaceComposer
    static const uint32_t ORIENTATION_MAP[] = {
        ISurfaceComposer::eRotateNone, // 0 == DISPLAY_ORIENTATION_0
        ISurfaceComposer::eRotate270, // 1 == DISPLAY_ORIENTATION_90
        ISurfaceComposer::eRotate180, // 2 == DISPLAY_ORIENTATION_180
        ISurfaceComposer::eRotate90, // 3 == DISPLAY_ORIENTATION_270
    };
    ScreenshotClient screenshot;
    sp<IBinder> display = SurfaceComposerClient::getBuiltInDisplay(displayId);
    if (display == NULL) {
        fprintf(stderr, "Unable to get handle for display %d\n", displayId);
        return 1;
    }
    Vector<DisplayInfo> configs;
    SurfaceComposerClient::getDisplayConfigs(display, &configs);
    int activeConfig = SurfaceComposerClient::getActiveConfig(display);
    if (static_cast<size_t>(activeConfig) >= configs.size()) {
        fprintf(stderr, "Active config %d not inside configs (size %zu)\n",
                activeConfig, configs.size());
        return 1;
    }
    uint8_t displayOrientation = configs[activeConfig].orientation;
    uint32_t captureOrientation = ORIENTATION_MAP[displayOrientation];
    status_t result = screenshot.update(display, Rect(), 0, 0, 0, -1U,
            false, captureOrientation);
    if (result == NO_ERROR) {
        base = screenshot.getPixels();
        w = screenshot.getWidth();
        h = screenshot.getHeight();
        s = screenshot.getStride();
        f = screenshot.getFormat();
        size = screenshot.getSize();
    } else {
        const char* fbpath = "/dev/graphics/fb0";
        int fb = open(fbpath, O_RDONLY);
        if (fb >= 0) {
            struct fb_var_screeninfo vinfo;
            if (ioctl(fb, FBIOGET_VSCREENINFO, &vinfo) == 0) {
                uint32_t bytespp;
                if (vinfoToPixelFormat(vinfo, &bytespp, &f) == NO_ERROR) {
                    size_t offset = (vinfo.xoffset + vinfo.yoffset*vinfo.xres) * bytespp;
                    w = vinfo.xres;
                    h = vinfo.yres;
                    s = vinfo.xres;
                    size = w*h*bytespp;
                    mapsize = offset + size;
                    mapbase = mmap(0, mapsize, PROT_READ, MAP_PRIVATE, fb, 0);
                    if (mapbase != MAP_FAILED) {
                        base = (void const *)((char const *)mapbase + offset);
                    }
                }
            }
            close(fb);
        }
    }
    if (base != NULL) {
        if (png) {
            const SkImageInfo info = SkImageInfo::Make(w, h, flinger2skia(f),
                                                       kPremul_SkAlphaType);
            SkAutoTUnref<SkData> data(SkImageEncoder::EncodeData(info, base, s*bytesPerPixel(f),
                    SkImageEncoder::kPNG_Type, SkImageEncoder::kDefaultQuality));
            if (data.get()) {
                write(fd, data->data(), data->size());
            }
            if (fn != NULL) {
                notifyMediaScanner(fn);
            }
        } else {
            write(fd, &w, 4);
            write(fd, &h, 4);
            write(fd, &f, 4);
            size_t Bpp = bytesPerPixel(f);
            for (size_t y=0 ; y<h ; y++) {
                write(fd, base, w*Bpp);
                base = (void *)((char *)base + s*Bpp);
            }
        }
    }
    close(fd);
    if (mapbase != MAP_FAILED) {
        munmap((void *)mapbase, mapsize);
    }
    return 0;
}


Частично разберем данный код.

DEFAULT_DISPLAY_ID — идентификатор дисплея, с которого необходимо получить скриншот. В нашем случае 0.

flinger2skia и vinfoToPixelFormat — отвечает за определение, в каком формате должно быть изображение.

notifyMediaScanner — после того, как изображение будет создано в файловой системе необходимо послать broadcast, чтобы файл смог корректно отображаться. Если не вызывать данный broadcast, то не все приложении смогут увидеть созданный файл.
Функция main не является сложной для «чтения», поэтому разберем только важные моменты, которые непосредственно отвечают за получение данных о изображении.

/dev/graphics/fb0 — это так называемый framebuffer. Framebuffer — это область видеопамяти для кратковременного хранения одного или нескольких видеокадров. Исходя из кода main видно, что существуют версии Android устройств, которые хранят изображение экрана в этом файле. Таким образом, если вы «счастливчик», то узнав vinfo.xoffset и vinfo.yoffset (в большинстве случаев они будут равны 0) и используя консоль вы с легкостью сможете получить информацию о цвете:

dd if=/dev/graphics/fb0 bs=<bytes per pixel> count=1 skip=<pixel offset> 2>/dev/null | hd

В моём случае оказалось все не так просто и данный файл не содержать какую либо информацию о изображении.

screenshot.update — это второй способ, как можно получить изображение. Данная функция имеет несколько перегруженных методов с которыми можно ознакомиться тут.

Рассмотрим описание функции с самым большим количеством параметров:

ScreenshotClient::update(const sp<IBinder>& display,
        Rect sourceCrop, uint32_t reqWidth, uint32_t reqHeight,
        uint32_t minLayerZ, uint32_t maxLayerZ,
        bool useIdentityTransform, uint32_t rotation)

display — ссылка на необходимый display.
sourceCrop — кроп выбранной области изображения. Может содержать координаты верхней левой точки и нижней правой (всего 4 параметра xLeft, yTop, xRight, yBottom). Начальная точка координат — верхний левый угол.
reqWidth — ширина возвращаемого изображения
reqHeight — высота возвращаемого изображения
minLayerZ и maxLayerZ — как именно работают данные параметры не удалось понять. Перебор значений выдавал иногда черный кран
useIdentityTransform — Если true, то отключает слой наложения поверх приложений, т.е. тех Activity, которые используют ACTION_MANAGE_OVERLAY_PERMISSION
rotation — поворот изображения.

Таким образом, чтобы нам получить цвет пикселя, нам необходимо задать xLeft и yTop, сдвинув их на 1, т.к. отсчет будет идти с 0, а указанные координаты установить в xRight, yBottom. В reqWidth и reqHeight установить значение равным 1. Изменяя параметры данной функции мы сможем определять границы нужной для нас области.

Компиляция базового screencap.cpp


На самом деле это самая сложная часть, которая может занять несколько дней или целую неделю. К сожалению мне не удалось найти каких-то быстрых решений сборки новой версии screencap в сети, поэтому пришлось конкретно помучаться с clang'ом и его параметрами.

Если вы сразу же попробуете скомпилировать данный код, компилятор будет постоянно ругаться, что нет какого-то файла, а иногда может и сообщить, что файл то есть, но вот нет нужного конструктора.

Поэтому, первоначально я добавил все файлы, которые отсутствовали в библиотеке NDK. Ошибки дают представления, где примерно должен находиться тот или иной файл. Для этого, вам необходимо добавить недостающие файлы, а чтобы узнать где находится sysroot (директория, где ищет clang), можно воспользоваться следующей «фичей»:

echo "#include <bogus.h> int main(){}" > t.c; ~/arm/bin/clang  -v t.c; rm t.c

В ошибке будет виден путь до данной директории. Если clang у вас находится в другом месте — измените путь до него.

Log
Android (4751641 based on r328903) clang version 7.0.2 (https://android.googlesource.com/toolchain/clang 003100370607242ddd5815e4a043907ea9004281) (https://android.googlesource.com/toolchain/llvm 1d739ffb0366421d383e04ff80ec2ee591315116) (based on LLVM 7.0.2svn)
Target: armv7a-none-linux-android16
Thread model: posix
InstalledDir: /Users/macbookair/arm/bin
Found candidate GCC installation: /Users/macbookair/arm/bin/../lib/gcc/arm-linux-androideabi/4.9.x
Selected GCC installation: /Users/macbookair/arm/bin/../lib/gcc/arm-linux-androideabi/4.9.x
Candidate multilib: thumb;@mthumb
Candidate multilib: armv7-a;@march=armv7-a
Candidate multilib: armv7-a/thumb;@march=armv7-a@mthumb
Candidate multilib: .;
Selected multilib: armv7-a;@march=armv7-a
 "/Users/macbookair/arm/bin/clang70" -cc1 -triple armv7-none-linux-android16 -emit-obj -mrelax-all -disable-free -disable-llvm-verifier -discard-value-names -main-file-name t.c -mrelocation-model pic -pic-level 2 -pic-is-pie -mthread-model posix -mdisable-fp-elim -fmath-errno -masm-verbose -mconstructor-aliases -fuse-init-array -target-cpu generic -target-feature +soft-float-abi -target-abi aapcs-linux -mfloat-abi soft -fallow-half-arguments-and-returns -dwarf-column-info -debugger-tuning=gdb -target-linker-version 241.9 -v -resource-dir /Users/macbookair/arm/lib64/clang/7.0.2 -isysroot /Users/macbookair/arm/bin/../sysroot -internal-isystem /Users/macbookair/arm/bin/../sysroot/usr/local/include -internal-isystem /Users/macbookair/arm/lib64/clang/7.0.2/include -internal-externc-isystem /Users/macbookair/arm/bin/../sysroot/include -internal-externc-isystem /Users/macbookair/arm/bin/../sysroot/usr/include -fdebug-compilation-dir /Users/macbookair/Downloads/sji-android-screen-capture-master/src/android/capture -ferror-limit 19 -fmessage-length 80 -fno-signed-char -fobjc-runtime=gcc -fdiagnostics-show-option -fcolor-diagnostics -o /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/t-9f5ef7.o -x c t.c
clang -cc1 version 7.0.2 based upon LLVM 7.0.2svn default target x86_64-apple-darwin18.0.0
ignoring nonexistent directory "/Users/macbookair/arm/bin/../sysroot/include"
#include "..." search starts here:
#include <...> search starts here:
 /Users/macbookair/arm/bin/../sysroot/usr/local/include
 /Users/macbookair/arm/lib64/clang/7.0.2/include
 /Users/macbookair/arm/bin/../sysroot/usr/include
End of search list.
t.c:1:10: error: expected "FILENAME" or <FILENAME>
#include <bogus.h> int main(){}
         ^
1 error generated.


Обращаемся к Google и ищем все необходимые файлы. В моем случае пришлось добавить следующие директории и файлы:

Директории и файлы
//Добавлены директории
~/arm/sysroot/usr/include/binder
~/arm/sysroot/usr/include/utils
~/arm/sysroot/usr/include/cutils
~/arm/sysroot/usr/include/log
~/arm/sysroot/usr/include/system
~/arm/sysroot/usr/include/gui
~/arm/sysroot/usr/include/ui
~/arm/sysroot/usr/include/hardware
~/arm/sysroot/usr/ports

//Добавлены файлы
~/arm/sysroot/usr/include/SkAnnotation.h
~/arm/sysroot/usr/include/SkAtomics.h
~/arm/sysroot/usr/include/SkBBHFactory.h
~/arm/sysroot/usr/include/SkBitmap.h
~/arm/sysroot/usr/include/SkBitmapDevice.h
~/arm/sysroot/usr/include/SkBlitRow.h
~/arm/sysroot/usr/include/SkBlurTypes.h
~/arm/sysroot/usr/include/SkCanvas.h
~/arm/sysroot/usr/include/SkChunkAlloc.h
~/arm/sysroot/usr/include/SkClipStack.h
~/arm/sysroot/usr/include/SkColor.h
~/arm/sysroot/usr/include/SkColorFilter.h
~/arm/sysroot/usr/include/SkColorPriv.h
~/arm/sysroot/usr/include/SkColorTable.h
~/arm/sysroot/usr/include/SkComposeShader.h
~/arm/sysroot/usr/include/SkData.h
~/arm/sysroot/usr/include/SkDataTable.h
~/arm/sysroot/usr/include/SkDeque.h
~/arm/sysroot/usr/include/SkDevice.h
~/arm/sysroot/usr/include/SkDither.h
~/arm/sysroot/usr/include/SkDocument.h
~/arm/sysroot/usr/include/SkDraw.h
~/arm/sysroot/usr/include/SkDrawable.h
~/arm/sysroot/usr/include/SkDrawFilter.h
~/arm/sysroot/usr/include/SkDrawLooper.h
~/arm/sysroot/usr/include/SkDrawPictureCallback.h
~/arm/sysroot/usr/include/SkEndian.h
~/arm/sysroot/usr/include/SkError.h
~/arm/sysroot/usr/include/SkFilterQuality.h
~/arm/sysroot/usr/include/SkFixed.h
~/arm/sysroot/usr/include/SkFlattenable.h
~/arm/sysroot/usr/include/SkFlattenableSerialization.h
~/arm/sysroot/usr/include/SkFloatBits.h
~/arm/sysroot/usr/include/SkFloatingPoint.h
~/arm/sysroot/usr/include/SkFont.h
~/arm/sysroot/usr/include/SkFontHost.h
~/arm/sysroot/usr/include/SkFontLCDConfig.h
~/arm/sysroot/usr/include/SkFontStyle.h
~/arm/sysroot/usr/include/SkGraphics.h
~/arm/sysroot/usr/include/SkImage.h
~/arm/sysroot/usr/include/SkImageDecoder.h
~/arm/sysroot/usr/include/SkImageEncoder.h
~/arm/sysroot/usr/include/SkImageFilter.h
~/arm/sysroot/usr/include/SkImageGenerator.h
~/arm/sysroot/usr/include/SkImageInfo.h
~/arm/sysroot/usr/include/SkInstCnt.h
~/arm/sysroot/usr/include/SkLazyPtr.h
~/arm/sysroot/usr/include/SkMallocPixelRef.h
~/arm/sysroot/usr/include/SkMask.h
~/arm/sysroot/usr/include/SkMaskFilter.h
~/arm/sysroot/usr/include/SkMath.h
~/arm/sysroot/usr/include/SkMatrix.h
~/arm/sysroot/usr/include/SkMetaData.h
~/arm/sysroot/usr/include/SkMultiPictureDraw.h
~/arm/sysroot/usr/include/SkMutex.h
~/arm/sysroot/usr/include/SkOnce.h
~/arm/sysroot/usr/include/SkOSFile.h
~/arm/sysroot/usr/include/SkPackBits.h
~/arm/sysroot/usr/include/SkPaint.h
~/arm/sysroot/usr/include/SkPath.h
~/arm/sysroot/usr/include/SkPathEffect.h
~/arm/sysroot/usr/include/SkPathMeasure.h
~/arm/sysroot/usr/include/SkPathRef.h
~/arm/sysroot/usr/include/SkPicture.h
~/arm/sysroot/usr/include/SkPictureRecorder.h
~/arm/sysroot/usr/include/SkPixelRef.h
~/arm/sysroot/usr/include/SkPixelSerializer.h
~/arm/sysroot/usr/include/SkPoint.h
~/arm/sysroot/usr/include/SkPostConfig.h
~/arm/sysroot/usr/include/SkPreConfig.h
~/arm/sysroot/usr/include/SkRasterizer.h
~/arm/sysroot/usr/include/SkRect.h
~/arm/sysroot/usr/include/SkRefCnt.h
~/arm/sysroot/usr/include/SkRegion.h
~/arm/sysroot/usr/include/SkRRect.h
~/arm/sysroot/usr/include/SkScalar.h
~/arm/sysroot/usr/include/SkShader.h
~/arm/sysroot/usr/include/SkSize.h
~/arm/sysroot/usr/include/SkSpinlock.h
~/arm/sysroot/usr/include/SkStream.h
~/arm/sysroot/usr/include/SkString.h
~/arm/sysroot/usr/include/SkStrokeRec.h
~/arm/sysroot/usr/include/SkSurface.h
~/arm/sysroot/usr/include/SkSurfaceProps.h
~/arm/sysroot/usr/include/SkTArray.h
~/arm/sysroot/usr/include/SkTDArray.h
~/arm/sysroot/usr/include/SkTDict.h
~/arm/sysroot/usr/include/SkTDStack.h
~/arm/sysroot/usr/include/SkTemplates.h
~/arm/sysroot/usr/include/SkTextBlob.h
~/arm/sysroot/usr/include/SkThread.h
~/arm/sysroot/usr/include/SkTime.h
~/arm/sysroot/usr/include/SkTInternalLList.h
~/arm/sysroot/usr/include/SkTLazy.h
~/arm/sysroot/usr/include/SkTRegistry.h
~/arm/sysroot/usr/include/SkTSearch.h
~/arm/sysroot/usr/include/SkTypeface.h
~/arm/sysroot/usr/include/SkTypes.h
~/arm/sysroot/usr/include/SkUnPreMultiply.h
~/arm/sysroot/usr/include/SkUserConfig.h
~/arm/sysroot/usr/include/SkUtils.h
~/arm/sysroot/usr/include/SkWeakRefCnt.h
~/arm/sysroot/usr/include/SkWriteBuffer.h
~/arm/sysroot/usr/include/SkWriter32.h
~/arm/sysroot/usr/include/SkXfermode.h

//И небольшая правка кода
~/arm/sysroot/usr/include/android/log.h


И казалось бы, что вот он успех, все что надо добавлено. Выполняем компиляцию

~/arm/bin/clang -pie screencap.cpp -o ./screencap

и получаем ужасный результат:

Слабонервным не смотреть
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function main: error: undefined reference to 'android::ProcessState::self()'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function main: error: undefined reference to 'android::ProcessState::startThreadPool()'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function main: error: undefined reference to 'android::ScreenshotClient::ScreenshotClient()'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function main: error: undefined reference to 'android::SurfaceComposerClient::getBuiltInDisplay(int)'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function main: error: undefined reference to 'android::SurfaceComposerClient::getDisplayConfigs(android::sp<android::IBinder> const&, android::Vector<android::DisplayInfo>*)'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function main: error: undefined reference to 'android::SurfaceComposerClient::getActiveConfig(android::sp<android::IBinder> const&)'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function main: error: undefined reference to 'android::ScreenshotClient::update(android::sp<android::IBinder> const&, android::Rect, unsigned int, unsigned int, unsigned int, unsigned int, bool, unsigned int)'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function main: error: undefined reference to 'android::ScreenshotClient::getPixels() const'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function main: error: undefined reference to 'android::ScreenshotClient::getWidth() const'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function main: error: undefined reference to 'android::ScreenshotClient::getHeight() const'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function main: error: undefined reference to 'android::ScreenshotClient::getStride() const'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function main: error: undefined reference to 'android::ScreenshotClient::getFormat() const'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function main: error: undefined reference to 'android::ScreenshotClient::getSize() const'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function main: error: undefined reference to 'android::bytesPerPixel(int)'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function main: error: undefined reference to 'SkImageEncoder::EncodeData(SkImageInfo const&, void const*, unsigned int, SkImageEncoder::Type, int)'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function main: error: undefined reference to 'android::bytesPerPixel(int)'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function main: error: undefined reference to 'android::ScreenshotClient::~ScreenshotClient()'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function main: error: undefined reference to 'android::ScreenshotClient::~ScreenshotClient()'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function main: error: undefined reference to 'stderr'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function main: error: undefined reference to 'stderr'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function main: error: undefined reference to 'stderr'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function usage(char const*): error: undefined reference to 'stderr'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function notifyMediaScanner(char const*): error: undefined reference to 'android::String8::String8(char const*)'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function notifyMediaScanner(char const*): error: undefined reference to 'android::String8::String8(char const*)'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function notifyMediaScanner(char const*): error: undefined reference to 'android::String8::append(char const*)'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function notifyMediaScanner(char const*): error: undefined reference to 'android::String8::append(char const*)'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function notifyMediaScanner(char const*): error: undefined reference to 'android::String8::append(char const*)'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function notifyMediaScanner(char const*): error: undefined reference to 'android::String8::append(char const*)'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function notifyMediaScanner(char const*): error: undefined reference to 'android::String8::~String8()'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function notifyMediaScanner(char const*): error: undefined reference to 'android::String8::~String8()'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function notifyMediaScanner(char const*): error: undefined reference to 'android::String8::~String8()'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function notifyMediaScanner(char const*): error: undefined reference to 'android::String8::~String8()'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o(.ARM.extab+0x0): error: undefined reference to '__gxx_personality_v0'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o(.ARM.extab+0x48): error: undefined reference to '__gxx_personality_v0'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function android::sp<android::ProcessState>::~sp(): error: undefined reference to 'android::RefBase::decStrong(void const*) const'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o(.ARM.extab.text._ZN7android2spINS_12ProcessStateEED2Ev+0x0): error: undefined reference to '__gxx_personality_v0'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function android::Vector<android::DisplayInfo>::Vector(): error: undefined reference to 'android::VectorImpl::VectorImpl(unsigned int, unsigned int)'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function android::Vector<android::DisplayInfo>::operator[](unsigned int) const: error: undefined reference to '__android_log_assert'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o(.ARM.extab.text._ZN12SkAutoTUnrefI6SkDataED2Ev+0x0): error: undefined reference to '__gxx_personality_v0'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function android::Vector<android::DisplayInfo>::~Vector(): error: undefined reference to 'android::VectorImpl::finish_vector()'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function android::Vector<android::DisplayInfo>::~Vector(): error: undefined reference to 'android::VectorImpl::~VectorImpl()'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function android::Vector<android::DisplayInfo>::~Vector(): error: undefined reference to 'android::VectorImpl::~VectorImpl()'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function android::sp<android::IBinder>::~sp(): error: undefined reference to 'android::RefBase::decStrong(void const*) const'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function __clang_call_terminate: error: undefined reference to '__cxa_begin_catch'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function __clang_call_terminate: error: undefined reference to 'std::terminate()'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function android::Vector<android::DisplayInfo>::~Vector(): error: undefined reference to 'operator delete(void*)'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function SkRefCntBase::unref() const: error: undefined reference to 'SkDebugf(char const*, ...)'
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:typeinfo for android::Vector<android::DisplayInfo>: error: undefined reference to 'vtable for __cxxabiv1::__vmi_class_type_info'
/Users/macbookair/Documents/test/bin/../lib/gcc/arm-linux-androideabi/4.9.x/../../../../arm-linux-androideabi/bin/ld: the vtable symbol may be undefined because the class is missing its key function
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:typeinfo for android::Vector<android::DisplayInfo>: error: undefined reference to 'typeinfo for android::VectorImpl'
clang70: error: linker command failed with exit code 1 (use -v to see invocation)


По мне, это был реальный ад, т.к. множество файлов имело ошибки, чего по идее быть не должно, ведь содержимое данных файлов не менялось. На данном этапе я на долго «присел» и начал активно гуглить ошибки, но нужного ответа не нашел. Каким образом я решил поизучать туториал по clang я уже не помню, но это решение меня спасло!

Как оказалось, у clang есть параметр (а точнее у ld) --unresolved-symbols, который отвечает за работу с неразрешенными (unresolved) символами. Хотя по самой ошибки и не скажешь, что дело в этом. Добавляем параметр и выполняем компиляцию снова:

~/arm/bin/clang -pie screencap.cpp  -o ./screencap -Wl,--unresolved-symbols=ignore-all -s

Наконец-то компиляция прошла успешно! На самом деле, я уже думал, что большая часть проблем ушла, но не тут то было. Начали появляться ошибки, наподобие следующей:

CANNOT LINK EXECUTABLE: cannot locate symbol "_ZN7android12ProcessState4selfEv"

Как оказалось, компилятору было достаточно наших добавленных файлов, но на Android устройстве все эти файлы хранятся в .so библиотеках. Позже разобравший более менее хорошо в этом вопросе, я нашел относительно простой способ поиска необходимых библиотек. Для этого нужно открыть бинарных файл screencap, который находится на Android-устройстве (/system/bin/screencap) в текстовом редакторе и посмотреть все названия .so библиотек, которые используются в данном файле. В моем случае вот эта часть:

Часть с названиями используемых библиотек
...libdl.so__libc_initlibc.so__cxa_atexit__register_atfork__gnu_Unwind_Find_exidxLIBC_PRIVATE_ZNK7android6VectorINS_11DisplayInfoEE12do_constructEPvj__snprintf_chk__aeabi_memset__aeabi_memcpy_ZNK7android6VectorINS_11DisplayInfoEE10do_destroyEPvj_ZNK7android6VectorINS_11DisplayInfoEE7do_copyEPvPKvj_ZNK7android6VectorINS_11DisplayInfoEE8do_splatEPvPKvj_ZNK7android6VectorINS_11DisplayInfoEE15do_move_forwardEPvPKvj_ZNK7android6VectorINS_11DisplayInfoEE16do_move_backwardEPvPKvj_ZN7android10VectorImpl13finish_vectorEv_ZN7android10VectorImplD2Ev_ZTVN7android6VectorINS_11DisplayInfoEEE_ZdlPvsignal__android_log_print_ZN7android12ProcessState4selfEv_ZN7android12ProcessState15startThreadPoolEv_ZNK7android7RefBase9decStrongEPKvgetoptatoidupopen__errnostrerrorfprintfstrlenstrcmp_ZN7android16ScreenshotClientC1Ev_ZN7android21SurfaceComposerClient17getBuiltInDisplayEi_ZN7android10VectorImplC2Ejj_ZN7android21SurfaceComposerClient17getDisplayConfigsERKNS_2spINS_7IBinderEEEPNS_6VectorINS_11DisplayInfoEEE_ZN7android21SurfaceComposerClient15getActiveConfigERKNS_2spINS_7IBinderEEE_ZN7android16ScreenshotClient6updateERKNS_2spINS_7IBinderEEENS_4RectEjjjjbj_ZNK7android16ScreenshotClient9getPixelsEv_ZNK7android16ScreenshotClient8getWidthEv_ZNK7android16ScreenshotClient9getHeightEv_ZNK7android16ScreenshotClient9getStrideEv_ZNK7android16ScreenshotClient9getFormatEv_ZNK7android16ScreenshotClient7getSizeEvioctlclose_ZN7android13bytesPerPixelEi_ZN14SkImageEncoder10EncodeDataERK11SkImageInfoPKvjNS_4TypeEiwrite_ZN7android7String8C1EPKc_ZN7android7String86appendEPKcsystemfputs_ZN7android7String8D1Evmunmap_ZN7android16ScreenshotClientD1Evmmapoptargstderroptindabort_edata__bss_start_endlibcutils.solibutils.solibbinder.solibskia.solibui.solibgui.solibc++.solibm.so...


Вы можете сравнить данную часть с вашим скомпилированным файлом и найти, каких же библиотек вам не хватает. В данном случае ими оказались: libgui.so, libui.so, libcutils.so, libutils.so, libbinder.so, libskia.so. Ищем их расположение (на самом деле, они находятся в одном и том же месте):

find -name 'libskia.so' 2>/dev/null
/system/lib/libskia.so

Выполняем копирование на sdcard/libs всех библиотек (пример для libskia.so):

./system/lib/libskia.so /sdcard/

Используя adb pull копируем файлы с моб. устройства на компьютер:

adb pull /sdcard/libs ~/arm

Располагаем их в нужной для вас директории и выполняем компиляцию, но уже с новым параметром *.so, который указывает на то, что нужно так же использовать все .so библиотеки при сборке, а точнее — указать ссылки на них в файле.

~/arm/bin/clang -pie screencap.cpp *.so -o ./screencap -Wl,--unresolved-symbols=ignore-all -s

Теперь и компиляция пройдет успешно и запуск бинарного файла на моб. устройстве будет без ошибок.

Заключение и результат


Как вы могли заметить, добавление своего функционала «в чужой код» является не очень простым занятием.

Измененный код файла screencap приведен ниже. Сборка его происходит таким же образом, как и оригинального. Добавленные изменения думаю будут понятны большинству читателей, поэтому решил его не комментировать.

Улучшенный screencap.cpp
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <linux/fb.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <binder/ProcessState.h>
#include <gui/SurfaceComposerClient.h>
#include <gui/ISurfaceComposer.h>
#include <ui/DisplayInfo.h>
#include <ui/PixelFormat.h>
// TODO: Fix Skia.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
#include <SkImageEncoder.h>
#include <SkData.h>
#pragma GCC diagnostic pop
using namespace android;
static uint32_t DEFAULT_DISPLAY_ID = ISurfaceComposer::eDisplayIdMain;
static void usage(const char* pname)
{
    fprintf(stderr,
            "usage: %s [-hpcr] [-d display-id] [FILENAME] [x1 y1] [x2 y2]\n"
            "   -h: this message\n"
            "   -p: save the file as a png.\n"
            "   -d: specify the display id to capture, default %d.\n"
            "   -c: print pixel color [x y].\n"
            "   -r: get rectangle area [x1 - left y1 - top x2 - right y2 - bottom].\n"
            "If FILENAME ends with .png it will be saved as a png.\n"
            "If FILENAME is not given, the results will be printed to stdout.\n",
            pname, DEFAULT_DISPLAY_ID
    );
}
static SkColorType flinger2skia(PixelFormat f)
{
    switch (f) {
        case PIXEL_FORMAT_RGB_565:
            return kRGB_565_SkColorType;
        default:
            return kN32_SkColorType;
    }
}
static status_t vinfoToPixelFormat(const fb_var_screeninfo& vinfo,
        uint32_t* bytespp, uint32_t* f)
{
    switch (vinfo.bits_per_pixel) {
        case 16:
            *f = PIXEL_FORMAT_RGB_565;
            *bytespp = 2;
            break;
        case 24:
            *f = PIXEL_FORMAT_RGB_888;
            *bytespp = 3;
            break;
        case 32:
            // TODO: do better decoding of vinfo here
            *f = PIXEL_FORMAT_RGBX_8888;
            *bytespp = 4;
            break;
        default:
            return BAD_VALUE;
    }
    return NO_ERROR;
}
static status_t notifyMediaScanner(const char* fileName) {
    String8 cmd("am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d file://");
    String8 fileUrl("\"");
    fileUrl.append(fileName);
    fileUrl.append("\"");
    cmd.append(fileName);
    cmd.append(" > /dev/null");
    int result = system(cmd.string());
    if (result < 0) {
        fprintf(stderr, "Unable to broadcast intent for media scanner.\n");
        return UNKNOWN_ERROR;
    }
    return NO_ERROR;
}
int main(int argc, char** argv)
{
    ProcessState::self()->startThreadPool();
    const char* pname = argv[0];
    int x1,x2,y1,y2;
    bool png = false;
    bool color = false;
    bool rectangle = false;
    int32_t displayId = DEFAULT_DISPLAY_ID;
    int c;
    while ((c = getopt(argc, argv, "phdcr:")) != -1) {
        switch (c) {
            case 'p':
                png = true;
                break;
            case 'd':
                displayId = atoi(optarg);
                break;
            case 'c':
                color = true;
                break;
            case 'r':
                rectangle = true;
                break;
            case '?':
            case 'h':
                usage(pname);
                return 1;
        }
    }

    argc -= optind;
    
    if (color) {
        x1 = atoi(argv[2])-1;
        y1 = atoi(argv[3])-1;
        x2 = atoi(argv[2]);
        y2 = atoi(argv[3]);
    }
    else if (rectangle) {
        x1 = atoi(argv[3]);
        y1 = atoi(argv[4]);
        x2 = atoi(argv[5]);
        y2 = atoi(argv[6]);
        optind--;
    }

    
    argv += optind;

    int fd = -1;
    const char* fn = NULL;
    if (argc == 0) {
        fd = dup(STDOUT_FILENO);
    } else if (argc == 1 || argc == 4) {
        fn = argv[0];
        fd = open(fn, O_WRONLY | O_CREAT | O_TRUNC, 0664);
        if (fd == -1) {
            fprintf(stderr, "Error opening file: %s (%s)\n", fn, strerror(errno));
            return 1;
        }
        const int len = strlen(fn);
        if (len >= 4 && 0 == strcmp(fn+len-4, ".png")) {
            png = true;
        }
    }
    
    if (fd == -1 && !color && !rectangle) {
        usage(pname);
        return 1;
    }
    
    
    void const* mapbase = MAP_FAILED;
    ssize_t mapsize = -1;
    void const* base = NULL;
    uint32_t w, s, h, f;
    size_t size = 0;
    // Maps orientations from DisplayInfo to ISurfaceComposer
    static const uint32_t ORIENTATION_MAP[] = {
        ISurfaceComposer::eRotateNone, // 0 == DISPLAY_ORIENTATION_0
        ISurfaceComposer::eRotate270, // 1 == DISPLAY_ORIENTATION_90
        ISurfaceComposer::eRotate180, // 2 == DISPLAY_ORIENTATION_180
        ISurfaceComposer::eRotate90, // 3 == DISPLAY_ORIENTATION_270
    };
    ScreenshotClient screenshot;
    sp<IBinder> display = SurfaceComposerClient::getBuiltInDisplay(displayId);
    if (display == NULL) {
        fprintf(stderr, "Unable to get handle for display %d\n", displayId);
        return 1;
    }
    Vector<DisplayInfo> configs;
    SurfaceComposerClient::getDisplayConfigs(display, &configs);
    int activeConfig = SurfaceComposerClient::getActiveConfig(display);
    if (static_cast<size_t>(activeConfig) >= configs.size()) {
        fprintf(stderr, "Active config %d not inside configs (size %zu)\n",
                activeConfig, configs.size());
        return 1;
    }
    uint8_t displayOrientation = configs[activeConfig].orientation;
    uint32_t captureOrientation = ORIENTATION_MAP[displayOrientation];
    status_t result;
    if (color || rectangle) {
        result = screenshot.update(display, Rect(x1,y1,x2,y2), x2-x1, y2-y1, 0, -1U,
            false, captureOrientation);
    }
    else {
        result = screenshot.update(display, Rect(), 0, 0, 0, -1U,
            false, captureOrientation);
    }

    if (result == NO_ERROR) {
        base = screenshot.getPixels();
        w = screenshot.getWidth();
        h = screenshot.getHeight();
        s = screenshot.getStride();
        f = screenshot.getFormat();
        size = screenshot.getSize();

        if (color) {
            size_t Bpp = 4; //bytesPerPixel
            char* rawImageData;
            rawImageData = (char *)base;
            base = (void *)((char *)base + Bpp);
            for (int i=0; i<sizeof(rawImageData); i++) {
                printf( "%02X", rawImageData[i]);
            }
            
            return 1;
        }
    } else {
        const char* fbpath = "/dev/graphics/fb0";
        int fb = open(fbpath, O_RDONLY);
        if (fb >= 0) {
            struct fb_var_screeninfo vinfo;
            if (ioctl(fb, FBIOGET_VSCREENINFO, &vinfo) == 0) {
                uint32_t bytespp;
                if (vinfoToPixelFormat(vinfo, &bytespp, &f) == NO_ERROR) {
                    size_t offset = (vinfo.xoffset + vinfo.yoffset*vinfo.xres) * bytespp;
                    w = vinfo.xres;
                    h = vinfo.yres;
                    s = vinfo.xres;
                    size = w*h*bytespp;
                    mapsize = offset + size;
                    mapbase = mmap(0, mapsize, PROT_READ, MAP_PRIVATE, fb, 0);
                    if (mapbase != MAP_FAILED) {
                        base = (void const *)((char const *)mapbase + offset);
                    }
                }
            }
            close(fb);
        }
    }
    if (base != NULL) {
        if (png) {
            const SkImageInfo info = SkImageInfo::Make(w, h, flinger2skia(f), kPremul_SkAlphaType);
            SkAutoTUnref<SkData> data(SkImageEncoder::EncodeData(info, base, s*bytesPerPixel(f),
                    SkImageEncoder::kPNG_Type, SkImageEncoder::kDefaultQuality));
            if (data.get()) {
                write(fd, data->data(), data->size());
            }
            if (fn != NULL) {
                notifyMediaScanner(fn);
            }
        } else {
            write(fd, &w, 4);
            write(fd, &h, 4);
            write(fd, &f, 4);
            size_t Bpp = bytesPerPixel(f);
            for (size_t y=0 ; y<h ; y++) {
                write(fd, base, w*Bpp);
                base = (void *)((char *)base + s*Bpp);
            }
        }
    }
    close(fd);
    if (mapbase != MAP_FAILED) {
        munmap((void *)mapbase, mapsize);
    }
    return 0;
}


Пример получения цвета пикселя:

./screencap -c 100 100

Пример получения изображения по заданной области:

./screencap -r /sdcard/test.png 0 0 720 640

Данный проект залит на GitHub, так что кому интересно — заходите.

P.S. Если вы знаете лучший способ, как найти нужный исходный код исполняемого файла или необходимых библиотек для компиляции — прошу добавить комментарий и я добавлю данный совет в статью.
Tags:
Hubs:
Total votes 6: ↑5 and ↓1+4
Comments7

Articles