From 436d9bbcbb15220bd1305125d0d0f79ae95aa28f Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Mon, 4 Mar 2013 14:49:33 +0400 Subject: [PATCH] Base for Native Activity example added. --- cmake/OpenCVDetectAndroidSDK.cmake | 10 + samples/android/CMakeLists.txt | 4 + .../native-activity/AndroidManifest.xml | 28 +++ .../android/native-activity/jni/Android.mk | 12 + .../native-activity/jni/Application.mk | 2 + .../android/native-activity/jni/native.cpp | 222 ++++++++++++++++++ .../native-activity/res/drawable/icon.png | Bin 0 -> 1997 bytes .../native-activity/res/values/strings.xml | 4 + 8 files changed, 282 insertions(+) create mode 100644 samples/android/native-activity/AndroidManifest.xml create mode 100644 samples/android/native-activity/jni/Android.mk create mode 100644 samples/android/native-activity/jni/Application.mk create mode 100644 samples/android/native-activity/jni/native.cpp create mode 100644 samples/android/native-activity/res/drawable/icon.png create mode 100644 samples/android/native-activity/res/values/strings.xml 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 @@ + + + + + + + + + + + + + + + + + + + + + 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 + +#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 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(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 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 0000000000000000000000000000000000000000..630454927b592eb585c21527c430fc739c7970a6 GIT binary patch literal 1997 zcmV;;2Qv7HP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L04^f{04^f|c%?sf00007bV*G`2iyk& z2s4Z(uWcvB1k+?B|HcZ5+p(*;Q^&-8AtzSnVCK7?)^Xiwf0*7z0ZN;t#z9i`sgtpqdV3hDNF*c2bKc!qcQaX zUny)Tz}^_l!dPca%!U9i64N>!i~1_h=?F58~*Mqs?aUag-wNp3eJ zaArHlobMV1PMO^yg*noOAUz|E0i{}8`nIiHOW|~F4mjoR_GH_vZVKN?bHNdXe;To> zxdyn_TnD>62p5lGlR~e9qrf7C+ui6sX@;J1@Mx>Yo=qMBRs~*)bDAoXUZYSHTab?f z_PACCXF@bcF}lW`X>mi~9D%@SGZ2{F%CTpbhLf>?v)%*vE3D~)z{*wz=t^t3pfEGA zffL(4AU5DX%yUkKoB_Jb(8oFW!NI-`j{z#&Z&^_sT-34vIXFYp`=GEPgYAu)4n9D4 z%K`*+8v7m2z|O!Kz|Xto?PB|!;VL`0G=ur`jDlQ$D=+i6dSt)j#Si?i#3rh3ZDkkx z+9TWVDcFEP;8fsZmts4LZyQ^=NS&&oMq^DBPd9*r!p~~YrdwMdQuxcK)CgcvlC7iQ z6m}XDL=_ke!d;S<2IvOy5aI>4B-sk!6i-qAqn6i#qR%}BH;aqaE~#zv6|u2ViZHn? z1hW8E8rkz`nyn$2WU0dhK4^p<)W{{jH|2^S^#FZ&jV&V)7;HC58VOgls*{S?bCuB! zgJG~P)&TrE+Oa9jPq^~G`MQI^ISDar1?}7f7D?&qi-Zc{Oivqep0%xlm22B7?$lt? zRDoZ$&ZSsjO2nE0ft%CR$aVrKp5WRX7^zue>Cw3+QGx=M@MFBYs{CEWmLZ zN*9hpz)s*B96QRjjj`Qi_;Vb>Z73h0DK9}$-axr}l%1BFSp9)x9Ky;`(^n%*$`F!V zkY*oPaAK+6&unXUeSit`5c;F3iU&9)kOVILi%-C3#za9jc>jUr;Wvs~#jZ#FaZ!2fi34Sb1N@BsFCj&@J zs;eb(o@Ffe=D?d6qPKGdXKQjn9~5j94Pu>ge`)`61V~3frX{l7>dTEi%6<8;N9K*(u}%^bWomltk*Vp@k|^lo)yA?qH}(jBlCHBo51XA|gNf(* zoeh<>k{r9Y4)pw$!k7FX&+PyB(?mq~(*^;K7{UQF&X#s!ILS zHX&7zhmyZ|_z=x$C7sRWW=r7+&daHU&gPWWQt)uC*X>tEFZ6J;H6WaAcCCQRo2VjP z>v8^kmJJ*U_eqdHY-p9+xixW&Umgf^mpLS>_nMj zvaJlD2pv8o7#@|SuT=D$XZ)5=GJyZFvEPhNoO#NE^Nc>q2{?8F#Z+({^MQk9zw9zH z=)VjA4HzfT(Fq(eDSwVGl!7_^3!$70OgHG7MYIxpx7Q{~Cgnag|7WoceAizs@IKwlGBBq*Ioi0qby4ylKkgqvR5BcLR&Vy39?8 + + OCV Native Activity +