在 JNI 中将字节数组转换为浮点向量时遇到错误
Facing error in converting byte array to vector of float in JNI
在我的本机代码中,我生成了一个浮点向量,需要通过将其转换为字节数组(使用小端模式)将其发送到 java 部分。后来我重新发送这个字节数组,需要将它转换回原来的浮点向量。我找不到确切的示例并编写了下面的代码,一次将获取 4 个字节值并将其转换为浮点数并将其添加到浮点数的最终向量中。我不会修改任何数据,只是执行一些计算,因此需要速度快,并尽可能避免内存复制。
目前它警告我 "Using unsigned char for signed value of type jbyte"。有人可以指导我如何进行吗?
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
}
更新:
正如@Alex 所建议的,字节数组的消费者和生产者都将是 C++,因此不需要任何字节顺序。所以我要采取的方法如下:
A) Java 结束 我初始化一个所需长度的 byte[](4 * 浮点数)
B) 将其作为 jbyteArray 传递给 JNI 函数
现在,如何从 C++ 端填充这个 byteArray?
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) 现在,稍后我需要通过将此数据从 Java 传递到 C++ 来再次使用此数据。所以将 jbyteArray 传递给 JNI 函数
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
这行得通吗?
来自https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html 我看到jbyte 是一个带符号的8 位类型。所以在你的联盟中使用 signed char 是有意义的。警告应该消失了。修复后,会出现一个问题,即浮点数在本机端的表示方式与在 java 端的表示方式相同。
不幸的是,更新后的代码也不起作用。
但首先,让我们解决一下您给 @Botje.
的
java end just stores the data in the database and maybe send this data further to servers
这是两个重要的限制。首先,如果您的数据库接口仅将字节数组作为 blob,这将阻止您使用 DirectByteBuffer。其次,如果数组可能被发送到不同的机器,您必须确保浮点值存储在那里与生成字节数组的机器完全一样。大多数情况下,这 won't be a problem,但您最好在部署分布式解决方案之前进行检查。
现在,回到您的 JNI 代码。实际上不需要在 Java 端预分配数组。 FaceRecognizeGenerateFeatureData()
方法可以简单地 return 它创建的新字节数组:
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;
}
反序列化可以使用互补的 GetByteArrayRegion() 并避免双重复制:
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);
…
请注意,使用此体系结构,您可以通过使用 DirectByteBuffer 而不是字节数组来获得一些好处。您的 PeopleCounting 引擎生成一个无法映射到外部缓冲区的向量;另一方面,您 can 包装缓冲区以填充 vecFloatFeatureDataNew 向量而不进行复制。我相信这种优化不会很重要,但会减少繁琐的代码。
在我的本机代码中,我生成了一个浮点向量,需要通过将其转换为字节数组(使用小端模式)将其发送到 java 部分。后来我重新发送这个字节数组,需要将它转换回原来的浮点向量。我找不到确切的示例并编写了下面的代码,一次将获取 4 个字节值并将其转换为浮点数并将其添加到浮点数的最终向量中。我不会修改任何数据,只是执行一些计算,因此需要速度快,并尽可能避免内存复制。
目前它警告我 "Using unsigned char for signed value of type jbyte"。有人可以指导我如何进行吗?
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
}
更新: 正如@Alex 所建议的,字节数组的消费者和生产者都将是 C++,因此不需要任何字节顺序。所以我要采取的方法如下:
A) Java 结束 我初始化一个所需长度的 byte[](4 * 浮点数) B) 将其作为 jbyteArray 传递给 JNI 函数
现在,如何从 C++ 端填充这个 byteArray?
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) 现在,稍后我需要通过将此数据从 Java 传递到 C++ 来再次使用此数据。所以将 jbyteArray 传递给 JNI 函数
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
这行得通吗?
来自https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html 我看到jbyte 是一个带符号的8 位类型。所以在你的联盟中使用 signed char 是有意义的。警告应该消失了。修复后,会出现一个问题,即浮点数在本机端的表示方式与在 java 端的表示方式相同。
不幸的是,更新后的代码也不起作用。
但首先,让我们解决一下您给 @Botje.
的java end just stores the data in the database and maybe send this data further to servers
这是两个重要的限制。首先,如果您的数据库接口仅将字节数组作为 blob,这将阻止您使用 DirectByteBuffer。其次,如果数组可能被发送到不同的机器,您必须确保浮点值存储在那里与生成字节数组的机器完全一样。大多数情况下,这 won't be a problem,但您最好在部署分布式解决方案之前进行检查。
现在,回到您的 JNI 代码。实际上不需要在 Java 端预分配数组。 FaceRecognizeGenerateFeatureData()
方法可以简单地 return 它创建的新字节数组:
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;
}
反序列化可以使用互补的 GetByteArrayRegion() 并避免双重复制:
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);
…
请注意,使用此体系结构,您可以通过使用 DirectByteBuffer 而不是字节数组来获得一些好处。您的 PeopleCounting 引擎生成一个无法映射到外部缓冲区的向量;另一方面,您 can 包装缓冲区以填充 vecFloatFeatureDataNew 向量而不进行复制。我相信这种优化不会很重要,但会减少繁琐的代码。