ndk_camera: Fixes
Removed debug logs and unused code Turned CaptureSession struct for simplicity Added support for camera reload Fixed ANativeWindow not released
This commit is contained in:
parent
be539cb115
commit
1a659ae4b6
@ -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
|
* Button type for use in onTouchEvent
|
||||||
*/
|
*/
|
||||||
|
@ -202,6 +202,12 @@ public final class EmulationActivity extends AppCompatActivity {
|
|||||||
NativeLibrary.retryDisplayAlertPrompt();
|
NativeLibrary.retryDisplayAlertPrompt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRestart() {
|
||||||
|
super.onRestart();
|
||||||
|
NativeLibrary.ReloadCameraDevices();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBackPressed() {
|
public void onBackPressed() {
|
||||||
NativeLibrary.PauseEmulation();
|
NativeLibrary.PauseEmulation();
|
||||||
|
@ -26,11 +26,13 @@ namespace Camera::NDK {
|
|||||||
* The pixel format is 'Android 420' which can contain a variety of YUV420 formats. The exact
|
* 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'.
|
* format used can be determined by examing the 'pixel stride'.
|
||||||
*/
|
*/
|
||||||
class CaptureSession final {
|
struct CaptureSession final {
|
||||||
public:
|
explicit CaptureSession(ACameraManager* manager, const std::string& id) {
|
||||||
explicit CaptureSession(ACameraManager* manager, const std::string& id);
|
Load(manager, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Load(ACameraManager* manager, const std::string& id);
|
||||||
|
|
||||||
private:
|
|
||||||
std::pair<int, int> selected_resolution{};
|
std::pair<int, int> selected_resolution{};
|
||||||
|
|
||||||
ACameraDevice_StateCallbacks device_callbacks{};
|
ACameraDevice_StateCallbacks device_callbacks{};
|
||||||
@ -48,9 +50,7 @@ private:
|
|||||||
|
|
||||||
MEMBER(ACameraDevice, device, close);
|
MEMBER(ACameraDevice, device, close);
|
||||||
MEMBER(AImageReader, image_reader, delete);
|
MEMBER(AImageReader, image_reader, delete);
|
||||||
|
MEMBER(ANativeWindow, native_window, release);
|
||||||
// This window doesn't need to be destructed as it is managed by AImageReader
|
|
||||||
ANativeWindow* native_window{};
|
|
||||||
|
|
||||||
MEMBER(ACaptureSessionOutputContainer, outputs, free);
|
MEMBER(ACaptureSessionOutputContainer, outputs, free);
|
||||||
MEMBER(ACaptureSessionOutput, output, free);
|
MEMBER(ACaptureSessionOutput, output, free);
|
||||||
@ -74,12 +74,19 @@ private:
|
|||||||
int sensor_orientation{}; // Sensor Orientation
|
int sensor_orientation{}; // Sensor Orientation
|
||||||
bool facing_front{}; // Whether this camera faces front. Used for handling device rotation.
|
bool facing_front{}; // Whether this camera faces front. Used for handling device rotation.
|
||||||
|
|
||||||
friend class Interface;
|
std::mutex status_mutex;
|
||||||
friend void ImageCallback(void* context, AImageReader* reader);
|
bool disconnected{}; // Whether this device has been closed and should be reopened
|
||||||
|
bool reload_requested{};
|
||||||
};
|
};
|
||||||
|
|
||||||
static void OnDisconnected(void* context, ACameraDevice* device) {
|
void OnDisconnected(void* context, ACameraDevice* device) {
|
||||||
LOG_ERROR(Service_CAM, "Camera device disconnected");
|
LOG_WARNING(Service_CAM, "Camera device disconnected");
|
||||||
|
|
||||||
|
CaptureSession* that = reinterpret_cast<CaptureSession*>(context);
|
||||||
|
{
|
||||||
|
std::lock_guard lock{that->status_mutex};
|
||||||
|
that->disconnected = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void OnError(void* context, ACameraDevice* device, int error) {
|
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) {
|
void ImageCallback(void* context, AImageReader* reader) {
|
||||||
AImage* image{};
|
AImage* image{};
|
||||||
MEDIA_CALL(AImageReader_acquireLatestImage(reader, &image))
|
MEDIA_CALL(AImageReader_acquireLatestImage(reader, &image));
|
||||||
SCOPE_EXIT({ AImage_delete(image); });
|
SCOPE_EXIT({ AImage_delete(image); });
|
||||||
|
|
||||||
std::array<std::shared_ptr<u8>, 3> data;
|
std::array<std::shared_ptr<u8>, 3> data;
|
||||||
@ -115,7 +122,6 @@ void ImageCallback(void* context, AImageReader* reader) {
|
|||||||
u8* ptr;
|
u8* ptr;
|
||||||
int size;
|
int size;
|
||||||
MEDIA_CALL(AImage_getPlaneData(image, plane, &ptr, &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<u8[]>());
|
data[plane].reset(new u8[size], std::default_delete<u8[]>());
|
||||||
std::memcpy(data[plane].get(), ptr, static_cast<std::size_t>(size));
|
std::memcpy(data[plane].get(), ptr, static_cast<std::size_t>(size));
|
||||||
|
|
||||||
@ -123,16 +129,15 @@ void ImageCallback(void* context, AImageReader* reader) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
CaptureSession* that = reinterpret_cast<CaptureSession*>(context);
|
CaptureSession* that = reinterpret_cast<CaptureSession*>(context);
|
||||||
if (!that->ready) {
|
|
||||||
that->active_event.Set(); // Mark the session as active
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
{
|
||||||
std::lock_guard lock{that->data_mutex};
|
std::lock_guard lock{that->data_mutex};
|
||||||
that->data = data;
|
that->data = data;
|
||||||
that->row_stride = row_stride;
|
that->row_stride = row_stride;
|
||||||
MEDIA_CALL(AImage_getPlanePixelStride(image, 1, &that->pixel_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) \
|
#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 OnReady(void* context, ACameraCaptureSession* session) {}
|
||||||
static void OnActive(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 = {
|
device_callbacks = {
|
||||||
/*context*/ nullptr,
|
/*context*/ this,
|
||||||
/*onDisconnected*/ &OnDisconnected,
|
/*onDisconnected*/ &OnDisconnected,
|
||||||
/*onError*/ &OnError,
|
/*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_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,
|
CREATE(ACaptureSessionOutput, output,
|
||||||
CAMERA_CALL(ACaptureSessionOutput_create(native_window, &raw)));
|
CAMERA_CALL(ACaptureSessionOutput_create(native_window.get(), &raw)));
|
||||||
|
|
||||||
CREATE(ACaptureSessionOutputContainer, outputs,
|
CREATE(ACaptureSessionOutputContainer, outputs,
|
||||||
CAMERA_CALL(ACaptureSessionOutputContainer_create(&raw)));
|
CAMERA_CALL(ACaptureSessionOutputContainer_create(&raw)));
|
||||||
@ -231,9 +246,8 @@ CaptureSession::CaptureSession(ACameraManager* manager, const std::string& id) {
|
|||||||
CREATE(ACaptureRequest, request,
|
CREATE(ACaptureRequest, request,
|
||||||
CAMERA_CALL(ACameraDevice_createCaptureRequest(device.get(), TEMPLATE_PREVIEW, &raw)));
|
CAMERA_CALL(ACameraDevice_createCaptureRequest(device.get(), TEMPLATE_PREVIEW, &raw)));
|
||||||
|
|
||||||
ANativeWindow_acquire(native_window);
|
|
||||||
CREATE(ACameraOutputTarget, target,
|
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()));
|
CAMERA_CALL(ACaptureRequest_addTarget(request.get(), target.get()));
|
||||||
|
|
||||||
requests = {request.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
|
image.y.data(), image.width, image.u.data(), image.width / 2, image.v.data(), image.width / 2
|
||||||
|
|
||||||
std::vector<u16> Interface::ReceiveFrame() {
|
std::vector<u16> 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<std::shared_ptr<u8>, 3> data;
|
std::array<std::shared_ptr<u8>, 3> data;
|
||||||
std::array<int, 3> row_stride;
|
std::array<int, 3> row_stride;
|
||||||
{
|
{
|
||||||
@ -472,4 +499,13 @@ std::unique_ptr<CameraInterface> Factory::Create(const std::string& config,
|
|||||||
return std::make_unique<Camera::BlankCamera>();
|
return std::make_unique<Camera::BlankCamera>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
} // namespace Camera::NDK
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
namespace Camera::NDK {
|
namespace Camera::NDK {
|
||||||
|
|
||||||
class CaptureSession;
|
struct CaptureSession;
|
||||||
class Factory;
|
class Factory;
|
||||||
|
|
||||||
class Interface : public CameraInterface {
|
class Interface : public CameraInterface {
|
||||||
@ -31,7 +31,6 @@ public:
|
|||||||
bool IsPreviewAvailable() override;
|
bool IsPreviewAvailable() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// jstring path;
|
|
||||||
Factory& factory;
|
Factory& factory;
|
||||||
std::shared_ptr<CaptureSession> session;
|
std::shared_ptr<CaptureSession> session;
|
||||||
std::string id;
|
std::string id;
|
||||||
@ -45,8 +44,6 @@ private:
|
|||||||
bool invert{};
|
bool invert{};
|
||||||
|
|
||||||
Service::CAM::OutputFormat format;
|
Service::CAM::OutputFormat format;
|
||||||
// std::vector<u16> image; // Data fetched from the frontend
|
|
||||||
// bool opened{}; // Whether the camera was successfully opened
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Placeholders to mean 'use any front/back camera'
|
// Placeholders to mean 'use any front/back camera'
|
||||||
@ -61,8 +58,12 @@ public:
|
|||||||
std::unique_ptr<CameraInterface> Create(const std::string& config,
|
std::unique_ptr<CameraInterface> Create(const std::string& config,
|
||||||
const Service::CAM::Flip& flip) override;
|
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:
|
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_requested = false;
|
||||||
bool camera_permission_granted = false;
|
bool camera_permission_granted = false;
|
||||||
|
|
||||||
|
@ -88,6 +88,8 @@ static int AlertPromptButton() {
|
|||||||
IDCache::GetAlertPromptButton()));
|
IDCache::GetAlertPromptButton()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Camera::NDK::Factory* g_ndk_factory{};
|
||||||
|
|
||||||
static Core::System::ResultStatus RunCitra(const std::string& filepath) {
|
static Core::System::ResultStatus RunCitra(const std::string& filepath) {
|
||||||
// Citra core only supports a single running instance
|
// Citra core only supports a single running instance
|
||||||
std::lock_guard<std::mutex> lock(running_mutex);
|
std::lock_guard<std::mutex> lock(running_mutex);
|
||||||
@ -110,7 +112,10 @@ 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>());
|
|
||||||
|
auto ndk_factory = std::make_unique<Camera::NDK::Factory>();
|
||||||
|
g_ndk_factory = ndk_factory.get();
|
||||||
|
Camera::RegisterFactory("ndk", std::move(ndk_factory));
|
||||||
|
|
||||||
// Register frontend applets
|
// Register frontend applets
|
||||||
Frontend::RegisterDefaultApplets();
|
Frontend::RegisterDefaultApplets();
|
||||||
@ -458,4 +463,10 @@ jobjectArray Java_org_citra_citra_1emu_NativeLibrary_GetTextureFilterNames(JNIEn
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Java_org_citra_citra_1emu_NativeLibrary_ReloadCameraDevices(JNIEnv* env, jclass clazz) {
|
||||||
|
if (g_ndk_factory) {
|
||||||
|
g_ndk_factory->ReloadCameraDevices();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
|
@ -49,15 +49,15 @@ JNIEXPORT jstring JNICALL Java_org_citra_citra_1emu_NativeLibrary_GetTitle(JNIEn
|
|||||||
jclass clazz,
|
jclass clazz,
|
||||||
jstring j_filename);
|
jstring j_filename);
|
||||||
|
|
||||||
JNIEXPORT jstring JNICALL Java_org_citra_citra_1emu_NativeLibrary_GetDescription(JNIEnv* env,
|
JNIEXPORT jstring JNICALL Java_org_citra_citra_1emu_NativeLibrary_GetDescription(
|
||||||
jclass clazz,
|
JNIEnv* env, jclass clazz, jstring j_filename);
|
||||||
jstring j_filename);
|
|
||||||
|
|
||||||
JNIEXPORT jstring JNICALL Java_org_citra_citra_1emu_NativeLibrary_GetGameId(JNIEnv* env,
|
JNIEXPORT jstring JNICALL Java_org_citra_citra_1emu_NativeLibrary_GetGameId(JNIEnv* env,
|
||||||
jclass clazz,
|
jclass clazz,
|
||||||
jstring j_filename);
|
jstring j_filename);
|
||||||
|
|
||||||
JNIEXPORT jstring JNICALL Java_org_citra_citra_1emu_NativeLibrary_GetRegions(JNIEnv* env, jclass clazz,
|
JNIEXPORT jstring JNICALL Java_org_citra_citra_1emu_NativeLibrary_GetRegions(JNIEnv* env,
|
||||||
|
jclass clazz,
|
||||||
jstring j_filename);
|
jstring j_filename);
|
||||||
|
|
||||||
JNIEXPORT jstring JNICALL Java_org_citra_citra_1emu_NativeLibrary_GetCompany(JNIEnv* env,
|
JNIEXPORT jstring JNICALL Java_org_citra_citra_1emu_NativeLibrary_GetCompany(JNIEnv* env,
|
||||||
@ -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,
|
JNIEXPORT jstring JNICALL Java_org_citra_citra_1emu_NativeLibrary_GetGitRevision(JNIEnv* env,
|
||||||
jclass clazz);
|
jclass clazz);
|
||||||
|
|
||||||
JNIEXPORT void JNICALL Java_org_citra_citra_1emu_NativeLibrary_SetUserDirectory(JNIEnv* env,
|
JNIEXPORT void JNICALL Java_org_citra_citra_1emu_NativeLibrary_SetUserDirectory(
|
||||||
jclass clazz,
|
JNIEnv* env, jclass clazz, jstring j_directory);
|
||||||
jstring j_directory);
|
|
||||||
|
|
||||||
JNIEXPORT void JNICALL Java_org_citra_citra_1emu_utils_DirectoryInitialization_SetSysDirectory(
|
JNIEXPORT void JNICALL Java_org_citra_citra_1emu_utils_DirectoryInitialization_SetSysDirectory(
|
||||||
JNIEnv* env, jclass clazz, jstring path_);
|
JNIEnv* env, jclass clazz, jstring path_);
|
||||||
@ -112,14 +111,16 @@ JNIEXPORT void JNICALL Java_org_citra_citra_1emu_NativeLibrary_SurfaceChanged(JN
|
|||||||
JNIEXPORT void JNICALL Java_org_citra_citra_1emu_NativeLibrary_SurfaceDestroyed(JNIEnv* env,
|
JNIEXPORT void JNICALL Java_org_citra_citra_1emu_NativeLibrary_SurfaceDestroyed(JNIEnv* env,
|
||||||
jclass clazz);
|
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);
|
jstring j_game_id);
|
||||||
|
|
||||||
JNIEXPORT void JNICALL Java_org_citra_citra_1emu_NativeLibrary_ReloadSettings(JNIEnv* env,
|
JNIEXPORT void JNICALL Java_org_citra_citra_1emu_NativeLibrary_ReloadSettings(JNIEnv* env,
|
||||||
jclass clazz);
|
jclass clazz);
|
||||||
|
|
||||||
JNIEXPORT void JNICALL Java_org_citra_citra_1emu_NativeLibrary_SetUserSetting(
|
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(
|
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);
|
||||||
@ -127,8 +128,11 @@ JNIEXPORT jstring JNICALL Java_org_citra_citra_1emu_NativeLibrary_GetUserSetting
|
|||||||
JNIEXPORT jdoubleArray JNICALL Java_org_citra_citra_1emu_NativeLibrary_GetPerfStats(JNIEnv* env,
|
JNIEXPORT jdoubleArray JNICALL Java_org_citra_citra_1emu_NativeLibrary_GetPerfStats(JNIEnv* env,
|
||||||
jclass clazz);
|
jclass clazz);
|
||||||
|
|
||||||
JNIEXPORT jobjectArray JNICALL Java_org_citra_citra_1emu_NativeLibrary_GetTextureFilterNames(
|
JNIEXPORT jobjectArray JNICALL
|
||||||
JNIEnv* env, jclass clazz);
|
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
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user