Query Android device orientation on every camera frame received.
Remove orientation listener from Android camera, since device orientation change events are not well synchronized with actual device display orientation. Plus these event may not be delivered at all if device is in stationary position causing initial camera frames appear rotated. BUG= R=braveyao@webrtc.org Review URL: https://webrtc-codereview.appspot.com/23009004 git-svn-id: http://webrtc.googlecode.com/svn/trunk@7467 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
parent
9c58ea8d56
commit
f7030d4ed7
@ -24,9 +24,10 @@ import android.os.Handler;
|
|||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.OrientationEventListener;
|
import android.view.Surface;
|
||||||
import android.view.SurfaceHolder.Callback;
|
import android.view.SurfaceHolder.Callback;
|
||||||
import android.view.SurfaceHolder;
|
import android.view.SurfaceHolder;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
|
||||||
// Wrapper for android Camera, with support for direct local preview rendering.
|
// Wrapper for android Camera, with support for direct local preview rendering.
|
||||||
// Threading notes: this class is called from ViE C++ code, and from Camera &
|
// Threading notes: this class is called from ViE C++ code, and from Camera &
|
||||||
@ -44,10 +45,9 @@ public class VideoCaptureAndroid implements PreviewCallback, Callback {
|
|||||||
private Camera camera; // Only non-null while capturing.
|
private Camera camera; // Only non-null while capturing.
|
||||||
private CameraThread cameraThread;
|
private CameraThread cameraThread;
|
||||||
private Handler cameraThreadHandler;
|
private Handler cameraThreadHandler;
|
||||||
|
private Context context;
|
||||||
private final int id;
|
private final int id;
|
||||||
private final Camera.CameraInfo info;
|
private final Camera.CameraInfo info;
|
||||||
private final OrientationEventListener orientationListener;
|
|
||||||
private boolean orientationListenerEnabled;
|
|
||||||
private final long native_capturer; // |VideoCaptureAndroid*| in C++.
|
private final long native_capturer; // |VideoCaptureAndroid*| in C++.
|
||||||
private SurfaceTexture cameraSurfaceTexture;
|
private SurfaceTexture cameraSurfaceTexture;
|
||||||
private int[] cameraGlTextures = null;
|
private int[] cameraGlTextures = null;
|
||||||
@ -70,34 +70,13 @@ public class VideoCaptureAndroid implements PreviewCallback, Callback {
|
|||||||
public VideoCaptureAndroid(int id, long native_capturer) {
|
public VideoCaptureAndroid(int id, long native_capturer) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.native_capturer = native_capturer;
|
this.native_capturer = native_capturer;
|
||||||
|
this.context = GetContext();
|
||||||
this.info = new Camera.CameraInfo();
|
this.info = new Camera.CameraInfo();
|
||||||
Camera.getCameraInfo(id, info);
|
Camera.getCameraInfo(id, info);
|
||||||
|
|
||||||
// Must be the last thing in the ctor since we pass a reference to |this|!
|
|
||||||
final VideoCaptureAndroid self = this;
|
|
||||||
orientationListener = new OrientationEventListener(GetContext()) {
|
|
||||||
@Override public void onOrientationChanged(int degrees) {
|
|
||||||
if (!self.orientationListenerEnabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (degrees == OrientationEventListener.ORIENTATION_UNKNOWN) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
|
|
||||||
degrees = (info.orientation - degrees + 360) % 360;
|
|
||||||
} else { // back-facing
|
|
||||||
degrees = (info.orientation + degrees) % 360;
|
|
||||||
}
|
|
||||||
self.OnOrientationChanged(self.native_capturer, degrees);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// Don't add any code here; see the comment above |self| above!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the global application context.
|
// Return the global application context.
|
||||||
private static native Context GetContext();
|
private static native Context GetContext();
|
||||||
// Request frame rotation post-capture.
|
|
||||||
private native void OnOrientationChanged(long captureObject, int degrees);
|
|
||||||
|
|
||||||
private class CameraThread extends Thread {
|
private class CameraThread extends Thread {
|
||||||
private Exchanger<Handler> handlerExchanger;
|
private Exchanger<Handler> handlerExchanger;
|
||||||
@ -137,8 +116,6 @@ public class VideoCaptureAndroid implements PreviewCallback, Callback {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
boolean startResult = exchange(result, false); // |false| is a dummy value.
|
boolean startResult = exchange(result, false); // |false| is a dummy value.
|
||||||
orientationListenerEnabled = true;
|
|
||||||
orientationListener.enable();
|
|
||||||
return startResult;
|
return startResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,6 +161,8 @@ public class VideoCaptureAndroid implements PreviewCallback, Callback {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "Camera orientation: " + info.orientation +
|
||||||
|
" .Device orientation: " + getDeviceOrientation());
|
||||||
Camera.Parameters parameters = camera.getParameters();
|
Camera.Parameters parameters = camera.getParameters();
|
||||||
Log.d(TAG, "isVideoStabilizationSupported: " +
|
Log.d(TAG, "isVideoStabilizationSupported: " +
|
||||||
parameters.isVideoStabilizationSupported());
|
parameters.isVideoStabilizationSupported());
|
||||||
@ -223,8 +202,6 @@ public class VideoCaptureAndroid implements PreviewCallback, Callback {
|
|||||||
// Called by native code. Returns true when camera is known to be stopped.
|
// Called by native code. Returns true when camera is known to be stopped.
|
||||||
private synchronized boolean stopCapture() {
|
private synchronized boolean stopCapture() {
|
||||||
Log.d(TAG, "stopCapture");
|
Log.d(TAG, "stopCapture");
|
||||||
orientationListener.disable();
|
|
||||||
orientationListenerEnabled = false;
|
|
||||||
final Exchanger<Boolean> result = new Exchanger<Boolean>();
|
final Exchanger<Boolean> result = new Exchanger<Boolean>();
|
||||||
cameraThreadHandler.post(new Runnable() {
|
cameraThreadHandler.post(new Runnable() {
|
||||||
@Override public void run() {
|
@Override public void run() {
|
||||||
@ -279,8 +256,32 @@ public class VideoCaptureAndroid implements PreviewCallback, Callback {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int getDeviceOrientation() {
|
||||||
|
int orientation = 0;
|
||||||
|
if (context != null) {
|
||||||
|
WindowManager wm = (WindowManager) context.getSystemService(
|
||||||
|
Context.WINDOW_SERVICE);
|
||||||
|
switch(wm.getDefaultDisplay().getRotation()) {
|
||||||
|
case Surface.ROTATION_90:
|
||||||
|
orientation = 90;
|
||||||
|
break;
|
||||||
|
case Surface.ROTATION_180:
|
||||||
|
orientation = 180;
|
||||||
|
break;
|
||||||
|
case Surface.ROTATION_270:
|
||||||
|
orientation = 270;
|
||||||
|
break;
|
||||||
|
case Surface.ROTATION_0:
|
||||||
|
default:
|
||||||
|
orientation = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return orientation;
|
||||||
|
}
|
||||||
|
|
||||||
private native void ProvideCameraFrame(
|
private native void ProvideCameraFrame(
|
||||||
byte[] data, int length, long timeStamp, long captureObject);
|
byte[] data, int length, int rotation, long timeStamp, long captureObject);
|
||||||
|
|
||||||
// Called on cameraThread so must not "synchronized".
|
// Called on cameraThread so must not "synchronized".
|
||||||
@Override
|
@Override
|
||||||
@ -306,7 +307,15 @@ public class VideoCaptureAndroid implements PreviewCallback, Callback {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
lastCaptureTimeMs = captureTimeMs;
|
lastCaptureTimeMs = captureTimeMs;
|
||||||
ProvideCameraFrame(data, data.length, captureTimeMs, native_capturer);
|
|
||||||
|
int rotation = getDeviceOrientation();
|
||||||
|
if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
|
||||||
|
rotation = 360 - rotation;
|
||||||
|
}
|
||||||
|
rotation = (info.orientation + rotation) % 360;
|
||||||
|
|
||||||
|
ProvideCameraFrame(data, data.length, rotation,
|
||||||
|
captureTimeMs, native_capturer);
|
||||||
camera.addCallbackBuffer(data);
|
camera.addCallbackBuffer(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,6 +37,7 @@ void JNICALL ProvideCameraFrame(
|
|||||||
jobject,
|
jobject,
|
||||||
jbyteArray javaCameraFrame,
|
jbyteArray javaCameraFrame,
|
||||||
jint length,
|
jint length,
|
||||||
|
jint rotation,
|
||||||
jlong timeStamp,
|
jlong timeStamp,
|
||||||
jlong context) {
|
jlong context) {
|
||||||
webrtc::videocapturemodule::VideoCaptureAndroid* captureModule =
|
webrtc::videocapturemodule::VideoCaptureAndroid* captureModule =
|
||||||
@ -44,30 +45,10 @@ void JNICALL ProvideCameraFrame(
|
|||||||
context);
|
context);
|
||||||
jbyte* cameraFrame = env->GetByteArrayElements(javaCameraFrame, NULL);
|
jbyte* cameraFrame = env->GetByteArrayElements(javaCameraFrame, NULL);
|
||||||
captureModule->OnIncomingFrame(
|
captureModule->OnIncomingFrame(
|
||||||
reinterpret_cast<uint8_t*>(cameraFrame), length, 0);
|
reinterpret_cast<uint8_t*>(cameraFrame), length, rotation, 0);
|
||||||
env->ReleaseByteArrayElements(javaCameraFrame, cameraFrame, JNI_ABORT);
|
env->ReleaseByteArrayElements(javaCameraFrame, cameraFrame, JNI_ABORT);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called by Java when the device orientation has changed.
|
|
||||||
void JNICALL OnOrientationChanged(
|
|
||||||
JNIEnv* env, jobject, jlong context, jint degrees) {
|
|
||||||
webrtc::videocapturemodule::VideoCaptureAndroid* captureModule =
|
|
||||||
reinterpret_cast<webrtc::videocapturemodule::VideoCaptureAndroid*>(
|
|
||||||
context);
|
|
||||||
degrees = (360 + degrees) % 360;
|
|
||||||
assert(degrees >= 0 && degrees < 360);
|
|
||||||
VideoCaptureRotation rotation =
|
|
||||||
(degrees <= 45 || degrees > 315) ? kCameraRotate0 :
|
|
||||||
(degrees > 45 && degrees <= 135) ? kCameraRotate90 :
|
|
||||||
(degrees > 135 && degrees <= 225) ? kCameraRotate180 :
|
|
||||||
(degrees > 225 && degrees <= 315) ? kCameraRotate270 :
|
|
||||||
kCameraRotate0; // Impossible.
|
|
||||||
int32_t status =
|
|
||||||
captureModule->VideoCaptureImpl::SetCaptureRotation(rotation);
|
|
||||||
RTC_UNUSED(status);
|
|
||||||
assert(status == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t SetCaptureAndroidVM(JavaVM* javaVM, jobject context) {
|
int32_t SetCaptureAndroidVM(JavaVM* javaVM, jobject context) {
|
||||||
if (javaVM) {
|
if (javaVM) {
|
||||||
assert(!g_jvm);
|
assert(!g_jvm);
|
||||||
@ -88,14 +69,11 @@ int32_t SetCaptureAndroidVM(JavaVM* javaVM, jobject context) {
|
|||||||
{"GetContext",
|
{"GetContext",
|
||||||
"()Landroid/content/Context;",
|
"()Landroid/content/Context;",
|
||||||
reinterpret_cast<void*>(&GetContext)},
|
reinterpret_cast<void*>(&GetContext)},
|
||||||
{"OnOrientationChanged",
|
|
||||||
"(JI)V",
|
|
||||||
reinterpret_cast<void*>(&OnOrientationChanged)},
|
|
||||||
{"ProvideCameraFrame",
|
{"ProvideCameraFrame",
|
||||||
"([BIJJ)V",
|
"([BIIJJ)V",
|
||||||
reinterpret_cast<void*>(&ProvideCameraFrame)}};
|
reinterpret_cast<void*>(&ProvideCameraFrame)}};
|
||||||
if (ats.env()->RegisterNatives(g_java_capturer_class,
|
if (ats.env()->RegisterNatives(g_java_capturer_class,
|
||||||
native_methods, 3) != 0)
|
native_methods, 2) != 0)
|
||||||
assert(false);
|
assert(false);
|
||||||
} else {
|
} else {
|
||||||
if (g_jvm) {
|
if (g_jvm) {
|
||||||
@ -129,9 +107,23 @@ VideoCaptureModule* VideoCaptureImpl::Create(
|
|||||||
|
|
||||||
int32_t VideoCaptureAndroid::OnIncomingFrame(uint8_t* videoFrame,
|
int32_t VideoCaptureAndroid::OnIncomingFrame(uint8_t* videoFrame,
|
||||||
int32_t videoFrameLength,
|
int32_t videoFrameLength,
|
||||||
|
int32_t degrees,
|
||||||
int64_t captureTime) {
|
int64_t captureTime) {
|
||||||
if (!_captureStarted)
|
if (!_captureStarted)
|
||||||
return 0;
|
return 0;
|
||||||
|
VideoCaptureRotation current_rotation =
|
||||||
|
(degrees <= 45 || degrees > 315) ? kCameraRotate0 :
|
||||||
|
(degrees > 45 && degrees <= 135) ? kCameraRotate90 :
|
||||||
|
(degrees > 135 && degrees <= 225) ? kCameraRotate180 :
|
||||||
|
(degrees > 225 && degrees <= 315) ? kCameraRotate270 :
|
||||||
|
kCameraRotate0; // Impossible.
|
||||||
|
if (_rotation != current_rotation) {
|
||||||
|
LOG(LS_INFO) << "New camera rotation: " << degrees;
|
||||||
|
_rotation = current_rotation;
|
||||||
|
int32_t status = VideoCaptureImpl::SetCaptureRotation(_rotation);
|
||||||
|
if (status != 0)
|
||||||
|
return status;
|
||||||
|
}
|
||||||
return IncomingFrame(
|
return IncomingFrame(
|
||||||
videoFrame, videoFrameLength, _captureCapability, captureTime);
|
videoFrame, videoFrameLength, _captureCapability, captureTime);
|
||||||
}
|
}
|
||||||
@ -165,6 +157,7 @@ int32_t VideoCaptureAndroid::Init(const int32_t id,
|
|||||||
_jCapturer = env->NewGlobalRef(
|
_jCapturer = env->NewGlobalRef(
|
||||||
env->NewObject(g_java_capturer_class, ctor, camera_id, j_this));
|
env->NewObject(g_java_capturer_class, ctor, camera_id, j_this));
|
||||||
assert(_jCapturer);
|
assert(_jCapturer);
|
||||||
|
_rotation = kCameraRotate0;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@ class VideoCaptureAndroid : public VideoCaptureImpl {
|
|||||||
|
|
||||||
int32_t OnIncomingFrame(uint8_t* videoFrame,
|
int32_t OnIncomingFrame(uint8_t* videoFrame,
|
||||||
int32_t videoFrameLength,
|
int32_t videoFrameLength,
|
||||||
|
int32_t degrees,
|
||||||
int64_t captureTime = 0);
|
int64_t captureTime = 0);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@ -40,6 +41,7 @@ class VideoCaptureAndroid : public VideoCaptureImpl {
|
|||||||
DeviceInfoAndroid _deviceInfo;
|
DeviceInfoAndroid _deviceInfo;
|
||||||
jobject _jCapturer; // Global ref to Java VideoCaptureAndroid object.
|
jobject _jCapturer; // Global ref to Java VideoCaptureAndroid object.
|
||||||
VideoCaptureCapability _captureCapability;
|
VideoCaptureCapability _captureCapability;
|
||||||
|
VideoCaptureRotation _rotation;
|
||||||
bool _captureStarted;
|
bool _captureStarted;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -108,9 +108,14 @@ class TestVideoCaptureCallback : public VideoCaptureDataCallback {
|
|||||||
virtual void OnIncomingCapturedFrame(const int32_t id,
|
virtual void OnIncomingCapturedFrame(const int32_t id,
|
||||||
webrtc::I420VideoFrame& videoFrame) {
|
webrtc::I420VideoFrame& videoFrame) {
|
||||||
CriticalSectionScoped cs(capture_cs_.get());
|
CriticalSectionScoped cs(capture_cs_.get());
|
||||||
|
|
||||||
int height = videoFrame.height();
|
int height = videoFrame.height();
|
||||||
int width = videoFrame.width();
|
int width = videoFrame.width();
|
||||||
|
#if ANDROID
|
||||||
|
// Android camera frames may be rotated depending on test device
|
||||||
|
// orientation.
|
||||||
|
EXPECT_TRUE(height == capability_.height || height == capability_.width);
|
||||||
|
EXPECT_TRUE(width == capability_.width || width == capability_.height);
|
||||||
|
#else
|
||||||
if (rotate_frame_ == webrtc::kCameraRotate90 ||
|
if (rotate_frame_ == webrtc::kCameraRotate90 ||
|
||||||
rotate_frame_ == webrtc::kCameraRotate270) {
|
rotate_frame_ == webrtc::kCameraRotate270) {
|
||||||
EXPECT_EQ(width, capability_.height);
|
EXPECT_EQ(width, capability_.height);
|
||||||
@ -119,6 +124,7 @@ class TestVideoCaptureCallback : public VideoCaptureDataCallback {
|
|||||||
EXPECT_EQ(height, capability_.height);
|
EXPECT_EQ(height, capability_.height);
|
||||||
EXPECT_EQ(width, capability_.width);
|
EXPECT_EQ(width, capability_.width);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
// RenderTimstamp should be the time now.
|
// RenderTimstamp should be the time now.
|
||||||
EXPECT_TRUE(
|
EXPECT_TRUE(
|
||||||
videoFrame.render_time_ms() >= TickTime::MillisecondTimestamp()-30 &&
|
videoFrame.render_time_ms() >= TickTime::MillisecondTimestamp()-30 &&
|
||||||
|
Loading…
x
Reference in New Issue
Block a user