diff --git a/cmake/OpenCVDetectAndroidSDK.cmake b/cmake/OpenCVDetectAndroidSDK.cmake index 0e0240ca8..b125561d4 100644 --- a/cmake/OpenCVDetectAndroidSDK.cmake +++ b/cmake/OpenCVDetectAndroidSDK.cmake @@ -264,13 +264,23 @@ macro(add_android_project target path) ocv_list_filterout(android_proj_jni_files "\\\\.svn") if(android_proj_jni_files AND EXISTS ${path}/jni/Android.mk AND NOT DEFINED JNI_LIB_NAME) + # find local module name in Android.mk file to build native lib file(STRINGS "${path}/jni/Android.mk" JNI_LIB_NAME REGEX "LOCAL_MODULE[ ]*:=[ ]*.*" ) string(REGEX REPLACE "LOCAL_MODULE[ ]*:=[ ]*([a-zA-Z_][a-zA-Z_0-9]*)[ ]*" "\\1" JNI_LIB_NAME "${JNI_LIB_NAME}") + # find using of native app glue to determine native activity + file(STRINGS "${path}/jni/Android.mk" NATIVE_APP_GLUE REGEX ".*(call import-module,android/native_app_glue)" ) + if(JNI_LIB_NAME) ocv_include_modules_recurse(${android_proj_NATIVE_DEPS}) ocv_include_directories("${path}/jni") + if (NATIVE_APP_GLUE) + include_directories(${ANDROID_NDK}/sources/android/native_app_glue) + list(APPEND android_proj_jni_files ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c) + set(android_proj_NATIVE_DEPS ${android_proj_NATIVE_DEPS} android) + endif() + add_library(${JNI_LIB_NAME} MODULE ${android_proj_jni_files}) target_link_libraries(${JNI_LIB_NAME} ${OPENCV_LINKER_LIBS} ${android_proj_NATIVE_DEPS}) diff --git a/samples/android/CMakeLists.txt b/samples/android/CMakeLists.txt index a0794cb97..9d7b0cbf0 100644 --- a/samples/android/CMakeLists.txt +++ b/samples/android/CMakeLists.txt @@ -11,6 +11,10 @@ add_subdirectory(face-detection) add_subdirectory(image-manipulations) add_subdirectory(color-blob-detection) +if (ANDROID_NATIVE_API_LEVEL GREATER 8) + add_subdirectory(native-activity) +endif() + add_subdirectory(tutorial-1-camerapreview) add_subdirectory(tutorial-2-mixedprocessing) add_subdirectory(tutorial-3-cameracontrol) diff --git a/samples/android/native-activity/AndroidManifest.xml b/samples/android/native-activity/AndroidManifest.xml new file mode 100644 index 000000000..e7b1f1217 --- /dev/null +++ b/samples/android/native-activity/AndroidManifest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="org.opencv.examples.native_activity" + android:versionCode="1" + android:versionName="1.0"> + <uses-sdk android:minSdkVersion="9" /> + <application android:label="@string/app_name" + android:icon="@drawable/icon" + android:hasCode="false" android:debuggable="true"> + <activity android:name="android.app.NativeActivity" + android:label="@string/app_name"> + <meta-data android:name="android.app.lib_name" + android:value="native_activity" /> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> + + <uses-permission android:name="android.permission.CAMERA"/> + + <uses-feature android:name="android.hardware.camera" android:required="false"/> + <uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/> + <uses-feature android:name="android.hardware.camera.front" android:required="false"/> + <uses-feature android:name="android.hardware.camera.front.autofocus" android:required="false"/> + +</manifest> diff --git a/samples/android/native-activity/jni/Android.mk b/samples/android/native-activity/jni/Android.mk new file mode 100644 index 000000000..6b1564aa9 --- /dev/null +++ b/samples/android/native-activity/jni/Android.mk @@ -0,0 +1,12 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := native_activity +LOCAL_SRC_FILES := native.cpp +LOCAL_LDLIBS := -lm -llog -landroid +LOCAL_STATIC_LIBRARIES := android_native_app_glue + +include $(BUILD_SHARED_LIBRARY) + +$(call import-module,android/native_app_glue) diff --git a/samples/android/native-activity/jni/Application.mk b/samples/android/native-activity/jni/Application.mk new file mode 100644 index 000000000..a89e12df1 --- /dev/null +++ b/samples/android/native-activity/jni/Application.mk @@ -0,0 +1,2 @@ +APP_ABI := armeabi-v7a +APP_PLATFORM := android-9 diff --git a/samples/android/native-activity/jni/native.cpp b/samples/android/native-activity/jni/native.cpp new file mode 100644 index 000000000..494ad66bb --- /dev/null +++ b/samples/android/native-activity/jni/native.cpp @@ -0,0 +1,222 @@ +#include <android_native_app_glue.h> + +#include <errno.h> +#include <jni.h> +#include <sys/time.h> +#include <time.h> +#include <android/log.h> + +#include <stdio.h> +#include <stdlib.h> +#include <math.h> +#include <float.h> +#include <queue> + +#include <opencv2/core/core.hpp> +#include <opencv2/imgproc/imgproc.hpp> +#include <opencv2/highgui/highgui.hpp> + +#define LOG_TAG "OCV:libnative_activity" +#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__) +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__) +#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) + +struct Engine +{ + android_app* app; + cv::Ptr<cv::VideoCapture> capture; +}; + +cv::Size calcOptimalResolution(const char* supported, int width, int height) +{ + int frameWidth = 0; + int frameHeight = 0; + + size_t prev_idx = 0; + size_t idx = 0; + float minDiff = FLT_MAX; + + do + { + int tmp_width; + int tmp_height; + + prev_idx = idx; + while ((supported[idx] != '\0') && (supported[idx] != ',')) + idx++; + + sscanf(&supported[prev_idx], "%dx%d", &tmp_width, &tmp_height); + + int w_diff = width - tmp_width; + int h_diff = height - tmp_height; + if ((h_diff >= 0) && (w_diff >= 0)) + { + if ((h_diff <= minDiff) && (tmp_height <= 720)) + { + frameWidth = tmp_width; + frameHeight = tmp_height; + minDiff = h_diff; + } + } + + idx++; // to skip coma symbol + + } while(supported[idx-1] != '\0'); + + return cv::Size(frameWidth, frameHeight); +} + +static void engine_draw_frame(Engine* engine, const cv::Mat& frame) +{ + if (engine->app->window == NULL) + { + return; // No window. + } + + ANativeWindow_Buffer buffer; + if (ANativeWindow_lock(engine->app->window, &buffer, NULL) < 0) + { + LOGW("Unable to lock window buffer"); + return; + } + + void* pixels = buffer.bits; + + for (int yy = 0; yy < std::min(frame.rows, buffer.height); yy++) + { + unsigned char* line = (unsigned char*)pixels; + memcpy(line, frame.ptr<unsigned char>(yy), + std::min(frame.cols, buffer.width)*4*sizeof(unsigned char)); + // go to next line + pixels = (int32_t*)pixels + buffer.stride; + } + ANativeWindow_unlockAndPost(engine->app->window); +} + +static void engine_handle_cmd(android_app* app, int32_t cmd) +{ + Engine* engine = (Engine*)app->userData; + switch (cmd) + { + case APP_CMD_INIT_WINDOW: + if (app->window != NULL) + { + LOGI("APP_CMD_INIT_WINDOW"); + + engine->capture = new cv::VideoCapture(0); + + union {double prop; const char* name;} u; + u.prop = engine->capture->get(CV_CAP_PROP_SUPPORTED_PREVIEW_SIZES_STRING); + + cv::Size resolution; + if (u.name) + resolution = calcOptimalResolution(u.name, + ANativeWindow_getWidth(app->window), + ANativeWindow_getHeight(app->window)); + else + { + LOGE("Cannot get supported camera resolutions"); + resolution = cv::Size(ANativeWindow_getWidth(app->window), + ANativeWindow_getHeight(app->window)); + } + + if ((resolution.width != 0) && (resolution.height != 0)) + { + engine->capture->set(CV_CAP_PROP_FRAME_WIDTH, resolution.width); + engine->capture->set(CV_CAP_PROP_FRAME_HEIGHT, resolution.height); + } + + if (ANativeWindow_setBuffersGeometry(app->window, resolution.width, + resolution.height, WINDOW_FORMAT_RGBA_8888) < 0) + { + LOGE("Cannot set pixel format!"); + return; + } + + LOGI("Camera initialized at resoution %dx%d", resolution.width, resolution.height); + } + break; + case APP_CMD_TERM_WINDOW: + LOGI("APP_CMD_TERM_WINDOW"); + + engine->capture->release(); + break; + } +} + +void android_main(android_app* app) +{ + Engine engine; + + // Make sure glue isn't stripped. + app_dummy(); + + memset(&engine, 0, sizeof(engine)); + app->userData = &engine; + app->onAppCmd = engine_handle_cmd; + engine.app = app; + + float fps = 0; + cv::Mat drawingFrame; + bool firstFrame = true; + std::queue<int64> timeQueue; + + // loop waiting for stuff to do. + while (1) + { + // Read all pending events. + int ident; + int events; + android_poll_source* source; + + // Process system events + while ((ident=ALooper_pollAll(0, NULL, &events, (void**)&source)) >= 0) + { + // Process this event. + if (source != NULL) + { + source->process(app, source); + } + + // Check if we are exiting. + if (app->destroyRequested != 0) + { + LOGI("Engine thread destroy requested!"); + return; + } + } + + int64 then; + int64 now = cv::getTickCount(); + timeQueue.push(now); + + // Capture frame from camera and draw it + if (!engine.capture.empty()) + { + if (engine.capture->grab()) + { + engine.capture->retrieve(drawingFrame, CV_CAP_ANDROID_COLOR_FRAME_RGBA); +// if (firstFrame) +// { +// firstFrame = false; +// engine.capture->set(CV_CAP_PROP_AUTOGRAB, 1); +// } + } + char buffer[256]; + sprintf(buffer, "Display performance: %dx%d @ %.3f", drawingFrame.cols, drawingFrame.rows, fps); + cv::putText(drawingFrame, std::string(buffer), cv::Point(8,64), cv::FONT_HERSHEY_COMPLEX_SMALL, 1, cv::Scalar(0,255,0,255)); + engine_draw_frame(&engine, drawingFrame); + } + + if (timeQueue.size() >= 2) + then = timeQueue.front(); + else + then = 0; + + if (timeQueue.size() >= 25) + timeQueue.pop(); + + fps = 1.f*timeQueue.size()*(float)cv::getTickFrequency()/(float)(now-then); + } +} diff --git a/samples/android/native-activity/res/drawable/icon.png b/samples/android/native-activity/res/drawable/icon.png new file mode 100644 index 000000000..630454927 Binary files /dev/null and b/samples/android/native-activity/res/drawable/icon.png differ diff --git a/samples/android/native-activity/res/values/strings.xml b/samples/android/native-activity/res/values/strings.xml new file mode 100644 index 000000000..888534efe --- /dev/null +++ b/samples/android/native-activity/res/values/strings.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string name="app_name">OCV Native Activity</string> +</resources>