[Android] Updates for camera

-rotate camera frames to follow display orientation
-release camera resources when a game is closed
-release camera resources when the activity is paused
This commit is contained in:
Florin9doi 2019-08-22 15:41:01 +03:00
parent 088901324d
commit e1164cfe4f
3 changed files with 133 additions and 32 deletions

View File

@ -116,7 +116,6 @@ static int sceUsbCamReadVideoFrame(u32 bufAddr, u32 size) {
} }
static int sceUsbCamPollReadVideoFrameEnd() { static int sceUsbCamPollReadVideoFrameEnd() {
INFO_LOG(HLE, "UNIMPL sceUsbCamPollReadVideoFrameEnd: %d", nextVideoFrame);
return nextVideoFrame; return nextVideoFrame;
} }
@ -186,7 +185,12 @@ void Register_sceUsbCam()
void Camera::pushCameraImage(long long length, unsigned char* image) { void Camera::pushCameraImage(long long length, unsigned char* image) {
std::lock_guard<std::mutex> lock(videoBufferMutex); std::lock_guard<std::mutex> lock(videoBufferMutex);
videoBufferLength = length;
memset (videoBuffer, 0, sizeof(videoBuffer)); memset (videoBuffer, 0, sizeof(videoBuffer));
memcpy (videoBuffer, image, length); if (length > sizeof(videoBuffer)) {
videoBufferLength = 0;
ERROR_LOG(HLE, "pushCameraImage: length error");
} else {
videoBufferLength = length;
memcpy (videoBuffer, image, length);
}
} }

View File

@ -7,7 +7,12 @@ import android.graphics.Rect;
import android.graphics.SurfaceTexture; import android.graphics.SurfaceTexture;
import android.graphics.YuvImage; import android.graphics.YuvImage;
import android.hardware.Camera; import android.hardware.Camera;
import android.os.SystemClock;
import android.util.Log; import android.util.Log;
import android.view.Display;
import android.view.Surface;
import android.view.WindowManager;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
@ -15,67 +20,140 @@ import java.util.List;
@TargetApi(23) @TargetApi(23)
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
class CameraHelper { class CameraHelper {
private static final String TAG = "CameraHelper"; private static final String TAG = CameraHelper.class.getSimpleName();
private Display mDisplay;
private Camera mCamera = null; private Camera mCamera = null;
private int previewWidth = 0; private boolean mIsCameraRunning = false;
private int previewHeight = 0; private int mCameraOrientation = 0;
private long lastTime = 0; private Camera.Size mPreviewSize = null;
private long mLastFrameTime = 0;
private SurfaceTexture mSurfaceTexture; private SurfaceTexture mSurfaceTexture;
private int getCameraRotation() {
int displayRotation = mDisplay.getRotation();
int displayDegrees = 0;
switch (displayRotation) {
case Surface.ROTATION_0: displayDegrees = 0; break;
case Surface.ROTATION_90: displayDegrees = 90; break;
case Surface.ROTATION_180: displayDegrees = 180; break;
case Surface.ROTATION_270: displayDegrees = 270; break;
}
return (mCameraOrientation - displayDegrees + 360) % 360;
}
static byte[] rotateNV21(final byte[] input, final int inWidth, final int inHeight,
final int outWidth, final int outHeight, final int rotation) {
final int inFrameSize = inWidth * inHeight;
final int outFrameSize = outWidth * outHeight;
final byte[] output = new byte[outFrameSize + outFrameSize/2];
if (rotation == 0 || rotation == 180) {
final int crop_left = (inWidth - outWidth) / 2;
final int crop_top = (inHeight - outHeight) / 2;
for (int j = 0; j < outHeight; j++) {
final int yInCol = (crop_top + j) * inWidth + crop_left;
final int uvInCol = inFrameSize + ((crop_top + j) >> 1) * inWidth + crop_left;
final int jOut = rotation == 180 ? outHeight - j - 1 : j;
final int yOutCol = jOut * outWidth;
final int uvOutCol = outFrameSize + (jOut >> 1) * outWidth;
for (int i = 0; i < outWidth; i++) {
final int yIn = yInCol + i;
final int uIn = uvInCol + (i & ~1);
final int vIn = uIn + 1;
final int iOut = rotation == 180 ? outWidth - i - 1 : i;
final int yOut = yOutCol + iOut;
final int uOut = uvOutCol + (iOut & ~1);
final int vOut = uOut + 1;
output[yOut] = input[yIn];
output[uOut] = input[uIn];
output[vOut] = input[vIn];
}
}
} else if (rotation == 90 || rotation == 270) {
int crop_left = (inWidth - outHeight) / 2;
int crop_top = (inHeight - outWidth) / 2;
for (int j = 0; j < outWidth; j++) {
final int yInCol = (crop_top + j) * inWidth + crop_left;
final int uvInCol = inFrameSize + ((crop_top + j) >> 1) * inWidth + crop_left;
final int iOut = rotation == 90 ? outWidth - j - 1 : j;
for (int i = 0; i < outHeight; i++) {
final int yIn = yInCol + i;
final int uIn = uvInCol + (i & ~1);
final int vIn = uIn + 1;
final int jOut = rotation == 270 ? outHeight - i - 1 : i;
final int yOut = jOut * outWidth + iOut;
final int uOut = outFrameSize + (jOut >> 1) * outWidth + (iOut & ~1);
final int vOut = uOut + 1;
output[yOut] = input[yIn];
output[uOut] = input[uIn];
output[vOut] = input[vIn];
}
}
}
return output;
}
private Camera.PreviewCallback mPreviewCallback = new Camera.PreviewCallback() { private Camera.PreviewCallback mPreviewCallback = new Camera.PreviewCallback() {
@Override @Override
public void onPreviewFrame(byte[] previewData, Camera camera) { public void onPreviewFrame(byte[] previewData, Camera camera) {
// throttle at 100 ms // throttle at 66 ms
long currentTime = System.currentTimeMillis(); long currentTime = SystemClock.elapsedRealtime();
if (currentTime - lastTime < 100) { if (currentTime - mLastFrameTime < 66) {
return; return;
} }
lastTime = currentTime; mLastFrameTime = currentTime;
YuvImage yuvImage = new YuvImage(previewData, ImageFormat.NV21, previewWidth, previewHeight, null);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// the expected values arrives in sceUsbCamSetupVideo // the expected values arrives in sceUsbCamSetupVideo
int targetW = 480; int targetW = 480;
int targetH = 272; int targetH = 272;
// crop to expected size and convert to Jpeg int cameraRotation = getCameraRotation();
Rect rect = new Rect( byte[] newPreviewData = rotateNV21(previewData, mPreviewSize.width, mPreviewSize.height,
(previewWidth - targetW) / 2, targetW, targetH, cameraRotation);
(previewHeight - targetH) / 2, YuvImage yuvImage = new YuvImage(newPreviewData, ImageFormat.NV21, targetW, targetH, null);
previewWidth - (previewWidth - targetW) / 2, ByteArrayOutputStream baos = new ByteArrayOutputStream();
previewHeight - (previewHeight - targetH) / 2
); // convert to Jpeg
yuvImage.compressToJpeg(rect, 80, baos); Rect crop = new Rect(0, 0, targetW, targetH);
yuvImage.compressToJpeg(crop, 80, baos);
NativeApp.pushCameraImage(baos.toByteArray()); NativeApp.pushCameraImage(baos.toByteArray());
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
} }
}; };
@SuppressWarnings("unused") @SuppressWarnings("unused")
CameraHelper(Context context) { CameraHelper(final Context context) {
mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
mSurfaceTexture = new SurfaceTexture(10); mSurfaceTexture = new SurfaceTexture(10);
} }
void startCamera() { void startCamera() {
Log.d(TAG, "startCamera"); Log.d(TAG, "startCamera");
try { try {
Camera.CameraInfo info = new android.hardware.Camera.CameraInfo();
Camera.getCameraInfo(0, info);
mCameraOrientation = info.orientation;
mCamera = Camera.open(); mCamera = Camera.open();
Camera.Parameters param = mCamera.getParameters(); Camera.Parameters param = mCamera.getParameters();
// Set preview size // Set preview size
List<Camera.Size> previewSizes = param.getSupportedPreviewSizes(); List<Camera.Size> previewSizes = param.getSupportedPreviewSizes();
previewWidth = previewSizes.get(0).width; mPreviewSize = previewSizes.get(0);
previewHeight = previewSizes.get(0).height;
for (int i = 0; i < previewSizes.size(); i++) { for (int i = 0; i < previewSizes.size(); i++) {
Log.d(TAG, "getSupportedPreviewSizes[" + i + "]: " + previewSizes.get(i).height + " " + previewSizes.get(i).width); Log.d(TAG, "getSupportedPreviewSizes[" + i + "]: " + previewSizes.get(i).height + " " + previewSizes.get(i).width);
if (previewSizes.get(i).width <= 640 && previewSizes.get(i).height <= 480) { if (previewSizes.get(i).width <= 640 && previewSizes.get(i).height <= 480) {
previewWidth = previewSizes.get(i).width; mPreviewSize = previewSizes.get(i);
previewHeight = previewSizes.get(i).height;
break; break;
} }
} }
Log.d(TAG, "setPreviewSize(" + previewWidth + ", " + previewHeight + ")"); Log.d(TAG, "setPreviewSize(" + mPreviewSize.width + ", " + mPreviewSize.height + ")");
param.setPreviewSize(previewWidth, previewHeight); param.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
// Set preview FPS // Set preview FPS
int[] fps; int[] fps;
@ -94,18 +172,31 @@ class CameraHelper {
mCamera.setPreviewTexture(mSurfaceTexture); mCamera.setPreviewTexture(mSurfaceTexture);
mCamera.setPreviewCallback(mPreviewCallback); mCamera.setPreviewCallback(mPreviewCallback);
mCamera.startPreview(); mCamera.startPreview();
mIsCameraRunning = true;
} catch (IOException e) { } catch (IOException e) {
Log.e(TAG, "Cannot start camera: " + e.toString()); Log.e(TAG, "Cannot start camera: " + e.toString());
} }
} }
void stopCamera() { void pause() {
Log.d(TAG, "stopCamera"); if (mIsCameraRunning && mCamera != null) {
if (mCamera != null) { Log.d(TAG, "pause");
mCamera.setPreviewCallback(null); mCamera.setPreviewCallback(null);
mCamera.stopPreview(); mCamera.stopPreview();
mCamera.release(); mCamera.release();
mCamera = null; mCamera = null;
} }
} }
void resume() {
if (mIsCameraRunning) {
Log.d(TAG, "resume");
startCamera();
}
}
void stopCamera() {
pause();
mIsCameraRunning = false;
}
} }

View File

@ -740,6 +740,7 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C
Log.e(TAG, "mGLSurfaceView really shouldn't be null in onPause"); Log.e(TAG, "mGLSurfaceView really shouldn't be null in onPause");
} }
} }
mCameraHelper.pause();
Log.i(TAG, "onPause completed"); Log.i(TAG, "onPause completed");
} }
@ -779,6 +780,7 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C
mSurfaceView.onResume(); mSurfaceView.onResume();
} }
} }
mCameraHelper.resume();
gainAudioFocus(this.audioManager, this.audioFocusChangeListener); gainAudioFocus(this.audioManager, this.audioFocusChangeListener);
NativeApp.resume(); NativeApp.resume();
@ -1335,6 +1337,10 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C
// Only keep the screen bright ingame. // Only keep the screen bright ingame.
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
} }
} else if (command.equals("event")) {
if (params.equals("exitgame")) {
mCameraHelper.stopCamera();
}
} }
return false; return false;
} }