From 25823ea4857903248d9133ead9058f87a5020da5 Mon Sep 17 00:00:00 2001 From: Guillaume Beraudo Date: Mon, 29 Nov 2010 12:08:13 +0100 Subject: [PATCH] Added camera record manager to drive Video record class. --- src/org/linphone/DialerActivity.java | 23 +-- src/org/linphone/VideoCallActivity.java | 12 +- .../linphone/core/AndroidCameraRecord.java | 175 +++++----------- .../core/AndroidCameraRecordBufferedImpl.java | 11 +- .../core/AndroidCameraRecordImpl.java | 27 ++- .../core/AndroidCameraRecordManager.java | 190 ++++++++++++++++++ .../core/tutorials/JavaCameraRecordImpl.java | 16 +- .../core/tutorials/TestVideoActivity.java | 9 +- 8 files changed, 294 insertions(+), 169 deletions(-) create mode 100644 src/org/linphone/core/AndroidCameraRecordManager.java diff --git a/src/org/linphone/DialerActivity.java b/src/org/linphone/DialerActivity.java index 86d72b50b..4603263ef 100644 --- a/src/org/linphone/DialerActivity.java +++ b/src/org/linphone/DialerActivity.java @@ -18,11 +18,9 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package org.linphone; -import java.io.IOException; - import org.linphone.component.ToggleImageButton; import org.linphone.component.ToggleImageButton.OnCheckedChangeListener; -import org.linphone.core.AndroidCameraRecord; +import org.linphone.core.AndroidCameraRecordManager; import org.linphone.core.LinphoneAddress; import org.linphone.core.LinphoneCall; import org.linphone.core.LinphoneCallParams; @@ -42,7 +40,6 @@ import android.content.Intent; import android.content.SharedPreferences; import android.media.AudioManager; import android.media.MediaPlayer; -import android.media.Ringtone; import android.media.RingtoneManager; import android.os.Build; import android.os.Bundle; @@ -172,17 +169,15 @@ public class DialerActivity extends Activity implements LinphoneCoreListener { LinphoneCore lLinphoneCore = LinphoneService.instance().getLinphoneCore(); LinphoneCall lCall = lLinphoneCore.getCurrentCall(); LinphoneCallParams params = lCall.getCurrentParamsReadOnly(); - String msg; if (params.getVideoEnabled()) { - msg = "In video call; going back to video call activity"; + // In video call; going back to video call activity startVideoView(VIDEO_VIEW_ACTIVITY); } else { - msg = "Not in video call; should go try to reinvite with video"; + // Not in video call; should go try to reinvite with video params.setVideoEnabled(true); - AndroidCameraRecord.setMuteCamera(false); + getVideoManager().setMuted(false); lLinphoneCore.updateCall(lCall, params); } - Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show(); } }); @@ -470,7 +465,8 @@ public class DialerActivity extends Activity implements LinphoneCoreListener { } } else if (state == LinphoneCall.State.CallUpdated) { if (LinphoneService.instance().getLinphoneCore().getCurrentCall().getCurrentParamsReadOnly().getVideoEnabled()) { - AndroidCameraRecord.invalidateParameters(); +// getVideoManager().invalidateParameters(); // no, when addinv video to audio call the filters are created before callupdated event is received + // so the parameters are invalidated and the record is never launched finishActivity(VIDEO_VIEW_ACTIVITY); } } @@ -549,7 +545,7 @@ public class DialerActivity extends Activity implements LinphoneCoreListener { // Privacy setting to not share the user camera by default boolean prefVideoEnable = mPref.getBoolean(getString(R.string.pref_video_enable_key), false); boolean prefAutomaticallyShareMyCamera = mPref.getBoolean(getString(R.string.pref_video_automatically_share_my_video_key), false); - AndroidCameraRecord.setMuteCamera(!(prefVideoEnable && prefAutomaticallyShareMyCamera)); + getVideoManager().setMuted(!(prefVideoEnable && prefAutomaticallyShareMyCamera)); startRinging(); } public void newOutgoingCall(String aTo) { @@ -588,7 +584,7 @@ public class DialerActivity extends Activity implements LinphoneCoreListener { boolean prefInitiateWithVideo = mPref.getBoolean(getString(R.string.pref_video_initiate_call_with_video_key), false); if (prefVideoEnable && prefInitiateWithVideo && lParams.getVideoEnabled()) { - AndroidCameraRecord.setMuteCamera(false); + getVideoManager().setMuted(false); lParams.setVideoEnabled(true); lLinphoneCore.inviteAddressWithParams(lAddress, lParams); } else { @@ -700,5 +696,8 @@ public class DialerActivity extends Activity implements LinphoneCoreListener { } } + private AndroidCameraRecordManager getVideoManager() { + return AndroidCameraRecordManager.getInstance(AndroidCameraRecordManager.CAMERA_ID_FIXME_USE_PREFERENCE); + } } diff --git a/src/org/linphone/VideoCallActivity.java b/src/org/linphone/VideoCallActivity.java index 6190366bc..2254a6597 100644 --- a/src/org/linphone/VideoCallActivity.java +++ b/src/org/linphone/VideoCallActivity.java @@ -20,7 +20,7 @@ package org.linphone; -import org.linphone.core.AndroidCameraRecord; +import org.linphone.core.AndroidCameraRecordManager; import org.linphone.core.LinphoneCore; import android.app.Activity; @@ -35,6 +35,7 @@ import android.view.SurfaceView; public class VideoCallActivity extends Activity { SurfaceView mVideoView; SurfaceView mVideoCaptureView; + AndroidCameraRecordManager recordManager; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -46,15 +47,14 @@ public class VideoCallActivity extends Activity { mVideoCaptureView = (SurfaceView) findViewById(R.id.video_capture_surface); final int rotation = getWindowManager().getDefaultDisplay().getRotation(); - AndroidCameraRecord.setOrientationCode(rotation); - - AndroidCameraRecord.setSurfaceView(mVideoCaptureView); + recordManager = AndroidCameraRecordManager.getInstance(AndroidCameraRecordManager.CAMERA_ID_FIXME_USE_PREFERENCE); + recordManager.setSurfaceView(mVideoCaptureView, rotation); mVideoCaptureView.setZOrderOnTop(true); } private void rewriteToggleCameraItem(MenuItem item) { - if (AndroidCameraRecord.getCameraMuted()) { + if (recordManager.isRecording()) { item.setTitle(getString(R.string.menu_videocall_toggle_camera_enable)); } else { item.setTitle(getString(R.string.menu_videocall_toggle_camera_disable)); @@ -117,7 +117,7 @@ public class VideoCallActivity extends Activity { finish(); break; case R.id.videocall_menu_toggle_camera: - AndroidCameraRecord.toggleMute(); + recordManager.toggleMute(); rewriteToggleCameraItem(item); break; default: diff --git a/src/org/linphone/core/AndroidCameraRecord.java b/src/org/linphone/core/AndroidCameraRecord.java index 138912f57..560f4c947 100644 --- a/src/org/linphone/core/AndroidCameraRecord.java +++ b/src/org/linphone/core/AndroidCameraRecord.java @@ -26,70 +26,36 @@ import android.hardware.Camera.ErrorCallback; import android.hardware.Camera.Parameters; import android.hardware.Camera.PreviewCallback; import android.hardware.Camera.Size; -import android.os.Build; import android.util.Log; import android.view.SurfaceHolder; import android.view.SurfaceView; -import android.view.SurfaceHolder.Callback; public abstract class AndroidCameraRecord { - public static final int ANDROID_VERSION = Integer.parseInt(Build.VERSION.SDK); - protected static Camera camera; - private static SurfaceView surfaceView; + protected Camera camera; + private RecorderParams params; - protected int fps; - protected int height; - protected int width; private PreviewCallback storedPreviewCallback; - - private static AndroidCameraRecord instance; - private static boolean previewStarted; - private static boolean parametersSet; - protected static int orientationCode; - private static boolean muted; + private boolean previewStarted; + protected int orientationCode; private static final String tag="Linphone"; - private static List supportedVideoSizes; + private List supportedVideoSizes; - public AndroidCameraRecord() { - // TODO check if another instance is loaded and kill it. - instance = this; - } - - public void setParameters(int height, int width, float fps) { - this.fps = Math.round(fps); - this.height = height; - this.width = width; - parametersSet = true; - startPreview(); + public AndroidCameraRecord(RecorderParams parameters) { + this.params = parameters; + setRotation(parameters.rotation); } - /* - * AndroidCameraRecord.setSurfaceView() should be called first, from the Activity code. - * It will start automatically - */ - private void startPreview() { - if (muted) { - Log.d(tag, "Not starting preview as camera has been muted"); - return; - } - if (surfaceView == null) { - Log.w(tag, "Surfaceview not defined; postponning video capture"); - return; - } - if (!parametersSet) { - Log.w(tag, "Parameters not set; postponning video capture"); - return; - } - + + public void startPreview() { // FIXME throws exception? if (previewStarted) { Log.w(tag, "Already started"); return; } - if (surfaceView.getVisibility() != SurfaceView.VISIBLE) { + if (params.surfaceView.getVisibility() != SurfaceView.VISIBLE) { // Illegal state Log.e(tag, "Illegal state: video capture surface view is not visible"); return; @@ -109,8 +75,9 @@ public abstract class AndroidCameraRecord { supportedVideoSizes = camera.getParameters().getSupportedPreviewSizes(); } - parameters.setPreviewSize(width, height); - parameters.setPreviewFrameRate(fps); + parameters.set("camera-id", params.cameraId); + parameters.setPreviewSize(params.width, params.height); + parameters.setPreviewFrameRate(Math.round(params.fps)); if (parameters.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_AUTO)) { Log.w(tag, "Auto Focus supported by camera device"); parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO); @@ -127,9 +94,8 @@ public abstract class AndroidCameraRecord { onSettingParameters(parameters); camera.setParameters(parameters); - - SurfaceHolder holder = surfaceView.getHolder(); + SurfaceHolder holder = params.surfaceView.getHolder(); try { camera.setPreviewDisplay(holder); } @@ -151,7 +117,7 @@ public abstract class AndroidCameraRecord { // Register callback to get capture buffer if (storedPreviewCallback != null) { - reallySetPreviewCallback(camera, storedPreviewCallback); + lowLevelSetPreviewCallback(camera, storedPreviewCallback); } @@ -171,120 +137,69 @@ public abstract class AndroidCameraRecord { */ public void onCameraStarted(Camera camera) {} - public void setOrStorePreviewCallBack(PreviewCallback cb) { + public void storePreviewCallBack(PreviewCallback cb) { if (camera == null) { Log.w(tag, "Capture camera not ready, storing callback"); this.storedPreviewCallback = cb; return; } - reallySetPreviewCallback(camera, cb); + lowLevelSetPreviewCallback(camera, cb); } - private static void stopPreview() { - camera.setPreviewCallback(null); // TODO check if used whatever the SDK version + void stopPreview() { + if (!previewStarted) return; + lowLevelSetPreviewCallback(camera, null); camera.stopPreview(); camera.release(); camera=null; previewStarted = false; } - public static final void setSurfaceView(final SurfaceView sv) { - SurfaceHolder holder = sv.getHolder(); - holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); - - holder.addCallback(new Callback() { - public void surfaceDestroyed(SurfaceHolder holder) { - AndroidCameraRecord.surfaceView = null; - - if (camera == null) { - Log.e(tag, "Video capture: illegal state: surface destroyed but camera is already null"); - return; - } - stopPreview(); - Log.w(tag, "Video capture Surface destroyed"); - } - - public void surfaceCreated(SurfaceHolder holder) { - AndroidCameraRecord.surfaceView = sv; - Log.w(tag, "Video capture surface created"); - - if (instance != null) { - instance.startPreview(); - } - - holder.isCreating(); - } - - public void surfaceChanged(SurfaceHolder holder, int format, int width, - int height) { - Log.w(tag, "Video capture surface changed"); - } - }); - } - - - public void stopCaptureCallback() { if (camera != null) { - reallySetPreviewCallback(camera, null); + lowLevelSetPreviewCallback(camera, null); } } - protected void reallySetPreviewCallback(Camera camera, PreviewCallback cb) { - camera.setPreviewCallback(cb); - } + protected abstract void lowLevelSetPreviewCallback(Camera camera, PreviewCallback cb); - public static void setOrientationCode(int orientation) { - AndroidCameraRecord.orientationCode = (4 + 1 - orientation) % 4; + public void setRotation(int rotation) { + orientationCode = (4 + 1 - rotation) % 4; } protected int getOrientationCode() { return orientationCode; } - public static void setMuteCamera(boolean m) { - if (m == muted) return; - muted = m; - if (muted && previewStarted) { - stopPreview(); - return; - } + + + public static class RecorderParams { + public float fps; + public int height; + public int width; + + final long filterDataNativePtr; + int cameraId; + int rotation; + public SurfaceView surfaceView; - if (!muted) { - instance.startPreview(); + public RecorderParams(long ptr) { + filterDataNativePtr = ptr; } - } - public static void toggleMute() { - setMuteCamera(!muted); + + + + public boolean isStarted() { + return previewStarted; } - public static List supportedVideoSizes() { - if (supportedVideoSizes != null) { - return new ArrayList(supportedVideoSizes); - } - - if (camera == null) { - camera = Camera.open(); - supportedVideoSizes = camera.getParameters().getSupportedPreviewSizes(); - camera.release(); - return supportedVideoSizes; - } - - throw new RuntimeException("Should not be there"); - } - - public static boolean getCameraMuted() { - return muted; - } - - public static void invalidateParameters() { - parametersSet = false; - stopPreview(); + public List getSupportedVideoSizes() { + return new ArrayList(supportedVideoSizes); } } diff --git a/src/org/linphone/core/AndroidCameraRecordBufferedImpl.java b/src/org/linphone/core/AndroidCameraRecordBufferedImpl.java index 886ad222e..9445b40ee 100644 --- a/src/org/linphone/core/AndroidCameraRecordBufferedImpl.java +++ b/src/org/linphone/core/AndroidCameraRecordBufferedImpl.java @@ -32,13 +32,16 @@ import android.util.Log; */ public class AndroidCameraRecordBufferedImpl extends AndroidCameraRecordImpl { - public AndroidCameraRecordBufferedImpl(long filterCtxPtr) { - super(filterCtxPtr); + + public AndroidCameraRecordBufferedImpl(RecorderParams parameters) { + super(parameters); } @Override - protected void reallySetPreviewCallback(Camera camera, PreviewCallback cb) { - Log.d("Linphone", "Setting optimized callback with buffer (Android >= 8). Remember to manage the pool of buffers!!!"); + protected void lowLevelSetPreviewCallback(Camera camera, PreviewCallback cb) { + if (cb != null) { + Log.d("Linphone", "Setting optimized callback with buffer (Android >= 8). Remember to manage the pool of buffers!!!"); + } camera.setPreviewCallbackWithBuffer(cb); } diff --git a/src/org/linphone/core/AndroidCameraRecordImpl.java b/src/org/linphone/core/AndroidCameraRecordImpl.java index db4e128de..38ca02fec 100644 --- a/src/org/linphone/core/AndroidCameraRecordImpl.java +++ b/src/org/linphone/core/AndroidCameraRecordImpl.java @@ -34,17 +34,14 @@ public class AndroidCameraRecordImpl extends AndroidCameraRecord implements Prev private long filterCtxPtr; private double timeElapsedBetweenFrames = 0; private long lastFrameTime = 0; + private final long expectedTimeBetweenFrames; - public AndroidCameraRecordImpl(long filterCtxPtr) { - super(); + public AndroidCameraRecordImpl(RecorderParams parameters) { + super(parameters); + expectedTimeBetweenFrames = 1l / Math.round(parameters.fps); + filterCtxPtr = parameters.filterDataNativePtr; - try { - this.filterCtxPtr = filterCtxPtr; - setOrStorePreviewCallBack(this); - } catch (Throwable e) { - Log.e("Linphone", "Error"); - } - + storePreviewCallBack(this); } @@ -56,6 +53,10 @@ public class AndroidCameraRecordImpl extends AndroidCameraRecord implements Prev Log.e("Linphone", "onPreviewFrame Called with null buffer"); return; } + if (filterCtxPtr == 0l) { + Log.e("Linphone", "onPreviewFrame Called with no filterCtxPtr set"); + return; + } Size s = camera.getParameters().getPreviewSize(); int expectedBuffLength = s.width * s.height * 3 /2; @@ -73,7 +74,7 @@ public class AndroidCameraRecordImpl extends AndroidCameraRecord implements Prev } double currentTimeElapsed = 0.8 * (curTime - lastFrameTime) / 1000 + 0.2 * timeElapsedBetweenFrames; - if (1 / currentTimeElapsed > fps) { + if (currentTimeElapsed < expectedTimeBetweenFrames) { // Log.d("Linphone", "Clipping frame " + Math.round(1 / currentTimeElapsed) + " > " + fps); return; } @@ -85,5 +86,11 @@ public class AndroidCameraRecordImpl extends AndroidCameraRecord implements Prev } + @Override + protected void lowLevelSetPreviewCallback(Camera camera, PreviewCallback cb) { + camera.setPreviewCallback(cb); + } + + } diff --git a/src/org/linphone/core/AndroidCameraRecordManager.java b/src/org/linphone/core/AndroidCameraRecordManager.java new file mode 100644 index 000000000..9295bf0b0 --- /dev/null +++ b/src/org/linphone/core/AndroidCameraRecordManager.java @@ -0,0 +1,190 @@ +/* +AndroidCameraRecordManager.java +Copyright (C) 2010 Belledonne Communications, Grenoble, France + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +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.core; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.linphone.core.AndroidCameraRecord.RecorderParams; + +import android.hardware.Camera; +import android.hardware.Camera.Size; +import android.os.Build; +import android.util.Log; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.SurfaceHolder.Callback; + + + +/** + * Manage the video capture; one instance per camera. + * + * @author Guillaume Beraudo + * + */ +public class AndroidCameraRecordManager { + public static final int CAMERA_ID_FIXME_USE_PREFERENCE = 0; + private static final int version = Integer.parseInt(Build.VERSION.SDK); + private static Map instances = new HashMap(); + + + // singleton + private AndroidCameraRecordManager(int cameraId) { + this.cameraId = cameraId; + } + + /** + * @param cameraId : see max_camera_id + * @return + */ + public static final synchronized AndroidCameraRecordManager getInstance(int cameraId) { + if (cameraId < 0) { + Log.e("Linphone", "Asking unmanageable camera " + cameraId); + return null; + } + + AndroidCameraRecordManager m = instances.get(cameraId); + if (m == null) { + m = new AndroidCameraRecordManager(cameraId); + instances.put(cameraId, m); + } + return m; + } + + public static final synchronized AndroidCameraRecordManager getInstance() { + return getInstance(0); + } + + private AndroidCameraRecord.RecorderParams parameters; + private SurfaceView surfaceView; + private boolean muted; + + + private AndroidCameraRecord recorder; + private final Integer cameraId; + + private List supportedVideoSizes; + private int rotation; + + + public void setParametersFromFilter(long filterDataPtr, int height, int width, float fps) { + RecorderParams p = new RecorderParams(filterDataPtr); + p.fps = fps; + p.width = width; + p.height = height; + p.cameraId = cameraId; + parameters = p; + } + + + public final void setSurfaceView(final SurfaceView sv, final int rotation) { + this.rotation = rotation; + SurfaceHolder holder = sv.getHolder(); + holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); + + holder.addCallback(new Callback() { + public void surfaceDestroyed(SurfaceHolder holder) { + surfaceView = null; + stopVideoRecording(); + } + + public void surfaceCreated(SurfaceHolder holder) { + surfaceView = sv; + tryToStartVideoRecording(); + } + + public void surfaceChanged(SurfaceHolder holder, int format, int width, + int height) {} + }); + } + + public void setMuted(boolean muteState) { + if (muteState == muted) return; + muted = muteState; + if (muted) { + stopVideoRecording(); + } else { + tryToStartVideoRecording(); + } + } + public void toggleMute() { + setMuted(!muted); + } + public boolean isMuted() { + return muted; + } + + + private void tryToStartVideoRecording() { + if (muted || surfaceView == null || parameters == null) return; + + parameters.rotation = rotation; + parameters.surfaceView = surfaceView; + if (version > 8) { + recorder = new AndroidCameraRecordBufferedImpl(parameters); + } else { + recorder = new AndroidCameraRecordImpl(parameters); + } + + recorder.startPreview(); + } + + public void stopVideoRecording() { + if (recorder != null) { + recorder.stopPreview(); + recorder = null; + } + } + + + // FIXME select right camera + public List supportedVideoSizes() { + if (supportedVideoSizes != null) { + return supportedVideoSizes; + } + + if (recorder != null) { + supportedVideoSizes = recorder.getSupportedVideoSizes(); + if (supportedVideoSizes != null) return supportedVideoSizes; + } + + Camera camera = Camera.open(); + supportedVideoSizes = camera.getParameters().getSupportedPreviewSizes(); + camera.release(); + return supportedVideoSizes; + } + + + public boolean isRecording() { + if (recorder != null) { + return recorder.isStarted(); + } + + return false; + } + + public void invalidateParameters() { + if (isRecording()) stopVideoRecording(); + parameters = null; + + } + +} diff --git a/src/org/linphone/core/tutorials/JavaCameraRecordImpl.java b/src/org/linphone/core/tutorials/JavaCameraRecordImpl.java index 30c1838f6..12594ccef 100644 --- a/src/org/linphone/core/tutorials/JavaCameraRecordImpl.java +++ b/src/org/linphone/core/tutorials/JavaCameraRecordImpl.java @@ -33,11 +33,13 @@ public class JavaCameraRecordImpl extends AndroidCameraRecord implements Preview private long startTime; private long endTime; + private int fps; - - public JavaCameraRecordImpl() { - super(); - setOrStorePreviewCallBack(this); + + public JavaCameraRecordImpl(AndroidCameraRecord.RecorderParams parameters) { + super(parameters); + storePreviewCallBack(this); + fps = Math.round(parameters.fps); } @@ -61,4 +63,10 @@ public class JavaCameraRecordImpl extends AndroidCameraRecord implements Preview Log.d("onPreviewFrame:", msg); } + + @Override + protected void lowLevelSetPreviewCallback(Camera camera, PreviewCallback cb) { + camera.setPreviewCallback(cb); + } + } diff --git a/src/org/linphone/core/tutorials/TestVideoActivity.java b/src/org/linphone/core/tutorials/TestVideoActivity.java index 0623e34dc..4b4ccc09e 100644 --- a/src/org/linphone/core/tutorials/TestVideoActivity.java +++ b/src/org/linphone/core/tutorials/TestVideoActivity.java @@ -50,11 +50,14 @@ public class TestVideoActivity extends Activity { // SurfaceHolder holder=surfaceView.getHolder(); // holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); - AndroidCameraRecord.setSurfaceView(surfaceView); + AndroidCameraRecord.RecorderParams params = new AndroidCameraRecord.RecorderParams(0); + params.surfaceView = surfaceView; + params.width = 352; + params.height = 288; + params.fps = rate; - JavaCameraRecordImpl recorder = new JavaCameraRecordImpl(); + JavaCameraRecordImpl recorder = new JavaCameraRecordImpl(params); recorder.setDebug((TextView) findViewById(R.id.videotest_debug)); - recorder.setParameters(288, 352, rate); }