I use native c to read data from an audio file to jbyte pointer. Now i want to send it to java as an jbyteArray.
jbyteArray Java_com_app_audio_player_readData(JNIEnv * env, jobject jobj,jstring readPath)
{
FILE *fin;
const char *inFile= (*env)->GetStringUTFChars(env,readPath,0);
fin = fopen(inFile, "r");
fseek(fin, 0, SEEK_END); // seek to end of file
int size = ftell(fin); // get current file pointer
fseek(fin, 0, SEEK_SET);
jbyte *data=(jbyte *)malloc(size*sizeof(jbyte));
int charCnt = 0;
charCnt=fread(data, 1, size, fin);
jbyteArray result=(*env)->NewByteArray(env, size);
//-- I want to convert data to jbyteArray and return it to java
fclose(fin);
return result;
}
How it is done?
use SetByteArrayRegion
charCnt=fread(data, 1, size, fin);
jbyteArray result=(*env)->NewByteArray(env, size);
(*env)->SetByteArrayRegion(env, result, 0, size, data);
one could also use GetByteArrayElements
eg:
jboolean isCopy;
jbyte* rawjBytes = (*env)->GetByteArrayElements(env, result, &isCopy);
//do stuff to raw bytes
memcpy(rawjBytes, data, size*sizeof(jbyte));
(*env)->ReleaseByteArrayElements(env, result, rawjBytes, 0);
see here for more details on SetByteArrayRegion, GetByteArrayElements and ReleaseByteArrayElements.
NB: this question is probably a special case of this question
Related
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?
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.
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.
I keep trying Multi Processing (using Shared Memory) in Android Studio (With NDK). I installed NDK, LLDB, CMake. Also I use API Level 26 and min SDK is also 26 (OREO, 8.0).
I created native_lib.cpp, and making some files for testing FD.
I made simple small class for testing .
Class has int FileDescriptor, char* buffer.
I checked variables and it seems like made succesfully. ASharedMemory_Create() returns fd, and i can get size of memory from ASharedMemory_getSize(int fd).
But how can i access shared memory from another process? Am i need to use java for IPC? If i can i want to use native only. Currently java is only for UI.
https://developer.android.com/ndk/reference/group/memory
I checekd here. If there are something that worth i can refer please let me know. Thank you.
Edited---------------------------------------------------
I want to make shared memory in both of parent and child. Also want to access shared memory freely. Child to child, child to parents, parents to child.
Making File Descriptor and buffer in parents works smoothly, but when i try to make buffer or fd in child, i cant access it.
I used same name and size for ASharedMemory_create, but other process making different File descriptor in my opinion. Don't know whats wrong.
Below is my native_lib.cpp for testing. Functions matched with Buttons except Init. Init called onCreate.
int fd[4];
char* buffer[4];
int myFd;
int* cBuf;
const char* names[4] = {"Test", "Inter", "Process", "Mech"};
const int size = 512;
extern "C" JNIEXPORT void JNICALL
Java_org_techtwon_multipro_MainActivity_SyncBuffer(
JNIEnv *env,
jobject /* this */) {
int cVal;
memcpy(&cVal, cBuf, sizeof(int));
for(int i = 0 ; i < cVal; ++i)
{
if(fd[i] != NULL) {
buffer[i] = (char *) mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd[i], 0);
}
}
}
extern "C" JNIEXPORT void JNICALL
Java_org_techtwon_multipro_MainActivity_MakeFileDesc(
JNIEnv *env,
jobject /* this */) {
pid_t pid = fork();
int cVal;
if(pid < 0) {
return;
} else if(pid == 0) {
memcpy(&cVal, cBuf, sizeof(int));
fd[cVal] = ASharedMemory_create(names[cVal], size);
buffer[cVal] = (char*) mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd[cVal], 0);
memset(buffer[cVal], 0, size);
memcpy(buffer[cVal], names[cVal], strlen(names[cVal]));
cVal++;
memcpy(cBuf, &cVal, sizeof(int));
sleep(1);
exit(1);
}
}
extern "C" JNIEXPORT void JNICALL
Java_org_techtwon_multipro_MainActivity_Init(
JNIEnv *env,
jobject /* this */) {
myFd = ASharedMemory_create("Num", sizeof(int));
cBuf = (int*) mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED, myFd, 0);
for(int i = 0 ; i < 4; ++i) fd[i] = ASharedMemory_create(names[i], size);
buffer[0] = (char*) mmap(NULL, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED, fd[0], 0);
memcpy(buffer[0], names[0], strlen(names[0]));
memset(cBuf, 0, sizeof(int));
}
extern "C" JNIEXPORT jint JNICALL
Java_org_techtwon_multipro_MainActivity_GetFd(
JNIEnv *env,
jobject /* this */, jint idx) {
return fd[idx];
}
extern "C" JNIEXPORT jbyteArray JNICALL
Java_org_techtwon_multipro_MainActivity_GetBuffer(
JNIEnv *env,
jobject /* this */, jint idx) {
jbyteArray tmp = (*env).NewByteArray(strlen(buffer[idx]));
env->SetByteArrayRegion(tmp, 0, strlen(buffer[idx]), (jbyte*) buffer[idx]);
return tmp;
}
extern "C" JNIEXPORT jint JNICALL
Java_org_techtwon_multipro_MainActivity_GetcVal(
JNIEnv *env,
jobject /* this */, jint idx) {
int cVal;
memcpy(&cVal, cBuf, sizeof(int));
return cVal;
}
The code snippet in official docs has it quite clear:
int fd = ASharedMemory_create("memory", 128);
// By default it has PROT_READ | PROT_WRITE | PROT_EXEC.
char *buffer = (char *) mmap(NULL, 128, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
strcpy(buffer, "This is an example."); // trivially initialize content
// limit access to read only
ASharedMemory_setProt(fd, PROT_READ);
// share fd with another process here and the other process can only map with PROT_READ.
The name has no meaning, only helpful for debugging. The size should match.
This is the API you should use for API 29 and higher, the old ways (below) don't work anymore.
If you need also to cover devices below API 26, you need a fallback that makes direct IOCTLs to /dev/ashmem file descriptors. This was available since Android early days:
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <linux/ashmem.h>
int fd = open("/dev/ashmem", O_RDWR);
ioctl(fd, ASHMEM_SET_NAME, "memory");
ioctl(fd, ASHMEM_SET_SIZE, 128);
char *buffer = (char * ) mmap(NULL, 128, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
There even is a nice example of wrapping this shared memory for use in Java: ANDROID – CREATING SHARED MEMORY USING ASHMEM.
I have 2 separate pthread, and one static struct array. One of the pthread writes the decoding object which include bytes, size, width and height. the other pthread is actually reading the stack and doing some image manipulation and posting to a java function the result.
Here is the problem, on pthread1 I convert the jbytearray to unsigned char*, and store to the position 0 on the static array.
But when it comes pthread2 to convert it back to jbytearray something happens and i always get fatal signal.
This is the top of my cpp class
struct DecodeObject {
unsigned char* data;
int data_size;
int width;
int height;
int orientation;
};
static int decodeLimit = 200 ;
static DecodeObject decodeList[200] ;
static int decodeSize = -1 ;
Here is part of my pthread1
//Values
jbyteArray imageData = (jbyteArray) env->CallObjectMethod(decodeObject,getData);
jint width = (jint) env->CallIntMethod(decodeObject,getWidth);
jint height = (jint) env->CallIntMethod(decodeObject,getHeight);
jint orientation = (jint) env->CallIntMethod(decodeObject,getOrientation);
if(decodeSize<decodeLimit-1){
DecodeObject object;
object.data_size = env->GetArrayLength (imageData);
object.data = as_unsigned_char_array(env,imageData);
object.width = width;
object.height = height;
object.orientation = orientation;
decodeSize++;
decodeList[decodeSize] = object;
}
else {
LOGD("ERROR => BUFFER IS FULL");
}
Here is part of my pthread2
//PREPARE RUNS OK
LOGD("PREPARE"); // RUNS OK
tempObject.data = Prepare(tempObject.data,tempObject.width,tempObject.height);
LOGD("CONVERT BACK TO JBYTEARRAY"); //HERE FAILS
jbyteArray converted = as_byte_array(env,tempObject.data,tempObject.data_size);
LOGD("DONE CONVERTING");
And finally here is the function i am using to convert
unsigned char* as_unsigned_char_array(JNIEnv* &env,jbyteArray array) {
int len = env->GetArrayLength (array);
unsigned char* buf = new unsigned char[len];
env->GetByteArrayRegion (array, 0, len, reinterpret_cast<jbyte*>(buf));
return buf;
}
jbyteArray as_byte_array(JNIEnv* &env,unsigned char* buf, jsize len) {
jbyteArray array = env->NewByteArray(len);
//HERE I GET THE ERROR, I HAVE BEEN TRYING WITH len/2 and WORKS , PROBABLY SOME BYTS ARE GETTING LOST.
env->SetByteArrayRegion (array, 0, len, (jbyte*)(buf));
return array;
}
I have two functions and I'm getting a ReferenceTable overflow.
The summary of consumed array entries is:
1 of byte[] (3 elements)
446 of byte[] (75 elements) (2 unique instances)
576 of byte[] (147 elements) (2 unique instances)
1 of int[] (25 elements)
I really checked the code to find any mistake, but didn't found it. Im releasing arrays after getting them. The only thing is that these functions are called thousands of times, can this be the cause?
Here is all my native code:
called once:
JNIEXPORT void JNICALL Java_ar_com_teasoft_Image_nativeUnlock(
JNIEnv *env, jclass clazz, jobject bitmap) {
AndroidBitmap_unlockPixels(env, bitmap);
}
called once:
JNIEXPORT jlong JNICALL Java_ar_com_teasoft_Image_nativeLock(
JNIEnv *env, jclass clazz, jobject bitmap) {
int ret;
AndroidBitmapInfo info;
if ((ret = AndroidBitmap_getInfo(env, bitmap, &info)) < 0) {
LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
return 0;
}
if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) {
LOGE("Bitmap format is not RGBA_8888!");
return 0;
}
void* bitmapPixels;
if ((ret = AndroidBitmap_lockPixels(env, bitmap, &bitmapPixels)) < 0) {
LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
return 0;
}
return (jlong) bitmapPixels;
}
called lots of times:
JNIEXPORT void JNICALL Java_ar_com_teasoft_Image_nativeCopyPixels(
JNIEnv *env, jclass clazz, jlong dataRef, jintArray sourceIndexes,
jintArray targetIndexes, jint count) {
argb* sourcePixels;
argb* targetPixels;
jint *sourceArray = env->GetIntArrayElements(sourceIndexes, NULL);
jint *targetArray = env->GetIntArrayElements(targetIndexes, NULL);
for (int i = 0; i < count; i++) {
sourcePixels = (argb*)((char*) dataRef + sourceArray[i] * 4);
targetPixels = (argb*)((char*) dataRef + targetArray[i] * 4);
(*targetPixels) = (*sourcePixels);
}
env->ReleaseIntArrayElements(sourceIndexes, sourceArray, JNI_ABORT);
env->ReleaseIntArrayElements(targetIndexes, targetArray, JNI_ABORT);
}
called lots of times:
JNIEXPORT void JNICALL Java_ar_com_teasoft_Image_nativeGetRGB(
JNIEnv *env, jclass clazz, jlong dataRef, jintArray indexes,
jbyteArray destRgb) {
jint *array = env->GetIntArrayElements(indexes, NULL);
jbyte *dstarray = env->GetByteArrayElements(destRgb, NULL);
int size = env->GetArrayLength(indexes);
char* sourcePixels;
int dstCount = 0;
for (int i = 0; i < size; i++) {
sourcePixels = (char*) dataRef + array[i] * 4;
dstarray[dstCount++] = (*(sourcePixels + 1));
dstarray[dstCount++] = (*(sourcePixels + 2));
dstarray[dstCount++] = (*(sourcePixels + 3));
}
env->ReleaseIntArrayElements(indexes, array, JNI_ABORT);
env->ReleaseByteArrayElements(destRgb, dstarray, JNI_COMMIT);
}
Based on the summary, it looks like the one that is not released, is of byte[], so it has to be the one in function nativeGetRGB. But i cannot find where the mistake is.
Please Help!
Regards,
Juan Ignacio
Java_ar_com_teasoft_Image_nativeGetRGB():
As far as I can see, you'd need to commit and free any temporary array copy by passing 0 instead of JNI_COMMIT to ReleaseByteArrayElements(). The second argument of Get*ArrayElements() is a pointer to a boolean, which will be set to true, if the returned array is a copy, instead of pinned memory.
Java_ar_com_teasoft_Image_nativeCopyPixels():
You might also want to pass 0 instead of JNI_ABORT, which discards everything, here:
env->ReleaseIntArrayElements(targetIndexes, targetArray, JNI_ABORT);
The tricky thing with arrays is, that the release mode applies to copied arrays only, since pinned memory gets modified directly. There's no way to force either array copy or pinning.
i am trying to do real time image processing in android using jni. I have a native method to decode image data and i call this method for every frame. After a few seconds later i get out of memory and my app terminats.
LOG OUTPUT:
12-03 20:54:19.780: E/dalvikvm-heap(8119): Out of memory on a 3686416-byte allocation.
MY NATIVE METHOD:
JNIEXPORT jintArray JNICALL Java_net_oyunyazar_arcc_data_FrameManager_processImage(JNIEnv* env, jobject javaThis, jint width, jint height, jbyteArray arr) {
jint *convertedData;
convertedData = (jint*)malloc((width*height) * sizeof(jint));
jintArray result = (*env)->NewIntArray(env, width*height);
jint y,x;
jbyte grey;
jsize len = (*env)->GetArrayLength(env, arr);
jbyte *YUVData = (*env)->GetByteArrayElements(env, arr, 0);
for (y = 0; y < height; y++){
for (x = 0; x < width; x++){
grey = YUVData[y * width + x];
convertedData[y*width+x] =(jint) grey & 0xff;
}
}
LOGD("Random [%d]",len);
(*env)->SetIntArrayRegion(env, result, 0, (width*height),convertedData );
free(convertedData);
(*env)->ReleaseByteArrayElements(env, YUVData, (jbyte*)arr, 0);
return result;
}
Thanks for any help.
I have the same problem as yours.
In your specific case, while you are using pixel (and probably bitmap) you can send a bitmap instead of your bytearray and modify it
void *pixel_bm;
int retValue;
AndroidBitmapInfo info;
if ((retValue = AndroidBitmap_getInfo(env, bitmap, &info)) < 0) return 0;
if ((retValue = AndroidBitmap_lockPixels(env, bitmap, &pixel_bm)) < 0) return 0;
// you can now read an write into pixel_bm
AndroidBitmap_unlockPixels(env, bitmap);
If you find a solution to correctly free a GetByteArrayElement result, I'm instrested by the solution !!!
I have solved this problem by releasing the parameters.
(*env)->ReleaseByteArrayElements(env, arr, YUVData, 0);
It works great now.