|  | /* | 
|  | * Copyright (C) 2006 The Android Open Source Project | 
|  | * | 
|  | * Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | * you may not use this file except in compliance with the License. | 
|  | * You may obtain a copy of the License at | 
|  | * | 
|  | *      http://www.apache.org/licenses/LICENSE-2.0 | 
|  | * | 
|  | * Unless required by applicable law or agreed to in writing, software | 
|  | * distributed under the License is distributed on an "AS IS" BASIS, | 
|  | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | * See the License for the specific language governing permissions and | 
|  | * limitations under the License. | 
|  | */ | 
|  |  | 
|  | #include <assert.h> | 
|  | #include <stdlib.h> | 
|  | #include <stdio.h> | 
|  | #include <fcntl.h> | 
|  | #include <unistd.h> | 
|  |  | 
|  | #include <utils/misc.h> | 
|  | #include <utils/String8.h> | 
|  | #include <utils/Log.h> | 
|  |  | 
|  | #include <android/bitmap.h> | 
|  |  | 
|  | #include "jni.h" | 
|  | #include <nativehelper/JNIHelp.h> | 
|  |  | 
|  | using namespace android; | 
|  |  | 
|  | extern "C" | 
|  | { | 
|  | #include <fd_emb_sdk.h> | 
|  | } | 
|  |  | 
|  | struct FaceData | 
|  | { | 
|  | float confidence; | 
|  | float midpointx; | 
|  | float midpointy; | 
|  | float eyedist; | 
|  | }; | 
|  |  | 
|  | struct FaceOffsets | 
|  | { | 
|  | jfieldID    confidence; | 
|  | jfieldID    midpointx; | 
|  | jfieldID    midpointy; | 
|  | jfieldID    eyedist; | 
|  | jfieldID    eulerx; | 
|  | jfieldID    eulery; | 
|  | jfieldID    eulerz; | 
|  | } gFaceOffsets; | 
|  |  | 
|  | struct FaceDetectorOffsets | 
|  | { | 
|  | jfieldID    fd; | 
|  | jfieldID    sdk; | 
|  | jfieldID    dcr; | 
|  | jfieldID    width; | 
|  | jfieldID    height; | 
|  | jfieldID    maxFaces; | 
|  | jfieldID    bwbuffer; | 
|  | } gFaceDetectorOffsets; | 
|  |  | 
|  | // --------------------------------------------------------------------------- | 
|  |  | 
|  | static void getFaceData(btk_HDCR hdcr, FaceData* fdata) | 
|  | { | 
|  | btk_Node leftEye, rightEye; | 
|  |  | 
|  | btk_DCR_getNode(hdcr, 0, &leftEye); | 
|  | btk_DCR_getNode(hdcr, 1, &rightEye); | 
|  |  | 
|  | fdata->eyedist = (float)(rightEye.x - leftEye.x) / (1 << 16); | 
|  | fdata->midpointx = (float)(rightEye.x + leftEye.x) / (1 << 17); | 
|  | fdata->midpointy = (float)(rightEye.y + leftEye.y) / (1 << 17); | 
|  | fdata->confidence = (float)btk_DCR_confidence(hdcr) / (1 << 24); | 
|  | } | 
|  |  | 
|  | // --------------------------------------------------------------------------- | 
|  |  | 
|  | static void doThrow(JNIEnv* env, const char* exc, const char* msg = NULL) | 
|  | { | 
|  | jclass npeClazz = env->FindClass(exc); | 
|  | env->ThrowNew(npeClazz, msg); | 
|  | } | 
|  |  | 
|  | static void | 
|  | nativeClassInit | 
|  | (JNIEnv *_env, jclass _this) | 
|  | { | 
|  | gFaceDetectorOffsets.fd             = _env->GetFieldID(_this, "mFD", "J"); | 
|  | gFaceDetectorOffsets.sdk            = _env->GetFieldID(_this, "mSDK", "J"); | 
|  | gFaceDetectorOffsets.dcr            = _env->GetFieldID(_this, "mDCR", "J"); | 
|  | gFaceDetectorOffsets.width          = _env->GetFieldID(_this, "mWidth", "I"); | 
|  | gFaceDetectorOffsets.height         = _env->GetFieldID(_this, "mHeight", "I"); | 
|  | gFaceDetectorOffsets.maxFaces       = _env->GetFieldID(_this, "mMaxFaces", "I"); | 
|  | gFaceDetectorOffsets.bwbuffer       = _env->GetFieldID(_this, "mBWBuffer", "[B"); | 
|  |  | 
|  | jclass faceClass = _env->FindClass("android/media/FaceDetector$Face"); | 
|  | gFaceOffsets.confidence  = _env->GetFieldID(faceClass, "mConfidence", "F"); | 
|  | gFaceOffsets.midpointx   = _env->GetFieldID(faceClass, "mMidPointX", "F"); | 
|  | gFaceOffsets.midpointy   = _env->GetFieldID(faceClass, "mMidPointY", "F"); | 
|  | gFaceOffsets.eyedist     = _env->GetFieldID(faceClass, "mEyesDist", "F"); | 
|  | gFaceOffsets.eulerx      = _env->GetFieldID(faceClass, "mPoseEulerX", "F"); | 
|  | gFaceOffsets.eulery      = _env->GetFieldID(faceClass, "mPoseEulerY", "F"); | 
|  | gFaceOffsets.eulerz      = _env->GetFieldID(faceClass, "mPoseEulerZ", "F"); | 
|  | } | 
|  |  | 
|  | // --------------------------------------------------------------------------- | 
|  |  | 
|  | static jint | 
|  | initialize(JNIEnv *_env, jobject _this, | 
|  | jint w, jint h, jint maxFaces) | 
|  | { | 
|  | // load the configuration file | 
|  | const char* root = getenv("ANDROID_ROOT"); | 
|  | String8 path(root); | 
|  | path.appendPath("usr/share/bmd/RFFstd_501.bmd"); | 
|  | // path.appendPath("usr/share/bmd/RFFspeed_501.bmd"); | 
|  |  | 
|  | const int MAX_FILE_SIZE = 65536; | 
|  | void* initData = malloc( MAX_FILE_SIZE ); /* enough to fit entire file */ | 
|  | int filedesc = open(path.string(), O_RDONLY); | 
|  | int initDataSize = read(filedesc, initData, MAX_FILE_SIZE); | 
|  | close(filedesc); | 
|  |  | 
|  | // -------------------------------------------------------------------- | 
|  | btk_HSDK sdk = NULL; | 
|  | btk_SDKCreateParam sdkParam = btk_SDK_defaultParam(); | 
|  | sdkParam.fpMalloc = malloc; | 
|  | sdkParam.fpFree = free; | 
|  | sdkParam.maxImageWidth = w; | 
|  | sdkParam.maxImageHeight = h; | 
|  |  | 
|  | btk_Status status = btk_SDK_create(&sdkParam, &sdk); | 
|  | // make sure everything went well | 
|  | if (status != btk_STATUS_OK) { | 
|  | // XXX: be more precise about what went wrong | 
|  | doThrow(_env, "java/lang/OutOfMemoryError", NULL); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | btk_HDCR dcr = NULL; | 
|  | btk_DCRCreateParam dcrParam = btk_DCR_defaultParam(); | 
|  | btk_DCR_create( sdk, &dcrParam, &dcr ); | 
|  |  | 
|  | btk_HFaceFinder fd = NULL; | 
|  | btk_FaceFinderCreateParam fdParam = btk_FaceFinder_defaultParam(); | 
|  | fdParam.pModuleParam = initData; | 
|  | fdParam.moduleParamSize = initDataSize; | 
|  | fdParam.maxDetectableFaces = maxFaces; | 
|  | status = btk_FaceFinder_create( sdk, &fdParam, &fd ); | 
|  | btk_FaceFinder_setRange(fd, 20, w/2); /* set eye distance range */ | 
|  |  | 
|  | // make sure everything went well | 
|  | if (status != btk_STATUS_OK) { | 
|  | // XXX: be more precise about what went wrong | 
|  | doThrow(_env, "java/lang/OutOfMemoryError", NULL); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | // free the configuration file | 
|  | free(initData); | 
|  |  | 
|  | // initialize the java object | 
|  | _env->SetLongField(_this, gFaceDetectorOffsets.fd,  (jlong)fd); | 
|  | _env->SetLongField(_this, gFaceDetectorOffsets.sdk, (jlong)sdk); | 
|  | _env->SetLongField(_this, gFaceDetectorOffsets.dcr, (jlong)dcr); | 
|  |  | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | static void | 
|  | destroy(JNIEnv *_env, jobject _this) | 
|  | { | 
|  | btk_HFaceFinder hfd = | 
|  | (btk_HFaceFinder)(_env->GetLongField(_this, gFaceDetectorOffsets.fd)); | 
|  | btk_FaceFinder_close( hfd ); | 
|  |  | 
|  | btk_HDCR hdcr = (btk_HDCR)(_env->GetLongField(_this, gFaceDetectorOffsets.dcr)); | 
|  | btk_DCR_close( hdcr ); | 
|  |  | 
|  | btk_HSDK hsdk = (btk_HSDK)(_env->GetLongField(_this, gFaceDetectorOffsets.sdk)); | 
|  | btk_SDK_close( hsdk ); | 
|  | } | 
|  |  | 
|  | static jint | 
|  | detect(JNIEnv *_env, jobject _this, | 
|  | jobject bitmap) | 
|  | { | 
|  | // get the fields we need | 
|  | btk_HDCR hdcr = (btk_HDCR)(_env->GetLongField(_this, gFaceDetectorOffsets.dcr)); | 
|  | btk_HFaceFinder hfd = | 
|  | (btk_HFaceFinder)(_env->GetLongField(_this, gFaceDetectorOffsets.fd)); | 
|  | u32 width = _env->GetIntField(_this, gFaceDetectorOffsets.width); | 
|  | u32 height = _env->GetIntField(_this, gFaceDetectorOffsets.height); | 
|  |  | 
|  | jbyteArray bwbufferObject = (jbyteArray) | 
|  | _env->GetObjectField(_this, gFaceDetectorOffsets.bwbuffer); | 
|  |  | 
|  | // get to our BW temporary buffer | 
|  | jbyte* bwbuffer = _env->GetByteArrayElements(bwbufferObject, 0); | 
|  |  | 
|  | // convert the image to B/W | 
|  | uint8_t* dst = (uint8_t*)bwbuffer; | 
|  |  | 
|  | uint16_t const* src; | 
|  | AndroidBitmapInfo bitmapInfo; | 
|  | AndroidBitmap_getInfo(_env, bitmap, &bitmapInfo); | 
|  | AndroidBitmap_lockPixels(_env, bitmap, (void**) &src); | 
|  |  | 
|  | int wpr = bitmapInfo.stride / 2; | 
|  | for (u32 y=0 ; y<height; y++) { | 
|  | for (u32 x=0 ; x<width ; x++) { | 
|  | uint16_t rgb = src[x]; | 
|  | int r  = rgb >> 11; | 
|  | int g2 = (rgb >> 5) & 0x3F; | 
|  | int b  = rgb & 0x1F; | 
|  | // L coefficients 0.299 0.587 0.11 | 
|  | int L = (r<<1) + (g2<<1) + (g2>>1) + b; | 
|  | *dst++ = L; | 
|  | } | 
|  | src += wpr; | 
|  | } | 
|  |  | 
|  | // run detection | 
|  | btk_DCR_assignGrayByteImage(hdcr, bwbuffer, width, height); | 
|  |  | 
|  | int numberOfFaces = 0; | 
|  | if (btk_FaceFinder_putDCR(hfd, hdcr) == btk_STATUS_OK) { | 
|  | numberOfFaces = btk_FaceFinder_faces(hfd); | 
|  | } else { | 
|  | ALOGE("ERROR: Return 0 faces because error exists in btk_FaceFinder_putDCR.\n"); | 
|  | } | 
|  |  | 
|  | // release the arrays we're using | 
|  | AndroidBitmap_unlockPixels(_env, bitmap); | 
|  | _env->ReleaseByteArrayElements(bwbufferObject, bwbuffer, 0); | 
|  | return numberOfFaces; | 
|  | } | 
|  |  | 
|  | static void | 
|  | get_face(JNIEnv *_env, jobject _this, | 
|  | jobject face, jint) | 
|  | { | 
|  | btk_HDCR hdcr = (btk_HDCR)(_env->GetLongField(_this, gFaceDetectorOffsets.dcr)); | 
|  | btk_HFaceFinder hfd = | 
|  | (btk_HFaceFinder)(_env->GetLongField(_this, gFaceDetectorOffsets.fd)); | 
|  |  | 
|  | FaceData faceData; | 
|  | btk_FaceFinder_getDCR(hfd, hdcr); | 
|  | getFaceData(hdcr, &faceData); | 
|  |  | 
|  | _env->SetFloatField(face, gFaceOffsets.confidence,  faceData.confidence); | 
|  | _env->SetFloatField(face, gFaceOffsets.midpointx,   faceData.midpointx); | 
|  | _env->SetFloatField(face, gFaceOffsets.midpointy,   faceData.midpointy); | 
|  | _env->SetFloatField(face, gFaceOffsets.eyedist,     faceData.eyedist); | 
|  | _env->SetFloatField(face, gFaceOffsets.eulerx,      0); | 
|  | _env->SetFloatField(face, gFaceOffsets.eulery,      0); | 
|  | _env->SetFloatField(face, gFaceOffsets.eulerz,      0); | 
|  | } | 
|  |  | 
|  | // --------------------------------------------------------------------------- | 
|  |  | 
|  | static const char *classPathName = "android/media/FaceDetector"; | 
|  |  | 
|  | static JNINativeMethod methods[] = { | 
|  | {"nativeClassInit", "()V",                                  (void*)nativeClassInit }, | 
|  | {"fft_initialize",  "(III)I",                               (void*)initialize }, | 
|  | {"fft_detect",      "(Landroid/graphics/Bitmap;)I",         (void*)detect }, | 
|  | {"fft_get_face",    "(Landroid/media/FaceDetector$Face;I)V",(void*)get_face }, | 
|  | {"fft_destroy",     "()V",                                  (void*)destroy }, | 
|  | }; | 
|  |  | 
|  | int register_android_media_FaceDetector(JNIEnv *_env) | 
|  | { | 
|  | return jniRegisterNativeMethods(_env, classPathName, methods, NELEM(methods)); | 
|  | } | 
|  |  | 
|  | // --------------------------------------------------------------------------- | 
|  |  | 
|  | jint JNI_OnLoad(JavaVM* vm, void*) | 
|  | { | 
|  | JNIEnv* env = NULL; | 
|  | jint result = -1; | 
|  |  | 
|  | if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { | 
|  | ALOGE("ERROR: GetEnv failed\n"); | 
|  | goto bail; | 
|  | } | 
|  | assert(env != NULL); | 
|  |  | 
|  | if (register_android_media_FaceDetector(env) < 0) { | 
|  | ALOGE("ERROR: MediaPlayer native registration failed\n"); | 
|  | goto bail; | 
|  | } | 
|  |  | 
|  | /* success -- return valid version number */ | 
|  | result = JNI_VERSION_1_4; | 
|  |  | 
|  | bail: | 
|  | return result; | 
|  | } |