video output rotation

* Change camera settings and reinvite on screen rotation.
* Apply rotation in the JNI layer

Change-Id: I48fe8f5eb47287f964012be416875a656993e912
Tuleap: #566
This commit is contained in:
Adrien Béraud
2016-04-12 18:06:11 -04:00
committed by gerrit2
parent 1231a71284
commit 8d6a48376f
6 changed files with 232 additions and 64 deletions

View File

@ -29,8 +29,13 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
import android.hardware.display.DisplayManager;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Build;
import android.support.v4.app.NotificationManagerCompat;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
@ -112,6 +117,8 @@ public class CallFragment extends Fragment implements CallInterface {
private ViewGroup rootView = null;
private boolean ongoingCall = false;
private DisplayManager.DisplayListener displayListener;
@Override
public void onAttach(Activity activity) {
Log.i(TAG, "onAttach");
@ -156,15 +163,37 @@ public class CallFragment extends Fragment implements CallInterface {
setHasOptionsMenu(true);
PowerManager powerManager = (PowerManager) getActivity().getSystemService(Context.POWER_SERVICE);
mScreenWakeLock = powerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE,
"cx.ring.onIncomingCall");
mScreenWakeLock = powerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE, "cx.ring.onIncomingCall");
mScreenWakeLock.setReferenceCounted(false);
Log.d(TAG, "Acquire wake up lock");
if (mScreenWakeLock != null && !mScreenWakeLock.isHeld()) {
mScreenWakeLock.acquire();
}
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
displayListener = new DisplayManager.DisplayListener() {
@Override
public void onDisplayAdded(int displayId) {}
@Override
public void onDisplayRemoved(int displayId) {}
@Override
public void onDisplayChanged(int displayId) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
try {
mCallbacks.getRemoteService().switchInput(getConference().getId(), lastVideoSource);
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
};
}
setRetainInstance(true);
}
@ -354,10 +383,16 @@ public class CallFragment extends Fragment implements CallInterface {
@Override
public void onStop() {
super.onStop();
Conference c = getConference();
Log.w(TAG, "onStop() haveVideo="+haveVideo);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
DisplayManager displayManager = (DisplayManager) getActivity().getSystemService(Context.DISPLAY_SERVICE);
displayManager.unregisterDisplayListener(displayListener);
}
DRingService.videoSurfaces.remove(c.getId());
DRingService.mCameraPreviewSurface.clear();
try {
@ -369,13 +404,17 @@ public class CallFragment extends Fragment implements CallInterface {
} catch (RemoteException e) {
e.printStackTrace();
}
super.onStop();
}
@Override
public void onStart() {
super.onStart();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
DisplayManager displayManager = (DisplayManager) getActivity().getSystemService(Context.DISPLAY_SERVICE);
displayManager.registerDisplayListener(displayListener, null);
}
Conference c = getConference();
if (c != null && video != null && c.resumeVideo) {
Log.w(TAG, "Resuming video");
@ -571,6 +610,7 @@ public class CallFragment extends Fragment implements CallInterface {
public void onConfigurationChanged(Configuration newConfig) {
if (videoPreview.getVisibility() == View.VISIBLE) {
try {
mCallbacks.getRemoteService().setPreviewSettings();
mCallbacks.getRemoteService().videoPreviewSurfaceAdded();
} catch (RemoteException e) {
e.printStackTrace();

View File

@ -28,6 +28,7 @@ package cx.ring.service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.hardware.Camera;
import android.media.AudioManager;
import android.os.Handler;
@ -230,41 +231,66 @@ public class DRingService extends Service {
this.height = height;
this.rate = rate;
}
public VideoParams(VideoParams p) {
this.id = p.id;
this.format = p.format;
this.width = p.width;
this.height = p.height;
this.rate = p.rate;
}
public int id;
public int format;
// size as captured by Android
public int width;
public int height;
//size, rotated, as seen by the daemon
public int rot_width;
public int rot_height;
public int rate;
public int rotation;
}
public int setCameraDisplayOrientation(int cameraId, android.hardware.Camera camera) {
android.hardware.Camera.CameraInfo info =
new android.hardware.Camera.CameraInfo();
android.hardware.Camera.getCameraInfo(cameraId, info);
WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
int rotation = windowManager.getDefaultDisplay().getRotation();
int degrees = 0;
switch (rotation) {
case Surface.ROTATION_0: degrees = 0; break;
case Surface.ROTATION_90: degrees = 90; break;
case Surface.ROTATION_180: degrees = 180; break;
case Surface.ROTATION_270: degrees = 270; break;
static public int rotationToDegrees(int r) {
switch (r) {
case Surface.ROTATION_0: return 0;
case Surface.ROTATION_90: return 90;
case Surface.ROTATION_180: return 180;
case Surface.ROTATION_270: return 270;
}
int result;
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
result = (info.orientation + degrees) % 360;
result = (360 - result) % 360; // compensate the mirror
} else { // back-facing
result = (info.orientation - degrees + 360) % 360;
}
camera.setDisplayOrientation(result);
return result;
return 0;
}
public void startCapture(VideoParams p) {
public void setVideoRotation(VideoParams p, Camera.CameraInfo info) {
WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
int rotation = rotationToDegrees(windowManager.getDefaultDisplay().getRotation());
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
p.rotation = (info.orientation + rotation + 360) % 360;
} else {
p.rotation = (info.orientation - rotation + 360) % 360;
}
}
public void setCameraDisplayOrientation(int cam_id, android.hardware.Camera camera) {
android.hardware.Camera.CameraInfo info =
new android.hardware.Camera.CameraInfo();
android.hardware.Camera.getCameraInfo(cam_id, info);
WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
int rotation = rotationToDegrees(windowManager.getDefaultDisplay().getRotation());
int result;
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
result = (info.orientation + rotation) % 360;
result = (360 - result) % 360; // compensate the mirror
} else { // back-facing
result = (info.orientation - rotation + 360) % 360;
}
camera.setDisplayOrientation(result);
}
public void startCapture(final VideoParams p) {
stopCapture();
SurfaceHolder surface = mCameraPreviewSurface.get();
@ -281,12 +307,12 @@ public class DRingService extends Service {
Log.w(TAG, "startCapture: no video parameters ");
return;
}
Log.w(TAG, "startCapture " + p.id);
Log.d(TAG, "startCapture " + p.id);
final Camera preview;
try {
preview = Camera.open(p.id);
p.rotation = setCameraDisplayOrientation(p.id, preview);
setCameraDisplayOrientation(p.id, preview);
} catch (Exception e) {
Log.e(TAG, e.getMessage());
return;
@ -317,7 +343,15 @@ public class DRingService extends Service {
Log.e(TAG, e.getMessage());
}
preview.setPreviewCallback(videoManagerCallback);
preview.setPreviewCallback(new Camera.PreviewCallback() {
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
long ptr = RingserviceJNI.obtainFrame(data.length);
if (ptr != 0)
RingserviceJNI.setVideoFrame(data, data.length, ptr, p.width, p.height, p.rotation);
RingserviceJNI.releaseFrame(ptr);
}
});
preview.setErrorCallback(new Camera.ErrorCallback() {
@Override
public void onError(int error, Camera cam) {
@ -334,19 +368,19 @@ public class DRingService extends Service {
Intent intent = new Intent(VIDEO_EVENT);
intent.putExtra("camera", p.id == 1);
intent.putExtra("started", true);
boolean invert = p.rotation == 90 || p.rotation == 270;
intent.putExtra("width", invert ? p.height : p.width);
intent.putExtra("height", invert ? p.width : p.height);
intent.putExtra("width", p.rot_width);
intent.putExtra("height", p.rot_height);
sendBroadcast(intent);
}
public void stopCapture() {
Log.w(TAG, "stopCapture " + previewCamera);
Log.d(TAG, "stopCapture " + previewCamera);
if (previewCamera != null) {
final Camera preview = previewCamera;
final VideoParams p = previewParams;
previewCamera = null;
preview.setPreviewCallback(null);
preview.setErrorCallback(null);
preview.stopPreview();
preview.release();
@ -1390,7 +1424,7 @@ public class DRingService extends Service {
public void videoSurfaceAdded(String id)
{
Log.i(TAG, "DRingService.videoSurfaceAdded() " + id);
Log.d(TAG, "DRingService.videoSurfaceAdded() " + id);
Shm shm = videoInputs.get(id);
SurfaceHolder holder = videoSurfaces.get(id).get();
if (shm != null && holder != null && shm.window == 0)
@ -1399,7 +1433,7 @@ public class DRingService extends Service {
public void videoSurfaceRemoved(String id)
{
Log.i(TAG, "DRingService.videoSurfaceRemoved() " + id);
Log.d(TAG, "DRingService.videoSurfaceRemoved() " + id);
Shm shm = videoInputs.get(id);
if (shm != null)
stopVideo(shm);
@ -1419,13 +1453,26 @@ public class DRingService extends Service {
getExecutor().execute(new SipRunnable() {
@Override
protected void doRun() throws SameThreadException, RemoteException {
String uri = "camera://" + (front ? videoManagerCallback.cameraFront : videoManagerCallback.cameraBack);
int cam_id = (front ? videoManagerCallback.cameraFront : videoManagerCallback.cameraBack);
String uri = "camera://" + cam_id;
Log.i(TAG, "DRingService.switchInput() " + uri);
Ringservice.applySettings(id, videoManagerCallback.getNativeParams(cam_id).toMap(getResources().getConfiguration().orientation));
Ringservice.switchInput(id, uri);
}
});
}
public void setPreviewSettings() {
getExecutor().execute(new SipRunnable() {
@Override
protected void doRun() throws SameThreadException, RemoteException {
for (int i=0, n=Camera.getNumberOfCameras(); i<n; i++) {
Ringservice.applySettings(Integer.toString(i), videoManagerCallback.getNativeParams(i).toMap(getResources().getConfiguration().orientation));
}
}
});
}
public int exportAccounts(final List accountIDs, final String toDir, final String password) {
return getExecutor().executeAndReturn(new SipRunnableWithReturn<Integer>() {
@Override

View File

@ -82,6 +82,7 @@ interface IDRingService {
void attendedTransfer(in String transferID, in String targetID);
/* Video */
void setPreviewSettings();
void switchInput(in String call, in boolean front);
void videoSurfaceAdded(in String call);
void videoSurfaceRemoved(in String call);

View File

@ -182,12 +182,12 @@ public class LocalService extends Service implements SharedPreferences.OnSharedP
contact = findContactByNumber(call.getNumberUri());
Conversation conv = startConversation(contact);
try {
mService.setPreviewSettings();
SipUri number = call.getNumberUri();
if (number == null || number.isEmpty())
number = contact.getPhones().get(0).getNumber();
String callId = mService.placeCall(call.getAccount(), number.getUriString());
if (callId == null || callId.isEmpty()) {
//CallActivity.this.terminateCall();
return null;
}
call.setCallID(callId);
@ -1335,6 +1335,12 @@ public class LocalService extends Service implements SharedPreferences.OnSharedP
toAdd.showCallNotification(LocalService.this);
updateAudioState();
try {
mService.setPreviewSettings();
} catch (RemoteException e) {
e.printStackTrace();
}
sendBroadcast(new Intent(ACTION_CONF_UPDATE));
break;
}

View File

@ -19,21 +19,37 @@
*/
package cx.ring.service;
import android.content.res.Configuration;
import android.hardware.Camera;
import android.util.Log;
import android.view.SurfaceHolder;
import android.util.LongSparseArray;
import android.graphics.Point;
import java.io.IOException;
import java.util.HashMap;
public class VideoManagerCallback extends VideoCallback implements Camera.PreviewCallback
public class VideoManagerCallback extends VideoCallback
{
private static final String TAG = VideoManagerCallback.class.getSimpleName();
private final DRingService mService;
private final LongSparseArray<DeviceParams> native_params = new LongSparseArray<>();
private final HashMap<String, DRingService.VideoParams> params = new HashMap<>();
class DeviceParams {
Point size;
long rate;
Camera.CameraInfo infos;
public StringMap toMap(int orientation) {
StringMap p = new StringMap();
boolean rotated = (size.x > size.y) == (orientation == Configuration.ORIENTATION_PORTRAIT);
p.set("size", Integer.toString(rotated ? size.y : size.x) + "x" + Integer.toString(rotated ? size.x : size.y));
p.set("rate", Long.toString(rate));
return p;
}
}
public int cameraFront = 0;
public int cameraBack = 0;
@ -56,6 +72,10 @@ public class VideoManagerCallback extends VideoCallback implements Camera.Previe
RingserviceJNI.setDefaultDevice(Integer.toString(cameraFront));
}
DeviceParams getNativeParams(int i) {
return native_params.get(i);
}
@Override
public void decodingStarted(String id, String shm_path, int w, int h, boolean is_mixer) {
mService.decodingStarted(id, shm_path, w, h, is_mixer);
@ -66,17 +86,14 @@ public class VideoManagerCallback extends VideoCallback implements Camera.Previe
mService.decodingStopped(id);
}
public void onPreviewFrame(byte[] data, Camera camera) {
int ptr = RingserviceJNI.obtainFrame(data.length);
if (ptr != 0)
RingserviceJNI.setVideoFrame(data, data.length, ptr);
RingserviceJNI.releaseFrame(ptr);
}
public void setParameters(String camid, int format, int width, int height, int rate) {
int id = Integer.valueOf(camid);
DRingService.VideoParams p = new DRingService.VideoParams(id, format, width, height, rate);
params.put(camid, p);
DeviceParams p = native_params.get(id);
DRingService.VideoParams new_params = new DRingService.VideoParams(id, format, p.size.x, p.size.y, rate);
new_params.rot_width = width;
new_params.rot_height = height;
mService.setVideoRotation(new_params, p.infos);
params.put(camid, new_params);
}
public void startCapture(String camid) {
@ -106,15 +123,25 @@ public class VideoManagerCallback extends VideoCallback implements Camera.Previe
return;
}
Camera.CameraInfo camInfo = new Camera.CameraInfo();
Camera.getCameraInfo(id, camInfo);
Camera.Parameters param = cam.getParameters();
cam.release();
getFormats(param, formats);
getSizes(param, sizes);
DeviceParams p = new DeviceParams();
p.size = getSizeToUse(param);
sizes.add(p.size.x);
sizes.add(p.size.y);
sizes.add(p.size.y);
sizes.add(p.size.x);
getRates(param, rates);
p.rate = rates.get(0);
p.infos = new Camera.CameraInfo();
Camera.getCameraInfo(id, p.infos);
native_params.put(id, p);
}
private int getNumberOfCameras() {
@ -127,7 +154,7 @@ public class VideoManagerCallback extends VideoCallback implements Camera.Previe
}
}
private void getSizes(Camera.Parameters param, UintVect sizes) {
private Point getSizeToUse(Camera.Parameters param) {
int sw = 1280, sh = 720;
for(Camera.Size s : param.getSupportedPreviewSizes()) {
if (s.width < sw) {
@ -136,8 +163,7 @@ public class VideoManagerCallback extends VideoCallback implements Camera.Previe
}
}
Log.d(TAG, "Supported size: " + sw + " x " + sh);
sizes.add(sw);
sizes.add(sh);
return new Point(sw, sh);
}
private void getRates(Camera.Parameters param, UintVect rates_) {

View File

@ -50,9 +50,55 @@ public:
std::map<ANativeWindow*, std::unique_ptr<DRing::FrameBuffer>> windows {};
std::mutex windows_mutex;
JNIEXPORT void JNICALL Java_cx_ring_service_RingserviceJNI_setVideoFrame(JNIEnv *jenv, jclass jcls, void * jarg1, jint jarg2, jlong jarg3)
std::vector<uint8_t> workspace;
void rotateNV21(std::vector<uint8_t>& input, unsigned width, unsigned height, int rotation, uint8_t* output)
{
jenv->GetByteArrayRegion(jarg1, 0, jarg2, jarg3);
if (rotation == 0) {
std::copy_n(input.begin(), input.size(), output);
return;
}
if (rotation % 90 != 0 || rotation < 0 || rotation > 270) {
__android_log_print(ANDROID_LOG_ERROR, "videomanager.i", "%u %u %d", width, height, rotation);
return;
}
unsigned frameSize = width * height;
bool swap = rotation % 180 != 0;
bool xflip = rotation % 270 != 0;
bool yflip = rotation >= 180;
unsigned wOut = swap ? height : width;
unsigned hOut = swap ? width : height;
for (unsigned j = 0; j < height; j++) {
for (unsigned i = 0; i < width; i++) {
unsigned yIn = j * width + i;
unsigned uIn = frameSize + (j >> 1) * width + (i & ~1);
unsigned vIn = uIn + 1;
unsigned iSwapped = swap ? j : i;
unsigned jSwapped = swap ? i : j;
unsigned iOut = xflip ? wOut - iSwapped - 1 : iSwapped;
unsigned jOut = yflip ? hOut - jSwapped - 1 : jSwapped;
unsigned yOut = jOut * wOut + iOut;
unsigned uOut = frameSize + (jOut >> 1) * wOut + (iOut & ~1);
unsigned vOut = uOut + 1;
output[yOut] = input[yIn];
output[uOut] = input[uIn];
output[vOut] = input[vIn];
}
}
return output;
}
JNIEXPORT void JNICALL Java_cx_ring_service_RingserviceJNI_setVideoFrame(JNIEnv *jenv, jclass jcls, void* frame, int frame_size, jlong target, int w, int h, int rotation)
{
uint8_t* f_target = (uint8_t*) ((intptr_t) target);
if (rotation == 0)
jenv->GetByteArrayRegion(frame, 0, frame_size, f_target);
else {
workspace.resize(frame_size);
jenv->GetByteArrayRegion(frame, 0, frame_size, workspace.data());
rotateNV21(workspace, w, h, rotation, f_target);
}
}
JNIEXPORT jlong JNICALL Java_cx_ring_service_RingserviceJNI_acquireNativeWindow(JNIEnv *jenv, jclass jcls, jobject javaSurface)
@ -60,7 +106,7 @@ JNIEXPORT jlong JNICALL Java_cx_ring_service_RingserviceJNI_acquireNativeWindow(
return (jlong)(intptr_t)ANativeWindow_fromSurface(jenv, javaSurface);
}
JNIEXPORT jlong JNICALL Java_cx_ring_service_RingserviceJNI_releaseNativeWindow(JNIEnv *jenv, jclass jcls, jlong window_)
JNIEXPORT void JNICALL Java_cx_ring_service_RingserviceJNI_releaseNativeWindow(JNIEnv *jenv, jclass jcls, jlong window_)
{
std::lock_guard<std::mutex> guard(windows_mutex);
ANativeWindow *window = (ANativeWindow*)((intptr_t) window_);
@ -162,7 +208,7 @@ JNIEXPORT void JNICALL Java_cx_ring_service_RingserviceJNI_unregisterVideoCallba
}
%}
%native(setVideoFrame) void setVideoFrame(void *, int, long);
%native(setVideoFrame) void setVideoFrame(void*, int, jlong, int, int, int);
%native(acquireNativeWindow) jlong acquireNativeWindow(jobject);
%native(releaseNativeWindow) void releaseNativeWindow(jlong);
%native(setNativeWindowGeometry) void setNativeWindowGeometry(jlong, int, int);
@ -180,11 +226,13 @@ void stopCamera();
bool hasCameraStarted();
bool switchInput(const std::string& resource);
bool switchToCamera();
std::map<std::string, std::string> getSettings(const std::string& name);
void applySettings(const std::string& name, const std::map<std::string, std::string>& settings);
void addVideoDevice(const std::string &node);
void removeVideoDevice(const std::string &node);
long obtainFrame(int length);
void releaseFrame(long frame);
uintptr_t obtainFrame(int length);
void releaseFrame(uintptr_t frame);
void registerSinkTarget(const std::string& sinkId, const DRing::SinkTarget& target);
}