diff --git a/CMakeLists.txt b/CMakeLists.txt index 4c2807145d..29d28cf8d4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1554,6 +1554,11 @@ if(WITH_GPHOTO2 OR HAVE_GPHOTO2) status(" gPhoto2:" HAVE_GPHOTO2 THEN "YES" ELSE NO) endif() +if(ANDROID) + status(" MEDIANDK:" HAVE_ANDROID_MEDIANDK THEN "YES" ELSE NO) + status(" NDK Camera:" HAVE_ANDROID_NATIVE_CAMERA THEN "YES" ELSE NO) +endif() + # Order is similar to CV_PARALLEL_FRAMEWORK in core/src/parallel.cpp ocv_build_features_string(parallel_status EXCLUSIVE IF HAVE_TBB THEN "TBB (ver ${TBB_VERSION_MAJOR}.${TBB_VERSION_MINOR} interface ${TBB_INTERFACE_VERSION})" diff --git a/modules/java/android_sdk/CMakeLists.txt b/modules/java/android_sdk/CMakeLists.txt index b3308c03f6..0bcc89ae29 100644 --- a/modules/java/android_sdk/CMakeLists.txt +++ b/modules/java/android_sdk/CMakeLists.txt @@ -27,12 +27,19 @@ if(ANDROID_SDK_COMPATIBLE_TARGET) set(ANDROID_SDK_COMPATIBLE_TARGET "${ANDROID_SDK_COMPATIBLE_TARGET}" CACHE INTERNAL "") endif() string(REGEX REPLACE "android-" "" android_sdk_target_num ${ANDROID_SDK_COMPATIBLE_TARGET}) + if( (ANDROID_SDK_TARGET AND ANDROID_SDK_TARGET LESS 21) OR (android_sdk_target_num LESS 21) ) message(STATUS "[OpenCV for Android SDK]: A new OpenGL Camera Bridge (CameraGLSurfaceView, CameraGLRendererBase, CameraRenderer, Camera2Renderer) is disabled, because ANDROID_SDK_TARGET (${android_sdk_target_num}) < 21") else() ocv_copyfiles_append_dir(JAVA_SRC_COPY "${OPENCV_JAVA_BINDINGS_DIR}/gen/android-21/java" "${java_src_dir}") endif() +if( (ANDROID_SDK_TARGET AND ANDROID_SDK_TARGET LESS 24) OR (android_sdk_target_num LESS 24) ) + message(STATUS "[OpenCV for Android SDK]: An experiemntal Native Camera is disabled, because ANDROID_SDK_TARGET (${android_sdk_target_num}) < 24") +else() + ocv_copyfiles_append_dir(JAVA_SRC_COPY "${OPENCV_JAVA_BINDINGS_DIR}/gen/android-24/java" "${java_src_dir}") +endif() + # copy boilerplate file(GLOB_RECURSE seed_project_files_rel RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}/android_lib/" "${CMAKE_CURRENT_SOURCE_DIR}/android_lib/*") list(REMOVE_ITEM seed_project_files_rel "${ANDROID_MANIFEST_FILE}") @@ -113,6 +120,7 @@ else() # gradle build #TODO: INSTALL ONLY ocv_copyfiles_append_dir(JAVA_SRC_COPY "${OPENCV_JAVA_BINDINGS_DIR}/gen/android/java" "${java_src_dir}") ocv_copyfiles_append_dir(JAVA_SRC_COPY "${OPENCV_JAVA_BINDINGS_DIR}/gen/android-21/java" "${java_src_dir}") +ocv_copyfiles_append_dir(JAVA_SRC_COPY "${OPENCV_JAVA_BINDINGS_DIR}/gen/android-24/java" "${java_src_dir}") # copy boilerplate set(__base_dir "${CMAKE_CURRENT_SOURCE_DIR}/android_gradle_lib/") diff --git a/modules/java/generator/android-24/java/org/opencv/android/NativeCameraView.java b/modules/java/generator/android-24/java/org/opencv/android/NativeCameraView.java new file mode 100644 index 0000000000..44ed8c4114 --- /dev/null +++ b/modules/java/generator/android-24/java/org/opencv/android/NativeCameraView.java @@ -0,0 +1,191 @@ +package org.opencv.android; + +import org.opencv.core.Mat; +import org.opencv.core.Size; + +import org.opencv.imgproc.Imgproc; + +import org.opencv.videoio.Videoio; +import org.opencv.videoio.VideoCapture; +import org.opencv.videoio.VideoWriter; + +import android.content.Context; +import android.util.AttributeSet; +import android.util.Log; +import android.view.ViewGroup.LayoutParams; + +/** + * This class is an implementation of a bridge between SurfaceView and OpenCV VideoCapture. + * The class is experimental implementation and not recoomended for production usage. + */ +public class NativeCameraView extends CameraBridgeViewBase { + + public static final String TAG = "NativeCameraView"; + private boolean mStopThread; + private Thread mThread; + + protected VideoCapture mCamera; + protected NativeCameraFrame mFrame; + + public NativeCameraView(Context context, int cameraId) { + super(context, cameraId); + } + + public NativeCameraView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected boolean connectCamera(int width, int height) { + + /* 1. We need to instantiate camera + * 2. We need to start thread which will be getting frames + */ + /* First step - initialize camera connection */ + if (!initializeCamera(width, height)) + return false; + + /* now we can start update thread */ + mThread = new Thread(new CameraWorker()); + mThread.start(); + + return true; + } + + @Override + protected void disconnectCamera() { + /* 1. We need to stop thread which updating the frames + * 2. Stop camera and release it + */ + if (mThread != null) { + try { + mStopThread = true; + mThread.join(); + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + mThread = null; + mStopThread = false; + } + } + + /* Now release camera */ + releaseCamera(); + } + + public static class OpenCvSizeAccessor implements ListItemAccessor { + + public int getWidth(Object obj) { + Size size = (Size)obj; + return (int)size.width; + } + + public int getHeight(Object obj) { + Size size = (Size)obj; + return (int)size.height; + } + + } + + private boolean initializeCamera(int width, int height) { + synchronized (this) { + + if (mCameraIndex == -1) { + Log.d(TAG, "Try to open default camera"); + mCamera = new VideoCapture(0, Videoio.CAP_ANDROID); + } else { + Log.d(TAG, "Try to open camera with index " + mCameraIndex); + mCamera = new VideoCapture(mCameraIndex, Videoio.CAP_ANDROID); + } + + if (mCamera == null) + return false; + + if (mCamera.isOpened() == false) + return false; + + mFrame = new NativeCameraFrame(mCamera); + + mCamera.set(Videoio.CAP_PROP_FRAME_WIDTH, width); + mCamera.set(Videoio.CAP_PROP_FRAME_HEIGHT, height); + + mFrameWidth = (int)mCamera.get(Videoio.CAP_PROP_FRAME_WIDTH); + mFrameHeight = (int)mCamera.get(Videoio.CAP_PROP_FRAME_HEIGHT); + + if ((getLayoutParams().width == LayoutParams.MATCH_PARENT) && (getLayoutParams().height == LayoutParams.MATCH_PARENT)) + mScale = Math.min(((float)height)/mFrameHeight, ((float)width)/mFrameWidth); + else + mScale = 0; + + if (mFpsMeter != null) { + mFpsMeter.setResolution(mFrameWidth, mFrameHeight); + } + + AllocateCache(); + } + + Log.i(TAG, "Selected camera frame size = (" + mFrameWidth + ", " + mFrameHeight + ")"); + + return true; + } + + private void releaseCamera() { + synchronized (this) { + if (mFrame != null) mFrame.release(); + if (mCamera != null) mCamera.release(); + } + } + + private static class NativeCameraFrame implements CvCameraViewFrame { + + @Override + public Mat rgba() { + mCapture.set(Videoio.CAP_PROP_FOURCC, VideoWriter.fourcc('R','G','B','3')); + mCapture.retrieve(mBgr); + Log.d(TAG, "Retrived frame with size " + mBgr.cols() + "x" + mBgr.rows() + " and channels: " + mBgr.channels()); + Imgproc.cvtColor(mBgr, mRgba, Imgproc.COLOR_RGB2RGBA); + return mRgba; + } + + @Override + public Mat gray() { + mCapture.set(Videoio.CAP_PROP_FOURCC, VideoWriter.fourcc('G','R','E','Y')); + mCapture.retrieve(mGray); + Log.d(TAG, "Retrived frame with size " + mGray.cols() + "x" + mGray.rows() + " and channels: " + mGray.channels()); + return mGray; + } + + public NativeCameraFrame(VideoCapture capture) { + mCapture = capture; + mGray = new Mat(); + mRgba = new Mat(); + mBgr = new Mat(); + } + + public void release() { + if (mGray != null) mGray.release(); + if (mRgba != null) mRgba.release(); + if (mBgr != null) mBgr.release(); + } + + private VideoCapture mCapture; + private Mat mRgba; + private Mat mGray; + private Mat mBgr; + }; + + private class CameraWorker implements Runnable { + + public void run() { + do { + if (!mCamera.grab()) { + Log.e(TAG, "Camera frame grab failed"); + break; + } + + deliverAndDrawFrame(mFrame); + } while (!mStopThread); + } + } + +} diff --git a/modules/java/generator/gen_java.py b/modules/java/generator/gen_java.py index 7b301193e5..c893c0833a 100755 --- a/modules/java/generator/gen_java.py +++ b/modules/java/generator/gen_java.py @@ -1419,7 +1419,8 @@ if __name__ == "__main__": java_base_path = os.path.join(dstdir, 'java'); mkdir_p(java_base_path) java_test_base_path = os.path.join(dstdir, 'test'); mkdir_p(java_test_base_path) - for (subdir, target_subdir) in [('src/java', 'java'), ('android/java', None), ('android-21/java', None)]: + for (subdir, target_subdir) in [('src/java', 'java'), ('android/java', None), + ('android-21/java', None), ('android-24/java', None)]: if target_subdir is None: target_subdir = subdir java_files_dir = os.path.join(SCRIPT_DIR, subdir) diff --git a/modules/videoio/include/opencv2/videoio.hpp b/modules/videoio/include/opencv2/videoio.hpp index 2ea95ac5b5..0e4956a552 100644 --- a/modules/videoio/include/opencv2/videoio.hpp +++ b/modules/videoio/include/opencv2/videoio.hpp @@ -108,7 +108,7 @@ enum VideoCaptureAPIs { CAP_PVAPI = 800, //!< PvAPI, Prosilica GigE SDK CAP_OPENNI = 900, //!< OpenNI (for Kinect) CAP_OPENNI_ASUS = 910, //!< OpenNI (for Asus Xtion) - CAP_ANDROID = 1000, //!< Android - not used + CAP_ANDROID = 1000, //!< MediaNDK (API Level 21+) and NDK Camera (API level 24+) for Android CAP_XIAPI = 1100, //!< XIMEA Camera API CAP_AVFOUNDATION = 1200, //!< AVFoundation framework for iOS (OS X Lion will have the same API) CAP_GIGANETIX = 1300, //!< Smartek Giganetix GigEVisionSDK diff --git a/modules/videoio/src/cap_android_camera.cpp b/modules/videoio/src/cap_android_camera.cpp index 84034e6208..b74810368d 100644 --- a/modules/videoio/src/cap_android_camera.cpp +++ b/modules/videoio/src/cap_android_camera.cpp @@ -533,6 +533,7 @@ public: cachedIndex = index; cameraManager = std::shared_ptr(ACameraManager_create(), deleter_ACameraManager); if (!cameraManager) { + LOGE("Cannot create camera manager!"); return false; } ACameraIdList* cameraIds = nullptr; @@ -591,6 +592,7 @@ public: } } } + LOGI("Best resolution match: %dx%d", bestMatchWidth, bestMatchHeight); ACameraMetadata_const_entry val = { 0, }; camera_status_t status = ACameraMetadata_getConstEntry(cameraMetadata.get(), ACAMERA_SENSOR_INFO_EXPOSURE_TIME_RANGE, &val); @@ -654,7 +656,11 @@ public: return false; } sessionOutput = std::shared_ptr(output, deleter_ACaptureSessionOutput); - ACaptureSessionOutputContainer_add(outputContainer.get(), sessionOutput.get()); + cStatus = ACaptureSessionOutputContainer_add(outputContainer.get(), sessionOutput.get()); + if (cStatus != ACAMERA_OK) { + LOGE("CaptureSessionOutput Container add failed with error code: %d", cStatus); + return false; + } sessionOutputAdded = true; ACameraOutputTarget* target;