GardenMobile & interfaces

Android JNI and NDK working notes

An older collection of CMake, native logging, bitmap, and Java-array boundary examples.

JNI code joins two memory and type systems. These older notes collect small examples from that boundary: finding native sources, loading libraries, passing arrays, and releasing acquired resources.

Historical fragments. These snippets come from different application contexts. They are not a complete native library and have not been compiled for this revision. Application classes, OpenCV setup, and resource ownership must be reviewed together before reuse.

The source recorded a directory/glob example and an include-path note:

My project directory was like this:
    --project
    ---Classes
    ----Application
    -----.h and .c files
    ----OtherFolders
    --main.cpp
And what I used to include the files in all those folders:
    file(GLOB source_files
            "*.h"
            "*.cpp"
            "Classes/*/*.cpp"
            "Classes/*/*.h"
    )

    add_executable(Server ${source_files})
Add include_directories("/your/path/here").
This will be similar to calling gcc with -I/your/path/here/ option.
Make sure you put double quotes around the path.

Its Android shared-library configuration linked native bitmap and logging support:

cmake_minimum_required(VERSION 3.4.1)
add_library(
        MagicJni
        SHARED
        MagicJni.cpp
        beautify/MagicBeautify.cpp
        beautify/MagicBeautify.h
        bitmap/BitmapOperation.cpp
        bitmap/BitmapOperation.h
        bitmap/Conversion.cpp
        bitmap/Conversion.h
        bitmap/JniBitmap.h
        )

find_library(
        log-lib
        log)


target_link_libraries( # Specifies the target library.
        MagicJni

        -ljnigraphics
        ${log-lib})

Enter the native boundary

Native logging, a JNI entry-point skeleton, and a string return were recorded separately:

#include <android/log.h>

#define log_print __android_log_print
#define  LOG_TAG    "MagicJni"
#define  LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)
#define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)

….
#Using

    LOGE("no bitmap data was stored. returning null...”);
    log_print(ANDROID_LOG_VERBOSE, "stabilize", "Pointf %f %f", tp.GetPoint().x, tp.GetPoint().y);
//
// Original example, 2019-06-18.
//

#include <jni.h>
#include <string>
#include <stddef.h>

#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <android/bitmap.h>
#include <android/log.h>

#define  LOG_TAG    "native-lib"
#define  LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)
#define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
#define log_print __android_log_print

using namespace cv;

extern "C" JNIEXPORT void JNICALL
Java_com_nhancv_facemask_tracking_PointState_init(JNIEnv *env, jobject /* this */) {
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_nhancv_facemask_tracking_PointState_stringFromJNI(JNIEnv *env, jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

The symbol names encode the Java package and method names. Renaming only one side breaks that binding. The logging excerpt includes explanatory text rather than being a standalone translation unit.

Work with a bitmap

This fragment locks bitmap pixels, uses OpenCV, and releases the acquired pixel storage:

extern "C" JNIEXPORT void JNICALL
Java_com_nhancv_facemask_tracking_PointState_canny(
        JNIEnv *env,
        jobject /* this */,
        jobject bitmap,
        jstring destination) {


    // Get information about format and size
    AndroidBitmapInfo info;
    AndroidBitmap_getInfo(env, bitmap, &info);


    // Get pointer to pixel buffer
    void *pixels = 0;
    AndroidBitmap_lockPixels(env, bitmap, &pixels);

    {


        Mat input(info.height, info.width, CV_8UC4, pixels);
        Mat dst;
        // Convert to gray
        cvtColor(input, input, COLOR_BGR2GRAY);
        // Histogram equalization
        equalizeHist(input, dst);
        // Saturation by 10%
        float alpha = 1.1f;
        float beta = 12.75f;
        dst.convertTo(dst, -1, alpha, beta);


        // Save to destination
        const char *dest = env->GetStringUTFChars(destination, 0);
        imwrite(dest, dst);
        env->ReleaseStringUTFChars(destination, dest);
    }


    // Release the Bitmap buffer once we have it inside our Mat
    AndroidBitmap_unlockPixels(env, bitmap);
}

Bitmap format, error handling, stride, and lifetime all need explicit consideration; the excerpt alone does not validate those conditions.

Transfer numeric arrays

Load the corresponding native library before calling its methods:

static {
    System.loadLibrary("SuperpoweredAudio");
}

The Java conversion example chooses little-endian byte order:

public short[] toShorts(byte[] bytes) {
    short[] out = new short[bytes.length / 2];
    ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(out);
    return out;
}

public byte[] toBytes(short[] shorts) {
    byte[] out = new byte[shorts.length * 2];
    ByteBuffer.wrap(out).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().put(shorts);
    return out;
}

The short-array example acquires elements, creates a result, and releases the acquired elements:

extern "C" JNIEXPORT jshortArray
Java_com_nhancv_opentokeffects_SuperpoweredAudio_processData(
        JNIEnv *__unused env,
        jobject  __unused obj,
        jshortArray data
) {
    // Take data from java
    jshort *tempPointer = env->GetShortArrayElements(data, JNI_FALSE);
    int dataSize = env->GetArrayLength(data);
    
    // Return
    jshortArray result = (env)->NewShortArray(dataSize);
    (env)->SetShortArrayRegion(result, 0, dataSize, (jshort *) tempPointer);
    (env)->ReleaseShortArrayElements(data, tempPointer, JNI_ABORT);
    return result;
}

Allocation and vectors

The remaining fragments show allocation, vector flattening, and a float-array result:

static float *floatBuffer;

// allocate buffer
floatBuffer = (float *) malloc(sizeof(float) * 2 * bufferSize);
std::vector<PointState> trackPoints;

std::vector<float> resultVector;
for (const PointState &tp : trackPoints) {
    resultVector.push_back(tp.GetPoint().x);
    resultVector.push_back(tp.GetPoint().y);
}
extern "C" JNIEXPORT jfloatArray JNICALL
Java_com_nhancv_facemask_tracking_PointState_stabilize(JNIEnv *env, jobject /* this */,
        jfloatArray landmark_points) {
    // Read
    jfloat *tempPointer = env->GetFloatArrayElements(landmark_points, JNI_FALSE);
    int dataSize = env->GetArrayLength(landmark_points);
    // Populate data to vector
    std::vector<cv::Point2f> landmarks;
    for (int i = 0; i < dataSize; i += 2) {
        landmarks.emplace_back(Point2f(*(tempPointer + i), *(tempPointer + i + 1)));
    }
    
    for (const cv::Point2f &lp : landmarks) {
        trackPoints.emplace_back(lp);
    }
    
    std::vector<float> resultVector;
    for (const PointState &tp : trackPoints) {
        resultVector.push_back(tp.GetPoint().x);
        resultVector.push_back(tp.GetPoint().y);
    }

    // Format data to return
    jfloatArray result = (env)->NewFloatArray(dataSize);
    (env)->SetFloatArrayRegion(result, 0, dataSize, (jfloat *) &*resultVector.begin());

    (env)->ReleaseFloatArrayElements(landmark_points, tempPointer, JNI_ABORT);
    return result;
}

Allocation must have an owner and matching cleanup. Empty vectors, element counts, JNI failures, and application-specific tracking objects are outside the small examples’ guarantees. For the recorded size_t include error, the source added:

#include <stddef.h>

← Back to the garden