Search code examples
androidc++floating-pointjava-native-interfacebyte

Facing error in converting byte array to vector of float in JNI


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?


Solution

  • 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.