1

In my native code I generate a vector of float and need to send this to java part by converting it to byte array (using little endian scheme). Later I resend this byte array and need to convert it back to original float vector. I could not find exact example and wrote below code, that will take 4 byte values at a time and will convert it to float and add it to final vector of float. I will not be modifying any of the data, just perform some calculations, so need it to be fast and if possible avoid memory copy wherever possible.

Currently it is giving me warning that "Using unsigned char for signed value of type jbyte". Can someone guide me how to proceed?

JNIEXPORT jfloat JNICALL Java_com_xyzxyzxcyzxczxczc(JNIEnv *env, jclass type, jlong hEngineHandle, jbyteArray feature1){
try {
    PeopleCounting *obj = (PeopleCounting *) hEngineHandle;
    jbyte *f1 = (jbyte *)env->GetByteArrayElements(feature1, NULL);
    if(obj->faceRecognitionByteArraySize == 0){ // Setting it once for future use as it not going to change for my use case
        obj->faceRecognitionByteArraySize = env->GetArrayLength(feature1);
    }

    union UStuff
    {
        float   f;
        unsigned char   c[4];
    };


    UStuff f1bb;

    std::vector<float> f1vec;

    //Convert every 4 bytes to float using a union
    for (int i = 0; i < obj->faceRecognitionByteArraySize; i+=4){
        //Going backwards - due to endianness

        // Warning here. // Using unsigned char for signed value of type jbyte
        f1bb.c[3] = f1[i];
        f1bb.c[2] = f1[i+1];
        f1bb.c[1] = f1[i+2];
        f1bb.c[0] = f1[i+3];

        f1vec.push_back(f1bb.f);

    }

    // release it
    env->ReleaseByteArrayElements(feature1, f1, 0 );

 // Work with f1vec data
}

UPDATES: As suggested by @Alex both consumer and producer of byte array will be the C++ then there is no need for any endianess. So the approach I am going to take is as below:

A) Java end I initialize a byte[] of needed length (4 * number of float values) B) Pass this as jbyteArray to JNI function

Now, How to fill this byteArray from C++ end?

JNIEXPORT void JNICALL Java_com_xyz_FaceRecognizeGenerateFeatureData(JNIEnv *env, jclass type, jlong hEngineHandle, jlong addrAlignedFaceMat, jbyteArray featureData){
try {
    PeopleCounting *obj = (PeopleCounting *) hEngineHandle;
    Mat *pMat = (Mat *) addrAlignedFaceMat;

    vector<float> vecFloatFeatureData = obj->faceRecognizeGenerateFeatureData(*pMat);
    void* data = env->GetDirectBufferAddress(featureData); // How to fill the byteArray with values from vecFloatFeatureData? (If requied I can have a constant having the length of the array or number of actual float values i.e. len of array/4

C) Now, later on I need to consume this data again by passing this from Java to C++. So passing the jbyteArray to the JNI function

JNIEXPORT jfloat JNICALL Java_com_xyz_ConsumeData(JNIEnv *env, jclass type, jlong hEngineHandle, jbyteArray feature1){
 try {
  PeopleCounting *obj = (PeopleCounting *) hEngineHandle;

  void* data = env->GetDirectBufferAddress(featureData);
  float *floatBuffer = (float *) data1;
  vector<float> vecFloatFeature1Data(floatBuffer, floatBuffer + obj->_faceRecognitionByteArraySize); // _faceRecognitionByteArraySize contains the byte array size i.e. 4*no. of floats

Would this work?

Debasish Mitra
  • 1,394
  • 1
  • 14
  • 17
  • Note that `GetByteArrayElements` *can* incur a memory copy. If you really want zero-copy, look into direct FloatBuffers or ByteBuffers. You can grab a pointer via [GetDirectBufferAddress](https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/functions.html#GetDirectBufferAddress). – Botje Mar 16 '20 at 07:41
  • Even if you must use byte array on the Java side, why should you care to store these bytes as little endian? You can use memcpy to fill the whole array both from C to Java and back. You can avoid this copy operation if you use a DirectByteBuffer, as suggests @Botje. – Alex Cohn Mar 16 '20 at 09:42
  • @AlexCohn And would changing unsigned char to signed char work for converting floats properly? – Debasish Mitra Mar 16 '20 at 09:55
  • @AlexCohn can you take a look at the updated parts of the question? I need some code to look at regarding how to go ahead filling java byte array efficiently. – Debasish Mitra Mar 16 '20 at 11:50
  • @Botje Can you take a look at the updated parts of the question? – Debasish Mitra Mar 16 '20 at 11:50
  • Does the Java side actually _do_ something with the array? Otherwise you're best just doing all the allocation on the native side and just handing Java a pointer with the address. – Botje Mar 16 '20 at 15:39
  • @Botje No java end just stores the data in the database and maybe send this data further to servers. But there is no direct interaction with data at java end. – Debasish Mitra Mar 16 '20 at 15:57
  • "stores the data in the database" counts as "doing something with the array". – Botje Mar 16 '20 at 16:10
  • @Botje What I meant was I store the byte array directly as a blob in the database. I don't need to access individual float values that constitute the byte array. – Debasish Mitra Mar 16 '20 at 16:29

2 Answers2

1

Unfortunately, the updated code won't work either.

But first, let's address the answer you gave to @Botje.

java end just stores the data in the database and maybe send this data further to servers

These are two signoficant restrictions. First, if your database interface takes only byte arrays as blobs, this would prevent you from using DirectByteBuffer. Second, if the array may be sent to a different machine, you must be sure that the floating point values are stored there exactly as on the machine that produced the byte array. Mostly, this won't be a problem, but you should better check before deploying your distributed solution.

Now, back to your JNI code. There is actually no need to preallocate an array on Java side. The FaceRecognizeGenerateFeatureData() method can simply return the new byte array it creates:

JNIEXPORT jbyteArray JNICALL Java_com_xyz_FaceRecognizeGenerateFeatureData(JNIEnv *env, jclass type,
  jlong hEngineHandle, jlong addrAlignedFaceMat) {

    PeopleCounting *obj = (PeopleCounting *) hEngineHandle;
    Mat *pMat = (Mat *) addrAlignedFaceMat;

    vector<float> vecFloatFeatureData = obj->faceRecognizeGenerateFeatureData(*pMat);
    jbyte* dataBytes = reinterpret_cast<jbyte*>(vecFloatFeatureData.data());
    size_t dataLen = vecFloatFeatureData.size()*sizeof(vecFloatFeatureData[0]);
    jbyteArray featureData = env->NewByteArray(dataLen);
    env->SetByteArrayRegion(featureData, 0, dataLen, dataBytes);
    return featureData;
}

Deserialization can use the complementary GetByteArrayRegion() and avoid double copy:

JNIEXPORT jfloat JNICALL Java_com_xyz_ConsumeData(JNIEnv *env, jclass type,
  jlong hEngineHandle, jbyteArray featureData) {

    PeopleCounting *obj = (PeopleCounting *) hEngineHandle;
    size_t dataLen = env->GetArrayLength(featureData);
    vector<float> vecFloatFeatureDataNew(dataLen/sizeof(float));
    jbyte* dataBytes = reinterpret_cast<jbyte*>(vecFloatFeatureDataNew.data());
    env->GetByteArrayRegion(featureData, 0, dataLen, dataBytes);
    …

Note that with this architecture, you could gain a bit from using DirectByteBuffer instead of byte array. Your PeopleCounting engine produces a vector which cannot be mapped to an external buffer; on the other side, you can wrap the buffer to fill the vecFloatFeatureDataNew vector without copy. I believe this optimization would not be significant, but lead to less more cumbersome code.

Debasish Mitra
  • 1,394
  • 1
  • 14
  • 17
Alex Cohn
  • 56,089
  • 9
  • 113
  • 307
0

From https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html I see that jbyte is a signed 8 bits type. So it would make sense to use signed char in your union. The warnings should be gone then. After that is fixed, there is the issue of if floats are represented on the native side the same way they are on the java side.

Andreas_75
  • 44
  • 5