Added camera record manager to drive Video record class.

This commit is contained in:
Guillaume Beraudo 2010-11-29 12:08:13 +01:00
parent cb9ac80f39
commit 25823ea485
8 changed files with 294 additions and 169 deletions

View file

@ -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);
}
}

View file

@ -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:

View file

@ -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 <Size> supportedVideoSizes;
private List <Size> 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<Size> supportedVideoSizes() {
if (supportedVideoSizes != null) {
return new ArrayList<Size>(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<Size> getSupportedVideoSizes() {
return new ArrayList<Size>(supportedVideoSizes);
}
}

View file

@ -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);
}

View file

@ -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);
}
}

View file

@ -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<Integer, AndroidCameraRecordManager> instances = new HashMap<Integer, AndroidCameraRecordManager>();
// 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<Size> 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<Size> 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;
}
}

View file

@ -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);
}
}

View file

@ -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);
}