From f371369e853d8f3c238d7070d0626dd988c58e7b Mon Sep 17 00:00:00 2001 From: zhupengfei Date: Sat, 18 Apr 2020 19:50:31 +0800 Subject: [PATCH] ndk_camera: Fixes Removed debug logs and unused code Turned CaptureSession struct for simplicity Added support for camera reload Fixed ANativeWindow not released --- .../org/citra/citra_emu/NativeLibrary.java | 3 + .../activities/EmulationActivity.java | 6 ++ .../app/src/main/jni/camera/ndk_camera.cpp | 82 +++++++++++++------ .../app/src/main/jni/camera/ndk_camera.h | 11 +-- src/android/app/src/main/jni/native.cpp | 13 ++- src/android/app/src/main/jni/native.h | 36 ++++---- 6 files changed, 106 insertions(+), 45 deletions(-) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.java b/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.java index a4a57a4b8..fd4c78451 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.java +++ b/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.java @@ -438,6 +438,9 @@ public final class NativeLibrary { } } + /// Notifies that the activity is now in foreground and camera devices can now be reloaded + public static native void ReloadCameraDevices(); + /** * Button type for use in onTouchEvent */ diff --git a/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.java b/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.java index 00169a8a5..c16ea0bf9 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.java +++ b/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.java @@ -202,6 +202,12 @@ public final class EmulationActivity extends AppCompatActivity { NativeLibrary.retryDisplayAlertPrompt(); } + @Override + public void onRestart() { + super.onRestart(); + NativeLibrary.ReloadCameraDevices(); + } + @Override public void onBackPressed() { NativeLibrary.PauseEmulation(); diff --git a/src/android/app/src/main/jni/camera/ndk_camera.cpp b/src/android/app/src/main/jni/camera/ndk_camera.cpp index 3bb6396c9..f20e2a635 100644 --- a/src/android/app/src/main/jni/camera/ndk_camera.cpp +++ b/src/android/app/src/main/jni/camera/ndk_camera.cpp @@ -26,11 +26,13 @@ namespace Camera::NDK { * The pixel format is 'Android 420' which can contain a variety of YUV420 formats. The exact * format used can be determined by examing the 'pixel stride'. */ -class CaptureSession final { -public: - explicit CaptureSession(ACameraManager* manager, const std::string& id); +struct CaptureSession final { + explicit CaptureSession(ACameraManager* manager, const std::string& id) { + Load(manager, id); + } + + void Load(ACameraManager* manager, const std::string& id); -private: std::pair selected_resolution{}; ACameraDevice_StateCallbacks device_callbacks{}; @@ -48,9 +50,7 @@ private: MEMBER(ACameraDevice, device, close); MEMBER(AImageReader, image_reader, delete); - - // This window doesn't need to be destructed as it is managed by AImageReader - ANativeWindow* native_window{}; + MEMBER(ANativeWindow, native_window, release); MEMBER(ACaptureSessionOutputContainer, outputs, free); MEMBER(ACaptureSessionOutput, output, free); @@ -74,12 +74,19 @@ private: int sensor_orientation{}; // Sensor Orientation bool facing_front{}; // Whether this camera faces front. Used for handling device rotation. - friend class Interface; - friend void ImageCallback(void* context, AImageReader* reader); + std::mutex status_mutex; + bool disconnected{}; // Whether this device has been closed and should be reopened + bool reload_requested{}; }; -static void OnDisconnected(void* context, ACameraDevice* device) { - LOG_ERROR(Service_CAM, "Camera device disconnected"); +void OnDisconnected(void* context, ACameraDevice* device) { + LOG_WARNING(Service_CAM, "Camera device disconnected"); + + CaptureSession* that = reinterpret_cast(context); + { + std::lock_guard lock{that->status_mutex}; + that->disconnected = true; + } } static void OnError(void* context, ACameraDevice* device, int error) { @@ -106,7 +113,7 @@ static void OnError(void* context, ACameraDevice* device, int error) { void ImageCallback(void* context, AImageReader* reader) { AImage* image{}; - MEDIA_CALL(AImageReader_acquireLatestImage(reader, &image)) + MEDIA_CALL(AImageReader_acquireLatestImage(reader, &image)); SCOPE_EXIT({ AImage_delete(image); }); std::array, 3> data; @@ -115,7 +122,6 @@ void ImageCallback(void* context, AImageReader* reader) { u8* ptr; int size; MEDIA_CALL(AImage_getPlaneData(image, plane, &ptr, &size)); - LOG_CRITICAL(Service_CAM, "Plane data for {} got, size {}", plane, size); data[plane].reset(new u8[size], std::default_delete()); std::memcpy(data[plane].get(), ptr, static_cast(size)); @@ -123,16 +129,15 @@ void ImageCallback(void* context, AImageReader* reader) { } CaptureSession* that = reinterpret_cast(context); - if (!that->ready) { - that->active_event.Set(); // Mark the session as active - } - { std::lock_guard lock{that->data_mutex}; that->data = data; that->row_stride = row_stride; MEDIA_CALL(AImage_getPlanePixelStride(image, 1, &that->pixel_stride)); } + if (!that->ready) { + that->active_event.Set(); // Mark the session as active + } } #define CREATE(type, name, statement) \ @@ -147,9 +152,16 @@ static void OnClosed(void* context, ACameraCaptureSession* session) {} static void OnReady(void* context, ACameraCaptureSession* session) {} static void OnActive(void* context, ACameraCaptureSession* session) {} -CaptureSession::CaptureSession(ACameraManager* manager, const std::string& id) { +void CaptureSession::Load(ACameraManager* manager, const std::string& id) { + if (ready) { + // If already open, we'd like to first shutdown the session + // session.reset(); + // native_window.reset(); + } + ready = disconnected = reload_requested = false; + device_callbacks = { - /*context*/ nullptr, + /*context*/ this, /*onDisconnected*/ &OnDisconnected, /*onError*/ &OnError, }; @@ -211,9 +223,12 @@ CaptureSession::CaptureSession(ACameraManager* manager, const std::string& id) { }; MEDIA_CALL(AImageReader_setImageListener(image_reader.get(), &listener)); - MEDIA_CALL(AImageReader_getWindow(image_reader.get(), &native_window)); + CREATE(ANativeWindow, native_window, + MEDIA_CALL(AImageReader_getWindow(image_reader.get(), &raw))); + ANativeWindow_acquire(native_window.get()); + CREATE(ACaptureSessionOutput, output, - CAMERA_CALL(ACaptureSessionOutput_create(native_window, &raw))); + CAMERA_CALL(ACaptureSessionOutput_create(native_window.get(), &raw))); CREATE(ACaptureSessionOutputContainer, outputs, CAMERA_CALL(ACaptureSessionOutputContainer_create(&raw))); @@ -231,9 +246,8 @@ CaptureSession::CaptureSession(ACameraManager* manager, const std::string& id) { CREATE(ACaptureRequest, request, CAMERA_CALL(ACameraDevice_createCaptureRequest(device.get(), TEMPLATE_PREVIEW, &raw))); - ANativeWindow_acquire(native_window); CREATE(ACameraOutputTarget, target, - CAMERA_CALL(ACameraOutputTarget_create(native_window, &raw))); + CAMERA_CALL(ACameraOutputTarget_create(native_window.get(), &raw))); CAMERA_CALL(ACaptureRequest_addTarget(request.get(), target.get())); requests = {request.get()}; @@ -316,6 +330,19 @@ struct YUVImage { image.y.data(), image.width, image.u.data(), image.width / 2, image.v.data(), image.width / 2 std::vector Interface::ReceiveFrame() { + bool should_reload = false; + { + std::lock_guard lock{session->status_mutex}; + if (session->reload_requested) { + session->reload_requested = false; + should_reload = session->disconnected; + } + } + if (should_reload) { + LOG_INFO(Service_CAM, "Reloading camera session"); + session->Load(factory.manager.get(), id); + } + std::array, 3> data; std::array row_stride; { @@ -472,4 +499,13 @@ std::unique_ptr Factory::Create(const std::string& config, return std::make_unique(); } +void Factory::ReloadCameraDevices() { + for (const auto& [id, ptr] : opened_camera_map) { + if (auto session = ptr.lock()) { + std::lock_guard lock{session->status_mutex}; + session->reload_requested = true; + } + } +} + } // namespace Camera::NDK diff --git a/src/android/app/src/main/jni/camera/ndk_camera.h b/src/android/app/src/main/jni/camera/ndk_camera.h index f8972fd7b..fe30e9e6a 100644 --- a/src/android/app/src/main/jni/camera/ndk_camera.h +++ b/src/android/app/src/main/jni/camera/ndk_camera.h @@ -13,7 +13,7 @@ namespace Camera::NDK { -class CaptureSession; +struct CaptureSession; class Factory; class Interface : public CameraInterface { @@ -31,7 +31,6 @@ public: bool IsPreviewAvailable() override; private: - // jstring path; Factory& factory; std::shared_ptr session; std::string id; @@ -45,8 +44,6 @@ private: bool invert{}; Service::CAM::OutputFormat format; - // std::vector image; // Data fetched from the frontend - // bool opened{}; // Whether the camera was successfully opened }; // Placeholders to mean 'use any front/back camera' @@ -61,8 +58,12 @@ public: std::unique_ptr Create(const std::string& config, const Service::CAM::Flip& flip) override; + // Request the reopening of all previously disconnected camera devices. + // Called when the application is brought to foreground (i.e. we have priority with the camera) + void ReloadCameraDevices(); + private: - // Avoid requesting for permisson more than once on each call + // Avoid requesting for permission more than once on each call bool camera_permission_requested = false; bool camera_permission_granted = false; diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 17a767b16..cb325b144 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -88,6 +88,8 @@ static int AlertPromptButton() { IDCache::GetAlertPromptButton())); } +static Camera::NDK::Factory* g_ndk_factory{}; + static Core::System::ResultStatus RunCitra(const std::string& filepath) { // Citra core only supports a single running instance std::lock_guard lock(running_mutex); @@ -110,7 +112,10 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) { Settings::Apply(); Camera::RegisterFactory("image", std::make_unique()); - Camera::RegisterFactory("ndk", std::make_unique()); + + auto ndk_factory = std::make_unique(); + g_ndk_factory = ndk_factory.get(); + Camera::RegisterFactory("ndk", std::move(ndk_factory)); // Register frontend applets Frontend::RegisterDefaultApplets(); @@ -458,4 +463,10 @@ jobjectArray Java_org_citra_citra_1emu_NativeLibrary_GetTextureFilterNames(JNIEn return ret; } +void Java_org_citra_citra_1emu_NativeLibrary_ReloadCameraDevices(JNIEnv* env, jclass clazz) { + if (g_ndk_factory) { + g_ndk_factory->ReloadCameraDevices(); + } +} + } // extern "C" diff --git a/src/android/app/src/main/jni/native.h b/src/android/app/src/main/jni/native.h index 5eafac129..98b831965 100644 --- a/src/android/app/src/main/jni/native.h +++ b/src/android/app/src/main/jni/native.h @@ -27,10 +27,10 @@ JNIEXPORT jboolean JNICALL Java_org_citra_citra_1emu_NativeLibrary_onGamePadEven JNIEnv* env, jclass clazz, jstring j_device, jint j_button, jint action); JNIEXPORT jboolean JNICALL Java_org_citra_citra_1emu_NativeLibrary_onGamePadMoveEvent( - JNIEnv* env, jclass clazz, jstring j_device, jint axis, jfloat x, jfloat y); + JNIEnv* env, jclass clazz, jstring j_device, jint axis, jfloat x, jfloat y); JNIEXPORT jboolean JNICALL Java_org_citra_citra_1emu_NativeLibrary_onGamePadAxisEvent( - JNIEnv* env, jclass clazz, jstring j_device, jint axis_id, jfloat axis_val); + JNIEnv* env, jclass clazz, jstring j_device, jint axis_id, jfloat axis_val); JNIEXPORT void JNICALL Java_org_citra_citra_1emu_NativeLibrary_onTouchEvent(JNIEnv* env, jclass clazz, jfloat x, @@ -49,16 +49,16 @@ JNIEXPORT jstring JNICALL Java_org_citra_citra_1emu_NativeLibrary_GetTitle(JNIEn jclass clazz, jstring j_filename); -JNIEXPORT jstring JNICALL Java_org_citra_citra_1emu_NativeLibrary_GetDescription(JNIEnv* env, - jclass clazz, - jstring j_filename); +JNIEXPORT jstring JNICALL Java_org_citra_citra_1emu_NativeLibrary_GetDescription( + JNIEnv* env, jclass clazz, jstring j_filename); JNIEXPORT jstring JNICALL Java_org_citra_citra_1emu_NativeLibrary_GetGameId(JNIEnv* env, jclass clazz, jstring j_filename); -JNIEXPORT jstring JNICALL Java_org_citra_citra_1emu_NativeLibrary_GetRegions(JNIEnv* env, jclass clazz, - jstring j_filename); +JNIEXPORT jstring JNICALL Java_org_citra_citra_1emu_NativeLibrary_GetRegions(JNIEnv* env, + jclass clazz, + jstring j_filename); JNIEXPORT jstring JNICALL Java_org_citra_citra_1emu_NativeLibrary_GetCompany(JNIEnv* env, jclass clazz, @@ -67,9 +67,8 @@ JNIEXPORT jstring JNICALL Java_org_citra_citra_1emu_NativeLibrary_GetCompany(JNI JNIEXPORT jstring JNICALL Java_org_citra_citra_1emu_NativeLibrary_GetGitRevision(JNIEnv* env, jclass clazz); -JNIEXPORT void JNICALL Java_org_citra_citra_1emu_NativeLibrary_SetUserDirectory(JNIEnv* env, - jclass clazz, - jstring j_directory); +JNIEXPORT void JNICALL Java_org_citra_citra_1emu_NativeLibrary_SetUserDirectory( + JNIEnv* env, jclass clazz, jstring j_directory); JNIEXPORT void JNICALL Java_org_citra_citra_1emu_utils_DirectoryInitialization_SetSysDirectory( JNIEnv* env, jclass clazz, jstring path_); @@ -103,7 +102,7 @@ JNIEXPORT void JNICALL Java_org_citra_citra_1emu_NativeLibrary_Run__Ljava_lang_S JNIEXPORT void JNICALL Java_org_citra_citra_1emu_NativeLibrary_Run__Ljava_lang_String_2Ljava_lang_String_2Z( - JNIEnv* env, jclass clazz, jstring j_file, jstring j_savestate, jboolean j_delete_savestate); + JNIEnv* env, jclass clazz, jstring j_file, jstring j_savestate, jboolean j_delete_savestate); JNIEXPORT void JNICALL Java_org_citra_citra_1emu_NativeLibrary_SurfaceChanged(JNIEnv* env, jclass clazz, @@ -112,23 +111,28 @@ JNIEXPORT void JNICALL Java_org_citra_citra_1emu_NativeLibrary_SurfaceChanged(JN JNIEXPORT void JNICALL Java_org_citra_citra_1emu_NativeLibrary_SurfaceDestroyed(JNIEnv* env, jclass clazz); -JNIEXPORT void JNICALL Java_org_citra_citra_1emu_NativeLibrary_InitGameIni(JNIEnv* env, jclass clazz, +JNIEXPORT void JNICALL Java_org_citra_citra_1emu_NativeLibrary_InitGameIni(JNIEnv* env, + jclass clazz, jstring j_game_id); JNIEXPORT void JNICALL Java_org_citra_citra_1emu_NativeLibrary_ReloadSettings(JNIEnv* env, jclass clazz); JNIEXPORT void JNICALL Java_org_citra_citra_1emu_NativeLibrary_SetUserSetting( - JNIEnv* env, jclass clazz, jstring j_game_id, jstring j_section, jstring j_key, jstring j_value); + JNIEnv* env, jclass clazz, jstring j_game_id, jstring j_section, jstring j_key, + jstring j_value); JNIEXPORT jstring JNICALL Java_org_citra_citra_1emu_NativeLibrary_GetUserSetting( - JNIEnv* env, jclass clazz, jstring game_id, jstring section, jstring key); + JNIEnv* env, jclass clazz, jstring game_id, jstring section, jstring key); JNIEXPORT jdoubleArray JNICALL Java_org_citra_citra_1emu_NativeLibrary_GetPerfStats(JNIEnv* env, jclass clazz); -JNIEXPORT jobjectArray JNICALL Java_org_citra_citra_1emu_NativeLibrary_GetTextureFilterNames( - JNIEnv* env, jclass clazz); +JNIEXPORT jobjectArray JNICALL +Java_org_citra_citra_1emu_NativeLibrary_GetTextureFilterNames(JNIEnv* env, jclass clazz); + +JNIEXPORT void JNICALL Java_org_citra_citra_1emu_NativeLibrary_ReloadCameraDevices(JNIEnv* env, + jclass clazz); #ifdef __cplusplus }