android: Add NDK camera implementation
Not tested yet as my device doesn't support camera2...
This commit is contained in:
parent
200045529b
commit
2ff2155ebc
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
package org.citra.citra_emu;
|
package org.citra.citra_emu;
|
||||||
|
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.text.Html;
|
import android.text.Html;
|
||||||
import android.text.method.LinkMovementMethod;
|
import android.text.method.LinkMovementMethod;
|
||||||
@ -20,9 +21,12 @@ import androidx.appcompat.app.AlertDialog;
|
|||||||
import org.citra.citra_emu.activities.EmulationActivity;
|
import org.citra.citra_emu.activities.EmulationActivity;
|
||||||
import org.citra.citra_emu.utils.EmulationMenuSettings;
|
import org.citra.citra_emu.utils.EmulationMenuSettings;
|
||||||
import org.citra.citra_emu.utils.Log;
|
import org.citra.citra_emu.utils.Log;
|
||||||
|
import org.citra.citra_emu.utils.PermissionsHandler;
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
|
|
||||||
|
import static android.Manifest.permission.CAMERA;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class which contains methods that interact
|
* Class which contains methods that interact
|
||||||
* with the native side of the Citra code.
|
* with the native side of the Citra code.
|
||||||
@ -401,6 +405,38 @@ public final class NativeLibrary {
|
|||||||
sEmulationActivity.clear();
|
sEmulationActivity.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final Object cameraPermissionLock = new Object();
|
||||||
|
private static boolean cameraPermissionGranted = false;
|
||||||
|
public static final int REQUEST_CODE_NATIVE_CAMERA = 800;
|
||||||
|
|
||||||
|
public static boolean RequestCameraPermission() {
|
||||||
|
final EmulationActivity emulationActivity = sEmulationActivity.get();
|
||||||
|
if (emulationActivity == null) {
|
||||||
|
Log.error("[NativeLibrary] EmulationActivity not present");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (ContextCompat.checkSelfPermission(emulationActivity, CAMERA) == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
// Permission already granted
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
emulationActivity.requestPermissions(new String[]{CAMERA}, REQUEST_CODE_NATIVE_CAMERA);
|
||||||
|
|
||||||
|
// Wait until result is returned
|
||||||
|
synchronized (cameraPermissionLock) {
|
||||||
|
try {
|
||||||
|
cameraPermissionLock.wait();
|
||||||
|
} catch (InterruptedException ignored) {}
|
||||||
|
}
|
||||||
|
return cameraPermissionGranted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void CameraPermissionResult(boolean granted) {
|
||||||
|
cameraPermissionGranted = granted;
|
||||||
|
synchronized (cameraPermissionLock) {
|
||||||
|
cameraPermissionLock.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Button type for use in onTouchEvent
|
* Button type for use in onTouchEvent
|
||||||
*/
|
*/
|
||||||
|
@ -4,6 +4,7 @@ import android.app.Activity;
|
|||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
@ -228,6 +229,18 @@ public final class EmulationActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||||
|
switch (requestCode) {
|
||||||
|
case NativeLibrary.REQUEST_CODE_NATIVE_CAMERA:
|
||||||
|
NativeLibrary.CameraPermissionResult(grantResults[0] == PackageManager.PERMISSION_GRANTED);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void enableFullscreenImmersive() {
|
private void enableFullscreenImmersive() {
|
||||||
// It would be nice to use IMMERSIVE_STICKY, but that doesn't show the toolbar.
|
// It would be nice to use IMMERSIVE_STICKY, but that doesn't show the toolbar.
|
||||||
mDecorView.setSystemUiVisibility(
|
mDecorView.setSystemUiVisibility(
|
||||||
|
@ -8,10 +8,12 @@ import android.os.Build;
|
|||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.fragment.app.FragmentActivity;
|
import androidx.fragment.app.FragmentActivity;
|
||||||
|
|
||||||
|
import static android.Manifest.permission.CAMERA;
|
||||||
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
|
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
|
||||||
|
|
||||||
public class PermissionsHandler {
|
public class PermissionsHandler {
|
||||||
public static final int REQUEST_CODE_WRITE_PERMISSION = 500;
|
public static final int REQUEST_CODE_WRITE_PERMISSION = 500;
|
||||||
|
public static final int REQUEST_CODE_CAMERA = 700;
|
||||||
|
|
||||||
// We use permissions acceptance as an indicator if this is a first boot for the user.
|
// We use permissions acceptance as an indicator if this is a first boot for the user.
|
||||||
public static boolean isFirstBoot(final FragmentActivity activity) {
|
public static boolean isFirstBoot(final FragmentActivity activity) {
|
||||||
|
@ -3,6 +3,8 @@ add_library(main SHARED
|
|||||||
applets/swkbd.h
|
applets/swkbd.h
|
||||||
button_manager.cpp
|
button_manager.cpp
|
||||||
button_manager.h
|
button_manager.h
|
||||||
|
camera/ndk_camera.cpp
|
||||||
|
camera/ndk_camera.h
|
||||||
camera/still_image_camera.cpp
|
camera/still_image_camera.cpp
|
||||||
camera/still_image_camera.h
|
camera/still_image_camera.h
|
||||||
config.cpp
|
config.cpp
|
||||||
@ -21,6 +23,6 @@ add_library(main SHARED
|
|||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(main PRIVATE common core input_common network)
|
target_link_libraries(main PRIVATE common core input_common network)
|
||||||
target_link_libraries(main PRIVATE android jnigraphics EGL glad inih log yuv)
|
target_link_libraries(main PRIVATE android camera2ndk mediandk jnigraphics EGL glad inih log yuv)
|
||||||
|
|
||||||
set(CPACK_PACKAGE_EXECUTABLES ${CPACK_PACKAGE_EXECUTABLES} main)
|
set(CPACK_PACKAGE_EXECUTABLES ${CPACK_PACKAGE_EXECUTABLES} main)
|
||||||
|
346
src/android/app/src/main/jni/camera/ndk_camera.cpp
Normal file
346
src/android/app/src/main/jni/camera/ndk_camera.cpp
Normal file
@ -0,0 +1,346 @@
|
|||||||
|
// Copyright 2020 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
#include <camera/NdkCameraCaptureSession.h>
|
||||||
|
#include <camera/NdkCameraDevice.h>
|
||||||
|
#include <camera/NdkCameraManager.h>
|
||||||
|
#include <camera/NdkCameraMetadata.h>
|
||||||
|
#include <camera/NdkCaptureRequest.h>
|
||||||
|
#include <libyuv.h>
|
||||||
|
#include <media/NdkImageReader.h>
|
||||||
|
#include "common/scope_exit.h"
|
||||||
|
#include "core/frontend/camera/blank_camera.h"
|
||||||
|
#include "jni/camera/ndk_camera.h"
|
||||||
|
#include "jni/id_cache.h"
|
||||||
|
|
||||||
|
namespace Camera::NDK {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation detail of NDK camera interface, holding a ton of different structs.
|
||||||
|
* As long as the object lives, the camera is opened and capturing image. To turn off the camera,
|
||||||
|
* one needs to destruct the object.
|
||||||
|
* It captures at the maximum resolution supported by the device, capped at the 3DS camera's maximum
|
||||||
|
* resolution (640x480). The pixel format is I420.
|
||||||
|
*/
|
||||||
|
class CaptureSession final {
|
||||||
|
public:
|
||||||
|
explicit CaptureSession(ACameraManager* manager, const std::string& id);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::pair<int, int> selected_resolution{};
|
||||||
|
|
||||||
|
ACameraDevice_StateCallbacks device_callbacks{};
|
||||||
|
AImageReader_ImageListener listener{};
|
||||||
|
ACameraCaptureSession_stateCallbacks session_callbacks{};
|
||||||
|
std::array<ACaptureRequest*, 1> requests{};
|
||||||
|
|
||||||
|
#define MEMBER(type, name, func) \
|
||||||
|
struct type##Deleter { \
|
||||||
|
void operator()(type* ptr) { \
|
||||||
|
type##_##func(ptr); \
|
||||||
|
} \
|
||||||
|
}; \
|
||||||
|
std::unique_ptr<type, type##Deleter> name
|
||||||
|
|
||||||
|
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(ACaptureSessionOutputContainer, outputs, free);
|
||||||
|
MEMBER(ACaptureSessionOutput, output, free);
|
||||||
|
MEMBER(ACameraOutputTarget, target, free);
|
||||||
|
MEMBER(ACaptureRequest, request, free);
|
||||||
|
|
||||||
|
// Put session last to close the session before we destruct everything else
|
||||||
|
MEMBER(ACameraCaptureSession, session, close);
|
||||||
|
#undef MEMBER
|
||||||
|
|
||||||
|
bool ready = false;
|
||||||
|
|
||||||
|
std::mutex data_mutex;
|
||||||
|
|
||||||
|
// Clang does not yet have shared_ptr to arrays support. Managed data are actually arrays.
|
||||||
|
std::array<std::shared_ptr<u8>, 3> data; // I420 format, planes are Y, U, V.
|
||||||
|
|
||||||
|
friend class Interface;
|
||||||
|
friend void ImageCallback(void* context, AImageReader* reader);
|
||||||
|
};
|
||||||
|
|
||||||
|
static void OnDisconnected(void* context, ACameraDevice* device) {
|
||||||
|
LOG_ERROR(Service_CAM, "Camera device disconnected");
|
||||||
|
// TODO: Do something here?
|
||||||
|
// CaptureSession* that = reinterpret_cast<CaptureSession*>(context);
|
||||||
|
// that->CloseCamera();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void OnError(void* context, ACameraDevice* device, int error) {
|
||||||
|
LOG_ERROR(Service_CAM, "Camera device error {}", error);
|
||||||
|
// TODO: Do something here?
|
||||||
|
// CaptureSession* that = reinterpret_cast<CaptureSession*>(context);
|
||||||
|
// that->CloseCamera();
|
||||||
|
}
|
||||||
|
|
||||||
|
#define MEDIA_CALL(func) \
|
||||||
|
{ \
|
||||||
|
auto ret = func; \
|
||||||
|
if (ret != AMEDIA_OK) { \
|
||||||
|
LOG_ERROR(Service_CAM, "Call " #func " returned error {}", ret); \
|
||||||
|
return; \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define CAMERA_CALL(func) \
|
||||||
|
{ \
|
||||||
|
auto ret = func; \
|
||||||
|
if (ret != ACAMERA_OK) { \
|
||||||
|
LOG_ERROR(Service_CAM, "Call " #func " returned error {}", ret); \
|
||||||
|
return; \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageCallback(void* context, AImageReader* reader) {
|
||||||
|
AImage* image{};
|
||||||
|
MEDIA_CALL(AImageReader_acquireLatestImage(reader, &image))
|
||||||
|
SCOPE_EXIT({ AImage_delete(image); });
|
||||||
|
|
||||||
|
std::array<std::shared_ptr<u8>, 3> data;
|
||||||
|
for (const int plane : {0, 1, 2}) {
|
||||||
|
u8* ptr;
|
||||||
|
int size;
|
||||||
|
MEDIA_CALL(AImage_getPlaneData(image, plane, &ptr, &size));
|
||||||
|
data[plane].reset(new u8[size], std::default_delete<u8[]>());
|
||||||
|
std::memcpy(data[plane].get(), ptr, static_cast<size_t>(size));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
CaptureSession* that = reinterpret_cast<CaptureSession*>(context);
|
||||||
|
std::lock_guard lock{that->data_mutex};
|
||||||
|
that->data = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define CREATE(type, name, statement) \
|
||||||
|
{ \
|
||||||
|
type* raw; \
|
||||||
|
statement; \
|
||||||
|
name.reset(raw); \
|
||||||
|
}
|
||||||
|
|
||||||
|
CaptureSession::CaptureSession(ACameraManager* manager, const std::string& id) {
|
||||||
|
device_callbacks = {
|
||||||
|
/*context*/ nullptr,
|
||||||
|
/*onDisconnected*/ &OnDisconnected,
|
||||||
|
/*onError*/ &OnError,
|
||||||
|
};
|
||||||
|
|
||||||
|
CREATE(ACameraDevice, device,
|
||||||
|
CAMERA_CALL(ACameraManager_openCamera(manager, id.c_str(), &device_callbacks, &raw)));
|
||||||
|
|
||||||
|
ACameraMetadata* metadata;
|
||||||
|
CAMERA_CALL(ACameraManager_getCameraCharacteristics(manager, id.c_str(), &metadata));
|
||||||
|
|
||||||
|
ACameraMetadata_const_entry entry;
|
||||||
|
CAMERA_CALL(ACameraMetadata_getConstEntry(
|
||||||
|
metadata, ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, &entry));
|
||||||
|
selected_resolution = {};
|
||||||
|
for (std::size_t i = 0; i < entry.count; i += 4) {
|
||||||
|
// (format, width, height, input?)
|
||||||
|
if (entry.data.i32[i + 3] & ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_INPUT) {
|
||||||
|
// This is an input stream
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int format = entry.data.i32[i + 0];
|
||||||
|
if (format == AIMAGE_FORMAT_YUV_420_888) {
|
||||||
|
int width = entry.data.i32[i + 1];
|
||||||
|
int height = entry.data.i32[i + 2];
|
||||||
|
if (width <= 640 && height <= 480) { // Maximum size the 3DS supports
|
||||||
|
selected_resolution = std::max(selected_resolution, std::make_pair(width, height));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ACameraMetadata_free(metadata);
|
||||||
|
|
||||||
|
if (selected_resolution == std::pair<int, int>{}) {
|
||||||
|
LOG_ERROR(Service_CAM, "Device does not support any YUV output format");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CREATE(AImageReader, image_reader,
|
||||||
|
MEDIA_CALL(AImageReader_new(selected_resolution.first, selected_resolution.second,
|
||||||
|
AIMAGE_FORMAT_YUV_420_888, 4, &raw)));
|
||||||
|
|
||||||
|
listener = {
|
||||||
|
/*context*/ this,
|
||||||
|
/*onImageAvailable*/ &ImageCallback,
|
||||||
|
};
|
||||||
|
MEDIA_CALL(AImageReader_setImageListener(image_reader.get(), &listener));
|
||||||
|
|
||||||
|
MEDIA_CALL(AImageReader_getWindow(image_reader.get(), &native_window));
|
||||||
|
CREATE(ACaptureSessionOutput, output,
|
||||||
|
CAMERA_CALL(ACaptureSessionOutput_create(native_window, &raw)));
|
||||||
|
|
||||||
|
CREATE(ACaptureSessionOutputContainer, outputs,
|
||||||
|
CAMERA_CALL(ACaptureSessionOutputContainer_create(&raw)));
|
||||||
|
CAMERA_CALL(ACaptureSessionOutputContainer_add(outputs.get(), output.get()));
|
||||||
|
|
||||||
|
CREATE(ACameraCaptureSession, session,
|
||||||
|
CAMERA_CALL(ACameraDevice_createCaptureSession(device.get(), outputs.get(),
|
||||||
|
&session_callbacks, &raw)));
|
||||||
|
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(ACaptureRequest_addTarget(request.get(), target.get()));
|
||||||
|
|
||||||
|
requests = {request.get()};
|
||||||
|
CAMERA_CALL(ACameraCaptureSession_setRepeatingRequest(session.get(), nullptr, 1,
|
||||||
|
requests.data(), nullptr));
|
||||||
|
|
||||||
|
ready = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef MEDIA_CALL
|
||||||
|
#undef CAMERA_CALL
|
||||||
|
#undef CREATE
|
||||||
|
|
||||||
|
Interface::Interface(Factory& factory_, const std::string& id_, const Service::CAM::Flip& flip_)
|
||||||
|
: factory(factory_), id(id_), flip(flip_) {}
|
||||||
|
|
||||||
|
Interface::~Interface() = default;
|
||||||
|
|
||||||
|
void Interface::StartCapture() {
|
||||||
|
session = factory.CreateCaptureSession(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interface::StopCapture() {
|
||||||
|
session.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interface::SetResolution(const Service::CAM::Resolution& resolution_) {
|
||||||
|
resolution = resolution_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interface::SetFlip(Service::CAM::Flip flip_) {
|
||||||
|
flip = flip_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interface::SetFormat(Service::CAM::OutputFormat format_) {
|
||||||
|
format = format_;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u16> Interface::ReceiveFrame() {
|
||||||
|
std::array<std::shared_ptr<u8>, 3> data;
|
||||||
|
{
|
||||||
|
std::lock_guard lock{session->data_mutex};
|
||||||
|
data = session->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto [width, height] = session->selected_resolution;
|
||||||
|
|
||||||
|
int crop_width{}, crop_height{};
|
||||||
|
if (resolution.width * height > resolution.height * width) {
|
||||||
|
crop_width = width;
|
||||||
|
crop_height = width * resolution.height / resolution.width;
|
||||||
|
} else {
|
||||||
|
crop_height = height;
|
||||||
|
crop_width = height * resolution.width / resolution.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
int crop_x = (width - crop_width) / 2;
|
||||||
|
int crop_y = (height - crop_height) / 2;
|
||||||
|
int offset = crop_y * width + crop_x;
|
||||||
|
std::vector<u8> scaled_y(resolution.width * resolution.height);
|
||||||
|
std::vector<u8> scaled_u(resolution.width * resolution.height / 4ul);
|
||||||
|
std::vector<u8> scaled_v(resolution.width * resolution.height / 4ul);
|
||||||
|
// Crop and scale
|
||||||
|
libyuv::I420Scale(data[0].get() + offset, width, data[1].get() + offset / 4, width / 4,
|
||||||
|
data[2].get() + offset / 4, width / 4, crop_width, crop_height,
|
||||||
|
scaled_y.data(), resolution.width, scaled_u.data(), resolution.width / 4,
|
||||||
|
scaled_v.data(), resolution.width / 4, resolution.width, resolution.height,
|
||||||
|
libyuv::kFilterBilinear);
|
||||||
|
// TODO: Record and apply flip
|
||||||
|
|
||||||
|
std::vector<u16> output(resolution.width * resolution.height);
|
||||||
|
if (format == Service::CAM::OutputFormat::RGB565) {
|
||||||
|
libyuv::I420ToRGB565(scaled_y.data(), resolution.width, scaled_u.data(),
|
||||||
|
resolution.width / 4, scaled_v.data(), resolution.width / 4,
|
||||||
|
reinterpret_cast<u8*>(output.data()), resolution.width * 2,
|
||||||
|
resolution.width, resolution.height);
|
||||||
|
} else {
|
||||||
|
libyuv::I420ToYUY2(scaled_y.data(), resolution.width, scaled_u.data(), resolution.width / 4,
|
||||||
|
scaled_v.data(), resolution.width / 4,
|
||||||
|
reinterpret_cast<u8*>(output.data()), resolution.width * 2,
|
||||||
|
resolution.width, resolution.height);
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Interface::IsPreviewAvailable() {
|
||||||
|
return session && session->ready;
|
||||||
|
}
|
||||||
|
|
||||||
|
Factory::Factory() = default;
|
||||||
|
|
||||||
|
Factory::~Factory() = default;
|
||||||
|
|
||||||
|
std::shared_ptr<CaptureSession> Factory::CreateCaptureSession(const std::string& id) {
|
||||||
|
if (opened_camera_map.count(id) && !opened_camera_map.at(id).expired()) {
|
||||||
|
return opened_camera_map.at(id).lock();
|
||||||
|
}
|
||||||
|
const auto& session = std::make_shared<CaptureSession>(manager.get(), id);
|
||||||
|
opened_camera_map.insert_or_assign(id, session);
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<CameraInterface> Factory::Create(const std::string& config,
|
||||||
|
const Service::CAM::Flip& flip) {
|
||||||
|
|
||||||
|
if (!manager) {
|
||||||
|
JNIEnv* env = IDCache::GetEnvForThread();
|
||||||
|
jboolean result = env->CallStaticBooleanMethod(IDCache::GetNativeLibraryClass(),
|
||||||
|
IDCache::GetRequestCameraPermission());
|
||||||
|
if (result != JNI_TRUE) {
|
||||||
|
LOG_ERROR(Service_CAM, "Camera permissions denied");
|
||||||
|
return std::make_unique<Camera::BlankCamera>();
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.reset(ACameraManager_create());
|
||||||
|
}
|
||||||
|
ACameraIdList* id_list = nullptr;
|
||||||
|
|
||||||
|
auto ret = ACameraManager_getCameraIdList(manager.get(), &id_list);
|
||||||
|
if (ret != ACAMERA_OK) {
|
||||||
|
LOG_ERROR(Service_CAM, "Failed to get camera ID list: ret {}", ret);
|
||||||
|
return std::make_unique<Camera::BlankCamera>();
|
||||||
|
}
|
||||||
|
|
||||||
|
SCOPE_EXIT({ ACameraManager_deleteCameraIdList(id_list); });
|
||||||
|
|
||||||
|
if (id_list->numCameras <= 0) {
|
||||||
|
LOG_ERROR(Service_CAM, "No camera devices found");
|
||||||
|
return std::make_unique<Camera::BlankCamera>();
|
||||||
|
}
|
||||||
|
if (config.empty()) {
|
||||||
|
LOG_WARNING(Service_CAM, "Camera ID not set, using default camera");
|
||||||
|
return std::make_unique<Interface>(*this, id_list->cameraIds[0], flip);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < id_list->numCameras; ++i) {
|
||||||
|
const char* id = id_list->cameraIds[i];
|
||||||
|
if (config == id) {
|
||||||
|
return std::make_unique<Interface>(*this, id, flip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_ERROR(Service_CAM, "Camera ID {} not found", config);
|
||||||
|
return std::make_unique<Camera::BlankCamera>();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Camera::NDK
|
71
src/android/app/src/main/jni/camera/ndk_camera.h
Normal file
71
src/android/app/src/main/jni/camera/ndk_camera.h
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
// Copyright 2020 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <camera/NdkCameraManager.h>
|
||||||
|
#include "common/common_types.h"
|
||||||
|
#include "core/frontend/camera/factory.h"
|
||||||
|
#include "core/frontend/camera/interface.h"
|
||||||
|
|
||||||
|
namespace Camera::NDK {
|
||||||
|
|
||||||
|
class CaptureSession;
|
||||||
|
class Factory;
|
||||||
|
|
||||||
|
class Interface : public CameraInterface {
|
||||||
|
public:
|
||||||
|
Interface(Factory& factory, const std::string& id, const Service::CAM::Flip& flip);
|
||||||
|
~Interface() override;
|
||||||
|
void StartCapture() override;
|
||||||
|
void StopCapture() override;
|
||||||
|
void SetResolution(const Service::CAM::Resolution& resolution) override;
|
||||||
|
void SetFlip(Service::CAM::Flip flip) override;
|
||||||
|
void SetEffect(Service::CAM::Effect effect) override{};
|
||||||
|
void SetFormat(Service::CAM::OutputFormat format) override;
|
||||||
|
void SetFrameRate(Service::CAM::FrameRate frame_rate) override{};
|
||||||
|
std::vector<u16> ReceiveFrame() override;
|
||||||
|
bool IsPreviewAvailable() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// jstring path;
|
||||||
|
Factory& factory;
|
||||||
|
std::shared_ptr<CaptureSession> session;
|
||||||
|
std::string id;
|
||||||
|
|
||||||
|
Service::CAM::Resolution resolution;
|
||||||
|
Service::CAM::Flip flip;
|
||||||
|
Service::CAM::OutputFormat format;
|
||||||
|
// std::vector<u16> image; // Data fetched from the frontend
|
||||||
|
// bool opened{}; // Whether the camera was successfully opened
|
||||||
|
};
|
||||||
|
|
||||||
|
class Factory final : public CameraFactory {
|
||||||
|
public:
|
||||||
|
explicit Factory();
|
||||||
|
~Factory() override;
|
||||||
|
|
||||||
|
std::unique_ptr<CameraInterface> Create(const std::string& config,
|
||||||
|
const Service::CAM::Flip& flip) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<CaptureSession> CreateCaptureSession(const std::string& id);
|
||||||
|
|
||||||
|
// The session is cached, to avoid opening the same camera twice.
|
||||||
|
// This is weak_ptr so that the session is destructed when all cameras are closed
|
||||||
|
std::unordered_map<std::string, std::weak_ptr<CaptureSession>> opened_camera_map;
|
||||||
|
|
||||||
|
struct ACameraManagerDeleter {
|
||||||
|
void operator()(ACameraManager* manager) {
|
||||||
|
ACameraManager_delete(manager);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
std::unique_ptr<ACameraManager, ACameraManagerDeleter> manager;
|
||||||
|
|
||||||
|
friend class Interface;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Camera::NDK
|
@ -24,6 +24,7 @@ static jmethodID s_alert_prompt_button;
|
|||||||
static jmethodID s_is_portrait_mode;
|
static jmethodID s_is_portrait_mode;
|
||||||
static jmethodID s_landscape_screen_layout;
|
static jmethodID s_landscape_screen_layout;
|
||||||
static jmethodID s_exit_emulation_activity;
|
static jmethodID s_exit_emulation_activity;
|
||||||
|
static jmethodID s_request_camera_permission;
|
||||||
|
|
||||||
namespace IDCache {
|
namespace IDCache {
|
||||||
|
|
||||||
@ -74,6 +75,10 @@ jmethodID GetExitEmulationActivity() {
|
|||||||
return s_exit_emulation_activity;
|
return s_exit_emulation_activity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jmethodID GetRequestCameraPermission() {
|
||||||
|
return s_request_camera_permission;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace IDCache
|
} // namespace IDCache
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
@ -112,6 +117,8 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
|
|||||||
env->GetStaticMethodID(s_native_library_class, "landscapeScreenLayout", "()I");
|
env->GetStaticMethodID(s_native_library_class, "landscapeScreenLayout", "()I");
|
||||||
s_exit_emulation_activity =
|
s_exit_emulation_activity =
|
||||||
env->GetStaticMethodID(s_native_library_class, "exitEmulationActivity", "(I)V");
|
env->GetStaticMethodID(s_native_library_class, "exitEmulationActivity", "(I)V");
|
||||||
|
s_request_camera_permission =
|
||||||
|
env->GetStaticMethodID(s_native_library_class, "RequestCameraPermission", "()Z");
|
||||||
|
|
||||||
SoftwareKeyboard::InitJNI(env);
|
SoftwareKeyboard::InitJNI(env);
|
||||||
Camera::StillImage::InitJNI(env);
|
Camera::StillImage::InitJNI(env);
|
||||||
|
@ -16,5 +16,6 @@ jmethodID GetAlertPromptButton();
|
|||||||
jmethodID GetIsPortraitMode();
|
jmethodID GetIsPortraitMode();
|
||||||
jmethodID GetLandscapeScreenLayout();
|
jmethodID GetLandscapeScreenLayout();
|
||||||
jmethodID GetExitEmulationActivity();
|
jmethodID GetExitEmulationActivity();
|
||||||
|
jmethodID GetRequestCameraPermission();
|
||||||
|
|
||||||
} // namespace IDCache
|
} // namespace IDCache
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
#include "jni/applets/swkbd.h"
|
#include "jni/applets/swkbd.h"
|
||||||
#include "jni/button_manager.h"
|
#include "jni/button_manager.h"
|
||||||
|
#include "jni/camera/ndk_camera.h"
|
||||||
#include "jni/camera/still_image_camera.h"
|
#include "jni/camera/still_image_camera.h"
|
||||||
#include "jni/config.h"
|
#include "jni/config.h"
|
||||||
#include "jni/emu_window/emu_window.h"
|
#include "jni/emu_window/emu_window.h"
|
||||||
@ -109,6 +110,7 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
|
|||||||
Settings::Apply();
|
Settings::Apply();
|
||||||
|
|
||||||
Camera::RegisterFactory("image", std::make_unique<Camera::StillImage::Factory>());
|
Camera::RegisterFactory("image", std::make_unique<Camera::StillImage::Factory>());
|
||||||
|
Camera::RegisterFactory("ndk", std::make_unique<Camera::NDK::Factory>());
|
||||||
|
|
||||||
// Register frontend applets
|
// Register frontend applets
|
||||||
Frontend::RegisterDefaultApplets();
|
Frontend::RegisterDefaultApplets();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user