I am trying to use jpeglib-turbo with android ndk to get the pixel rgb values of a jpeg image, and i am new to both C++,C and android NDK, till now i've tried using the solutions provided in Examples or tutorials of using libjpeg-turbo's TurboJPEG but i am unable to fix the issue at hand, Currently using https://github.com/openstf/android-libjpeg-turbo to build libjpeg-turbo.
Current code is
#include <turbojpeg.h>
#include <jni.h>
#include <android/log.h>
#include <syslog.h>
JNIEXPORT jbyteArray
Java_com_serelay_jpegturbo_MainActivity_getImagePixelData( JNIEnv* env,
jobject this,
jbyteArray data)
{
long unsigned int _jpegSize=2464742; //!< _jpegSize from above
unsigned char *_compressedImage; //!< _compressedImage from above
_compressedImage = data;
int width, height, jpegSubSamp;
// int jpegSubSamp = TJSAMP_444;
tjhandle _jpegDecompressor = tjInitDecompress();
tjDecompressHeader2(_jpegDecompressor, _compressedImage, _jpegSize, &width,
&height, &jpegSubSamp);
unsigned long len = width * height * 4;
unsigned long pitch = width * 4;
syslog(LOG_CRIT, "====================");
syslog(LOG_CRIT, "jpegSize %lu", _jpegSize);
syslog(LOG_CRIT, "width %d", width);
syslog(LOG_CRIT, "height %d", height);
syslog(LOG_CRIT, "total length %lu", len);
syslog(LOG_CRIT, "====================");
unsigned char buffer[len]; //!< will contain the decompressed image
tjDecompress2(_jpegDecompressor, _compressedImage, _jpegSize, buffer, width, pitch, height, TJPF_RGBA, 0);
tjDestroy(_jpegDecompressor);
// char array to byte array
jbyteArray jbytes = buffer;
//jbyteArray jbytes = _compressedImage;
return jbytes;
}
To call it from java i am using
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
TextView tv = (TextView) findViewById(R.id.sample_text);
System.loadLibrary("twolib-second");
InputStream inputStream= getResources().openRawResource(R.raw.image1);
byte[] bytes;
try {
bytes = new byte[inputStream.available()];
inputStream.read(bytes);
byte[] array = getImagePixelData(bytes);
tv.setText(String.valueOf(getImagePixelData(bytes).length));
} catch (IOException e) {
e.printStackTrace();
}
// tv.setText(getImagePixelData(5));
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native byte[] getImagePixelData(byte[] x);
}
I've been successfully calling from the code from java with the help of so files generated by ndk, but the issue i am faced was exceptions in the code which said
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0xeaa6cc30
on further inspection i logged values of some variables which felt a bit bad considering the image being passed is of size 3840*2160, upon logging the variables i get
03-07 20:26:05.180 13613-13613/com.serelay.jpegturbo E/com.serelay.jpegturbo:
====================
03-07 20:26:05.180 13613-13613/com.serelay.jpegturbo E/com.serelay.jpegturbo:
jpegSize 2464742
03-07 20:26:05.180 13613-13613/com.serelay.jpegturbo E/com.serelay.jpegturbo:
width 4
03-07 20:26:05.180 13613-13613/com.serelay.jpegturbo E/com.serelay.jpegturbo:
height -1386946560
03-07 20:26:05.180 13613-13613/com.serelay.jpegturbo E/com.serelay.jpegturbo:
total length 3578658816
03-07 20:26:05.180 13613-13613/com.serelay.jpegturbo E/com.serelay.jpegturbo:
====================
i can see the width and height values are very wrong but i can't figure out why and how to fix it.
It would be great if any help or directions can be provided to fix this issue, am stuck with this since last 2 days :) thanks.
Changes was required in tjDecompressHeader2 and we needed to use tjDecompressHeader3 in the library version, also we need to convert jbyteArray to unsigned char * and then back to jbyteArray in order to return a byte array to java, both of them can be observed in the code below.
#include <turbojpeg.h>
#include <jni.h>
#include <android/log.h>
#include <syslog.h>
JNIEXPORT jbyteArray
Java_com_serelay_jpegturbo_MainActivity_getImagePixelData( JNIEnv* env,
jobject this,
jbyteArray data,
jint dataLength)
{
long unsigned int _jpegSize=dataLength; //!< _jpegSize from above
unsigned char *_compressedImage; //!< _compressedImage from above
jboolean isCopy;
_compressedImage = (unsigned char*)(*env)->GetByteArrayElements(env, data,
&isCopy);
int width, height, jpegSubSamp, jpegColorSpace;
// int jpegSubSamp = TJSAMP_444;
tjhandle _jpegDecompressor = tjInitDecompress();
tjDecompressHeader3(_jpegDecompressor, _compressedImage, _jpegSize, &width,
&height, &jpegSubSamp, &jpegColorSpace);
long len = width * height * 4;
long pitch = width * 4;
syslog(LOG_CRIT, "====================");
syslog(LOG_CRIT, "jpegSize %lu", _jpegSize);
syslog(LOG_CRIT, "width %d", width);
syslog(LOG_CRIT, "height %d", height);
syslog(LOG_CRIT, "subsampl %d", jpegSubSamp);
syslog(LOG_CRIT, "colorSpace %d", jpegColorSpace);
syslog(LOG_CRIT, "len %lu", len);
syslog(LOG_CRIT, "pitch %lu", pitch);
syslog(LOG_CRIT, "====================");
syslog(LOG_CRIT, "===================0");
unsigned char* mumfer = (unsigned char*)malloc(len); //!< will contain the
decompressed image
syslog(LOG_CRIT, "===================1");
tjDecompress2(_jpegDecompressor, _compressedImage, _jpegSize, mumfer, width,
0, height, TJPF_RGBA, TJFLAG_FASTDCT);
syslog(LOG_CRIT, "===================2");
tjDestroy(_jpegDecompressor);
syslog(LOG_CRIT, "===================3");
// char array to byte array
jbyteArray array = (*env)->NewByteArray(env, len);
syslog(LOG_CRIT, "===================4");
//HERE I GET THE ERROR, I HAVE BEEN TRYING WITH len/2 and WORKS , PROBABLY
SOME BYTS ARE GETTING LOST.
(*env)->SetByteArrayRegion (env, array, 0, len, (jbyte*)(mumfer));
syslog(LOG_CRIT, "===================5");
return array;
}
Also in java we need to convert the byte[] to int[] in order to get the exact pixel color values which can be done with the help of the following function
public static int byteToColorInt(byte b) {
return b & 0xFF;
}
Related
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;
}
How can I display results od a timer with a putText in my OpenCV Android app? The is detecting features on the view from a camera and the main algorithm and the timer is written in C++. The full code of my C++ JNI file:
#include <jni.h>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <vector>
using namespace std;
using namespace cv;
extern "C" {
JNIEXPORT void JNICALL
Java_org_opencv_samples_tutorial3_Sample3View_FindFeatures(JNIEnv* env, jobject, jint
width, jint height, jbyteArray yuv, jintArray bgra)
{
jbyte* _yuv = env->GetByteArrayElements(yuv, 0);
jint* _bgra = env->GetIntArrayElements(bgra, 0);
Mat myuv(height + height/2, width, CV_8UC1, (unsigned char *)_yuv);
Mat mbgra(height, width, CV_8UC4, (unsigned char *)_bgra);
Mat mgray(height, width, CV_8UC1, (unsigned char *)_yuv);
//Please make attention about BGRA byte order
//ARGB stored in java as int array becomes BGRA at native level
cvtColor(myuv, mbgra, CV_YUV420sp2BGR, 4);
vector<KeyPoint> v;
OrbFeatureDetector detector(1);
double t = (double)getTickCount();
detector.detect(mgray, v);
t = ((double)getTickCount() - t)/getTickFrequency();
putText(mbgra, t+" detection time", Point2f(100,100), FONT_HERSHEY_PLAIN, 2, Scalar(0,0,255,255), 2);
for( size_t i = 0; i < v.size(); i++ )
circle(mbgra, Point(v[i].pt.x, v[i].pt.y), 10, Scalar(0,0,255,255));
env->ReleaseIntArrayElements(bgra, _bgra, 0);
env->ReleaseByteArrayElements(yuv, _yuv, 0); }}
The problem is in the line with putText: I get an error "invalid operands of types 'double' and 'char const [15]' to binary 'operator+'". Is my timer OK? How else can I display the results of it? I will be grateful for your help.
't' is of class double and the constant " detection time" is treated as a string. String + double is something that the compiler doesn't understand, which is why it pukes on you.
Instead, try this approach:
std::stringstream s;
s << t;
s << " detection time";
putText(mbgra, s.str(), Point2f(100,100), FONT_HERSHEY_PLAIN, 2, Scalar(0,0,255,255), 2);
In the above code, the stringstream class has all the overloads built in to the "<<" operator such that it knows what to do with doubles and integers and strings and how to mash them together. With a little more research in to the various attributes, you can get it to format the precision of decimel points and such.
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
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.