From 5fbf2d7713734bacf8645f50bcd6740e53f4e0cd Mon Sep 17 00:00:00 2001 From: Florin9doi Date: Thu, 9 Jan 2020 15:57:09 +0200 Subject: [PATCH] Add camera/location support for iOS Add front camera support for Android Allow to change the active camera in-game --- CMakeLists.txt | 9 +- Core/Config.cpp | 2 - Core/HLE/sceKernel.cpp | 1 + Core/HLE/sceUsbCam.cpp | 33 +++- Core/HLE/sceUsbCam.h | 1 + Core/HLE/sceUsbGps.cpp | 31 ++-- Core/HLE/sceUsbGps.h | 1 + Core/HW/Camera.cpp | 7 +- UI/GameSettingsScreen.cpp | 8 +- UI/GameSettingsScreen.h | 1 + UI/NativeApp.cpp | 2 +- android/jni/app-android.cpp | 36 ++++- .../src/org/ppsspp/ppsspp/CameraHelper.java | 33 +++- .../src/org/ppsspp/ppsspp/LocationHelper.java | 3 +- android/src/org/ppsspp/ppsspp/NativeApp.java | 6 +- ext/native/base/NativeApp.h | 4 +- ios/CameraHelper.h | 17 +++ ios/CameraHelper.mm | 144 ++++++++++++++++++ ios/LocationHelper.h | 18 +++ ios/LocationHelper.mm | 34 +++++ ios/PPSSPP-Info.plist | 6 + ios/ViewController.h | 9 +- ios/ViewController.mm | 35 +++++ ios/main.mm | 12 ++ 24 files changed, 409 insertions(+), 44 deletions(-) create mode 100644 ios/CameraHelper.h create mode 100644 ios/CameraHelper.mm create mode 100644 ios/LocationHelper.h create mode 100644 ios/LocationHelper.mm diff --git a/CMakeLists.txt b/CMakeLists.txt index 573235b787..e724ec6939 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -745,6 +745,10 @@ elseif(IOS) ios/ViewController.h ios/iOSCoreAudio.mm ios/iOSCoreAudio.h + ios/CameraHelper.mm + ios/CameraHelper.h + ios/LocationHelper.mm + ios/LocationHelper.h ios/PPSSPPUIApplication.h ios/PPSSPPUIApplication.mm ios/SmartKeyboardMap.cpp @@ -755,7 +759,7 @@ elseif(IOS) ios/iCade/iCadeReaderView.m ios/iCade/iCadeState.h ) - set(nativeExtraLibs ${nativeExtraLibs} "-framework Foundation -framework MediaPlayer -framework AudioToolbox -framework CoreGraphics -framework QuartzCore -framework UIKit -framework GLKit -framework OpenAL -framework AVFoundation") + set(nativeExtraLibs ${nativeExtraLibs} "-framework Foundation -framework MediaPlayer -framework AudioToolbox -framework CoreGraphics -framework QuartzCore -framework UIKit -framework GLKit -framework OpenAL -framework AVFoundation -framework CoreLocation -framework CoreVideo -framework CoreMedia" ) if(EXISTS "${CMAKE_IOS_SDK_ROOT}/System/Library/Frameworks/GameController.framework") set(nativeExtraLibs ${nativeExtraLibs} "-weak_framework GameController") endif() @@ -770,7 +774,8 @@ elseif(IOS) set_source_files_properties(ios/PPSSPPUIApplication.mm PROPERTIES COMPILE_FLAGS -fobjc-arc) set_source_files_properties(ios/iCade/iCadeReaderView.m PROPERTIES COMPILE_FLAGS -fobjc-arc) set_source_files_properties(ios/main.mm PROPERTIES COMPILE_FLAGS -fobjc-arc) - + set_source_files_properties(ios/CameraHelper.mm PROPERTIES COMPILE_FLAGS -fobjc-arc) + set_source_files_properties(ios/LocationHelper.mm PROPERTIES COMPILE_FLAGS -fobjc-arc) set(TargetBin PPSSPP) elseif(USING_QT_UI) diff --git a/Core/Config.cpp b/Core/Config.cpp index 0e7a27f655..4458042234 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -707,9 +707,7 @@ static ConfigSetting graphicsSettings[] = { #ifdef _WIN32 ConfigSetting("D3D11Device", &g_Config.sD3D11Device, "", true, false), #endif -#if (defined(_WIN32) && !PPSSPP_PLATFORM(UWP)) || PPSSPP_PLATFORM(LINUX) ConfigSetting("CameraDevice", &g_Config.sCameraDevice, "", true, false), -#endif ConfigSetting("VendorBugChecksEnabled", &g_Config.bVendorBugChecksEnabled, true, false, false), ReportedConfigSetting("RenderingMode", &g_Config.iRenderingMode, 1, true, true), ConfigSetting("SoftwareRenderer", &g_Config.bSoftwareRendering, false, true, true), diff --git a/Core/HLE/sceKernel.cpp b/Core/HLE/sceKernel.cpp index c375c59a04..2effbb5a26 100644 --- a/Core/HLE/sceKernel.cpp +++ b/Core/HLE/sceKernel.cpp @@ -169,6 +169,7 @@ void __KernelShutdown() kernelObjects.Clear(); __UsbCamShutdown(); + __UsbGpsShutdown(); __AudioCodecShutdown(); __VideoPmpShutdown(); diff --git a/Core/HLE/sceUsbCam.cpp b/Core/HLE/sceUsbCam.cpp index da850ce8f9..fdcf4d07c0 100644 --- a/Core/HLE/sceUsbCam.cpp +++ b/Core/HLE/sceUsbCam.cpp @@ -205,8 +205,18 @@ static int sceUsbCamSetupStillEx(u32 paramAddr) { return 0; } -static int sceUsbCamAutoImageReverseSW(int rev) { - INFO_LOG(HLE, "UNIMPL sceUsbCamAutoImageReverseSW"); +static int sceUsbCamAutoImageReverseSW(int on) { + INFO_LOG(HLE, "UNIMPL sceUsbCamAutoImageReverseSW: %d", on); + return 0; +} + +static int sceUsbCamGetLensDirection() { + INFO_LOG(HLE, "UNIMPL sceUsbCamGetLensDirection"); + return 0; +} + +static int sceUsbCamSetReverseMode(int reverseflags) { + INFO_LOG(HLE, "UNIMPL sceUsbCamSetReverseMode %d", reverseflags); return 0; } @@ -244,7 +254,7 @@ const HLEFunction sceUsbCam[] = { 0XF93C4669, &WrapI_I, "sceUsbCamAutoImageReverseSW", 'i', "i" }, { 0X11A1F128, nullptr, "sceUsbCamGetAutoImageReverseState", '?', "" }, - { 0X4C34F553, nullptr, "sceUsbCamGetLensDirection", '?', "" }, + { 0X4C34F553, &WrapI_V, "sceUsbCamGetLensDirection", 'i', "" }, { 0X383E9FA8, nullptr, "sceUsbCamGetSaturation", '?', "" }, { 0X6E205974, nullptr, "sceUsbCamSetSaturation", '?', "" }, @@ -259,7 +269,7 @@ const HLEFunction sceUsbCam[] = { 0X2BCD50C0, nullptr, "sceUsbCamGetEvLevel", '?', "" }, { 0X1D686870, nullptr, "sceUsbCamSetEvLevel", '?', "" }, { 0XD5279339, nullptr, "sceUsbCamGetReverseMode", '?', "" }, - { 0X951BEDF5, nullptr, "sceUsbCamSetReverseMode", '?', "" }, + { 0X951BEDF5, &WrapI_I, "sceUsbCamSetReverseMode", 'i', "i" }, { 0X9E8AAF8D, nullptr, "sceUsbCamGetZoom", '?', "" }, { 0XC484901F, nullptr, "sceUsbCamSetZoom", '?', "" }, { 0XAA7D94BA, nullptr, "sceUsbCamGetAntiFlicker", '?', "" }, @@ -275,7 +285,9 @@ void Register_sceUsbCam() } std::vector Camera::getDeviceList() { - #if PPSSPP_PLATFORM(LINUX) && !PPSSPP_PLATFORM(ANDROID) + #if PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(IOS) + return __cameraGetDeviceList(); + #elif PPSSPP_PLATFORM(LINUX) return __v4l_getDeviceList(); #elif defined(_WIN32) && !PPSSPP_PLATFORM(UWP) if (winCamera) { @@ -291,7 +303,7 @@ int Camera::startCapture() { INFO_LOG(HLE, "%s resolution: %dx%d", __FUNCTION__, width, height); config->mode = Camera::Mode::Video; - #if PPSSPP_PLATFORM(ANDROID) + #if PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(IOS) System_SendMessage("camera_command", "startVideo"); #elif PPSSPP_PLATFORM(LINUX) __v4l_startCapture(width, height); @@ -312,7 +324,7 @@ int Camera::startCapture() { int Camera::stopCapture() { INFO_LOG(HLE, "%s", __FUNCTION__); - #if PPSSPP_PLATFORM(ANDROID) + #if PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(IOS) System_SendMessage("camera_command", "stopVideo"); #elif PPSSPP_PLATFORM(LINUX) __v4l_stopCapture(); @@ -328,6 +340,13 @@ int Camera::stopCapture() { return 0; } +void Camera::onCameraDeviceChange() { + if (config != nullptr && config->mode == Camera::Mode::Video) { + stopCapture(); + startCapture(); + } +} + void Camera::pushCameraImage(long long length, unsigned char* image) { std::lock_guard lock(videoBufferMutex); if (!videoBuffer) { diff --git a/Core/HLE/sceUsbCam.h b/Core/HLE/sceUsbCam.h index 3d6bfbcf3f..ad1ebde215 100644 --- a/Core/HLE/sceUsbCam.h +++ b/Core/HLE/sceUsbCam.h @@ -117,6 +117,7 @@ namespace Camera { } Config; std::vector getDeviceList(); + void onCameraDeviceChange(); int startCapture(); int stopCapture(); void pushCameraImage(long long length, unsigned char *image); diff --git a/Core/HLE/sceUsbGps.cpp b/Core/HLE/sceUsbGps.cpp index 4213e7abd1..6a04812309 100644 --- a/Core/HLE/sceUsbGps.cpp +++ b/Core/HLE/sceUsbGps.cpp @@ -43,6 +43,15 @@ void __UsbGpsDoState(PointerWrap &p) { p.Do(gpsStatus); } +void __UsbGpsShutdown() { + gpsStatus = GPS_STATE_OFF; + System_SendMessage("gps_command", "close"); +}; + +static int sceUsbGpsGetInitDataLocation(u32 addr) { + return 0; +} + static int sceUsbGpsGetState(u32 stateAddr) { if (Memory::IsValidAddress(stateAddr)) { Memory::Write_U32(gpsStatus, stateAddr); @@ -77,17 +86,17 @@ static int sceUsbGpsGetData(u32 gpsDataAddr, u32 satDataAddr) { const HLEFunction sceUsbGps[] = { - {0X268F95CA, nullptr, "sceUsbGpsSetInitDataLocation", '?', "" }, - {0X31F95CDE, nullptr, "sceUsbGpsGetPowerSaveMode", '?', "" }, - {0X54D26AA4, nullptr, "sceUsbGpsGetInitDataLocation", '?', "" }, - {0X63D1F89D, nullptr, "sceUsbGpsResetInitialPosition", '?', "" }, - {0X69E4AAA8, nullptr, "sceUsbGpsSaveInitData", '?', "" }, - {0X6EED4811, &WrapI_V, "sceUsbGpsClose", 'i', "" }, - {0X7C16AC3A, &WrapI_U, "sceUsbGpsGetState", 'i', "x"}, - {0X934EC2B2, &WrapI_UU, "sceUsbGpsGetData", 'i', "xx" }, - {0X9D8F99E8, nullptr, "sceUsbGpsSetPowerSaveMode", '?', "" }, - {0X9F267D34, &WrapI_V, "sceUsbGpsOpen", 'i', "" }, - {0XA259CD67, nullptr, "sceUsbGpsReset", '?', "" }, + {0X268F95CA, nullptr, "sceUsbGpsSetInitDataLocation", '?', "" }, + {0X31F95CDE, nullptr, "sceUsbGpsGetPowerSaveMode", '?', "" }, + {0X54D26AA4, &WrapI_U, "sceUsbGpsGetInitDataLocation", 'i', "x" }, + {0X63D1F89D, nullptr, "sceUsbGpsResetInitialPosition", '?', "" }, + {0X69E4AAA8, nullptr, "sceUsbGpsSaveInitData", '?', "" }, + {0X6EED4811, &WrapI_V, "sceUsbGpsClose", 'i', "" }, + {0X7C16AC3A, &WrapI_U, "sceUsbGpsGetState", 'i', "x"}, + {0X934EC2B2, &WrapI_UU, "sceUsbGpsGetData", 'i', "xx" }, + {0X9D8F99E8, nullptr, "sceUsbGpsSetPowerSaveMode", '?', "" }, + {0X9F267D34, &WrapI_V, "sceUsbGpsOpen", 'i', "" }, + {0XA259CD67, nullptr, "sceUsbGpsReset", '?', "" }, }; void Register_sceUsbGps() diff --git a/Core/HLE/sceUsbGps.h b/Core/HLE/sceUsbGps.h index c9f1b7e8f0..fa7059b146 100644 --- a/Core/HLE/sceUsbGps.h +++ b/Core/HLE/sceUsbGps.h @@ -21,6 +21,7 @@ void Register_sceUsbGps(); void __UsbGpsInit(); void __UsbGpsDoState(PointerWrap &p); +void __UsbGpsShutdown(); #pragma pack(push, 1) diff --git a/Core/HW/Camera.cpp b/Core/HW/Camera.cpp index 2260f452e8..c03da48bd4 100644 --- a/Core/HW/Camera.cpp +++ b/Core/HW/Camera.cpp @@ -192,9 +192,10 @@ int __v4l_startCapture(int ideal_width, int ideal_height) { frmsize.index++; if (frmsize.type == V4L2_FRMSIZE_TYPE_DISCRETE) { INFO_LOG(HLE, "V4L2: frame size supported: %dx%d", frmsize.discrete.width, frmsize.discrete.height); - if (fmt.fmt.pix.width != 0 && fmt.fmt.pix.height != 0) { - continue; - } else if (frmsize.discrete.width >= ideal_width && frmsize.discrete.height >= ideal_height) { + if (frmsize.discrete.width >= ideal_width && frmsize.discrete.height >= ideal_height + && fmt.fmt.pix.width == 0 && fmt.fmt.pix.height == 0 + || frmsize.discrete.width >= ideal_width && frmsize.discrete.height >= ideal_height + && frmsize.discrete.width < fmt.fmt.pix.width && frmsize.discrete.height < fmt.fmt.pix.height) { fmt.fmt.pix.width = frmsize.discrete.width; fmt.fmt.pix.height = frmsize.discrete.height; } diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 90130177a2..f1bda2a044 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -263,13 +263,12 @@ void GameSettingsScreen::CreateViews() { softwareGPU->SetEnabled(false); } -#if (defined(_WIN32) && !PPSSPP_PLATFORM(UWP)) || PPSSPP_PLATFORM(LINUX) std::vector cameraList = Camera::getDeviceList(); if (cameraList.size() >= 1) { graphicsSettings->Add(new ItemHeader(gr->T("Camera"))); PopupMultiChoiceDynamic *cameraChoice = graphicsSettings->Add(new PopupMultiChoiceDynamic(&g_Config.sCameraDevice, gr->T("Camera Device"), cameraList, nullptr, screenManager())); + cameraChoice->OnChoice.Handle(this, &GameSettingsScreen::OnCameraDeviceChange); } -#endif graphicsSettings->Add(new ItemHeader(gr->T("Frame Rate Control"))); static const char *frameSkip[] = {"Off", "1", "2", "3", "4", "5", "6", "7", "8"}; @@ -1200,6 +1199,11 @@ UI::EventReturn GameSettingsScreen::OnRenderingDevice(UI::EventParams &e) { return UI::EVENT_DONE; } +UI::EventReturn GameSettingsScreen::OnCameraDeviceChange(UI::EventParams& e) { + Camera::onCameraDeviceChange(); + return UI::EVENT_DONE; +} + UI::EventReturn GameSettingsScreen::OnAudioDevice(UI::EventParams &e) { I18NCategory *a = GetI18NCategory("Audio"); if (g_Config.sAudioDevice == a->T("Auto")) { diff --git a/UI/GameSettingsScreen.h b/UI/GameSettingsScreen.h index e4b6232e5e..3b9cae93e8 100644 --- a/UI/GameSettingsScreen.h +++ b/UI/GameSettingsScreen.h @@ -97,6 +97,7 @@ private: UI::EventReturn OnRenderingMode(UI::EventParams &e); UI::EventReturn OnRenderingBackend(UI::EventParams &e); UI::EventReturn OnRenderingDevice(UI::EventParams &e); + UI::EventReturn OnCameraDeviceChange(UI::EventParams& e); UI::EventReturn OnAudioDevice(UI::EventParams &e); UI::EventReturn OnJitAffectingSetting(UI::EventParams &e); #if PPSSPP_PLATFORM(ANDROID) diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index ff4531beb4..43f237534b 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -1358,7 +1358,7 @@ void NativeShutdown() { // I think we handle most globals correctly or correct-enough now. } -void PushNewGpsData(float latitude, float longitude, float altitude, float speed, float bearing, long long time) { +void SetGpsData(float latitude, float longitude, float altitude, float speed, float bearing, long long time) { GPS::setGpsData(latitude, longitude, altitude, speed, bearing, time); } diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index 1f9655b3a7..a464418405 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -921,12 +921,40 @@ extern "C" jint JNICALL Java_org_ppsspp_ppsspp_NativeApp_getDesiredBackbufferHei return desiredBackbufferSizeY; } -extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_pushNewGpsData(JNIEnv *, jclass, - jfloat latitude, jfloat longitude, jfloat altitude, jfloat speed, jfloat bearing, jlong time) { - PushNewGpsData(latitude, longitude, altitude, speed, bearing, time); + +std::vector __cameraGetDeviceList() { + jclass cameraClass = findClass("org/ppsspp/ppsspp/CameraHelper"); + jmethodID deviceListMethod = getEnv()->GetStaticMethodID(cameraClass, "getDeviceList", "()Ljava/util/ArrayList;"); + jobject deviceListObject = getEnv()->CallStaticObjectMethod(cameraClass, deviceListMethod); + jclass arrayListClass = getEnv()->FindClass("java/util/ArrayList"); + jmethodID arrayListSize = getEnv()->GetMethodID(arrayListClass, "size", "()I"); + jmethodID arrayListGet = getEnv()->GetMethodID(arrayListClass, "get", "(I)Ljava/lang/Object;"); + + jint arrayListObjectLen = getEnv()->CallIntMethod(deviceListObject, arrayListSize); + std::vector deviceListVector; + + for (int i=0; i < arrayListObjectLen; i++) { + jstring dev = static_cast(getEnv()->CallObjectMethod(deviceListObject, arrayListGet, i)); + const char* cdev = getEnv()->GetStringUTFChars(dev, nullptr); + deviceListVector.push_back(cdev); + getEnv()->ReleaseStringUTFChars(dev, cdev); + getEnv()->DeleteLocalRef(dev); + } + return deviceListVector; } -extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_pushCameraImage(JNIEnv *env, jclass, +extern "C" jint Java_org_ppsspp_ppsspp_NativeApp_getSelectedCamera(JNIEnv *, jclass) { + int cameraId = 0; + sscanf(g_Config.sCameraDevice.c_str(), "%d:", &cameraId); + return cameraId; +} + +extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_setGpsDataAndroid(JNIEnv *, jclass, + jfloat latitude, jfloat longitude, jfloat altitude, jfloat speed, jfloat bearing, jlong time) { + SetGpsData(latitude, longitude, altitude, speed, bearing, time); +} + +extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_pushCameraImageAndroid(JNIEnv *env, jclass, jbyteArray image) { if (image != NULL) { diff --git a/android/src/org/ppsspp/ppsspp/CameraHelper.java b/android/src/org/ppsspp/ppsspp/CameraHelper.java index 9979b81660..bd18be1dc0 100644 --- a/android/src/org/ppsspp/ppsspp/CameraHelper.java +++ b/android/src/org/ppsspp/ppsspp/CameraHelper.java @@ -15,6 +15,7 @@ import android.view.WindowManager; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.util.ArrayList; import java.util.List; @TargetApi(23) @@ -24,6 +25,7 @@ class CameraHelper { private Display mDisplay; private Camera mCamera = null; private boolean mIsCameraRunning = false; + private int mCameraFacing = 0; private int mCameraOrientation = 0; private Camera.Size mPreviewSize = null; private long mLastFrameTime = 0; @@ -38,7 +40,11 @@ class CameraHelper { case Surface.ROTATION_180: displayDegrees = 180; break; case Surface.ROTATION_270: displayDegrees = 270; break; } - return (mCameraOrientation - displayDegrees + 360) % 360; + if (mCameraFacing == Camera.CameraInfo.CAMERA_FACING_FRONT) { + return (mCameraOrientation + displayDegrees) % 360; + } else { + return (mCameraOrientation - displayDegrees + 360) % 360; + } } static byte[] rotateNV21(final byte[] input, final int inWidth, final int inHeight, @@ -117,7 +123,7 @@ class CameraHelper { // convert to Jpeg Rect crop = new Rect(0, 0, targetW, targetH); yuvImage.compressToJpeg(crop, 80, baos); - NativeApp.pushCameraImage(baos.toByteArray()); + NativeApp.pushCameraImageAndroid(baos.toByteArray()); try { baos.close(); } catch (IOException e) { @@ -132,14 +138,29 @@ class CameraHelper { mSurfaceTexture = new SurfaceTexture(10); } + static ArrayList getDeviceList() { + ArrayList deviceList = new ArrayList<>(); + int nrCam = Camera.getNumberOfCameras(); + for (int index = 0; index < nrCam; index++) { + Camera.CameraInfo info = new Camera.CameraInfo(); + Camera.getCameraInfo(index, info); + String devName = index + ":" + (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK ? "Back Camera" : "Front Camera"); + deviceList.add(devName); + } + return deviceList; + } + void startCamera() { - Log.d(TAG, "startCamera"); try { - Camera.CameraInfo info = new android.hardware.Camera.CameraInfo(); - Camera.getCameraInfo(0, info); + int cameraId = NativeApp.getSelectedCamera(); + Log.d(TAG, "startCamera: " + cameraId); + + Camera.CameraInfo info = new Camera.CameraInfo(); + Camera.getCameraInfo(cameraId, info); + mCameraFacing = info.facing; mCameraOrientation = info.orientation; - mCamera = Camera.open(); + mCamera = Camera.open(cameraId); Camera.Parameters param = mCamera.getParameters(); // Set preview size diff --git a/android/src/org/ppsspp/ppsspp/LocationHelper.java b/android/src/org/ppsspp/ppsspp/LocationHelper.java index a71bcaeb19..73e9350f07 100644 --- a/android/src/org/ppsspp/ppsspp/LocationHelper.java +++ b/android/src/org/ppsspp/ppsspp/LocationHelper.java @@ -50,12 +50,13 @@ class LocationHelper implements LocationListener { public void onLocationChanged(Location location) { float latitude = (float) location.getLatitude(); float longitude = (float) location.getLongitude(); + // Android altitude is in meters above the WGS 84 reference ellipsoid float altitude = (float) location.getAltitude(); float speed = location.getSpeed(); float bearing = location.getBearing(); long time = location.getTime() / 1000; // ms to s !! - NativeApp.pushNewGpsData(latitude, longitude, altitude, speed, bearing, time); + NativeApp.setGpsDataAndroid(latitude, longitude, altitude, speed, bearing, time); } @Override diff --git a/android/src/org/ppsspp/ppsspp/NativeApp.java b/android/src/org/ppsspp/ppsspp/NativeApp.java index 140f3edd59..5db8abdc6d 100644 --- a/android/src/org/ppsspp/ppsspp/NativeApp.java +++ b/android/src/org/ppsspp/ppsspp/NativeApp.java @@ -51,7 +51,7 @@ public class NativeApp { public static native String queryConfig(String queryName); - public static native void pushNewGpsData(float latitude, float longitude, float altitude, float speed, float bearing, long time); - - public static native void pushCameraImage(byte[] image); + public static native int getSelectedCamera(); + public static native void setGpsDataAndroid(float latitude, float longitude, float altitude, float speed, float bearing, long time); + public static native void pushCameraImageAndroid(byte[] image); } diff --git a/ext/native/base/NativeApp.h b/ext/native/base/NativeApp.h index 623332d37e..d32353ccde 100644 --- a/ext/native/base/NativeApp.h +++ b/ext/native/base/NativeApp.h @@ -1,6 +1,7 @@ #pragma once #include +#include // The Native App API. // @@ -174,5 +175,6 @@ std::string System_GetProperty(SystemProperty prop); int System_GetPropertyInt(SystemProperty prop); bool System_GetPropertyBool(SystemProperty prop); -void PushNewGpsData(float latitude, float longitude, float altitude, float speed, float bearing, long long time); +std::vector __cameraGetDeviceList(); +void SetGpsData(float latitude, float longitude, float altitude, float speed, float bearing, long long time); void PushCameraImage(long long length, unsigned char* image); diff --git a/ios/CameraHelper.h b/ios/CameraHelper.h new file mode 100644 index 0000000000..8c7c2b5f2f --- /dev/null +++ b/ios/CameraHelper.h @@ -0,0 +1,17 @@ +#import +#import + +@protocol CameraFrameDelegate +@required +- (void) PushCameraImageIOS:(long long)len buffer:(unsigned char*)data; +@end + +@interface CameraHelper : NSObject + +@property (nonatomic, strong) id delegate; + +- (int) checkPermission; +- (void) startVideo; +- (void) stopVideo; + +@end diff --git a/ios/CameraHelper.mm b/ios/CameraHelper.mm new file mode 100644 index 0000000000..7a2f6c87b6 --- /dev/null +++ b/ios/CameraHelper.mm @@ -0,0 +1,144 @@ +#include +#include +#include "Core/Config.h" +#import "CameraHelper.h" +#import + +@interface CameraHelper() { + AVCaptureSession *captureSession; + AVCaptureVideoPreviewLayer *previewLayer; +} +@end + +@implementation CameraHelper + +std::vector __cameraGetDeviceList() { + std::vector deviceList; + for (AVCaptureDevice *device in [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]) { + deviceList.push_back([device.localizedName UTF8String]); + } + return deviceList; +} + +NSString *getSelectedCamera() { + NSString *selectedCamera = [NSString stringWithCString:g_Config.sCameraDevice.c_str() encoding:[NSString defaultCStringEncoding]]; + return selectedCamera; +} + +-(int) checkPermission { + AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]; + NSLog(@"CameraHelper::checkPermission %ld", (long)status); + + switch (status) { + case AVAuthorizationStatusNotDetermined: { + [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) { + if (granted) { + NSLog(@"camera permission granted"); + dispatch_async(dispatch_get_main_queue(), ^{ + [self startVideo]; + }); + } else { + NSLog(@"camera permission denied"); + } + }]; + return 1; + } + case AVAuthorizationStatusRestricted: + case AVAuthorizationStatusDenied: { + NSLog(@"camera permission denied"); + return 1; + } + case AVAuthorizationStatusAuthorized: { + return 0; + } + } +} + +-(void) startVideo { + NSLog(@"CameraHelper::startVideo"); + if ([self checkPermission]) { + return; + } + + dispatch_async(dispatch_get_main_queue(), ^{ + NSError *error = nil; + + captureSession = [[AVCaptureSession alloc] init]; + captureSession.sessionPreset = AVCaptureSessionPresetMedium; + + AVCaptureDeviceInput *videoInput = nil; + NSString *selectedCamera = getSelectedCamera(); + for (AVCaptureDevice *device in [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]) { + if ([device.localizedName isEqualToString:selectedCamera]) { + videoInput = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error]; + } + } + if (videoInput == nil || error) { + NSLog(@"selectedCamera error; try default device"); + + AVCaptureDevice *videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; + if (videoDevice == nil) { + NSLog(@"videoDevice error"); + return; + } + videoInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:&error]; + if (videoInput == nil) { + NSLog(@"videoInput error"); + return; + } + } + [captureSession addInput:videoInput]; + + AVCaptureVideoDataOutput *videoOutput = [[AVCaptureVideoDataOutput alloc] init]; + videoOutput.videoSettings = [NSDictionary dictionaryWithObject: [NSNumber numberWithInt:kCVPixelFormatType_32BGRA] forKey: (id)kCVPixelBufferPixelFormatTypeKey]; + + [captureSession addOutput:videoOutput]; + + dispatch_queue_t queue = dispatch_queue_create("cameraQueue", NULL); + [videoOutput setSampleBufferDelegate:self queue:queue]; + + previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:captureSession]; + previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; + + [previewLayer setFrame:CGRectMake(0, 0, 480, 272)]; + + [captureSession startRunning]; + }); +} + +-(void) stopVideo { + dispatch_async(dispatch_get_main_queue(), ^{ + [captureSession stopRunning]; + }); +} + +- (void) captureOutput:(AVCaptureOutput *)captureOutput + didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer + fromConnection:(AVCaptureConnection *)connection { + CGImageRef cgImage = [self imageFromSampleBuffer:sampleBuffer]; + UIImage *theImage = [UIImage imageWithCGImage: cgImage]; + CGImageRelease(cgImage); + NSData *imageData = UIImageJPEGRepresentation(theImage, 0.6); + + [self.delegate PushCameraImageIOS:imageData.length buffer:(unsigned char*)imageData.bytes]; +} + +- (CGImageRef) imageFromSampleBuffer:(CMSampleBufferRef) sampleBuffer { + CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); + CVPixelBufferLockBaseAddress(imageBuffer,0); + void* baseAddress = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0); + size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer); + size_t width = CVPixelBufferGetWidth(imageBuffer); + size_t height = CVPixelBufferGetHeight(imageBuffer); + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + + CGContextRef newContext = CGBitmapContextCreate(baseAddress, width, height, 8, bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst); + CGImageRef newImage = CGBitmapContextCreateImage(newContext); + CGContextRelease(newContext); + + CGColorSpaceRelease(colorSpace); + CVPixelBufferUnlockBaseAddress(imageBuffer,0); + return newImage; +} + +@end diff --git a/ios/LocationHelper.h b/ios/LocationHelper.h new file mode 100644 index 0000000000..f04a8ddb99 --- /dev/null +++ b/ios/LocationHelper.h @@ -0,0 +1,18 @@ +#import +#import + +@protocol LocationHandlerDelegate +@required +- (void) SetGpsDataIOS:(CLLocation*)newLocation; +@end + +@interface LocationHelper : NSObject { + CLLocationManager *locationManager; +} + +@property(nonatomic,strong) id delegate; + +- (void) startLocationUpdates; +- (void) stopLocationUpdates; + +@end diff --git a/ios/LocationHelper.mm b/ios/LocationHelper.mm new file mode 100644 index 0000000000..237a9e12fe --- /dev/null +++ b/ios/LocationHelper.mm @@ -0,0 +1,34 @@ +#import "LocationHelper.h" + +@interface LocationHelper() +@end + +@implementation LocationHelper + +-(id) init { + NSLog(@"LocationHelper::init"); + locationManager = [[CLLocationManager alloc] init]; + [locationManager setDelegate:self]; + return self; +} + +-(void) startLocationUpdates { + NSLog(@"LocationHelper::startLocationUpdates"); + if ([locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) { + [locationManager requestWhenInUseAuthorization]; + } + [locationManager startUpdatingLocation]; +} + +-(void) stopLocationUpdates { + NSLog(@"LocationHelper::stopLocationUpdates"); + [locationManager stopUpdatingLocation]; +} + +- (void) locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations { + for (id location in locations) { + [self.delegate SetGpsDataIOS:location]; + } +} + +@end diff --git a/ios/PPSSPP-Info.plist b/ios/PPSSPP-Info.plist index 8569dc54cd..74234337c8 100644 --- a/ios/PPSSPP-Info.plist +++ b/ios/PPSSPP-Info.plist @@ -4,6 +4,12 @@ UISupportsDocumentBrowser + NSLocationWhenInUseUsageDescription + Your location may be used to emulate Go!Explore, a GPS accessory + NSCameraUsageDescription + Your camera may be used to emulate Go!Cam, a camera accessory + NSMicrophoneUsageDescription + Your microphone may be used to emulate Go!Cam/Talkman, a microphone accessory CFBundleDevelopmentRegion en CFBundleDisplayName diff --git a/ios/ViewController.h b/ios/ViewController.h index 4eedd89e7c..7f93faaebb 100644 --- a/ios/ViewController.h +++ b/ios/ViewController.h @@ -6,11 +6,18 @@ #import #endif #import "iCade/iCadeReaderView.h" +#import "CameraHelper.h" +#import "LocationHelper.h" -@interface ViewController : GLKViewController +@interface ViewController : GLKViewController - (void)shutdown; @end extern __unsafe_unretained ViewController* sharedViewController; +void startVideo(); +void stopVideo(); +void startLocation(); +void stopLocation(); diff --git a/ios/ViewController.mm b/ios/ViewController.mm index ad334f0d8d..5eaea61c3a 100644 --- a/ios/ViewController.mm +++ b/ios/ViewController.mm @@ -84,6 +84,8 @@ static bool threadStopped = false; __unsafe_unretained ViewController* sharedViewController; static GraphicsContext *graphicsContext; +static CameraHelper *cameraHelper; +static LocationHelper *locationHelper; @interface ViewController () { std::map iCadeToKeyMap; @@ -202,6 +204,12 @@ static GraphicsContext *graphicsContext; volume.delegate = self; [self.view addSubview:volume]; [self.view bringSubviewToFront:volume]; + + cameraHelper = [[CameraHelper alloc] init]; + [cameraHelper setDelegate:self]; + + locationHelper = [[LocationHelper alloc] init]; + [locationHelper setDelegate:self]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ NativeInitGraphics(graphicsContext); @@ -667,6 +675,33 @@ static GraphicsContext *graphicsContext; } #endif +void startVideo() { + [cameraHelper startVideo]; +} + +void stopVideo() { + [cameraHelper stopVideo]; +} + +-(void) PushCameraImageIOS:(long long)len buffer:(unsigned char*)data { + PushCameraImage(len, data); +} + +void startLocation() { + [locationHelper startLocationUpdates]; +} + +void stopLocation() { + [locationHelper stopLocationUpdates]; +} + +-(void) SetGpsDataIOS:(CLLocation *)newLocation { + NSLog(@"SetGpsDataIOS: speed: %f", newLocation.speed); // m/s + SetGpsData(newLocation.coordinate.latitude, newLocation.coordinate.longitude, + newLocation.altitude, 0 /* speed */, 0 /* bearing */, + (long long)newLocation.timestamp.timeIntervalSince1970); +} + @end void OpenDirectory(const char *path) { diff --git a/ios/main.mm b/ios/main.mm index cad442c87b..dfa1548296 100644 --- a/ios/main.mm +++ b/ios/main.mm @@ -112,6 +112,18 @@ void System_SendMessage(const char *command, const char *parameter) { // [sharedViewController shutdown]; // exit(0); // }); + } else if (!strcmp(command, "camera_command")) { + if (!strcmp(parameter, "startVideo")) { + startVideo(); + } else if (!strcmp(parameter, "stopVideo")) { + stopVideo(); + } + } else if (!strcmp(command, "gps_command")) { + if (!strcmp(parameter, "open")) { + startLocation(); + } else if (!strcmp(parameter, "close")) { + stopLocation(); + } } }