AppRTCDemo(android): support app (UI) & capture rotation.
Now app UI rotates as the device orientation changes, and the captured stream tries to maintain real-world-up, matching Chrome/Android and Hangouts/Android behavior. BUG=2432 R=glaznev@webrtc.org, henrike@webrtc.org, wu@webrtc.org Review URL: https://webrtc-codereview.appspot.com/15689005 git-svn-id: http://webrtc.googlecode.com/svn/trunk@6354 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
@@ -1897,7 +1897,7 @@ JOW(jboolean, PeerConnectionFactory_initializeAndroidGlobals)(
|
|||||||
CHECK(g_jvm, "JNI_OnLoad failed to run?");
|
CHECK(g_jvm, "JNI_OnLoad failed to run?");
|
||||||
bool failure = false;
|
bool failure = false;
|
||||||
if (initialize_video)
|
if (initialize_video)
|
||||||
failure |= webrtc::VideoEngine::SetAndroidObjects(g_jvm);
|
failure |= webrtc::VideoEngine::SetAndroidObjects(g_jvm, context);
|
||||||
if (initialize_audio)
|
if (initialize_audio)
|
||||||
failure |= webrtc::VoiceEngine::SetAndroidObjects(g_jvm, jni, context);
|
failure |= webrtc::VoiceEngine::SetAndroidObjects(g_jvm, jni, context);
|
||||||
return !failure;
|
return !failure;
|
||||||
|
|||||||
@@ -21,7 +21,8 @@
|
|||||||
android:allowBackup="false">
|
android:allowBackup="false">
|
||||||
<activity android:name="AppRTCDemoActivity"
|
<activity android:name="AppRTCDemoActivity"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:screenOrientation="landscape"
|
android:screenOrientation="fullUser"
|
||||||
|
android:configChanges="orientation|screenSize"
|
||||||
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen">
|
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import android.app.Activity;
|
|||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.res.Configuration;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.Point;
|
import android.graphics.Point;
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
@@ -226,6 +227,13 @@ public class AppRTCDemoActivity extends Activity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConfigurationChanged (Configuration newConfig) {
|
||||||
|
Point displaySize = new Point();
|
||||||
|
getWindowManager().getDefaultDisplay().getSize(displaySize);
|
||||||
|
vsv.updateDisplaySize(displaySize);
|
||||||
|
super.onConfigurationChanged(newConfig);
|
||||||
|
}
|
||||||
|
|
||||||
// Just for fun (and to regression-test bug 2302) make sure that DataChannels
|
// Just for fun (and to regression-test bug 2302) make sure that DataChannels
|
||||||
// can be created, queried, and disposed.
|
// can be created, queried, and disposed.
|
||||||
|
|||||||
@@ -84,11 +84,15 @@ public class VideoStreamsView
|
|||||||
setRenderMode(RENDERMODE_WHEN_DIRTY);
|
setRenderMode(RENDERMODE_WHEN_DIRTY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void updateDisplaySize(Point screenDimensions) {
|
||||||
|
this.screenDimensions = screenDimensions;
|
||||||
|
}
|
||||||
|
|
||||||
/** Queue |frame| to be uploaded. */
|
/** Queue |frame| to be uploaded. */
|
||||||
public void queueFrame(final Endpoint stream, I420Frame frame) {
|
public void queueFrame(final Endpoint stream, I420Frame frame) {
|
||||||
// Paying for the copy of the YUV data here allows CSC and painting time
|
// Paying for the copy of the YUV data here allows CSC and painting time
|
||||||
// to get spent on the render thread instead of the UI thread.
|
// to get spent on the render thread instead of the UI thread.
|
||||||
abortUnless(framePool.validateDimensions(frame), "Frame too large!");
|
abortUnless(FramePool.validateDimensions(frame), "Frame too large!");
|
||||||
final I420Frame frameCopy = framePool.takeFrame(frame).copyFrom(frame);
|
final I420Frame frameCopy = framePool.takeFrame(frame).copyFrom(frame);
|
||||||
boolean needToScheduleRender;
|
boolean needToScheduleRender;
|
||||||
synchronized (framesToRender) {
|
synchronized (framesToRender) {
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ JOWW(void, NativeWebRtcContextRegistry_register)(
|
|||||||
jobject context) {
|
jobject context) {
|
||||||
webrtc_examples::SetVoeDeviceObjects(g_vm);
|
webrtc_examples::SetVoeDeviceObjects(g_vm);
|
||||||
webrtc_examples::SetVieDeviceObjects(g_vm);
|
webrtc_examples::SetVieDeviceObjects(g_vm);
|
||||||
CHECK(webrtc::VideoEngine::SetAndroidObjects(g_vm) == 0,
|
CHECK(webrtc::VideoEngine::SetAndroidObjects(g_vm, context) == 0,
|
||||||
"Failed to register android objects to video engine");
|
"Failed to register android objects to video engine");
|
||||||
CHECK(webrtc::VoiceEngine::SetAndroidObjects(g_vm, jni, context) == 0,
|
CHECK(webrtc::VoiceEngine::SetAndroidObjects(g_vm, jni, context) == 0,
|
||||||
"Failed to register android objects to voice engine");
|
"Failed to register android objects to voice engine");
|
||||||
@@ -47,7 +47,7 @@ JOWW(void, NativeWebRtcContextRegistry_register)(
|
|||||||
JOWW(void, NativeWebRtcContextRegistry_unRegister)(
|
JOWW(void, NativeWebRtcContextRegistry_unRegister)(
|
||||||
JNIEnv* jni,
|
JNIEnv* jni,
|
||||||
jclass) {
|
jclass) {
|
||||||
CHECK(webrtc::VideoEngine::SetAndroidObjects(NULL) == 0,
|
CHECK(webrtc::VideoEngine::SetAndroidObjects(NULL, NULL) == 0,
|
||||||
"Failed to unregister android objects from video engine");
|
"Failed to unregister android objects from video engine");
|
||||||
CHECK(webrtc::VoiceEngine::SetAndroidObjects(NULL, NULL, NULL) == 0,
|
CHECK(webrtc::VoiceEngine::SetAndroidObjects(NULL, NULL, NULL) == 0,
|
||||||
"Failed to unregister android objects from voice engine");
|
"Failed to unregister android objects from voice engine");
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import java.util.Locale;
|
|||||||
import java.util.concurrent.Exchanger;
|
import java.util.concurrent.Exchanger;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.graphics.ImageFormat;
|
import android.graphics.ImageFormat;
|
||||||
import android.graphics.PixelFormat;
|
import android.graphics.PixelFormat;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
@@ -25,6 +26,7 @@ import android.hardware.Camera;
|
|||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.view.OrientationEventListener;
|
||||||
import android.view.SurfaceHolder.Callback;
|
import android.view.SurfaceHolder.Callback;
|
||||||
import android.view.SurfaceHolder;
|
import android.view.SurfaceHolder;
|
||||||
|
|
||||||
@@ -46,6 +48,7 @@ public class VideoCaptureAndroid implements PreviewCallback, Callback {
|
|||||||
private Handler cameraThreadHandler;
|
private Handler cameraThreadHandler;
|
||||||
private final int id;
|
private final int id;
|
||||||
private final Camera.CameraInfo info;
|
private final Camera.CameraInfo info;
|
||||||
|
private final OrientationEventListener orientationListener;
|
||||||
private final long native_capturer; // |VideoCaptureAndroid*| in C++.
|
private final long native_capturer; // |VideoCaptureAndroid*| in C++.
|
||||||
private SurfaceTexture dummySurfaceTexture;
|
private SurfaceTexture dummySurfaceTexture;
|
||||||
// Arbitrary queue depth. Higher number means more memory allocated & held,
|
// Arbitrary queue depth. Higher number means more memory allocated & held,
|
||||||
@@ -66,8 +69,30 @@ public class VideoCaptureAndroid implements PreviewCallback, Callback {
|
|||||||
this.native_capturer = native_capturer;
|
this.native_capturer = native_capturer;
|
||||||
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 (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.
|
||||||
|
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;
|
||||||
public CameraThread(Exchanger<Handler> handlerExchanger) {
|
public CameraThread(Exchanger<Handler> handlerExchanger) {
|
||||||
@@ -96,6 +121,7 @@ public class VideoCaptureAndroid implements PreviewCallback, Callback {
|
|||||||
cameraThread = new CameraThread(handlerExchanger);
|
cameraThread = new CameraThread(handlerExchanger);
|
||||||
cameraThread.start();
|
cameraThread.start();
|
||||||
cameraThreadHandler = exchange(handlerExchanger, null);
|
cameraThreadHandler = exchange(handlerExchanger, null);
|
||||||
|
orientationListener.enable();
|
||||||
|
|
||||||
final Exchanger<Boolean> result = new Exchanger<Boolean>();
|
final Exchanger<Boolean> result = new Exchanger<Boolean>();
|
||||||
cameraThreadHandler.post(new Runnable() {
|
cameraThreadHandler.post(new Runnable() {
|
||||||
@@ -186,6 +212,7 @@ public class VideoCaptureAndroid implements PreviewCallback, Callback {
|
|||||||
}
|
}
|
||||||
cameraThreadHandler = null;
|
cameraThreadHandler = null;
|
||||||
cameraThread = null;
|
cameraThread = null;
|
||||||
|
orientationListener.disable();
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,9 +19,16 @@
|
|||||||
|
|
||||||
static JavaVM* g_jvm = NULL;
|
static JavaVM* g_jvm = NULL;
|
||||||
static jclass g_java_capturer_class = NULL; // VideoCaptureAndroid.class.
|
static jclass g_java_capturer_class = NULL; // VideoCaptureAndroid.class.
|
||||||
|
static jobject g_context = NULL; // Owned android.content.Context.
|
||||||
|
|
||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
|
|
||||||
|
// Called by Java to get the global application context.
|
||||||
|
jobject JNICALL GetContext(JNIEnv* env, jclass) {
|
||||||
|
assert(g_context);
|
||||||
|
return g_context;
|
||||||
|
}
|
||||||
|
|
||||||
// Called by Java when the camera has a new frame to deliver.
|
// Called by Java when the camera has a new frame to deliver.
|
||||||
void JNICALL ProvideCameraFrame(
|
void JNICALL ProvideCameraFrame(
|
||||||
JNIEnv* env,
|
JNIEnv* env,
|
||||||
@@ -38,11 +45,31 @@ void JNICALL ProvideCameraFrame(
|
|||||||
env->ReleaseByteArrayElements(javaCameraFrame, cameraFrame, JNI_ABORT);
|
env->ReleaseByteArrayElements(javaCameraFrame, cameraFrame, JNI_ABORT);
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t SetCaptureAndroidVM(JavaVM* javaVM) {
|
// 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);
|
||||||
|
assert(status == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t SetCaptureAndroidVM(JavaVM* javaVM, jobject context) {
|
||||||
if (javaVM) {
|
if (javaVM) {
|
||||||
assert(!g_jvm);
|
assert(!g_jvm);
|
||||||
g_jvm = javaVM;
|
g_jvm = javaVM;
|
||||||
AttachThreadScoped ats(g_jvm);
|
AttachThreadScoped ats(g_jvm);
|
||||||
|
g_context = ats.env()->NewGlobalRef(context);
|
||||||
|
|
||||||
videocapturemodule::DeviceInfoAndroid::Initialize(ats.env());
|
videocapturemodule::DeviceInfoAndroid::Initialize(ats.env());
|
||||||
|
|
||||||
@@ -53,12 +80,18 @@ int32_t SetCaptureAndroidVM(JavaVM* javaVM) {
|
|||||||
reinterpret_cast<jclass>(ats.env()->NewGlobalRef(j_capture_class));
|
reinterpret_cast<jclass>(ats.env()->NewGlobalRef(j_capture_class));
|
||||||
assert(g_java_capturer_class);
|
assert(g_java_capturer_class);
|
||||||
|
|
||||||
JNINativeMethod native_method = {
|
JNINativeMethod native_methods[] = {
|
||||||
"ProvideCameraFrame", "([BIJ)V",
|
{"GetContext",
|
||||||
reinterpret_cast<void*>(&ProvideCameraFrame)
|
"()Landroid/content/Context;",
|
||||||
};
|
reinterpret_cast<void*>(&GetContext)},
|
||||||
|
{"OnOrientationChanged",
|
||||||
|
"(JI)V",
|
||||||
|
reinterpret_cast<void*>(&OnOrientationChanged)},
|
||||||
|
{"ProvideCameraFrame",
|
||||||
|
"([BIJ)V",
|
||||||
|
reinterpret_cast<void*>(&ProvideCameraFrame)}};
|
||||||
if (ats.env()->RegisterNatives(g_java_capturer_class,
|
if (ats.env()->RegisterNatives(g_java_capturer_class,
|
||||||
&native_method, 1) != 0)
|
native_methods, 3) != 0)
|
||||||
assert(false);
|
assert(false);
|
||||||
} else {
|
} else {
|
||||||
if (g_jvm) {
|
if (g_jvm) {
|
||||||
@@ -66,6 +99,8 @@ int32_t SetCaptureAndroidVM(JavaVM* javaVM) {
|
|||||||
ats.env()->UnregisterNatives(g_java_capturer_class);
|
ats.env()->UnregisterNatives(g_java_capturer_class);
|
||||||
ats.env()->DeleteGlobalRef(g_java_capturer_class);
|
ats.env()->DeleteGlobalRef(g_java_capturer_class);
|
||||||
g_java_capturer_class = NULL;
|
g_java_capturer_class = NULL;
|
||||||
|
ats.env()->DeleteGlobalRef(g_context);
|
||||||
|
g_context = NULL;
|
||||||
videocapturemodule::DeviceInfoAndroid::DeInitialize();
|
videocapturemodule::DeviceInfoAndroid::DeInitialize();
|
||||||
g_jvm = NULL;
|
g_jvm = NULL;
|
||||||
}
|
}
|
||||||
@@ -198,8 +233,9 @@ int32_t VideoCaptureAndroid::CaptureSettings(
|
|||||||
int32_t VideoCaptureAndroid::SetCaptureRotation(
|
int32_t VideoCaptureAndroid::SetCaptureRotation(
|
||||||
VideoCaptureRotation rotation) {
|
VideoCaptureRotation rotation) {
|
||||||
CriticalSectionScoped cs(&_apiCs);
|
CriticalSectionScoped cs(&_apiCs);
|
||||||
if (VideoCaptureImpl::SetCaptureRotation(rotation) != 0)
|
int32_t status = VideoCaptureImpl::SetCaptureRotation(rotation);
|
||||||
return 0;
|
if (status != 0)
|
||||||
|
return status;
|
||||||
|
|
||||||
AttachThreadScoped ats(g_jvm);
|
AttachThreadScoped ats(g_jvm);
|
||||||
JNIEnv* env = ats.env();
|
JNIEnv* env = ats.env();
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ void EnsureInitialized() {}
|
|||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
|
|
||||||
// Declared in webrtc/modules/video_capture/include/video_capture.h.
|
// Declared in webrtc/modules/video_capture/include/video_capture.h.
|
||||||
int32_t SetCaptureAndroidVM(JavaVM* javaVM);
|
int32_t SetCaptureAndroidVM(JavaVM* javaVM, jobject g_context);
|
||||||
|
|
||||||
namespace videocapturemodule {
|
namespace videocapturemodule {
|
||||||
|
|
||||||
@@ -44,10 +44,11 @@ static pthread_once_t g_initialize_once = PTHREAD_ONCE_INIT;
|
|||||||
|
|
||||||
void EnsureInitializedOnce() {
|
void EnsureInitializedOnce() {
|
||||||
JNIEnv* jni = ::base::android::AttachCurrentThread();
|
JNIEnv* jni = ::base::android::AttachCurrentThread();
|
||||||
|
jobject context = ::base::android::GetApplicationContext();
|
||||||
JavaVM* jvm = NULL;
|
JavaVM* jvm = NULL;
|
||||||
int status = jni->GetJavaVM(&jvm);
|
int status = jni->GetJavaVM(&jvm);
|
||||||
ASSERT(status == 0);
|
ASSERT(status == 0);
|
||||||
status = webrtc::SetCaptureAndroidVM(jvm) == 0;
|
status = webrtc::SetCaptureAndroidVM(jvm, context) == 0;
|
||||||
ASSERT(status);
|
ASSERT(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
|
|
||||||
#if defined(ANDROID)
|
#if defined(ANDROID)
|
||||||
int32_t SetCaptureAndroidVM(JavaVM* javaVM);
|
int32_t SetCaptureAndroidVM(JavaVM* javaVM, jobject context);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class VideoCaptureModule: public RefCountedModule {
|
class VideoCaptureModule: public RefCountedModule {
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ class WEBRTC_DLLEXPORT VideoEngine {
|
|||||||
|
|
||||||
#if defined(ANDROID) && !defined(WEBRTC_CHROMIUM_BUILD)
|
#if defined(ANDROID) && !defined(WEBRTC_CHROMIUM_BUILD)
|
||||||
// Android specific.
|
// Android specific.
|
||||||
static int SetAndroidObjects(JavaVM* java_vm);
|
static int SetAndroidObjects(JavaVM* java_vm, jobject context);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ int ViEAutoTestAndroid::RunAutotest(int testSelection, int subTestSelection,
|
|||||||
JavaVM* javaVM, void* env, void* context) {
|
JavaVM* javaVM, void* env, void* context) {
|
||||||
ViEAutoTest vieAutoTest(window1, window2);
|
ViEAutoTest vieAutoTest(window1, window2);
|
||||||
ViETest::Log("RunAutoTest(%d, %d)", testSelection, subTestSelection);
|
ViETest::Log("RunAutoTest(%d, %d)", testSelection, subTestSelection);
|
||||||
webrtc::VideoEngine::SetAndroidObjects(javaVM);
|
webrtc::VideoEngine::SetAndroidObjects(javaVM, context);
|
||||||
#ifndef WEBRTC_ANDROID_OPENSLES
|
#ifndef WEBRTC_ANDROID_OPENSLES
|
||||||
// voice engine calls into ADM directly
|
// voice engine calls into ADM directly
|
||||||
webrtc::VoiceEngine::SetAndroidObjects(javaVM, env, context);
|
webrtc::VoiceEngine::SetAndroidObjects(javaVM, env, context);
|
||||||
|
|||||||
@@ -140,10 +140,10 @@ int VideoEngine::SetTraceCallback(TraceCallback* callback) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if defined(ANDROID) && !defined(WEBRTC_CHROMIUM_BUILD)
|
#if defined(ANDROID) && !defined(WEBRTC_CHROMIUM_BUILD)
|
||||||
int VideoEngine::SetAndroidObjects(JavaVM* javaVM) {
|
int VideoEngine::SetAndroidObjects(JavaVM* javaVM, jobject context) {
|
||||||
LOG_F(LS_INFO);
|
LOG_F(LS_INFO);
|
||||||
|
|
||||||
if (SetCaptureAndroidVM(javaVM) != 0) {
|
if (SetCaptureAndroidVM(javaVM, context) != 0) {
|
||||||
LOG(LS_ERROR) << "Could not set capture Android VM";
|
LOG(LS_ERROR) << "Could not set capture Android VM";
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user