Added menu to videocall activity:

- high/low resolution
- enable/disable camera
- return to dialer
- terminate call
Added bandwidth manager to manage video profiles and update current and subsequent calls.
Added VideoSize semantics and jni wrappings on linphoneCore.
This commit is contained in:
Guillaume Beraudo 2010-11-26 15:04:38 +01:00
parent b92ecb8d63
commit 5621c1bd08
9 changed files with 292 additions and 52 deletions

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/videocall_menu_toggle_camera" android:title="@string/menu_videocall_toggle_camera_title"></item>
<item android:id="@+id/videocall_menu_terminate_call" android:title="@string/menu_videocall_terminate_call_title"></item>
<item android:id="@+id/videocall_menu_change_resolution" android:title="@string/menu_videocall_change_resolution_title"></item>
<item android:id="@+id/videocall_menu_back_to_dialer" android:title="@string/menu_videocall_back_to_dialer_title"></item>
</menu>

View file

@ -1,5 +1,13 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="menu_videocall_back_to_dialer_title">Display dialer</string>
<string name="menu_videocall_change_resolution_when_low_resolution">High resolution</string>
<string name="menu_videocall_change_resolution_when_high_resolution">Low resolution</string>
<string name="menu_videocall_change_resolution_title">Change resolution</string>
<string name="menu_videocall_toggle_camera_title">Mute/Unmute camera</string>
<string name="menu_videocall_toggle_camera_disable">Disable camera</string>
<string name="menu_videocall_toggle_camera_enable">Enable camera</string>
<string name="menu_videocall_terminate_call_title">Terminate call</string>
<string name="pref_video_settings_title">Video settings</string> <string name="pref_video_settings_title">Video settings</string>
<string name="pref_video_automatically_share_my_video_title">Share my camera</string> <string name="pref_video_automatically_share_my_video_title">Share my camera</string>
<string name="pref_video_initiate_call_with_video_title">Initiate video calls</string> <string name="pref_video_initiate_call_with_video_title">Initiate video calls</string>

View file

@ -0,0 +1,81 @@
/*
BandwithManager.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;
import org.linphone.core.LinphoneCall;
import org.linphone.core.LinphoneCallParams;
import org.linphone.core.LinphoneCore;
import org.linphone.core.VideoSize;
public class BandwidthManager {
public static final int HIGH_RESOLUTION = 0;
public static final int LOW_RESOLUTION = 1;
public static final int LOW_BANDWIDTH = 2;
private static final int[][] bandwidthes = {{512,512}, {128,128}, {80,80}};
private static BandwidthManager instance;
private int currentProfile = LOW_RESOLUTION; // FIXME first profile never defined in C part
public int getCurrentProfile() {return currentProfile;}
public static final synchronized BandwidthManager getInstance() {
if (instance == null) instance = new BandwidthManager();
return instance;
}
private BandwidthManager() {}
public void changeTo(int profile) {
LinphoneCore lc = LinphoneService.instance().getLinphoneCore();
LinphoneCall lCall = lc.getCurrentCall();
LinphoneCallParams params = lCall.getCurrentParamsReadOnly().copy();
if (profile == LOW_BANDWIDTH) { // OR video disabled by settings?
// lc.enableVideo(false, false);
params.setVideoEnabled(false);
} else {
params.setVideoEnabled(true);
VideoSize targetVideoSize;
switch (profile) {
case LOW_RESOLUTION:
targetVideoSize = VideoSize.createStandard(VideoSize.HVGA);
break;
case HIGH_RESOLUTION:
targetVideoSize = VideoSize.createStandard(VideoSize.CIF);
break;
default:
throw new RuntimeException("profile not managed : " + profile);
}
lc.setPreferredVideoSize(targetVideoSize);
VideoSize actualVideoSize = lc.getPreferredVideoSize();
if (!targetVideoSize.equals(actualVideoSize)) {
lc.setPreferredVideoSize(VideoSize.createStandard(VideoSize.QCIF));
}
}
lc.setUploadBandwidth(bandwidthes[profile][0]);
lc.setDownloadBandwidth(bandwidthes[profile][1]);
lc.updateCall(lCall, params);
currentProfile = profile;
}
}

View file

@ -157,13 +157,17 @@ public class DialerActivity extends Activity implements LinphoneCoreListener {
mAddVideo.setOnClickListener(new OnClickListener() { mAddVideo.setOnClickListener(new OnClickListener() {
public void onClick(View v) { public void onClick(View v) {
LinphoneCore lLinphoneCore = LinphoneService.instance().getLinphoneCore(); LinphoneCore lLinphoneCore = LinphoneService.instance().getLinphoneCore();
LinphoneCallParams params = lLinphoneCore.getCurrentCall().getCurrentParamsReadOnly(); LinphoneCall lCall = lLinphoneCore.getCurrentCall();
LinphoneCallParams params = lCall.getCurrentParamsReadOnly();
String msg; String msg;
if (params.getVideoEnabled()) { if (params.getVideoEnabled()) {
msg = "In video call; going back to video call activity"; msg = "In video call; going back to video call activity";
startVideoView(); startVideoView(VIDEO_VIEW_ACTIVITY);
} else { } else {
msg = "Not in video call; should go try to reinvite with video"; msg = "Not in video call; should go try to reinvite with video";
params.setVideoEnabled(true);
AndroidCameraRecord.setMuteCamera(false);
lLinphoneCore.updateCall(lCall, params);
} }
Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show(); Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show();
} }
@ -228,7 +232,7 @@ public class DialerActivity extends Activity implements LinphoneCoreListener {
} else { } else {
mCall.setEnabled(false); mCall.setEnabled(false);
mHangup.setEnabled(!mCall.isEnabled()); mHangup.setEnabled(!mCall.isEnabled());
boolean prefVideoEnabled = getPref(getApplicationContext().getString(R.string.pref_video_enable_key)); boolean prefVideoEnabled = mPref.getBoolean(getString(R.string.pref_video_enable_key), false);
if (!prefVideoEnabled && !mCall.isEnabled()) { if (!prefVideoEnabled && !mCall.isEnabled()) {
mAddVideo.setEnabled(true); mAddVideo.setEnabled(true);
} }
@ -416,12 +420,10 @@ public class DialerActivity extends Activity implements LinphoneCoreListener {
} }
} }
} }
private void startVideoView() { private void startVideoView(int requestCode) {
//start video view
Intent lIntent = new Intent(); Intent lIntent = new Intent();
lIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
lIntent.setClass(this, VideoCallActivity.class); lIntent.setClass(this, VideoCallActivity.class);
startActivityForResult(lIntent,VIDEO_VIEW_ACTIVITY); startActivityForResult(lIntent,requestCode);
} }
public void registrationState(final LinphoneCore lc, final LinphoneProxyConfig cfg,final LinphoneCore.RegistrationState state,final String smessage) {/*nop*/}; public void registrationState(final LinphoneCore lc, final LinphoneProxyConfig cfg,final LinphoneCore.RegistrationState state,final String smessage) {/*nop*/};
@ -445,7 +447,12 @@ public class DialerActivity extends Activity implements LinphoneCoreListener {
exitCallMode(); exitCallMode();
} else if (state == LinphoneCall.State.StreamsRunning) { } else if (state == LinphoneCall.State.StreamsRunning) {
if (LinphoneService.instance().getLinphoneCore().getCurrentCall().getCurrentParamsReadOnly().getVideoEnabled()) { if (LinphoneService.instance().getLinphoneCore().getCurrentCall().getCurrentParamsReadOnly().getVideoEnabled()) {
startVideoView(); startVideoView(VIDEO_VIEW_ACTIVITY);
}
} else if (state == LinphoneCall.State.CallUpdated) {
if (LinphoneService.instance().getLinphoneCore().getCurrentCall().getCurrentParamsReadOnly().getVideoEnabled()) {
AndroidCameraRecord.invalidateParameters();
finishActivity(VIDEO_VIEW_ACTIVITY);
} }
} }
} }
@ -520,17 +527,14 @@ public class DialerActivity extends Activity implements LinphoneCoreListener {
routeAudioToSpeaker(); routeAudioToSpeaker();
// Privacy setting to not share the user camera by default // Privacy setting to not share the user camera by default
boolean prefVideoEnable = getPref(getApplicationContext().getString(R.string.pref_video_enable_key)); boolean prefVideoEnable = mPref.getBoolean(getString(R.string.pref_video_enable_key), false);
boolean prefAutomaticallyShareMyCamera = getPref(getApplicationContext().getString(R.string.pref_video_automatically_share_my_video_key)); boolean prefAutomaticallyShareMyCamera = mPref.getBoolean(getString(R.string.pref_video_automatically_share_my_video_key), false);
AndroidCameraRecord.setMuteCamera(!(prefVideoEnable && prefAutomaticallyShareMyCamera)); AndroidCameraRecord.setMuteCamera(!(prefVideoEnable && prefAutomaticallyShareMyCamera));
} }
public void newOutgoingCall(String aTo) { public void newOutgoingCall(String aTo) {
newOutgoingCall(aTo,null); newOutgoingCall(aTo,null);
} }
private boolean getPref(String key) {
return PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).getBoolean(key, false);
}
public synchronized void newOutgoingCall(String aTo, String displayName) { public synchronized void newOutgoingCall(String aTo, String displayName) {
String lto = aTo; String lto = aTo;
@ -559,20 +563,21 @@ public class DialerActivity extends Activity implements LinphoneCoreListener {
try { try {
LinphoneCallParams lParams = lLinphoneCore.createDefaultCallParameters().copy(); LinphoneCallParams lParams = lLinphoneCore.createDefaultCallParameters().copy();
boolean prefVideoEnable = getPref(getApplicationContext().getString(R.string.pref_video_enable_key)); boolean prefVideoEnable = mPref.getBoolean(getString(R.string.pref_video_enable_key), false);
boolean prefInitiateWithVideo = getPref(getApplicationContext().getString(R.string.pref_video_initiate_call_with_video_key)); boolean prefInitiateWithVideo = mPref.getBoolean(getString(R.string.pref_video_initiate_call_with_video_key), false);
if (prefVideoEnable && prefInitiateWithVideo && lParams.getVideoEnabled()) { if (prefVideoEnable && prefInitiateWithVideo && lParams.getVideoEnabled()) {
lParams.setVideoEnalbled(true); AndroidCameraRecord.setMuteCamera(false);
lParams.setVideoEnabled(true);
lLinphoneCore.inviteAddressWithParams(lAddress, lParams); lLinphoneCore.inviteAddressWithParams(lAddress, lParams);
} else { } else {
lParams.setVideoEnalbled(false); lParams.setVideoEnabled(false);
lLinphoneCore.inviteAddressWithParams(lAddress, lParams); lLinphoneCore.inviteAddressWithParams(lAddress, lParams);
} }
} catch (LinphoneCoreException e) { } catch (LinphoneCoreException e) {
Toast toast = Toast.makeText(DialerActivity.this Toast toast = Toast.makeText(DialerActivity.this
,String.format(getString(R.string.error_cannot_get_call_parameters),mAddress.getText().toString()) ,String.format(getString(R.string.error_cannot_get_call_parameters),mAddress.getText().toString())
, Toast.LENGTH_LONG); ,Toast.LENGTH_LONG);
toast.show(); toast.show();
return; return;
} }

View file

@ -21,18 +21,20 @@ package org.linphone;
import org.linphone.core.AndroidCameraRecord; import org.linphone.core.AndroidCameraRecord;
import org.linphone.core.LinphoneCore;
import android.app.Activity; import android.app.Activity;
import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log; import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.SurfaceView; import android.view.SurfaceView;
import android.view.View;
import android.widget.FrameLayout;
public class VideoCallActivity extends Activity { public class VideoCallActivity extends Activity {
SurfaceView mVideoView; SurfaceView mVideoView;
SurfaceView mVideoCaptureView; SurfaceView mVideoCaptureView;
private static boolean firstLaunch = true;
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -46,25 +48,97 @@ public class VideoCallActivity extends Activity {
final int rotation = getWindowManager().getDefaultDisplay().getRotation(); final int rotation = getWindowManager().getDefaultDisplay().getRotation();
AndroidCameraRecord.setOrientationCode(rotation); AndroidCameraRecord.setOrientationCode(rotation);
if (!firstLaunch) workaroundCapturePreviewHiddenOnSubsequentRotations();
AndroidCameraRecord.setSurfaceView(mVideoCaptureView); AndroidCameraRecord.setSurfaceView(mVideoCaptureView);
firstLaunch = false; mVideoCaptureView.setZOrderOnTop(true);
} }
private void rewriteToggleCameraItem(MenuItem item) {
if (AndroidCameraRecord.getCameraMuted()) {
item.setTitle(getString(R.string.menu_videocall_toggle_camera_enable));
} else {
item.setTitle(getString(R.string.menu_videocall_toggle_camera_disable));
}
}
private void workaroundCapturePreviewHiddenOnSubsequentRotations() { private void rewriteChangeResolutionItem(MenuItem item) {
View view = findViewById(R.id.video_frame); switch (BandwidthManager.getInstance().getCurrentProfile()) {
if (view == null) { case BandwidthManager.HIGH_RESOLUTION:
Log.e("Linphone", "Android BUG: video frame not found; mix with landscape???"); item.setTitle(getString(R.string.menu_videocall_change_resolution_when_high_resolution));
return; break;
case BandwidthManager.LOW_RESOLUTION:
item.setTitle(getString(R.string.menu_videocall_change_resolution_when_low_resolution));
break;
default:
throw new RuntimeException("Current profile is unknown " + BandwidthManager.getInstance().getCurrentProfile());
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the currently selected menu XML resource.
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.videocall_activity_menu, menu);
rewriteToggleCameraItem(menu.findItem(R.id.videocall_menu_toggle_camera));
rewriteChangeResolutionItem(menu.findItem(R.id.videocall_menu_change_resolution));
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.videocall_menu_back_to_dialer:
finish();
break;
case R.id.videocall_menu_change_resolution:
BandwidthManager manager = BandwidthManager.getInstance();
switch (manager.getCurrentProfile()) {
case BandwidthManager.HIGH_RESOLUTION:
manager.changeTo(BandwidthManager.LOW_RESOLUTION);
break;
case BandwidthManager.LOW_RESOLUTION:
manager.changeTo(BandwidthManager.HIGH_RESOLUTION);
break;
default:
throw new RuntimeException("Current profile is unknown " + manager.getCurrentProfile());
}
rewriteChangeResolutionItem(item);
break;
case R.id.videocall_menu_terminate_call:
LinphoneCore lLinphoneCore = LinphoneService.instance().getLinphoneCore();
if (lLinphoneCore.isIncall()) {
lLinphoneCore.terminateCall(lLinphoneCore.getCurrentCall());
}
finish();
break;
case R.id.videocall_menu_toggle_camera:
AndroidCameraRecord.toggleMute();
rewriteToggleCameraItem(item);
break;
default:
Log.e(LinphoneService.TAG, "Unknown menu item ["+item+"]");
break;
} }
FrameLayout frame = (FrameLayout) view; return false;
frame.removeAllViews(); }
frame.addView(mVideoCaptureView); protected void startprefActivity() {
frame.addView(mVideoView); Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setClass(this, LinphonePreferencesActivity.class);
startActivity(intent);
} }
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
}
} }

View file

@ -18,10 +18,14 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/ */
package org.linphone.core; package org.linphone.core;
import java.util.ArrayList;
import java.util.List;
import android.hardware.Camera; import android.hardware.Camera;
import android.hardware.Camera.ErrorCallback; import android.hardware.Camera.ErrorCallback;
import android.hardware.Camera.Parameters; import android.hardware.Camera.Parameters;
import android.hardware.Camera.PreviewCallback; import android.hardware.Camera.PreviewCallback;
import android.hardware.Camera.Size;
import android.os.Build; import android.os.Build;
import android.util.Log; import android.util.Log;
import android.view.SurfaceHolder; import android.view.SurfaceHolder;
@ -44,8 +48,9 @@ public abstract class AndroidCameraRecord {
private static boolean previewStarted; private static boolean previewStarted;
private static boolean parametersSet; private static boolean parametersSet;
protected static int orientationCode; protected static int orientationCode;
private static boolean mute; private static boolean muted;
private static final String tag="Linphone"; private static final String tag="Linphone";
private static List <Size> supportedVideoSizes;
public AndroidCameraRecord() { public AndroidCameraRecord() {
// TODO check if another instance is loaded and kill it. // TODO check if another instance is loaded and kill it.
@ -66,7 +71,7 @@ public abstract class AndroidCameraRecord {
* It will start automatically * It will start automatically
*/ */
private void startPreview() { private void startPreview() {
if (mute) { if (muted) {
Log.d(tag, "Not starting preview as camera has been muted"); Log.d(tag, "Not starting preview as camera has been muted");
return; return;
} }
@ -100,6 +105,9 @@ public abstract class AndroidCameraRecord {
Camera.Parameters parameters=camera.getParameters(); Camera.Parameters parameters=camera.getParameters();
if (supportedVideoSizes == null) {
supportedVideoSizes = camera.getParameters().getSupportedPreviewSizes();
}
parameters.setPreviewSize(width, height); parameters.setPreviewSize(width, height);
parameters.setPreviewFrameRate(fps); parameters.setPreviewFrameRate(fps);
@ -174,6 +182,13 @@ public abstract class AndroidCameraRecord {
} }
private static void stopPreview() {
camera.setPreviewCallback(null); // TODO check if used whatever the SDK version
camera.stopPreview();
camera.release();
camera=null;
previewStarted = false;
}
public static final void setSurfaceView(final SurfaceView sv) { public static final void setSurfaceView(final SurfaceView sv) {
SurfaceHolder holder = sv.getHolder(); SurfaceHolder holder = sv.getHolder();
@ -187,11 +202,7 @@ public abstract class AndroidCameraRecord {
Log.e(tag, "Video capture: illegal state: surface destroyed but camera is already null"); Log.e(tag, "Video capture: illegal state: surface destroyed but camera is already null");
return; return;
} }
camera.setPreviewCallback(null); // TODO check if used whatever the SDK version stopPreview();
camera.stopPreview();
camera.release();
camera=null;
previewStarted = false;
Log.w(tag, "Video capture Surface destroyed"); Log.w(tag, "Video capture Surface destroyed");
} }
@ -235,19 +246,45 @@ public abstract class AndroidCameraRecord {
} }
public static void setMuteCamera(boolean m) { public static void setMuteCamera(boolean m) {
if (m == mute) return; if (m == muted) return;
mute = m; muted = m;
if (mute && previewStarted) { if (muted && previewStarted) {
camera.stopPreview(); stopPreview();
return; return;
} }
if (!mute) { if (!muted) {
instance.startPreview(); instance.startPreview();
} }
} }
public static void toggleMute() {
setMuteCamera(!muted);
}
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();
}
} }

View file

@ -66,6 +66,4 @@ class LinphoneCallImpl implements LinphoneCall {
public LinphoneCallParams getCurrentParamsReadWrite() { public LinphoneCallParams getCurrentParamsReadWrite() {
return getCurrentParamsReadOnly().copy(); return getCurrentParamsReadOnly().copy();
} }
} }

View file

@ -34,7 +34,7 @@ public class LinphoneCallParamsImpl implements LinphoneCallParams {
return getVideoEnabled(nativePtr); return getVideoEnabled(nativePtr);
} }
public void setVideoEnalbled(boolean b) { public void setVideoEnabled(boolean b) {
enableVideo(nativePtr, b); enableVideo(nativePtr, b);
} }

View file

@ -81,7 +81,10 @@ class LinphoneCoreImpl implements LinphoneCore {
private native String getStunServer(long nativePtr); private native String getStunServer(long nativePtr);
private native long createDefaultCallParams(long nativePtr); private native long createDefaultCallParams(long nativePtr);
private native int updateCall(long ptrLc, long ptrCall, long ptrParams); 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 static String TAG = "LinphoneCore"; private static String TAG = "LinphoneCore";
@ -379,4 +382,27 @@ class LinphoneCoreImpl implements LinphoneCore {
return updateCall(nativePtr, ptrCall, ptrParams); 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;
}
} }