From 228982d53f48fa243f8572942a3676741871a7a6 Mon Sep 17 00:00:00 2001 From: Guillaume Beraudo Date: Mon, 20 Jun 2011 13:47:46 +0200 Subject: [PATCH] Rework video capture. Fix high/low resolution switch. --- src/org/linphone/BandwidthManager.java | 2 +- src/org/linphone/CallManager.java | 1 + src/org/linphone/LinphoneManager.java | 23 +--- src/org/linphone/LinphoneService.java | 6 +- src/org/linphone/VideoCallActivity.java | 67 ++++++---- .../core/video/AndroidCameraConf5.java | 4 +- .../core/video/AndroidCameraConf9.java | 7 +- .../core/video/AndroidCameraRecord.java | 5 + .../video/AndroidCameraRecordManager.java | 126 ++++++++++++++---- 9 files changed, 158 insertions(+), 83 deletions(-) diff --git a/src/org/linphone/BandwidthManager.java b/src/org/linphone/BandwidthManager.java index 7f3e4e3f5..f313e604b 100644 --- a/src/org/linphone/BandwidthManager.java +++ b/src/org/linphone/BandwidthManager.java @@ -120,6 +120,6 @@ public class BandwidthManager { } public VideoSize getMaximumVideoSize() { - return maximumVideoSize(currentProfile, AndroidCameraRecordManager.getInstance().outputIsPortrait()); + return maximumVideoSize(currentProfile, AndroidCameraRecordManager.getInstance().isOutputPortraitDependingOnCameraAndPhoneOrientations()); } } diff --git a/src/org/linphone/CallManager.java b/src/org/linphone/CallManager.java index a1be11c64..407df253e 100644 --- a/src/org/linphone/CallManager.java +++ b/src/org/linphone/CallManager.java @@ -121,6 +121,7 @@ class CallManager { } /** + * Change the preferred video size used by linphone core. (impact landscape/portrait buffer). * Update current call, without reinvite. * The camera will be restarted when mediastreamer chain is recreated and setParameters is called. */ diff --git a/src/org/linphone/LinphoneManager.java b/src/org/linphone/LinphoneManager.java index 6908eb09b..7017f31ee 100644 --- a/src/org/linphone/LinphoneManager.java +++ b/src/org/linphone/LinphoneManager.java @@ -85,7 +85,6 @@ import android.preference.PreferenceManager; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.util.Log; -import android.view.OrientationEventListener; /** * @@ -110,7 +109,6 @@ public final class LinphoneManager implements LinphoneCoreListener { private SharedPreferences mPref; private Resources mR; private LinphoneCore mLc; - private int mPhoneOrientation; private static Transports initialTransports; private static LinphonePreferenceManager lpm; @@ -130,19 +128,7 @@ public final class LinphoneManager implements LinphoneCoreListener { mPowerManager = (PowerManager) c.getSystemService(Context.POWER_SERVICE); mR = c.getResources(); - // Register a sensor to track phoneOrientation for placing new calls. - new OrientationEventListener(c) { - @Override - public void onOrientationChanged(int o) { - if (o == OrientationEventListener.ORIENTATION_UNKNOWN) return; - - o = 90 * (o / 90); - - if (Math.abs(mPhoneOrientation - o) < 90) return; - - mPhoneOrientation = o; - } - }.enable(); + AndroidCameraRecordManager.getInstance().startOrientationSensor(c.getApplicationContext()); } public static final String TAG=Version.TAG; @@ -268,7 +254,6 @@ public final class LinphoneManager implements LinphoneCoreListener { public void resetCameraFromPreferences() { boolean useFrontCam = mPref.getBoolean(mR.getString(R.string.pref_video_use_front_camera_key), false); AndroidCameraRecordManager.getInstance().setUseFrontCamera(useFrontCam); - AndroidCameraRecordManager.getInstance().setPhoneOrientation(mPhoneOrientation); } public static interface AddressType { @@ -307,7 +292,6 @@ public final class LinphoneManager implements LinphoneCoreListener { public void changeResolution() { BandwidthManager manager = BandwidthManager.getInstance(); manager.setUserRestriction(!manager.isUserRestriction()); - sendStaticImage(AndroidCameraRecordManager.getInstance().isMuted()); } public void terminateCall() { @@ -320,9 +304,8 @@ public final class LinphoneManager implements LinphoneCoreListener { * Camera will be restarted when mediastreamer chain is recreated and setParameters is called. */ public void switchCamera() { - AndroidCameraRecordManager rm = AndroidCameraRecordManager.getInstance(); - rm.stopVideoRecording(); - rm.toggleUseFrontCamera(); + AndroidCameraRecordManager.getInstance().stopVideoRecording(); + AndroidCameraRecordManager.getInstance().toggleUseFrontCamera(); CallManager.getInstance().updateCall(); } diff --git a/src/org/linphone/LinphoneService.java b/src/org/linphone/LinphoneService.java index cbb6f539b..7876cc291 100644 --- a/src/org/linphone/LinphoneService.java +++ b/src/org/linphone/LinphoneService.java @@ -19,6 +19,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. package org.linphone; import java.io.IOException; +import java.util.List; import org.linphone.LinphoneManager.LinphoneServiceListener; import org.linphone.LinphoneManager.NewOutgoingCallUiListener; @@ -28,6 +29,7 @@ import org.linphone.core.LinphoneCall.State; import org.linphone.core.LinphoneCore.GlobalState; import org.linphone.core.LinphoneCore.RegistrationState; +import android.app.ActivityManager; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; @@ -234,9 +236,11 @@ public final class LinphoneService extends Service implements LinphoneServiceLis .setClass(this, LinphoneActivity.class) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); } else if (state == LinphoneCall.State.StreamsRunning) { - if (LinphoneActivity.isInstanciated() + if (!VideoCallActivity.launched && LinphoneActivity.isInstanciated() && getResources().getBoolean(R.bool.use_video_activity) && call.getCurrentParamsCopy().getVideoEnabled()) { + // Do not call if video activity already launched as it would cause a pause() of the launched one + // and a race condition with capture surfaceview leading to a crash LinphoneActivity.instance().startVideoActivity(); } } diff --git a/src/org/linphone/VideoCallActivity.java b/src/org/linphone/VideoCallActivity.java index 9eff77c12..8d9e0dda2 100644 --- a/src/org/linphone/VideoCallActivity.java +++ b/src/org/linphone/VideoCallActivity.java @@ -15,7 +15,7 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ + */ package org.linphone; @@ -24,10 +24,12 @@ import org.linphone.core.LinphoneCore; import org.linphone.core.Version; import org.linphone.core.VideoSize; import org.linphone.core.video.AndroidCameraRecordManager; +import org.linphone.core.video.AndroidCameraRecordManager.OnCapturingStateChangedListener; import android.content.Context; import android.content.pm.ActivityInfo; import android.os.Bundle; +import android.os.Handler; import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.util.Log; @@ -42,7 +44,7 @@ import android.view.ViewGroup.LayoutParams; * @author Guillaume Beraudo * */ -public class VideoCallActivity extends SoftVolumeActivity { +public class VideoCallActivity extends SoftVolumeActivity implements OnCapturingStateChangedListener { private SurfaceView mVideoView; private SurfaceView mVideoCaptureView; private AndroidCameraRecordManager recordManager; @@ -50,8 +52,8 @@ public class VideoCallActivity extends SoftVolumeActivity { public static boolean launched = false; private WakeLock mWakeLock; private static final int capturePreviewLargestDimension = 150; - private int previousPhoneOrientation; - private int phoneOrientation; + private Handler handler = new Handler(); + public void onCreate(Bundle savedInstanceState) { launched = true; @@ -65,35 +67,36 @@ public class VideoCallActivity extends SoftVolumeActivity { mVideoCaptureView = (SurfaceView) findViewById(R.id.video_capture_surface); - previousPhoneOrientation = AndroidCameraRecordManager.getInstance().getPhoneOrientation(); - phoneOrientation = 90 * getWindowManager().getDefaultDisplay().getOrientation(); recordManager = AndroidCameraRecordManager.getInstance(); - recordManager.setSurfaceView(mVideoCaptureView, phoneOrientation); + recordManager.setOnCapturingStateChanged(this); + recordManager.setSurfaceView(mVideoCaptureView); mVideoCaptureView.setZOrderOnTop(true); - + if (!recordManager.isMuted()) LinphoneManager.getInstance().sendStaticImage(false); PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE); mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK|PowerManager.ON_AFTER_RELEASE,"Linphone"); mWakeLock.acquire(); - - if (Version.sdkStrictlyBelow(8)) { - // Force to display in portrait orientation for old devices - // as they do not support surfaceView rotation - setRequestedOrientation(recordManager.isCameraOrientationPortrait() ? - ActivityInfo.SCREEN_ORIENTATION_PORTRAIT : - ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); - } + fixScreenOrientationForOldDevices(); + } + + private void fixScreenOrientationForOldDevices() { + if (Version.sdkAboveOrEqual(Version.API08_FROYO_22)) return; + + // Force to display in portrait orientation for old devices + // as they do not support surfaceView rotation + setRequestedOrientation(recordManager.isCameraOrientationPortrait() ? + ActivityInfo.SCREEN_ORIENTATION_PORTRAIT : + ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); resizeCapturePreview(mVideoCaptureView); } - @Override protected void onResume() { - // Update call if orientation changed - if (Version.sdkAboveOrEqual(8) && previousPhoneOrientation != phoneOrientation) { + if (Version.sdkAboveOrEqual(8) && recordManager.isOutputOrientationMismatch()) { + Log.i(tag,"Phone orientation has changed: updating call."); CallManager.getInstance().updateCall(); - resizeCapturePreview(mVideoCaptureView); + // resizeCapturePreview by callback when recording started } super.onResume(); } @@ -122,12 +125,12 @@ public class VideoCallActivity extends SoftVolumeActivity { // Inflate the currently selected menu XML resource. MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.videocall_activity_menu, menu); - + rewriteToggleCameraItem(menu.findItem(R.id.videocall_menu_toggle_camera)); rewriteChangeResolutionItem(menu.findItem(R.id.videocall_menu_change_resolution)); - + if (!recordManager.hasSeveralCameras()) { - menu.findItem(R.id.videocall_menu_switch_camera).setVisible(false); + menu.findItem(R.id.videocall_menu_switch_camera).setVisible(false); } return true; } @@ -162,14 +165,11 @@ public class VideoCallActivity extends SoftVolumeActivity { public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.videocall_menu_back_to_dialer: - if (!recordManager.isMuted()) - LinphoneManager.getInstance().sendStaticImage(true); finish(); break; case R.id.videocall_menu_change_resolution: LinphoneManager.getInstance().changeResolution(); rewriteChangeResolutionItem(item); - resizeCapturePreview(mVideoCaptureView); break; case R.id.videocall_menu_terminate_call: LinphoneManager.getInstance().terminateCall(); @@ -181,7 +181,7 @@ public class VideoCallActivity extends SoftVolumeActivity { break; case R.id.videocall_menu_switch_camera: LinphoneManager.getInstance().switchCamera(); - resizeCapturePreview(mVideoCaptureView); + fixScreenOrientationForOldDevices(); break; default: Log.e(LinphoneManager.TAG, "Unknown menu item ["+item+"]"); @@ -205,5 +205,16 @@ public class VideoCallActivity extends SoftVolumeActivity { if (mWakeLock.isHeld()) mWakeLock.release(); super.onPause(); } - + + public void captureStarted() { + handler.post(new Runnable() { + public void run() { + resizeCapturePreview(mVideoCaptureView); + } + }); + } + + public void captureStopped() { + } + } diff --git a/src/org/linphone/core/video/AndroidCameraConf5.java b/src/org/linphone/core/video/AndroidCameraConf5.java index 8f9bab996..3deda1c69 100644 --- a/src/org/linphone/core/video/AndroidCameraConf5.java +++ b/src/org/linphone/core/video/AndroidCameraConf5.java @@ -74,9 +74,9 @@ class AndroidCameraConf5 implements AndroidCameraConf { // | | // | Phone | // |________| - return 90; + return 180; } - return 0; + return 90; } diff --git a/src/org/linphone/core/video/AndroidCameraConf9.java b/src/org/linphone/core/video/AndroidCameraConf9.java index 6b6f8a1ba..b32c80303 100644 --- a/src/org/linphone/core/video/AndroidCameraConf9.java +++ b/src/org/linphone/core/video/AndroidCameraConf9.java @@ -49,11 +49,10 @@ class AndroidCameraConf9 implements AndroidCameraConf { public int getCameraOrientation(int cameraId) { android.hardware.Camera.CameraInfo info = new android.hardware.Camera.CameraInfo(); Camera.getCameraInfo(cameraId, info); - Log.d(Version.TAG, String.format("Camera info for %d: orientation=%d returned=%d ", + Log.d(Version.TAG, String.format("Camera info for %d: orientation=%d", cameraId, - info.orientation, - (info.orientation - 90) %360)); - return (info.orientation - 90) %360; + info.orientation)); + return info.orientation; } public boolean isFrontCamera(int cameraId) { diff --git a/src/org/linphone/core/video/AndroidCameraRecord.java b/src/org/linphone/core/video/AndroidCameraRecord.java index ae477302e..1fa12b86f 100644 --- a/src/org/linphone/core/video/AndroidCameraRecord.java +++ b/src/org/linphone/core/video/AndroidCameraRecord.java @@ -211,6 +211,7 @@ public abstract class AndroidCameraRecord implements AutoFocusCallback { public SurfaceView surfaceView; public MirrorType mirror = MirrorType.NO; + public int phoneOrientation; public RecorderParams(long ptr) { filterDataNativePtr = ptr; @@ -238,4 +239,8 @@ public abstract class AndroidCameraRecord implements AutoFocusCallback { if (success) Log.i(tag, "Autofocus success"); else Log.i(tag, "Autofocus failure"); } + + public int getStoredPhoneOrientation() { + return params.phoneOrientation; + } } diff --git a/src/org/linphone/core/video/AndroidCameraRecordManager.java b/src/org/linphone/core/video/AndroidCameraRecordManager.java index a78550a42..f30f23e96 100644 --- a/src/org/linphone/core/video/AndroidCameraRecordManager.java +++ b/src/org/linphone/core/video/AndroidCameraRecordManager.java @@ -20,11 +20,14 @@ package org.linphone.core.video; import java.util.List; +import org.linphone.LinphoneManager; import org.linphone.core.Version; import org.linphone.core.video.AndroidCameraRecord.RecorderParams; +import android.content.Context; import android.hardware.Camera.Size; import android.util.Log; +import android.view.OrientationEventListener; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.SurfaceHolder.Callback; @@ -40,6 +43,8 @@ import android.view.SurfaceHolder.Callback; public class AndroidCameraRecordManager { private static final String tag = "Linphone"; private static AndroidCameraRecordManager instance; + private OrientationEventListener orientationEventListener; + private OnCapturingStateChangedListener capturingStateChangedListener; /** * @return instance @@ -59,9 +64,7 @@ public class AndroidCameraRecordManager { private AndroidCameraRecord recorder; private List supportedVideoSizes; - private int phoneOrientation; - public int getPhoneOrientation() {return phoneOrientation;} - public void setPhoneOrientation(int degrees) {this.phoneOrientation = degrees;} + private int mAlwaysChangingPhoneOrientation=0; // singleton @@ -112,7 +115,10 @@ public class AndroidCameraRecordManager { public void setParametersFromFilter(long filterDataPtr, int height, int width, float fps) { - stopVideoRecording(); + if (recorder != null) { + Log.w(tag, "Recorder should not be running"); + stopVideoRecording(); + } RecorderParams p = new RecorderParams(filterDataPtr); p.fps = fps; p.width = width; @@ -135,8 +141,7 @@ public class AndroidCameraRecordManager { } - public final void setSurfaceView(final SurfaceView sv, final int phoneOrientation) { - this.phoneOrientation = phoneOrientation; + public final void setSurfaceView(final SurfaceView sv) { SurfaceHolder holder = sv.getHolder(); holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); @@ -186,11 +191,20 @@ public class AndroidCameraRecordManager { if (isRecording()) return; tryToStartVideoRecording(); } - + private synchronized void tryToStartVideoRecording() { + if (orientationEventListener == null) { + throw new RuntimeException("startOrientationSensor was not called"); + } + if (muted || surfaceView == null || parameters == null) return; - - parameters.rotation = bufferRotationForCorrectImageOrientation(); + + if (recorder != null) { + Log.e(tag, "Recorder already present"); + stopVideoRecording(); + } + + parameters.rotation = bufferRotationToCompensateCameraAndPhoneOrientations(); parameters.surfaceView = surfaceView; if (Version.sdkAboveOrEqual(9)) { @@ -204,20 +218,26 @@ public class AndroidCameraRecordManager { } recorder.startPreview(); + + if (capturingStateChangedListener != null) { + capturingStateChangedListener.captureStarted(); + } } public synchronized void stopVideoRecording() { if (recorder != null) { recorder.stopPreview(); recorder = null; + if (capturingStateChangedListener != null) { + capturingStateChangedListener.captureStopped(); + } } } - // FIXME select right camera + /** - * Eventually null if API < 5. - * + * FIXME select right camera */ public List supportedVideoSizes() { if (supportedVideoSizes != null) { @@ -229,8 +249,6 @@ public class AndroidCameraRecordManager { if (supportedVideoSizes != null) return supportedVideoSizes; } - // eventually null - return supportedVideoSizes; } @@ -249,11 +267,11 @@ public class AndroidCameraRecordManager { parameters = null; } - public boolean outputIsPortrait() { - final int rotation = bufferRotationForCorrectImageOrientation(); + public boolean isOutputPortraitDependingOnCameraAndPhoneOrientations() { + final int rotation = bufferRotationToCompensateCameraAndPhoneOrientations(); final boolean isPortrait = (rotation % 180) == 90; - Log.d(tag, "Camera sensor in portrait orientation? " + isPortrait); + Log.d(tag, "Camera sensor in " + (isPortrait? "portrait":"landscape") + " orientation."); return isPortrait; } @@ -263,22 +281,76 @@ public class AndroidCameraRecordManager { public boolean isCameraOrientationPortrait() { - return (cc.getCameraOrientation(cameraId) % 180) == 90; + return (cc.getCameraOrientation(cameraId) % 180) == 0; } - private int bufferRotationForCorrectImageOrientation() { - if (Version.sdkAboveOrEqual(8)) { - final int cameraOrientation = cc.getCameraOrientation(cameraId); - final int rotation = (360 - cameraOrientation + 90 - phoneOrientation) % 360; - Log.d(tag, String.format( + private int bufferRotationToCompensateCameraAndPhoneOrientations() { + if (Version.sdkStrictlyBelow(Version.API08_FROYO_22)) { + // Don't perform any rotation + // Phone screen should use fitting orientation + return 0; + } + + final int phoneOrientation = mAlwaysChangingPhoneOrientation; + final int cameraOrientation = cc.getCameraOrientation(cameraId); + final int rotation = (cameraOrientation + phoneOrientation) % 360; + Log.d(tag, String.format( "Capture video buffer of cameraId=%d will need a rotation of " + "%d degrees: camera_orientation=%d, phone_orientation=%d", cameraId, rotation, cameraOrientation, phoneOrientation)); - return rotation; - } - - return 0; + return rotation; } + + + /** + * Register a sensor to track phoneOrientation changes + */ + public void startOrientationSensor(Context c) { + if (orientationEventListener == null) { + orientationEventListener = new LocalOrientationEventListener(c); + orientationEventListener.enable(); + } + } + + private class LocalOrientationEventListener extends OrientationEventListener { + public LocalOrientationEventListener(Context context) { + super(context); + } + @Override + public void onOrientationChanged(final int o) { + if (o == OrientationEventListener.ORIENTATION_UNKNOWN) return; + + int degrees=270; + if (o < 45 || o >315) degrees=0; + else if (o<135) degrees=90; + else if (o<225) degrees=180; + + if (mAlwaysChangingPhoneOrientation == degrees) return; + + Log.i(tag, "Phone orientation changed to " + degrees); + mAlwaysChangingPhoneOrientation = degrees; + } + } + + /** + * @return true if linphone core configured to send a A buffer while phone orientation induces !A buffer (A=landscape or portrait) + */ + public boolean isOutputOrientationMismatch() { + final boolean currentlyPortrait = LinphoneManager.getLc().getPreferredVideoSize().isPortrait(); + final boolean shouldBePortrait = isOutputPortraitDependingOnCameraAndPhoneOrientations(); + return currentlyPortrait ^ shouldBePortrait; + } + + public void setOnCapturingStateChanged(OnCapturingStateChangedListener listener) { + this.capturingStateChangedListener=listener; + } + + public static interface OnCapturingStateChangedListener { + void captureStarted(); + void captureStopped(); + } + + }