diff --git a/AndroidCameraRecord.java b/AndroidCameraRecord.java new file mode 100644 index 000000000..84ad452ef --- /dev/null +++ b/AndroidCameraRecord.java @@ -0,0 +1,217 @@ +/* +AndroidCameraRecordImpl.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.ArrayList; +import java.util.Collections; +import java.util.List; + +import android.hardware.Camera; +import android.hardware.Camera.ErrorCallback; +import android.hardware.Camera.Parameters; +import android.hardware.Camera.PreviewCallback; +import android.hardware.Camera.Size; +import android.util.Log; +import android.view.SurfaceHolder; +import android.view.SurfaceView; + + +public abstract class AndroidCameraRecord { + + protected Camera camera; + private RecorderParams params; + + private PreviewCallback storedPreviewCallback; + private boolean previewStarted; + protected int displayOrientation; + protected static final String tag="Linphone"; + private List supportedVideoSizes; + private Size currentPreviewSize; + + public AndroidCameraRecord(RecorderParams parameters) { + this.params = parameters; + setDisplayOrientation(parameters.rotation); + } + + protected List getSupportedPreviewSizes(Camera.Parameters parameters) { + return Collections.emptyList(); + } + + public void startPreview() { // FIXME throws exception? + if (previewStarted) { + Log.w(tag, "Already started"); + throw new RuntimeException("Video recorder already started"); + // return + } + + if (params.surfaceView.getVisibility() != SurfaceView.VISIBLE) { + // Illegal state + Log.e(tag, "Illegal state: video capture surface view is not visible"); + return; + } + + + camera=Camera.open(); + camera.setErrorCallback(new ErrorCallback() { + public void onError(int error, Camera camera) { + Log.e(tag, "Camera error : " + error); + } + }); + + + Camera.Parameters parameters=camera.getParameters(); + parameters.set("camera-id",params.cameraId); + camera.setParameters(parameters); + parameters = camera.getParameters(); + if (supportedVideoSizes == null) { + supportedVideoSizes = new ArrayList(getSupportedPreviewSizes(parameters)); + } + + + if (!params.videoDimensionsInverted) { + parameters.setPreviewSize(params.width, params.height); + } else { + parameters.setPreviewSize(params.height, params.width); + } + parameters.setPreviewFrameRate(Math.round(params.fps)); + + + onSettingCameraParameters(parameters); + camera.setParameters(parameters); + + currentPreviewSize = camera.getParameters().getPreviewSize(); + + SurfaceHolder holder = params.surfaceView.getHolder(); + try { + camera.setPreviewDisplay(holder); + } + catch (Throwable t) { + Log.e(tag, "Exception in Video capture setPreviewDisplay()", t); + } + + + try { + camera.startPreview(); + previewStarted = true; + } catch (Throwable e) { + Log.e(tag, "Can't start camera preview"); + } + + previewStarted = true; + + + // Register callback to get capture buffer + lowLevelSetPreviewCallback(camera, storedPreviewCallback); + + + onPreviewStarted(camera); + } + + + + + protected void onSettingCameraParameters(Parameters parameters) {} + + /** + * Hook. + * @param camera + */ + public void onPreviewStarted(Camera camera) {} + + public void storePreviewCallBack(PreviewCallback cb) { + this.storedPreviewCallback = cb; + if (camera == null) { + Log.w(tag, "Capture camera not ready, storing callback"); + return; + } + + lowLevelSetPreviewCallback(camera, cb); + } + + + public void stopPreview() { + if (!previewStarted) return; + lowLevelSetPreviewCallback(camera, null); + camera.stopPreview(); + camera.release(); + camera=null; + if (currentPreviewSize != null) currentPreviewSize = null; + previewStarted = false; + } + + + public void stopCaptureCallback() { + if (camera != null) { + lowLevelSetPreviewCallback(camera, null); + } + } + + protected abstract void lowLevelSetPreviewCallback(Camera camera, PreviewCallback cb); + + public void setDisplayOrientation(int rotation) { + displayOrientation = rotation; + } + + protected int rotateCapturedFrame() { + if (params.videoDimensionsInverted) { + return 1; // always rotate 90° + } else if (params.cameraId == 2) { + return 0; + } else { + return (4 + 1 - displayOrientation) % 4; + } + } + + + + + public static class RecorderParams { + public float fps; + public int height; + public int width; + + final long filterDataNativePtr; + public int cameraId; + public int rotation; + public SurfaceView surfaceView; + public boolean videoDimensionsInverted; + + public RecorderParams(long ptr) { + filterDataNativePtr = ptr; + } + } + + + + + public boolean isStarted() { + return previewStarted; + } + + public List getSupportedVideoSizes() { + return new ArrayList(supportedVideoSizes); + } + + + protected int getExpectedBufferLength() { + if (currentPreviewSize == null) return -1; + + return currentPreviewSize.width * currentPreviewSize.height * 3 /2; + } +} diff --git a/AndroidCameraRecordBufferedImpl.java b/AndroidCameraRecordBufferedImpl.java new file mode 100644 index 000000000..ad54341c3 --- /dev/null +++ b/AndroidCameraRecordBufferedImpl.java @@ -0,0 +1,73 @@ +/* +AndroidCameraRecord8Impl.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 android.hardware.Camera; +import android.hardware.Camera.Parameters; +import android.hardware.Camera.PreviewCallback; +import android.hardware.Camera.Size; +import android.util.Log; + +/** + * + * Android >= 8 (2.2) version. + * @author Guillaume Beraudo + * + */ +public class AndroidCameraRecordBufferedImpl extends AndroidCameraRecordImplAPI5 { + + + public AndroidCameraRecordBufferedImpl(RecorderParams parameters) { + super(parameters); + } + + @Override + 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); + } + + @Override + public void onPreviewStarted(Camera camera) { + super.onPreviewStarted(camera); + + Size s = camera.getParameters().getPreviewSize(); + int wishedBufferSize = s.height * s.width * 3 / 2; + + camera.addCallbackBuffer(new byte[wishedBufferSize]); + camera.addCallbackBuffer(new byte[wishedBufferSize]); + } + + @Override + public void onPreviewFrame(byte[] data, Camera camera) { + super.onPreviewFrame(data, camera); + camera.addCallbackBuffer(data); + } + + @Override + protected void onSettingCameraParameters(Parameters parameters) { + super.onSettingCameraParameters(parameters); + // Only on v8 hardware + camera.setDisplayOrientation(90 * rotateCapturedFrame()); + } + + +} diff --git a/AndroidCameraRecordImpl.java b/AndroidCameraRecordImpl.java new file mode 100644 index 000000000..daf26ea4e --- /dev/null +++ b/AndroidCameraRecordImpl.java @@ -0,0 +1,97 @@ +/* +AndroidCameraRecordImpl.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 android.hardware.Camera; +import android.hardware.Camera.PreviewCallback; +import android.util.Log; + +/** + * Record from Android camera. + * + * @author Guillaume Beraudo + * + */ +public class AndroidCameraRecordImpl extends AndroidCameraRecord implements PreviewCallback { + + private long filterCtxPtr; + private double timeElapsedBetweenFrames = 0; + private long lastFrameTime = 0; + private final double expectedTimeBetweenFrames; + private boolean sizesInverted; + + public AndroidCameraRecordImpl(RecorderParams parameters) { + super(parameters); + expectedTimeBetweenFrames = 1d / Math.round(parameters.fps); + filterCtxPtr = parameters.filterDataNativePtr; + sizesInverted = parameters.videoDimensionsInverted; + + storePreviewCallBack(this); + } + + + private native void putImage(long filterCtxPtr, byte[] buffer, int rotate, boolean sizesInverted); + + + public void onPreviewFrame(byte[] data, Camera camera) { + if (data == null) { + Log.e("Linphone", "onPreviewFrame Called with null buffer"); + return; + } + if (filterCtxPtr == 0l) { + Log.e("Linphone", "onPreviewFrame Called with no filterCtxPtr set"); + return; + } + + int expectedBuffLength = getExpectedBufferLength(); + if (expectedBuffLength != data.length) { + Log.e("Linphone", "onPreviewFrame called with bad buffer length " + data.length + + " whereas expected is " + expectedBuffLength + " don't calling putImage"); + return; + } + + long curTime = System.currentTimeMillis(); + if (lastFrameTime == 0) { + lastFrameTime = curTime; + putImage(filterCtxPtr, data, rotateCapturedFrame(), sizesInverted); + return; + } + + double currentTimeElapsed = 0.8 * (curTime - lastFrameTime) / 1000 + 0.2 * timeElapsedBetweenFrames; + if (currentTimeElapsed < expectedTimeBetweenFrames) { +// Log.d("Linphone", "Clipping frame " + Math.round(1 / currentTimeElapsed) + " > " + fps); + return; + } + lastFrameTime = curTime; + timeElapsedBetweenFrames = currentTimeElapsed; + + // Log.d("onPreviewFrame: ", Integer.toString(data.length)); + putImage(filterCtxPtr, data, rotateCapturedFrame(), sizesInverted); + } + + + + @Override + protected void lowLevelSetPreviewCallback(Camera camera, PreviewCallback cb) { + camera.setPreviewCallback(cb); + } + + + +} diff --git a/AndroidCameraRecordImplAPI5.java b/AndroidCameraRecordImplAPI5.java new file mode 100644 index 000000000..7ff307d03 --- /dev/null +++ b/AndroidCameraRecordImplAPI5.java @@ -0,0 +1,64 @@ +/* +AndroidCameraRecordImplAPI5.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.List; + +import android.hardware.Camera; +import android.hardware.Camera.Parameters; +import android.hardware.Camera.Size; +import android.util.Log; + + +public class AndroidCameraRecordImplAPI5 extends AndroidCameraRecordImpl { + + public AndroidCameraRecordImplAPI5(RecorderParams parameters) { + super(parameters); + } + + @Override + protected void onSettingCameraParameters(Parameters parameters) { + super.onSettingCameraParameters(parameters); + + 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); + } else { + Log.w(tag, "Auto Focus not supported by camera device"); + if (parameters.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_INFINITY)) { + Log.w(tag, "Infinity Focus supported by camera device"); + parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_INFINITY); + } else { + Log.w(tag, "Infinity Focus not supported by camera device"); + } + } + } + + public static List oneShotSupportedVideoSizes() { + Camera camera = Camera.open(); + List supportedVideoSizes =camera.getParameters().getSupportedPreviewSizes(); + camera.release(); + return supportedVideoSizes; + } + + @Override + protected List getSupportedPreviewSizes(Parameters parameters) { + return parameters.getSupportedPreviewSizes(); + } +} diff --git a/AndroidCameraRecordManager.java b/AndroidCameraRecordManager.java new file mode 100644 index 000000000..023dcffa4 --- /dev/null +++ b/AndroidCameraRecordManager.java @@ -0,0 +1,245 @@ +/* +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.List; + +import org.linphone.core.AndroidCameraRecord.RecorderParams; + +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, only on for all cameras. + * + * @author Guillaume Beraudo + * + */ +public class AndroidCameraRecordManager { + private static final int version = Integer.parseInt(Build.VERSION.SDK); + private static final String tag = "Linphone"; + private static AndroidCameraRecordManager instance; + + // singleton + private AndroidCameraRecordManager() {} + + + /** + * @return instance + */ + public static final synchronized AndroidCameraRecordManager getInstance() { + if (instance == null) { + instance = new AndroidCameraRecordManager(); + } + return instance; + } + + private AndroidCameraRecord.RecorderParams parameters; + private SurfaceView surfaceView; + private boolean muted; + + + private AndroidCameraRecord recorder; + + + private List supportedVideoSizes; + private int rotation; + + private boolean useFrontCamera; + public void setUseFrontCamera(boolean value) { + if (useFrontCamera == value) return; + this.useFrontCamera = value; + + if (parameters != null) { + parameters.cameraId = cameraId(); + if (isRecording()) { + stopVideoRecording(); + tryToStartVideoRecording(); + } + } + } + public boolean isUseFrontCamera() {return useFrontCamera;} + public boolean toggleUseFrontCamera() { + setUseFrontCamera(!useFrontCamera); + return useFrontCamera; + } + + + + public void setParametersFromFilter(long filterDataPtr, int height, int width, float fps) { + stopVideoRecording(); + RecorderParams p = new RecorderParams(filterDataPtr); + p.fps = fps; + p.width = width; + p.height = height; + p.cameraId = cameraId(); + p.videoDimensionsInverted = width < height; + // width and height will be inverted in Recorder on startPreview + parameters = p; + tryToStartVideoRecording(); + } + + + public final void setSurfaceView(final SurfaceView sv, final int rotation) { + this.rotation = useFrontCamera ? 1 : rotation; + SurfaceHolder holder = sv.getHolder(); + holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); + + holder.addCallback(new Callback() { + public void surfaceDestroyed(SurfaceHolder holder) { + surfaceView = null; + Log.d(tag , "Video capture surface destroyed"); + stopVideoRecording(); + } + + public void surfaceCreated(SurfaceHolder holder) { + surfaceView = sv; + Log.d(tag , "Video capture surface created"); + tryToStartVideoRecording(); + } + + public void surfaceChanged(SurfaceHolder holder, int format, int width, + int height) { + Log.d(tag , "Video capture surface changed"); + } + }); + } + + public void setMuted(boolean muteState) { + if (muteState == muted) return; + muted = muteState; + if (muted) { + stopVideoRecording(); + } else { + tryToStartVideoRecording(); + } + } + public boolean toggleMute() { + setMuted(!muted); + return muted; + } + public boolean isMuted() { + return muted; + } + + public void tryResumingVideoRecording() { + if (isRecording()) return; + tryToStartVideoRecording(); + } + + private void tryToStartVideoRecording() { + if (muted || surfaceView == null || parameters == null) return; + + parameters.rotation = rotation; + parameters.surfaceView = surfaceView; + if (version >= 8) { + recorder = new AndroidCameraRecordBufferedImpl(parameters); + } else if (version >= 5) { + recorder = new AndroidCameraRecordImplAPI5(parameters); + } else { + recorder = new AndroidCameraRecordImpl(parameters); + } + + recorder.startPreview(); + } + + public void stopVideoRecording() { + if (recorder != null) { + recorder.stopPreview(); + recorder = null; + } + } + + + // FIXME select right camera + /** + * Eventually null if API < 5. + * + */ + public List supportedVideoSizes() { + if (supportedVideoSizes != null) { + return supportedVideoSizes; + } + + if (recorder != null) { + supportedVideoSizes = recorder.getSupportedVideoSizes(); + if (supportedVideoSizes != null) return supportedVideoSizes; + } + + if (version >= 5) { + supportedVideoSizes = AndroidCameraRecordImplAPI5.oneShotSupportedVideoSizes(); + } + + // eventually null + + return supportedVideoSizes; + } + + + public boolean isRecording() { + if (recorder != null) { + return recorder.isStarted(); + } + + return false; + } + + + public void invalidateParameters() { + stopVideoRecording(); + parameters = null; + } + + /** + * Naive simple version. + * @param askedSize + * @return + */ + public VideoSize doYouSupportThisVideoSize(VideoSize askedSize) { + Log.d(tag, "Asking camera if it supports size "+askedSize); + if (useFrontCamera && askedSize.isPortrait()) { + return askedSize.createInverted(); // only landscape supported + } else { + return askedSize; + } + } + + + private VideoSize closestVideoSize(VideoSize vSize, int defaultSizeCode, boolean defaultIsPortrait) { + VideoSize testSize = vSize.isPortrait() ? vSize.createInverted() : vSize; + + for (Size s : AndroidCameraRecordManager.getInstance().supportedVideoSizes()) { + if (s.height == testSize.getHeight() && s.width == testSize.getWidth()) { + return vSize; + } + } + + return VideoSize.createStandard(defaultSizeCode, defaultIsPortrait); + } + + private static final int rearCamId() {return 1;} + private static final int frontCamId() {return 2;} + private final int cameraId() {return useFrontCamera? frontCamId() : rearCamId(); } +} diff --git a/AndroidVideoWindowImpl.java b/AndroidVideoWindowImpl.java new file mode 100644 index 000000000..ec7eb2cbb --- /dev/null +++ b/AndroidVideoWindowImpl.java @@ -0,0 +1,89 @@ +package org.linphone.core; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Bitmap.Config; +import android.util.Log; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.Surface.OutOfResourcesException; +import android.view.SurfaceHolder.Callback; + +public class AndroidVideoWindowImpl { + private Bitmap mBitmap; + private SurfaceView mView; + private Surface mSurface; + private VideoWindowListener mListener; + static private String TAG = "Linphone"; + public static interface VideoWindowListener{ + void onSurfaceReady(AndroidVideoWindowImpl vw); + void onSurfaceDestroyed(AndroidVideoWindowImpl vw); + }; + public AndroidVideoWindowImpl(SurfaceView view){ + mView=view; + mBitmap=null; + mSurface=null; + mListener=null; + view.getHolder().addCallback(new Callback(){ + public void surfaceChanged(SurfaceHolder holder, int format, + int width, int height) { + Log.i(TAG,"Surface is being changed."); + synchronized(AndroidVideoWindowImpl.this){ + mBitmap=Bitmap.createBitmap(width,height,Config.RGB_565); + mSurface=holder.getSurface(); + } + if (mListener!=null) mListener.onSurfaceReady(AndroidVideoWindowImpl.this); + Log.w("Linphone", "Video display surface changed"); + } + + public void surfaceCreated(SurfaceHolder holder) { + Log.w("Linphone", "Video display surface created"); + } + + public void surfaceDestroyed(SurfaceHolder holder) { + synchronized(AndroidVideoWindowImpl.this){ + mSurface=null; + mBitmap=null; + } + if (mListener!=null) + mListener.onSurfaceDestroyed(AndroidVideoWindowImpl.this); + Log.d("Linphone", "Video display surface destroyed"); + } + }); + } + static final int LANDSCAPE=0; + static final int PORTRAIT=1; + public void requestOrientation(int orientation){ + //Surface.setOrientation(0, orientation==LANDSCAPE ? 1 : 0); + //Log.d("Linphone", "Orientation changed."); + } + public void setListener(VideoWindowListener l){ + mListener=l; + } + public Surface getSurface(){ + return mView.getHolder().getSurface(); + } + public Bitmap getBitmap(){ + return mBitmap; + } + //Called by the mediastreamer2 android display filter + public synchronized void update(){ + if (mSurface!=null){ + try { + Canvas canvas=mSurface.lockCanvas(null); + canvas.drawBitmap(mBitmap, 0, 0, null); + mSurface.unlockCanvasAndPost(canvas); + + } catch (IllegalArgumentException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (OutOfResourcesException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } +} + + diff --git a/LinphoneCallImpl.java b/LinphoneCallImpl.java index ca9d9d55f..74c701a72 100644 --- a/LinphoneCallImpl.java +++ b/LinphoneCallImpl.java @@ -29,6 +29,9 @@ class LinphoneCallImpl implements LinphoneCall { private native boolean isIncoming(long nativePtr); native private long getRemoteAddress(long nativePtr); native private int getState(long nativePtr); + private native long getCurrentParamsCopy(long nativePtr); + private native void enableCamera(long nativePtr, boolean enabled); + protected LinphoneCallImpl(long aNativePtr) { nativePtr = aNativePtr; ref(nativePtr); @@ -58,12 +61,11 @@ class LinphoneCallImpl implements LinphoneCall { public State getState() { return LinphoneCall.State.fromInt(getState(nativePtr)); } - public LinphoneCallParams getCurrentParamsReadOnly() { - throw new RuntimeException("Not Implemenetd yet"); + public LinphoneCallParams getCurrentParamsCopy() { + return new LinphoneCallParamsImpl(getCurrentParamsCopy(nativePtr)); } + public void enableCamera(boolean enabled) { - throw new RuntimeException("Not Implemenetd yet"); + enableCamera(nativePtr, enabled); } - - } diff --git a/LinphoneCallLogImpl.java b/LinphoneCallLogImpl.java index c9c8b8ffe..1bdb84720 100644 --- a/LinphoneCallLogImpl.java +++ b/LinphoneCallLogImpl.java @@ -43,8 +43,7 @@ class LinphoneCallLogImpl implements LinphoneCallLog { return new LinphoneAddressImpl(getTo(nativePtr)); } public CallStatus getStatus() { - // TODO Auto-generated method stub - return null; + throw new RuntimeException("not implemented yet"); } } diff --git a/LinphoneCallParamsImpl.java b/LinphoneCallParamsImpl.java new file mode 100644 index 000000000..ef05eb228 --- /dev/null +++ b/LinphoneCallParamsImpl.java @@ -0,0 +1,51 @@ +/* +LinphoneCallParamsImpl.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; + +public class LinphoneCallParamsImpl implements LinphoneCallParams { + protected final long nativePtr; + + public LinphoneCallParamsImpl(long nativePtr) { + this.nativePtr = nativePtr; + } + + private native void enableVideo(long nativePtr, boolean b); + private native boolean getVideoEnabled(long nativePtr); + private native void audioBandwidth(long nativePtr, int bw); + private native void destroy(long nativePtr); + + + public boolean getVideoEnabled() { + return getVideoEnabled(nativePtr); + } + + public void setVideoEnabled(boolean b) { + enableVideo(nativePtr, b); + } + + @Override + protected void finalize() throws Throwable { + destroy(nativePtr); + super.finalize(); + } + + public void setAudioBandwidth(int value) { + audioBandwidth(nativePtr, value); + } +} diff --git a/LinphoneCoreImpl.java b/LinphoneCoreImpl.java index 83df6de86..929403c9d 100644 --- a/LinphoneCoreImpl.java +++ b/LinphoneCoreImpl.java @@ -22,6 +22,8 @@ import java.io.File; import java.io.IOException; import java.util.Vector; +import android.view.SurfaceView; + class LinphoneCoreImpl implements LinphoneCore { @@ -53,6 +55,7 @@ class LinphoneCoreImpl implements LinphoneCore { private native void muteMic(long nativePtr,boolean isMuted); private native long interpretUrl(long nativePtr,String destination); private native long inviteAddress(long nativePtr,long to); + private native long inviteAddressWithParams(long nativePtrLc,long to, long nativePtrParam); private native void sendDtmf(long nativePtr,char dtmf); private native void clearCallLogs(long nativePtr); private native boolean isMicMuted(long nativePtr); @@ -63,12 +66,31 @@ class LinphoneCoreImpl implements LinphoneCore { private native long getCurrentCall(long nativePtr) ; private native void playDtmf(long nativePtr,char dtmf,int duration); private native void stopDtmf(long nativePtr); - + private native void setVideoWindowId(long nativePtr, Object wid); + private native void setPreviewWindowId(long nativePtr, Object wid); + private AndroidVideoWindowImpl mVideoWindow; + private AndroidVideoWindowImpl mPreviewWindow; private native void addFriend(long nativePtr,long friend); private native void setPresenceInfo(long nativePtr,int minute_away, String alternative_contact,int status); private native long createChatRoom(long nativePtr,String to); + private native void enableVideo(long nativePtr,boolean vcap_enabled,boolean display_enabled); + private native boolean isVideoEnabled(long nativePtr); + private native void setFirewallPolicy(long nativePtr, int enum_value); + private native int getFirewallPolicy(long nativePtr); + private native void setStunServer(long nativePtr, String stun_server); + private native String getStunServer(long nativePtr); + private native long createDefaultCallParams(long nativePtr); + private native int updateCall(long ptrLc, long ptrCall, long ptrParams); + private native void setUploadBandwidth(long nativePtr, int bw); + private native void setDownloadBandwidth(long nativePtr, int bw); + private native void setPreferredVideoSize(long nativePtr, int width, int heigth); + private native int[] getPreferredVideoSize(long nativePtr); private native void setRing(long nativePtr, String path); private native String getRing(long nativePtr); + private native long[] listVideoPayloadTypes(long nativePtr); + + + private static final String TAG = "LinphoneCore"; LinphoneCoreImpl(LinphoneCoreListener listener, File userConfig,File factoryConfig,Object userdata) throws IOException { mListener=listener; @@ -200,14 +222,15 @@ class LinphoneCoreImpl implements LinphoneCore { throw new LinphoneCoreException("Cannot interpret ["+destination+"]"); } } - public LinphoneCall invite(LinphoneAddress to) { + public LinphoneCall invite(LinphoneAddress to) throws LinphoneCoreException { long lNativePtr = inviteAddress(nativePtr,((LinphoneAddressImpl)to).nativePtr); if (lNativePtr!=0) { return new LinphoneCallImpl(lNativePtr); } else { - return null; + throw new LinphoneCoreException("Unable to invite address " + to.asString()); } } + public void sendDtmf(char number) { sendDtmf(nativePtr,number); } @@ -295,73 +318,114 @@ class LinphoneCoreImpl implements LinphoneCore { return new LinphoneChatRoomImpl(createChatRoom(nativePtr,to)); } public void setPreviewWindow(Object w) { - throw new RuntimeException("not implemented yet"); - // TODO Auto-generated method stub - + if (mPreviewWindow!=null) + mPreviewWindow.setListener(null); + mPreviewWindow=new AndroidVideoWindowImpl((SurfaceView)w); + mPreviewWindow.setListener(new AndroidVideoWindowImpl.VideoWindowListener(){ + public void onSurfaceDestroyed(AndroidVideoWindowImpl vw) { + setPreviewWindowId(nativePtr,null); + } + + public void onSurfaceReady(AndroidVideoWindowImpl vw) { + setPreviewWindowId(nativePtr,vw); + } + }); } public void setVideoWindow(Object w) { - throw new RuntimeException("not implemented yet"); - // TODO Auto-generated method stub + if (mVideoWindow!=null) + mVideoWindow.setListener(null); + mVideoWindow=new AndroidVideoWindowImpl((SurfaceView) w); + mVideoWindow.setListener(new AndroidVideoWindowImpl.VideoWindowListener(){ + public void onSurfaceDestroyed(AndroidVideoWindowImpl vw) { + setVideoWindowId(nativePtr,null); + } + + public void onSurfaceReady(AndroidVideoWindowImpl vw) { + setVideoWindowId(nativePtr,vw); + } + }); } public void enableVideo(boolean vcap_enabled, boolean display_enabled) { - // TODO Auto-generated method stub - + enableVideo(nativePtr,vcap_enabled, display_enabled); } public boolean isVideoEnabled() { - // TODO Auto-generated method stub - return false; - } - public void setStunServer(String stun_server) { - // TODO Auto-generated method stub - - } - public String getStunServer() { - // TODO Auto-generated method stub - return null; - } - public void setFirewallPolicy(FirewallPolicy pol) { - // TODO Auto-generated method stub - + return isVideoEnabled(nativePtr); } public FirewallPolicy getFirewallPolicy() { - // TODO Auto-generated method stub - return null; + return FirewallPolicy.fromInt(getFirewallPolicy(nativePtr)); + } + public String getStunServer() { + return getStunServer(nativePtr); + } + public void setFirewallPolicy(FirewallPolicy pol) { + setFirewallPolicy(nativePtr,pol.value()); + } + public void setStunServer(String stunServer) { + setStunServer(nativePtr,stunServer); + } + + public LinphoneCallParams createDefaultCallParameters() { + return new LinphoneCallParamsImpl(createDefaultCallParams(nativePtr)); + } + + public LinphoneCall inviteAddressWithParams(LinphoneAddress to, LinphoneCallParams params) throws LinphoneCoreException { + long ptrDestination = ((LinphoneAddressImpl)to).nativePtr; + long ptrParams =((LinphoneCallParamsImpl)params).nativePtr; + + long lcNativePtr = inviteAddressWithParams(nativePtr, ptrDestination, ptrParams); + if (lcNativePtr!=0) { + return new LinphoneCallImpl(lcNativePtr); + } else { + throw new LinphoneCoreException("Unable to invite with params " + to.asString()); + } + } + + public int updateCall(LinphoneCall call, LinphoneCallParams params) { + long ptrCall = ((LinphoneCallImpl) call).nativePtr; + long ptrParams = ((LinphoneCallParamsImpl)params).nativePtr; + + return updateCall(nativePtr, ptrCall, ptrParams); + } + public void setUploadBandwidth(int bw) { + setUploadBandwidth(nativePtr, bw); + } + + public void setDownloadBandwidth(int bw) { + setDownloadBandwidth(nativePtr, bw); + } + + public void setPreferredVideoSize(VideoSize vSize) { + setPreferredVideoSize(nativePtr, vSize.getWidth(), vSize.getHeight()); + } + + public VideoSize getPreferredVideoSize() { + int[] nativeSize = getPreferredVideoSize(nativePtr); + + VideoSize vSize = new VideoSize(); + vSize.setWidth(nativeSize[0]); + vSize.setHeight(nativeSize[1]); + return vSize; } public void setRing(String path) { - setRing(nativePtr,path); - + setRing(nativePtr, path); } public String getRing() { return getRing(nativePtr); } - public LinphoneCall inviteAddressWithParams(LinphoneAddress destination, - LinphoneCallParams params) throws LinphoneCoreException { - throw new RuntimeException("Not Implemenetd yet"); - } - public int updateCall(LinphoneCall call, LinphoneCallParams params) { - throw new RuntimeException("Not Implemenetd yet"); - } - public LinphoneCallParams createDefaultCallParameters() { - throw new RuntimeException("Not Implemenetd yet"); + + public PayloadType[] listVideoCodecs() { + long[] typesPtr = listVideoPayloadTypes(nativePtr); + if (typesPtr == null) return null; + + PayloadType[] codecs = new PayloadType[typesPtr.length]; + + for (int i=0; i < codecs.length; i++) { + codecs[i] = new PayloadTypeImpl(typesPtr[i]); + } + + return codecs; } public boolean isNetworkReachable() { - throw new RuntimeException("Not Implemenetd yet"); + throw new RuntimeException("Not implemented"); } - public void setUploadBandwidth(int bw) { - throw new RuntimeException("Not Implemenetd yet"); - } - public void setDownloadBandwidth(int bw) { - throw new RuntimeException("Not Implemenetd yet"); - } - public void setPreferredVideoSize(VideoSize vSize) { - throw new RuntimeException("Not Implemenetd yet"); - } - public VideoSize getPreferredVideoSize() { - throw new RuntimeException("Not Implemenetd yet"); - } - public PayloadType[] listVideoCodecs() { - throw new RuntimeException("Not Implemenetd yet"); - } - - } diff --git a/PayloadTypeImpl.java b/PayloadTypeImpl.java index 821e1a091..864b094ff 100644 --- a/PayloadTypeImpl.java +++ b/PayloadTypeImpl.java @@ -24,15 +24,22 @@ class PayloadTypeImpl implements PayloadType { protected final long nativePtr; private native String toString(long ptr); - + private native String getMime(long ptr); + private native int getRate(long ptr); + protected PayloadTypeImpl(long aNativePtr) { nativePtr = aNativePtr; } - public String toString() { - return toString(nativePtr); + + public int getRate() { + return getRate(nativePtr); } public String getMime() { - throw new RuntimeException("Not Implemenetd yet"); + return getMime(nativePtr); + } + + public String toString() { + return toString(nativePtr); } } diff --git a/tutorials/JavaCameraRecordImpl.java b/tutorials/JavaCameraRecordImpl.java new file mode 100644 index 000000000..33cc6b22e --- /dev/null +++ b/tutorials/JavaCameraRecordImpl.java @@ -0,0 +1,81 @@ +/* +JavaCameraRecordImpl.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.tutorials; + +import org.linphone.core.AndroidCameraRecord; + +import android.hardware.Camera; +import android.hardware.Camera.PreviewCallback; +import android.hardware.Camera.Size; +import android.util.Log; +import android.widget.TextView; + +public class JavaCameraRecordImpl extends AndroidCameraRecord implements PreviewCallback { + + private TextView debug; + private long count = 0; + private float averageCalledRate; + + private long startTime; + private long endTime; + private int fps; + + + public JavaCameraRecordImpl(AndroidCameraRecord.RecorderParams parameters) { + super(parameters); + storePreviewCallBack(this); + fps = Math.round(parameters.fps); + } + + + public void setDebug(TextView debug) { + this.debug = debug; + } + + public void onPreviewFrame(byte[] data, Camera camera) { + + Size s = camera.getParameters().getPreviewSize(); + int expectedBuffLength = s.width * s.height * 3 /2; + if (expectedBuffLength != data.length) { + Log.e("Linphone", "onPreviewFrame called with bad buffer length " + data.length + + " whereas expected is " + expectedBuffLength + " don't calling putImage"); + return; + } + + if ((count % 2 * fps) == 0) { + endTime = System.currentTimeMillis(); + averageCalledRate = (100000 * 2 * fps) / (endTime - startTime); + averageCalledRate /= 100f; + startTime = endTime; + } + + count++; + + String msg = "Frame " + count + ": " + data.length + "bytes (avg="+averageCalledRate+"frames/s)"; + if (debug != null) debug.setText(msg); + Log.d("onPreviewFrame:", msg); + } + + + @Override + protected void lowLevelSetPreviewCallback(Camera camera, PreviewCallback cb) { + camera.setPreviewCallback(cb); + } + +} diff --git a/tutorials/TestVideoActivity.java b/tutorials/TestVideoActivity.java new file mode 100644 index 000000000..971b8f73f --- /dev/null +++ b/tutorials/TestVideoActivity.java @@ -0,0 +1,191 @@ +/* +TutorialHelloWorldActivity.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.tutorials; + +import java.util.Stack; + +import org.linphone.R; +import org.linphone.core.AndroidCameraRecord; +import org.linphone.core.VideoSize; + +import android.app.Activity; +import android.os.Bundle; +import android.util.Log; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.view.SurfaceHolder.Callback; +import android.view.View.OnClickListener; +import android.view.ViewGroup.LayoutParams; +import android.widget.Button; +import android.widget.TextView; +import static org.linphone.core.VideoSize.*; + +/** + * Activity for displaying and starting the HelloWorld example on Android phone. + * + * @author Guillaume Beraudo + * + */ +public class TestVideoActivity extends Activity implements Callback, OnClickListener { + + private SurfaceView surfaceView; + private static final int rate = 7; + private JavaCameraRecordImpl recorder; + private static String tag = "Linphone"; + private TextView debugView; + private Button nextSize; + private Button changeCamera; + private Button changeOrientation; + private AndroidCameraRecord.RecorderParams params; + + private Stack videoSizes = createSizesToTest(); + private int currentCameraId = 2; + private boolean currentOrientationIsPortrait = false; + private int width; + private int height; + private boolean started; + + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.videotest); + + surfaceView=(SurfaceView)findViewById(R.id.videotest_surfaceView); + + nextSize = (Button) findViewById(R.id.test_video_size); + nextSize.setOnClickListener(this); + + changeCamera = (Button) findViewById(R.id.test_video_camera); + changeCamera.setText("Cam"+otherCameraId(currentCameraId)); + changeCamera.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + changeCamera.setText("Cam"+currentCameraId); + currentCameraId = otherCameraId(currentCameraId); + updateRecording(); + } + }); + + changeOrientation = (Button) findViewById(R.id.test_video_orientation); + changeOrientation.setText(orientationToString(!currentOrientationIsPortrait)); + changeOrientation.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + currentOrientationIsPortrait = !currentOrientationIsPortrait; + changeOrientation.setText(orientationToString(!currentOrientationIsPortrait)); + + if (width == 0 || height == 0) return; + int newWidth = currentOrientationIsPortrait? Math.min(height, width) : Math.max(height, width); + int newHeight = currentOrientationIsPortrait? Math.max(height, width) : Math.min(height, width); + changeSurfaceViewLayout(newWidth, newHeight); // will change width and height on surfaceChanged + } + }); + + SurfaceHolder holder = surfaceView.getHolder(); + holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); + holder.addCallback(this); + + + debugView = (TextView) findViewById(R.id.videotest_debug); + } + + protected void updateRecording() { + if (width == 0 || height == 0) return; + if (recorder != null) recorder.stopPreview(); + + params = new AndroidCameraRecord.RecorderParams(0); + params.surfaceView = surfaceView; + params.width = width; + params.height = height; + params.fps = rate; + params.cameraId = currentCameraId; + + recorder = new JavaCameraRecordImpl(params); +// recorder.setDebug(debugView); + debugView.setText(orientationToString(currentOrientationIsPortrait) + + " w="+width + " h="+height+ " cam"+currentCameraId); + recorder.startPreview(); + + } + + private String orientationToString(boolean orientationIsPortrait) { + return orientationIsPortrait? "Por" : "Lan"; + } + private int otherCameraId(int currentId) { + return (currentId == 2) ? 1 : 2; + } + public void onClick(View v) { + nextSize.setText("Next"); + started=true; + if (videoSizes.isEmpty()) { + videoSizes = createSizesToTest(); + } + + VideoSize size = videoSizes.pop(); + changeSurfaceViewLayout(size.getWidth(), size.getHeight()); + + // on surface changed the recorder will be restarted with new values + // and the surface will be resized + } + + + private void changeSurfaceViewLayout(int width, int height) { + LayoutParams params = surfaceView.getLayoutParams(); + params.height = height; + params.width = width; + surfaceView.setLayoutParams(params); + + } + + private Stack createSizesToTest() { + Stack stack = new Stack(); + + stack.push(VideoSize.createStandard(QCIF, false)); + stack.push(VideoSize.createStandard(CIF, false)); + stack.push(VideoSize.createStandard(QVGA, false)); + stack.push(VideoSize.createStandard(HVGA, false)); + stack.push(new VideoSize(640,480)); + stack.push(new VideoSize(800,480)); + return stack; + } + + + + + public void surfaceDestroyed(SurfaceHolder holder) { + surfaceView = null; + Log.d(tag , "Video capture surface destroyed"); + if (recorder != null) recorder.stopPreview(); + } + + public void surfaceCreated(SurfaceHolder holder) { + Log.d(tag , "Video capture surface created"); + } + + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + if (!started) return; + if (recorder != null) recorder.stopPreview(); + + this.width = width; + this.height = height; + + updateRecording(); + } +} diff --git a/tutorials/TutorialRegistrationActivity.java b/tutorials/TutorialRegistrationActivity.java index 820360294..94670480c 100644 --- a/tutorials/TutorialRegistrationActivity.java +++ b/tutorials/TutorialRegistrationActivity.java @@ -76,6 +76,7 @@ public class TutorialRegistrationActivity extends TutorialHelloWorldActivity { } }); + Button buttonStop = (Button) findViewById(R.id.ButtonStop); buttonStop.setOnClickListener(new View.OnClickListener() { public void onClick(View v) {