android: rework mediastreamer Java API

parent 1c691461
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.linphone"
package="org.linphone.mediastream"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="3" />
......
......@@ -4,7 +4,7 @@
android:id="@+id/video_frame" android:orientation="vertical"
android:layout_height="fill_parent" android:layout_width="fill_parent">
<org.linphone.mediastream.GL2JNIView android:layout_height="fill_parent" android:layout_width="fill_parent" android:id="@+id/video_surface"></org.linphone.mediastream.GL2JNIView >
<org.linphone.mediastream.video.display.GL2JNIView android:layout_height="fill_parent" android:layout_width="fill_parent" android:id="@+id/video_surface"></org.linphone.mediastream.video.display.GL2JNIView >
<SurfaceView android:layout_height="72dip" android:layout_width="88dip" android:id="@+id/video_capture_surface" android:layout_gravity="right|bottom"
android:layout_margin="15dip"></SurfaceView>
</FrameLayout>
......
......@@ -3,11 +3,11 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/video_frame"
android:layout_height="fill_parent" android:layout_width="fill_parent">
<org.linphone.mediastream.GL2JNIView
<org.linphone.mediastream.video.display.GL2JNIView
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:id="@+id/video_surface">
</org.linphone.mediastream.GL2JNIView>
</org.linphone.mediastream.video.display.GL2JNIView>
<SurfaceView
android:layout_height="88dip"
......
/*
Hacks.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.media.AudioManager;
import android.os.Build;
public final class Hacks {
private Hacks() {}
public static boolean isGalaxySOrTabWithFrontCamera() {
return isGalaxySOrTab() && !isGalaxySOrTabWithoutFrontCamera();
}
private static boolean isGalaxySOrTabWithoutFrontCamera() {
return isSC02B() || isSGHI896();
}
public static boolean isGalaxySOrTab() {
return isGalaxyS() || isGalaxyTab();
}
public static boolean isGalaxyTab() {
return isGTP1000();
}
private static boolean isGalaxyS() {
return isGT9000() || isSC02B() || isSGHI896() || isSPHD700();
}
public static final boolean hasTwoCamerasRear0Front1() {
return isSPHD700() || isADR6400();
}
// HTC
private static final boolean isADR6400() {
return Build.MODEL.startsWith("ADR6400") || Build.DEVICE.startsWith("ADR6400");
} // HTC Thunderbolt
// Galaxy S variants
private static final boolean isSPHD700() {return Build.DEVICE.startsWith("SPH-D700");} // Epic
private static boolean isSGHI896() {return Build.DEVICE.startsWith("SGH-I896");} // Captivate
private static boolean isGT9000() {return Build.DEVICE.startsWith("GT-I9000");} // Galaxy S
private static boolean isSC02B() {return Build.DEVICE.startsWith("SC-02B");} // Docomo
private static boolean isGTP1000() {return Build.DEVICE.startsWith("GT-P1000");} // Tab
/* private static final boolean log(final String msg) {
Log.d(msg);
return true;
}*/
/* Not working as now
* Calling from Galaxy S to PC is "usable" even with no hack; other side is not even with this one*/
public static void galaxySSwitchToCallStreamUnMuteLowerVolume(AudioManager am) {
// Switch to call audio channel (Galaxy S)
am.setSpeakerphoneOn(false);
sleep(200);
// Lower volume
am.setStreamVolume(AudioManager.STREAM_VOICE_CALL, 1, 0);
// Another way to select call channel
am.setMode(AudioManager.MODE_NORMAL);
sleep(200);
// Mic is muted if not doing this
am.setMicrophoneMute(true);
sleep(200);
am.setMicrophoneMute(false);
sleep(200);
}
public static final void sleep(int time) {
try {
Thread.sleep(time);
} catch(InterruptedException ie){}
}
public static void dumpDeviceInformation() {
StringBuilder sb = new StringBuilder(" ==== Phone information dump ====\n");
sb.append("DEVICE=").append(Build.DEVICE).append("\n");
sb.append("MODEL=").append(Build.MODEL).append("\n");
//MANUFACTURER doesn't exist in android 1.5.
//sb.append("MANUFACTURER=").append(Build.MANUFACTURER).append("\n");
sb.append("SDK=").append(Build.VERSION.SDK);
Log.i(sb.toString());
}
public static boolean needSoftvolume() {
return isGalaxySOrTab();
}
public static boolean needRoutingAPI() {
return Version.sdkStrictlyBelow(5) /*<donut*/;
}
public static boolean needGalaxySAudioHack() {
return isGalaxySOrTab() && !isSC02B();
}
public static boolean needPausingCallForSpeakers() {
// return false;
return isGalaxySOrTab() && !isSC02B();
}
public static boolean hasTwoCameras() {
return isSPHD700() || isGalaxySOrTabWithFrontCamera();
}
public static boolean hasCamera() {
if (Version.sdkAboveOrEqual(Version.API09_GINGERBREAD_23)) {
int nb = 0;
try {
nb = (Integer) Camera.class.getMethod("getNumberOfCameras", (Class[])null).invoke(null);
} catch (Exception e) {
Log.e("Error getting number of cameras");
}
return nb > 0;
}
Log.i("Hack: considering there IS a camera.\n"
+ "If it is not the case, report DEVICE and MODEL to linphone-users@nongnu.org");
return true;
}
}
/*
Version.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.os.Build;
/**
* Centralize version access and allow simulation of lower versions.
* @author Guillaume Beraudo
*/
public class Version {
public static final int API03_CUPCAKE_15 = 3;
public static final int API04_DONUT_16 = 4;
public static final int API06_ECLAIR_20 = 6;
public static final int API07_ECLAIR_21 = 7;
public static final int API08_FROYO_22 = 8;
public static final int API09_GINGERBREAD_23 = 9;
public static final int API11_HONEYCOMB_30 = 11;
private static boolean nativeHasZrtp() { return false; }
private static boolean nativeHasNeon() { return true; }
private static Boolean hasNeon;
private static final int buildVersion =
Integer.parseInt(Build.VERSION.SDK);
// 8; // 2.2
// 7; // 2.1
public static final boolean sdkAboveOrEqual(int value) {
return buildVersion >= value;
}
public static final boolean sdkStrictlyBelow(int value) {
return buildVersion < value;
}
public static int sdk() {
return buildVersion;
}
public static boolean isArmv7() {
try {
return sdkAboveOrEqual(4)
&& Build.class.getField("CPU_ABI").get(null).toString().startsWith("armeabi-v7");
} catch (Throwable e) {}
return false;
}
public static boolean hasNeon(){
if (hasNeon == null) hasNeon = nativeHasNeon();
return hasNeon;
}
public static boolean isVideoCapable() {
return !Version.sdkStrictlyBelow(5) && Version.hasNeon() && Hacks.hasCamera();
}
public static boolean hasZrtp(){
return nativeHasZrtp();
}
public static void dumpCapabilities(){
StringBuilder sb = new StringBuilder(" ==== Capabilities dump ====\n");
sb.append("Has neon: ").append(Boolean.toString(hasNeon())).append("\n");
sb.append("Has ZRTP: ").append(Boolean.toString(hasZrtp())).append("\n");
Log.i(sb.toString());
}
}
/*
VideoSize.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;
/**
* @author Guillaume Beraudo
*/
public final class VideoSize {
public static final int QCIF = 0;
public static final int CIF = 1;
public static final int HVGA = 2;
public static final int QVGA = 3;
public int width;
public int height;
public VideoSize() {}
public VideoSize(int width, int height) {
this.width = width;
this.height = height;
}
public static final VideoSize createStandard(int code, boolean inverted) {
switch (code) {
case QCIF:
return inverted? new VideoSize(144, 176) : new VideoSize(176, 144);
case CIF:
return inverted? new VideoSize(288, 352) : new VideoSize(352, 288);
case HVGA:
return inverted? new VideoSize(320,480) : new VideoSize(480, 320);
case QVGA:
return inverted? new VideoSize(240, 320) : new VideoSize(320, 240);
default:
return new VideoSize(); // Invalid one
}
}
public boolean isValid() {
return width > 0 && height > 0;
}
// Generated
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + height;
result = prime * result + width;
return result;
}
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
VideoSize other = (VideoSize) obj;
if (height != other.height)
return false;
if (width != other.width)
return false;
return true;
}
public String toString() {
return "width = "+width + " height = " + height;
}
public boolean isPortrait() {
return height >= width;
}
public VideoSize createInverted() {
return new VideoSize(height, width);
}
}
package org.linphone;
package org.linphone.mediastream;
import java.util.ArrayList;
import java.util.List;
import org.linphone.core.AndroidVideoWindowImpl;
import org.linphone.mediastream.video.AndroidVideoWindowImpl;
import org.linphone.mediastream.video.capture.AndroidVideoApi8JniWrapper;
import org.linphone.mediastream.video.capture.AndroidVideoApi9JniWrapper;
import android.app.Activity;
import android.hardware.Camera;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.opengl.GLSurfaceView;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
......@@ -21,8 +20,7 @@ import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class MediastreamerActivity extends Activity implements
SensorEventListener {
public class MediastreamerActivity extends Activity {
native int runMediaStream(int argc, String[] argv);
native int stopMediaStream();
......@@ -36,7 +34,13 @@ public class MediastreamerActivity extends Activity implements
native void changeCamera(int newCameraId);
Thread msThread;
int cameraId;
int cameraId = 0;
String videoCodec = VP8_MIME_TYPE;
static String VP8_MIME_TYPE = "VP8-DRAFT-0-3-2";
static String H264_MIME_TYPE = "H264";
static String MPEG4_MIME_TYPE = "MP4V-ES";
private static void loadOptionalLibrary(String s) {
try {
......@@ -57,34 +61,6 @@ public class MediastreamerActivity extends Activity implements
System.loadLibrary("mediastreamer2");
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the currently selected menu XML resource.
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.videocall_activity_menu, menu);
if (Camera.getNumberOfCameras() == 1) {
menu.findItem(R.id.videocall_menu_change_camera).setVisible(false);
}
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.videocall_menu_exit:
this.finish();
break;
case R.id.videocall_menu_change_camera:
cameraId = (cameraId + 1) % Camera.getNumberOfCameras();
changeCamera(cameraId);
setVideoPreviewWindowId(findViewById(R.id.video_capture_surface));
break;
}
return true;
}
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
......@@ -92,8 +68,13 @@ public class MediastreamerActivity extends Activity implements
/* declare layout */
setContentView(R.layout.main);
cameraId = 0;
if (Build.VERSION.SDK_INT >= 9)
AndroidVideoApi9JniWrapper.setAndroidSdkVersion(Build.VERSION.SDK_INT);
else if (Build.VERSION.SDK_INT >= 8)
AndroidVideoApi8JniWrapper.setAndroidSdkVersion(Build.VERSION.SDK_INT);
//else
// AndroidVideoApi5JniWrapper.setAndroidSdkVersion(Build.VERSION.SDK_INT);
Log.i("ms", "Mediastreamer starting !");
/* retrieve preview surface */
......@@ -108,36 +89,33 @@ public class MediastreamerActivity extends Activity implements
view.setZOrderOnTop(false);
previewSurface.setZOrderOnTop(true);
/* register callback, allowing us to use preview surface when ready */
holder.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
setVideoPreviewWindowId(previewSurface);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format,
int width, int height) {
// ...
}
});
/* instanciate object responsible of video rendering */
AndroidVideoWindowImpl mVideoWindow = new AndroidVideoWindowImpl(view);
AndroidVideoWindowImpl mVideoWindow = new AndroidVideoWindowImpl(this, view, previewSurface);
mVideoWindow
.setListener(new AndroidVideoWindowImpl.VideoWindowListener() {
public void onSurfaceDestroyed(AndroidVideoWindowImpl vw) {
// setVideoWindowId(null);
.setListener(new AndroidVideoWindowImpl.VideoWindowListener() {
public void onVideoPreviewSurfaceReady(AndroidVideoWindowImpl vw) {
setVideoPreviewWindowId(previewSurface);
};
@Override
public void onVideoPreviewSurfaceDestroyed(
AndroidVideoWindowImpl vw) {
}
public void onSurfaceReady(AndroidVideoWindowImpl vw) {
public void onVideoRenderingSurfaceDestroyed(AndroidVideoWindowImpl vw) {};
public void onVideoRenderingSurfaceReady(AndroidVideoWindowImpl vw) {
setVideoWindowId(vw);
// set device rotation too
onSensorChanged(null);
setDeviceRotation(rotationToAngle(getWindowManager().getDefaultDisplay()
.getRotation()));
}
@Override
public void onDeviceOrientationChanged(
int rotationDegrees) {
setDeviceRotation(rotationDegrees);
}
});
......@@ -148,7 +126,7 @@ public class MediastreamerActivity extends Activity implements
args.add("--remote");
args.add("127.0.0.1:4000");
args.add("--payload");
args.add("103");
args.add("video/" + videoCodec + "/90000");
args.add("--camera");
args.add("Android0");
......@@ -174,37 +152,6 @@ public class MediastreamerActivity extends Activity implements
msThread.start();
}
@Override
protected void onResume() {
super.onResume();
SensorManager mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
Sensor mAccelerometer = mSensorManager
.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mSensorManager.registerListener(this, mAccelerometer,
SensorManager.SENSOR_DELAY_NORMAL);
}
@Override
protected void onPause() {
super.onPause();
((SensorManager) getSystemService(SENSOR_SERVICE))
.unregisterListener(this);
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
@Override
public void onSensorChanged(SensorEvent event) {
int rot = rotationToAngle(getWindowManager().getDefaultDisplay()
.getRotation());
// Returning rotation FROM ITS NATURAL ORIENTATION
setDeviceRotation(rot);
}
@Override
protected void onDestroy() {
stopMediaStream();
......@@ -216,6 +163,37 @@ public class MediastreamerActivity extends Activity implements
Log.d("ms", "MediastreamerActivity destroyed");
super.onDestroy();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the currently selected menu XML resource.
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.videocall_activity_menu, menu);
if (Build.VERSION.SDK_INT < 9)
menu.findItem(R.id.videocall_menu_change_camera).setVisible(false);
else {
if (Camera.getNumberOfCameras() == 1)
menu.findItem(R.id.videocall_menu_change_camera).setVisible(false);
}
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.videocall_menu_exit:
this.finish();
break;
case R.id.videocall_menu_change_camera:
cameraId = (cameraId + 1) % Camera.getNumberOfCameras();
changeCamera(cameraId);
setVideoPreviewWindowId(findViewById(R.id.video_capture_surface));
break;
}
return true;
}
static int rotationToAngle(int r) {
switch (r) {
......
......@@ -16,7 +16,7 @@ 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;
package org.linphone.mediastream;
import static android.util.Log.DEBUG;
import static android.util.Log.ERROR;
......@@ -28,7 +28,7 @@ import static android.util.Log.WARN;
*
* @author Guillaume Beraudo
*/
public final class Log {
public final class MediastreamerLog {
public static final String TAG = "Linphone";
private static final boolean useIsLoggable = false;
......
package org.linphone.core;
package org.linphone.mediastream.video;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import org.linphone.OpenGLESDisplay;
import org.linphone.mediastream.video.display.OpenGLESDisplay;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Bitmap.Config;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.opengl.GLSurfaceView;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
......@@ -16,38 +22,61 @@ import android.view.Surface.OutOfResourcesException;
import android.view.SurfaceHolder.Callback;
public class AndroidVideoWindowImpl {
private SurfaceView mVideoRenderingView;
private SurfaceView mVideoPreviewView;
private boolean useGLrendering;
private Bitmap mBitmap;
private SurfaceView mView;
private Surface mSurface;
private VideoWindowListener mListener;
private Renderer renderer;
/**
* Utility listener interface providing callback for Android events
* useful to Mediastreamer.
*/
public static interface VideoWindowListener{
void onSurfaceReady(AndroidVideoWindowImpl vw);
void onSurfaceDestroyed(AndroidVideoWindowImpl vw);
void onVideoRenderingSurfaceReady(AndroidVideoWindowImpl vw);
void onVideoRenderingSurfaceDestroyed(AndroidVideoWindowImpl vw);
void onVideoPreviewSurfaceReady(AndroidVideoWindowImpl vw);
void onVideoPreviewSurfaceDestroyed(AndroidVideoWindowImpl vw);
void onDeviceOrientationChanged(int newRotationDegrees);
};
public AndroidVideoWindowImpl(SurfaceView view){
useGLrendering = (view instanceof GLSurfaceView);
mView=view;
mBitmap=null;
mSurface=null;
mListener=null;
view.getHolder().addCallback(new Callback(){
/**
* @param renderingSurface Surface created by the application that will be used to render decoded video stream
* @param previewSurface Surface created by the application used by Android's Camera preview framework
*/
public AndroidVideoWindowImpl(final Activity activity, SurfaceView renderingSurface, SurfaceView previewSurface) {
mVideoRenderingView = renderingSurface;
mVideoPreviewView = previewSurface;
useGLrendering = (renderingSurface instanceof GLSurfaceView);
mBitmap = null;
mSurface = null;
mListener = null;
// register callback for rendering surface events
mVideoRenderingView.getHolder().addCallback(new Callback(){
public void surfaceChanged(SurfaceHolder holder, int format,
int width, int height) {
Log.i("Surface is being changed.");
Log.i("mediastream", "Video display surface is being changed.");
if (!useGLrendering) {
synchronized(AndroidVideoWindowImpl.this){
mBitmap=Bitmap.createBitmap(width,height,Config.RGB_565);
mSurface=holder.getSurface();
}
}
if (mListener!=null) mListener.onSurfaceReady(AndroidVideoWindowImpl.this);
Log.w("Video display surface changed");
if (mListener!=null) mListener.onVideoRenderingSurfaceReady(AndroidVideoWindowImpl.this);
Log.w("mediastream", "Video display surface changed");
}
public void surfaceCreated(SurfaceHolder holder) {
Log.w("Video display surface created");
Log.w("mediastream", "Video display surface created");
}