diff --git a/cmake/OpenCVDetectAndroidSDK.cmake b/cmake/OpenCVDetectAndroidSDK.cmake index 0e0240ca86..b125561d4a 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 a0794cb972..9d7b0cbf0f 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 0000000000..369bc75027 --- /dev/null +++ b/samples/android/native-activity/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/android/native-activity/jni/Android.mk b/samples/android/native-activity/jni/Android.mk new file mode 100644 index 0000000000..720d38b40d --- /dev/null +++ b/samples/android/native-activity/jni/Android.mk @@ -0,0 +1,14 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +include ../../sdk/native/jni/OpenCV.mk + +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 0000000000..a89e12df19 --- /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 0000000000..38dda06038 --- /dev/null +++ b/samples/android/native-activity/jni/native.cpp @@ -0,0 +1,221 @@ +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#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 capture; +}; + +cv::Size calc_optimal_camera_resolution(const char* supported, int width, int height) +{ + int frame_width = 0; + int frame_height = 0; + + size_t prev_idx = 0; + size_t idx = 0; + float min_diff = 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 <= min_diff) && (tmp_height <= 720)) + { + frame_width = tmp_width; + frame_height = tmp_height; + min_diff = h_diff; + } + } + + idx++; // to skip coma symbol + + } while(supported[idx-1] != '\0'); + + return cv::Size(frame_width, frame_height); +} + +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; + + int left_indent = (buffer.width-frame.cols)/2; + int top_indent = (buffer.height-frame.rows)/2; + + for (int yy = top_indent; yy < std::min(frame.rows+top_indent, buffer.height); yy++) + { + unsigned char* line = (unsigned char*)pixels; + memcpy(line+left_indent*4*sizeof(unsigned char), frame.ptr(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); + + int view_width = ANativeWindow_getWidth(app->window); + int view_height = ANativeWindow_getHeight(app->window); + + cv::Size camera_resolution; + if (u.name) + camera_resolution = calc_optimal_camera_resolution(u.name, 640, 480); + else + { + LOGE("Cannot get supported camera camera_resolutions"); + camera_resolution = cv::Size(ANativeWindow_getWidth(app->window), + ANativeWindow_getHeight(app->window)); + } + + if ((camera_resolution.width != 0) && (camera_resolution.height != 0)) + { + engine->capture->set(CV_CAP_PROP_FRAME_WIDTH, camera_resolution.width); + engine->capture->set(CV_CAP_PROP_FRAME_HEIGHT, camera_resolution.height); + } + + float scale = std::min((float)view_width/camera_resolution.width, + (float)view_height/camera_resolution.height); + + if (ANativeWindow_setBuffersGeometry(app->window, (int)(view_width/scale), + int(view_height/scale), WINDOW_FORMAT_RGBA_8888) < 0) + { + LOGE("Cannot set pixel format!"); + return; + } + + LOGI("Camera initialized at resoution %dx%d", camera_resolution.width, camera_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 drawing_frame; + std::queue time_queue; + + // 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(); + time_queue.push(now); + + // Capture frame from camera and draw it + if (!engine.capture.empty()) + { + if (engine.capture->grab()) + engine.capture->retrieve(drawing_frame, CV_CAP_ANDROID_COLOR_FRAME_RGBA); + + char buffer[256]; + sprintf(buffer, "Display performance: %dx%d @ %.3f", drawing_frame.cols, drawing_frame.rows, fps); + cv::putText(drawing_frame, std::string(buffer), cv::Point(8,64), + cv::FONT_HERSHEY_COMPLEX_SMALL, 1, cv::Scalar(0,255,0,255)); + engine_draw_frame(&engine, drawing_frame); + } + + if (time_queue.size() >= 2) + then = time_queue.front(); + else + then = 0; + + if (time_queue.size() >= 25) + time_queue.pop(); + + fps = time_queue.size() * (float)cv::getTickFrequency() / (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 0000000000..630454927b 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 0000000000..888534efe0 --- /dev/null +++ b/samples/android/native-activity/res/values/strings.xml @@ -0,0 +1,4 @@ + + + OCV Native Activity + diff --git a/samples/android/native-activity/src/org/opencv/samples/NativeActivity/CvNativeActivity.java b/samples/android/native-activity/src/org/opencv/samples/NativeActivity/CvNativeActivity.java new file mode 100644 index 0000000000..d8e437533e --- /dev/null +++ b/samples/android/native-activity/src/org/opencv/samples/NativeActivity/CvNativeActivity.java @@ -0,0 +1,44 @@ +package org.opencv.samples.NativeActivity; + +import org.opencv.android.BaseLoaderCallback; +import org.opencv.android.LoaderCallbackInterface; +import org.opencv.android.OpenCVLoader; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; + +public class CvNativeActivity extends Activity { + private static final String TAG = "OCVSample::Activity"; + + private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) { + @Override + public void onManagerConnected(int status) { + switch (status) { + case LoaderCallbackInterface.SUCCESS: + { + Log.i(TAG, "OpenCV loaded successfully"); + System.loadLibrary("native_activity"); + Intent intent = new Intent(CvNativeActivity.this, android.app.NativeActivity.class); + CvNativeActivity.this.startActivity(intent); + } break; + default: + { + super.onManagerConnected(status); + } break; + } + } + }; + + public CvNativeActivity() { + Log.i(TAG, "Instantiated new " + this.getClass()); + } + + @Override + public void onResume() + { + super.onResume(); + OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_3, this, mLoaderCallback); + } +} \ No newline at end of file