Rework video capture. Fix high/low resolution switch.

This commit is contained in:
Guillaume Beraudo 2011-06-20 13:47:46 +02:00
parent afb615ac9d
commit 228982d53f
9 changed files with 158 additions and 83 deletions

View file

@ -120,6 +120,6 @@ public class BandwidthManager {
} }
public VideoSize getMaximumVideoSize() { public VideoSize getMaximumVideoSize() {
return maximumVideoSize(currentProfile, AndroidCameraRecordManager.getInstance().outputIsPortrait()); return maximumVideoSize(currentProfile, AndroidCameraRecordManager.getInstance().isOutputPortraitDependingOnCameraAndPhoneOrientations());
} }
} }

View file

@ -121,6 +121,7 @@ class CallManager {
} }
/** /**
* Change the preferred video size used by linphone core. (impact landscape/portrait buffer).
* Update current call, without reinvite. * Update current call, without reinvite.
* The camera will be restarted when mediastreamer chain is recreated and setParameters is called. * The camera will be restarted when mediastreamer chain is recreated and setParameters is called.
*/ */

View file

@ -85,7 +85,6 @@ import android.preference.PreferenceManager;
import android.provider.Settings; import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException; import android.provider.Settings.SettingNotFoundException;
import android.util.Log; import android.util.Log;
import android.view.OrientationEventListener;
/** /**
* *
@ -110,7 +109,6 @@ public final class LinphoneManager implements LinphoneCoreListener {
private SharedPreferences mPref; private SharedPreferences mPref;
private Resources mR; private Resources mR;
private LinphoneCore mLc; private LinphoneCore mLc;
private int mPhoneOrientation;
private static Transports initialTransports; private static Transports initialTransports;
private static LinphonePreferenceManager lpm; private static LinphonePreferenceManager lpm;
@ -130,19 +128,7 @@ public final class LinphoneManager implements LinphoneCoreListener {
mPowerManager = (PowerManager) c.getSystemService(Context.POWER_SERVICE); mPowerManager = (PowerManager) c.getSystemService(Context.POWER_SERVICE);
mR = c.getResources(); mR = c.getResources();
// Register a sensor to track phoneOrientation for placing new calls. AndroidCameraRecordManager.getInstance().startOrientationSensor(c.getApplicationContext());
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();
} }
public static final String TAG=Version.TAG; public static final String TAG=Version.TAG;
@ -268,7 +254,6 @@ public final class LinphoneManager implements LinphoneCoreListener {
public void resetCameraFromPreferences() { public void resetCameraFromPreferences() {
boolean useFrontCam = mPref.getBoolean(mR.getString(R.string.pref_video_use_front_camera_key), false); boolean useFrontCam = mPref.getBoolean(mR.getString(R.string.pref_video_use_front_camera_key), false);
AndroidCameraRecordManager.getInstance().setUseFrontCamera(useFrontCam); AndroidCameraRecordManager.getInstance().setUseFrontCamera(useFrontCam);
AndroidCameraRecordManager.getInstance().setPhoneOrientation(mPhoneOrientation);
} }
public static interface AddressType { public static interface AddressType {
@ -307,7 +292,6 @@ public final class LinphoneManager implements LinphoneCoreListener {
public void changeResolution() { public void changeResolution() {
BandwidthManager manager = BandwidthManager.getInstance(); BandwidthManager manager = BandwidthManager.getInstance();
manager.setUserRestriction(!manager.isUserRestriction()); manager.setUserRestriction(!manager.isUserRestriction());
sendStaticImage(AndroidCameraRecordManager.getInstance().isMuted());
} }
public void terminateCall() { 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. * Camera will be restarted when mediastreamer chain is recreated and setParameters is called.
*/ */
public void switchCamera() { public void switchCamera() {
AndroidCameraRecordManager rm = AndroidCameraRecordManager.getInstance(); AndroidCameraRecordManager.getInstance().stopVideoRecording();
rm.stopVideoRecording(); AndroidCameraRecordManager.getInstance().toggleUseFrontCamera();
rm.toggleUseFrontCamera();
CallManager.getInstance().updateCall(); CallManager.getInstance().updateCall();
} }

View file

@ -19,6 +19,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
package org.linphone; package org.linphone;
import java.io.IOException; import java.io.IOException;
import java.util.List;
import org.linphone.LinphoneManager.LinphoneServiceListener; import org.linphone.LinphoneManager.LinphoneServiceListener;
import org.linphone.LinphoneManager.NewOutgoingCallUiListener; 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.GlobalState;
import org.linphone.core.LinphoneCore.RegistrationState; import org.linphone.core.LinphoneCore.RegistrationState;
import android.app.ActivityManager;
import android.app.Notification; import android.app.Notification;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
@ -234,9 +236,11 @@ public final class LinphoneService extends Service implements LinphoneServiceLis
.setClass(this, LinphoneActivity.class) .setClass(this, LinphoneActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
} else if (state == LinphoneCall.State.StreamsRunning) { } else if (state == LinphoneCall.State.StreamsRunning) {
if (LinphoneActivity.isInstanciated() if (!VideoCallActivity.launched && LinphoneActivity.isInstanciated()
&& getResources().getBoolean(R.bool.use_video_activity) && getResources().getBoolean(R.bool.use_video_activity)
&& call.getCurrentParamsCopy().getVideoEnabled()) { && 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(); LinphoneActivity.instance().startVideoActivity();
} }
} }

View file

@ -24,10 +24,12 @@ import org.linphone.core.LinphoneCore;
import org.linphone.core.Version; import org.linphone.core.Version;
import org.linphone.core.VideoSize; import org.linphone.core.VideoSize;
import org.linphone.core.video.AndroidCameraRecordManager; import org.linphone.core.video.AndroidCameraRecordManager;
import org.linphone.core.video.AndroidCameraRecordManager.OnCapturingStateChangedListener;
import android.content.Context; import android.content.Context;
import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.os.PowerManager; import android.os.PowerManager;
import android.os.PowerManager.WakeLock; import android.os.PowerManager.WakeLock;
import android.util.Log; import android.util.Log;
@ -42,7 +44,7 @@ import android.view.ViewGroup.LayoutParams;
* @author Guillaume Beraudo * @author Guillaume Beraudo
* *
*/ */
public class VideoCallActivity extends SoftVolumeActivity { public class VideoCallActivity extends SoftVolumeActivity implements OnCapturingStateChangedListener {
private SurfaceView mVideoView; private SurfaceView mVideoView;
private SurfaceView mVideoCaptureView; private SurfaceView mVideoCaptureView;
private AndroidCameraRecordManager recordManager; private AndroidCameraRecordManager recordManager;
@ -50,8 +52,8 @@ public class VideoCallActivity extends SoftVolumeActivity {
public static boolean launched = false; public static boolean launched = false;
private WakeLock mWakeLock; private WakeLock mWakeLock;
private static final int capturePreviewLargestDimension = 150; private static final int capturePreviewLargestDimension = 150;
private int previousPhoneOrientation; private Handler handler = new Handler();
private int phoneOrientation;
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
launched = true; launched = true;
@ -65,10 +67,9 @@ public class VideoCallActivity extends SoftVolumeActivity {
mVideoCaptureView = (SurfaceView) findViewById(R.id.video_capture_surface); mVideoCaptureView = (SurfaceView) findViewById(R.id.video_capture_surface);
previousPhoneOrientation = AndroidCameraRecordManager.getInstance().getPhoneOrientation();
phoneOrientation = 90 * getWindowManager().getDefaultDisplay().getOrientation();
recordManager = AndroidCameraRecordManager.getInstance(); recordManager = AndroidCameraRecordManager.getInstance();
recordManager.setSurfaceView(mVideoCaptureView, phoneOrientation); recordManager.setOnCapturingStateChanged(this);
recordManager.setSurfaceView(mVideoCaptureView);
mVideoCaptureView.setZOrderOnTop(true); mVideoCaptureView.setZOrderOnTop(true);
if (!recordManager.isMuted()) LinphoneManager.getInstance().sendStaticImage(false); if (!recordManager.isMuted()) LinphoneManager.getInstance().sendStaticImage(false);
@ -76,24 +77,26 @@ public class VideoCallActivity extends SoftVolumeActivity {
mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK|PowerManager.ON_AFTER_RELEASE,"Linphone"); mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK|PowerManager.ON_AFTER_RELEASE,"Linphone");
mWakeLock.acquire(); mWakeLock.acquire();
if (Version.sdkStrictlyBelow(8)) { fixScreenOrientationForOldDevices();
}
private void fixScreenOrientationForOldDevices() {
if (Version.sdkAboveOrEqual(Version.API08_FROYO_22)) return;
// Force to display in portrait orientation for old devices // Force to display in portrait orientation for old devices
// as they do not support surfaceView rotation // as they do not support surfaceView rotation
setRequestedOrientation(recordManager.isCameraOrientationPortrait() ? setRequestedOrientation(recordManager.isCameraOrientationPortrait() ?
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT : ActivityInfo.SCREEN_ORIENTATION_PORTRAIT :
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
resizeCapturePreview(mVideoCaptureView); resizeCapturePreview(mVideoCaptureView);
} }
@Override @Override
protected void onResume() { protected void onResume() {
// Update call if orientation changed if (Version.sdkAboveOrEqual(8) && recordManager.isOutputOrientationMismatch()) {
if (Version.sdkAboveOrEqual(8) && previousPhoneOrientation != phoneOrientation) { Log.i(tag,"Phone orientation has changed: updating call.");
CallManager.getInstance().updateCall(); CallManager.getInstance().updateCall();
resizeCapturePreview(mVideoCaptureView); // resizeCapturePreview by callback when recording started
} }
super.onResume(); super.onResume();
} }
@ -162,14 +165,11 @@ public class VideoCallActivity extends SoftVolumeActivity {
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.videocall_menu_back_to_dialer: case R.id.videocall_menu_back_to_dialer:
if (!recordManager.isMuted())
LinphoneManager.getInstance().sendStaticImage(true);
finish(); finish();
break; break;
case R.id.videocall_menu_change_resolution: case R.id.videocall_menu_change_resolution:
LinphoneManager.getInstance().changeResolution(); LinphoneManager.getInstance().changeResolution();
rewriteChangeResolutionItem(item); rewriteChangeResolutionItem(item);
resizeCapturePreview(mVideoCaptureView);
break; break;
case R.id.videocall_menu_terminate_call: case R.id.videocall_menu_terminate_call:
LinphoneManager.getInstance().terminateCall(); LinphoneManager.getInstance().terminateCall();
@ -181,7 +181,7 @@ public class VideoCallActivity extends SoftVolumeActivity {
break; break;
case R.id.videocall_menu_switch_camera: case R.id.videocall_menu_switch_camera:
LinphoneManager.getInstance().switchCamera(); LinphoneManager.getInstance().switchCamera();
resizeCapturePreview(mVideoCaptureView); fixScreenOrientationForOldDevices();
break; break;
default: default:
Log.e(LinphoneManager.TAG, "Unknown menu item ["+item+"]"); Log.e(LinphoneManager.TAG, "Unknown menu item ["+item+"]");
@ -206,4 +206,15 @@ public class VideoCallActivity extends SoftVolumeActivity {
super.onPause(); super.onPause();
} }
public void captureStarted() {
handler.post(new Runnable() {
public void run() {
resizeCapturePreview(mVideoCaptureView);
}
});
}
public void captureStopped() {
}
} }

View file

@ -74,9 +74,9 @@ class AndroidCameraConf5 implements AndroidCameraConf {
// | | // | |
// | Phone | // | Phone |
// |________| // |________|
return 90; return 180;
} }
return 0; return 90;
} }

View file

@ -49,11 +49,10 @@ class AndroidCameraConf9 implements AndroidCameraConf {
public int getCameraOrientation(int cameraId) { public int getCameraOrientation(int cameraId) {
android.hardware.Camera.CameraInfo info = new android.hardware.Camera.CameraInfo(); android.hardware.Camera.CameraInfo info = new android.hardware.Camera.CameraInfo();
Camera.getCameraInfo(cameraId, info); 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, cameraId,
info.orientation, info.orientation));
(info.orientation - 90) %360)); return info.orientation;
return (info.orientation - 90) %360;
} }
public boolean isFrontCamera(int cameraId) { public boolean isFrontCamera(int cameraId) {

View file

@ -211,6 +211,7 @@ public abstract class AndroidCameraRecord implements AutoFocusCallback {
public SurfaceView surfaceView; public SurfaceView surfaceView;
public MirrorType mirror = MirrorType.NO; public MirrorType mirror = MirrorType.NO;
public int phoneOrientation;
public RecorderParams(long ptr) { public RecorderParams(long ptr) {
filterDataNativePtr = ptr; filterDataNativePtr = ptr;
@ -238,4 +239,8 @@ public abstract class AndroidCameraRecord implements AutoFocusCallback {
if (success) Log.i(tag, "Autofocus success"); if (success) Log.i(tag, "Autofocus success");
else Log.i(tag, "Autofocus failure"); else Log.i(tag, "Autofocus failure");
} }
public int getStoredPhoneOrientation() {
return params.phoneOrientation;
}
} }

View file

@ -20,11 +20,14 @@ package org.linphone.core.video;
import java.util.List; import java.util.List;
import org.linphone.LinphoneManager;
import org.linphone.core.Version; import org.linphone.core.Version;
import org.linphone.core.video.AndroidCameraRecord.RecorderParams; import org.linphone.core.video.AndroidCameraRecord.RecorderParams;
import android.content.Context;
import android.hardware.Camera.Size; import android.hardware.Camera.Size;
import android.util.Log; import android.util.Log;
import android.view.OrientationEventListener;
import android.view.SurfaceHolder; import android.view.SurfaceHolder;
import android.view.SurfaceView; import android.view.SurfaceView;
import android.view.SurfaceHolder.Callback; import android.view.SurfaceHolder.Callback;
@ -40,6 +43,8 @@ import android.view.SurfaceHolder.Callback;
public class AndroidCameraRecordManager { public class AndroidCameraRecordManager {
private static final String tag = "Linphone"; private static final String tag = "Linphone";
private static AndroidCameraRecordManager instance; private static AndroidCameraRecordManager instance;
private OrientationEventListener orientationEventListener;
private OnCapturingStateChangedListener capturingStateChangedListener;
/** /**
* @return instance * @return instance
@ -59,9 +64,7 @@ public class AndroidCameraRecordManager {
private AndroidCameraRecord recorder; private AndroidCameraRecord recorder;
private List<Size> supportedVideoSizes; private List<Size> supportedVideoSizes;
private int phoneOrientation; private int mAlwaysChangingPhoneOrientation=0;
public int getPhoneOrientation() {return phoneOrientation;}
public void setPhoneOrientation(int degrees) {this.phoneOrientation = degrees;}
// singleton // singleton
@ -112,7 +115,10 @@ public class AndroidCameraRecordManager {
public void setParametersFromFilter(long filterDataPtr, int height, int width, float fps) { public void setParametersFromFilter(long filterDataPtr, int height, int width, float fps) {
if (recorder != null) {
Log.w(tag, "Recorder should not be running");
stopVideoRecording(); stopVideoRecording();
}
RecorderParams p = new RecorderParams(filterDataPtr); RecorderParams p = new RecorderParams(filterDataPtr);
p.fps = fps; p.fps = fps;
p.width = width; p.width = width;
@ -135,8 +141,7 @@ public class AndroidCameraRecordManager {
} }
public final void setSurfaceView(final SurfaceView sv, final int phoneOrientation) { public final void setSurfaceView(final SurfaceView sv) {
this.phoneOrientation = phoneOrientation;
SurfaceHolder holder = sv.getHolder(); SurfaceHolder holder = sv.getHolder();
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
@ -188,9 +193,18 @@ public class AndroidCameraRecordManager {
} }
private synchronized void tryToStartVideoRecording() { private synchronized void tryToStartVideoRecording() {
if (orientationEventListener == null) {
throw new RuntimeException("startOrientationSensor was not called");
}
if (muted || surfaceView == null || parameters == null) return; 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; parameters.surfaceView = surfaceView;
if (Version.sdkAboveOrEqual(9)) { if (Version.sdkAboveOrEqual(9)) {
@ -204,20 +218,26 @@ public class AndroidCameraRecordManager {
} }
recorder.startPreview(); recorder.startPreview();
if (capturingStateChangedListener != null) {
capturingStateChangedListener.captureStarted();
}
} }
public synchronized void stopVideoRecording() { public synchronized void stopVideoRecording() {
if (recorder != null) { if (recorder != null) {
recorder.stopPreview(); recorder.stopPreview();
recorder = null; recorder = null;
if (capturingStateChangedListener != null) {
capturingStateChangedListener.captureStopped();
}
} }
} }
// FIXME select right camera
/** /**
* Eventually null if API < 5. * FIXME select right camera
*
*/ */
public List<Size> supportedVideoSizes() { public List<Size> supportedVideoSizes() {
if (supportedVideoSizes != null) { if (supportedVideoSizes != null) {
@ -229,8 +249,6 @@ public class AndroidCameraRecordManager {
if (supportedVideoSizes != null) return supportedVideoSizes; if (supportedVideoSizes != null) return supportedVideoSizes;
} }
// eventually null
return supportedVideoSizes; return supportedVideoSizes;
} }
@ -249,11 +267,11 @@ public class AndroidCameraRecordManager {
parameters = null; parameters = null;
} }
public boolean outputIsPortrait() { public boolean isOutputPortraitDependingOnCameraAndPhoneOrientations() {
final int rotation = bufferRotationForCorrectImageOrientation(); final int rotation = bufferRotationToCompensateCameraAndPhoneOrientations();
final boolean isPortrait = (rotation % 180) == 90; 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; return isPortrait;
} }
@ -263,15 +281,21 @@ public class AndroidCameraRecordManager {
public boolean isCameraOrientationPortrait() { public boolean isCameraOrientationPortrait() {
return (cc.getCameraOrientation(cameraId) % 180) == 90; return (cc.getCameraOrientation(cameraId) % 180) == 0;
} }
private int bufferRotationForCorrectImageOrientation() { private int bufferRotationToCompensateCameraAndPhoneOrientations() {
if (Version.sdkAboveOrEqual(8)) { 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 cameraOrientation = cc.getCameraOrientation(cameraId);
final int rotation = (360 - cameraOrientation + 90 - phoneOrientation) % 360; final int rotation = (cameraOrientation + phoneOrientation) % 360;
Log.d(tag, String.format( Log.d(tag, String.format(
"Capture video buffer of cameraId=%d will need a rotation of " "Capture video buffer of cameraId=%d will need a rotation of "
+ "%d degrees: camera_orientation=%d, phone_orientation=%d", + "%d degrees: camera_orientation=%d, phone_orientation=%d",
@ -279,6 +303,54 @@ public class AndroidCameraRecordManager {
return rotation; return rotation;
} }
return 0;
/**
* 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();
}
}