Skip to content
Snippets Groups Projects
Select Git revision
  • c61e5444d44413f019aa10c5acf0d8cacbd08f9e
  • master default protected
  • release/202005
  • release/202001
  • release/201912
  • release/windows-test/201910
  • release/201908
  • release/201906
  • release/201905
  • release/201904
  • release/201903
  • release/201902
  • release/201901
  • release/201812
  • release/201811
  • release/201808
  • wip/patches_poly_2017/cedryk_doucet/abderahmane_bouziane
  • releases/beta1
  • android/release_464
  • android/release_463
  • android/release_462
  • android/release_461
  • android/release_460
  • android/release_459
  • android/release_458
  • android/release_457
  • android/release_456
  • android/release_455
  • android/release_454
  • android/release_453
  • android/release_452
  • android/release_451
  • android/release_450
  • android/release_449
  • android/release_448
  • android/release_447
  • android/release_446
  • android/release_445
38 results

HardwareServiceImpl.java

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    HardwareServiceImpl.java 20.42 KiB
    /*
     *  Copyright (C) 2017 Savoir-faire Linux Inc.
     *
     *  Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
     *
     *  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 3 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., 675 Mass Ave, Cambridge, MA 02139, USA.
     */
    package cx.ring.services;
    
    import android.content.Context;
    import android.content.res.Configuration;
    import android.graphics.ImageFormat;
    import android.graphics.Point;
    import android.hardware.Camera;
    import android.media.AudioManager;
    import android.os.Build;
    import android.support.annotation.Nullable;
    import android.util.LongSparseArray;
    import android.view.Surface;
    import android.view.SurfaceHolder;
    import android.view.WindowManager;
    
    import java.io.IOException;
    import java.lang.ref.WeakReference;
    import java.util.Collections;
    import java.util.HashMap;
    import java.util.Map;
    
    import cx.ring.daemon.IntVect;
    import cx.ring.daemon.StringMap;
    import cx.ring.daemon.UintVect;
    import cx.ring.model.ServiceEvent;
    import cx.ring.utils.Log;
    
    public class HardwareServiceImpl extends HardwareService {
    
        public static final String TAG = HardwareServiceImpl.class.getName();
        private static WeakReference<SurfaceHolder> mCameraPreviewSurface = new WeakReference<>(null);
        private static Map<String, WeakReference<SurfaceHolder>> videoSurfaces = Collections.synchronizedMap(new HashMap<String, WeakReference<SurfaceHolder>>());
        private final Map<String, Shm> videoInputs = new HashMap<>();
        private final HashMap<String, VideoParams> mParams = new HashMap<>();
        private final LongSparseArray<DeviceParams> mNativeParams = new LongSparseArray<>();
        private Context mContext;
        private int cameraFront = 0;
        private int cameraBack = 0;
        private int currentCamera = -1;
        private VideoParams previewParams = null;
        private Camera previewCamera = null;
    
        public HardwareServiceImpl(Context mContext) {
            this.mContext = mContext;
        }
    
        public void initVideo() {
            Log.i(TAG, "initVideo()");
            mNativeParams.clear();
            int numberCameras = Camera.getNumberOfCameras();
            Camera.CameraInfo camInfo = new Camera.CameraInfo();
            for (int i = 0; i < numberCameras; i++) {
                addVideoDevice(Integer.toString(i));
                Camera.getCameraInfo(i, camInfo);
                if (camInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
                    cameraFront = i;
                } else {
                    cameraBack = i;
                }
            }
            currentCamera = cameraFront;
            setDefaultVideoDevice(Integer.toString(cameraFront));
        }
    
        @Override
        public boolean isSpeakerPhoneOn() {
            AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
            return audioManager.isSpeakerphoneOn();
        }
    
        @Override
        public void switchSpeakerPhone() {
            AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
            audioManager.setSpeakerphoneOn(!audioManager.isSpeakerphoneOn());
        }
    
        public void decodingStarted(String id, String shmPath, int width, int height, boolean isMixer) {
            Log.i(TAG, "decodingStarted() " + id + " " + width + "x" + height);
            Shm shm = new Shm();
            shm.id = id;
            shm.w = width;
            shm.h = height;
            videoInputs.put(id, shm);
            WeakReference<SurfaceHolder> weakSurfaceHolder = videoSurfaces.get(id);
            if (weakSurfaceHolder != null) {
                SurfaceHolder holder = weakSurfaceHolder.get();
                if (holder != null) {
                    shm.window = startVideo(id, holder.getSurface(), width, height);
    
                    if (shm.window == 0) {
                        Log.i(TAG, "DRingService.decodingStarted() no window !");
    
                        ServiceEvent event = new ServiceEvent(ServiceEvent.EventType.VIDEO_EVENT);
                        event.addEventInput(ServiceEvent.EventInput.VIDEO_START, true);
                        setChanged();
                        notifyObservers(event);
                        return;
                    }
    
                    ServiceEvent event = new ServiceEvent(ServiceEvent.EventType.VIDEO_EVENT);
                    event.addEventInput(ServiceEvent.EventInput.VIDEO_CALL, shm.id);
                    event.addEventInput(ServiceEvent.EventInput.VIDEO_STARTED, true);
                    event.addEventInput(ServiceEvent.EventInput.VIDEO_WIDTH, shm.w);
                    event.addEventInput(ServiceEvent.EventInput.VIDEO_HEIGHT, shm.h);
                    setChanged();
                    notifyObservers(event);
                }
            }
        }
    
        @Override
        public void decodingStopped(String id, String shmPath, boolean isMixer) {
            Log.i(TAG, "decodingStopped() " + id);
            Shm shm = videoInputs.remove(id);
            if (shm == null) {
                return;
            }
            if (shm.window != 0) {
                try {
                    stopVideo(shm.id, shm.window);
                } catch (Exception e) {
                    Log.e(TAG, "decodingStopped error" + e);
                }
                shm.window = 0;
            }
        }
    
        @Override
        public void getCameraInfo(String camId, IntVect formats, UintVect sizes, UintVect rates) {
            Log.d(TAG, "getCameraInfo: " + camId);
            int id = Integer.valueOf(camId);
    
            if (id < 0 || id >= Camera.getNumberOfCameras()) {
                return;
            }
    
            Camera cam;
            try {
                cam = Camera.open(id);
            } catch (Exception e) {
                Log.d(TAG, e.getMessage());
                return;
            }
    
            Camera.Parameters param = cam.getParameters();
            cam.release();
    
            for (int fmt : param.getSupportedPreviewFormats()) {
                formats.add(fmt);
            }
    
            DeviceParams p = new DeviceParams();
    
            // Use a larger resolution for Android 6.0+, 64 bits devices
            final boolean useLargerSize = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Build.SUPPORTED_64_BIT_ABIS.length > 0;
            int MIN_WIDTH = useLargerSize ? 640 : 320;
            Point size = new Point(0, 0);
            /** {@link Camera.Parameters#getSupportedPreviewSizes} :
             * "This method will always return a list with at least one element."
             * Attempt to find the size with width closest (but above) MIN_WIDTH. */
            for (Camera.Size s : param.getSupportedPreviewSizes()) {
                if (s.width < s.height) {
                    continue;
                }
                if (size.x < MIN_WIDTH ? s.width > size.x : (s.width >= MIN_WIDTH && s.width < size.x)) {
                    size.x = s.width;
                    size.y = s.height;
                }
            }
    
            p.size = size;
    
            sizes.add(p.size.x);
            sizes.add(p.size.y);
            sizes.add(p.size.y);
            sizes.add(p.size.x);
    
            for (int fps[] : param.getSupportedPreviewFpsRange()) {
                int rate = (fps[Camera.Parameters.PREVIEW_FPS_MIN_INDEX] + fps[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]) / 2;
                rates.add(rate);
            }
            p.rate = rates.get(0);
            Log.d(TAG, "getCameraInfo: using resolution " + p.size.x + "x" + p.size.y + " " + p.rate / 1000 + " FPS");
    
            p.infos = new Camera.CameraInfo();
            Camera.getCameraInfo(id, p.infos);
    
            mNativeParams.put(id, p);
        }
    
        @Override
        public void setParameters(String camId, int format, int width, int height, int rate) {
            Log.d(TAG, "setParameters: " + camId + ", " + format + ", " + width + ", " + height + ", " + rate);
            int id = Integer.valueOf(camId);
            DeviceParams deviceParams = mNativeParams.get(id);
            VideoParams newParams = new VideoParams(id, format, deviceParams.size.x, deviceParams.size.y, rate);
            newParams.rotWidth = width;
            newParams.rotHeight = height;
            WindowManager windowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
            int rotation = rotationToDegrees(windowManager.getDefaultDisplay().getRotation());
            if (deviceParams.infos.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
                newParams.rotation = (deviceParams.infos.orientation + rotation + 360) % 360;
            } else {
                newParams.rotation = (deviceParams.infos.orientation - rotation + 360) % 360;
            }
            mParams.put(camId, newParams);
        }
    
        @Override
        public void startCapture(@Nullable String camId) {
            VideoParams videoParams;
    
            if (camId == null && previewParams != null) {
                videoParams = previewParams;
            } else if (camId != null) {
                videoParams = mParams.get(camId);
            } else if (mParams.size() == 2) {
                currentCamera = cameraFront;
                videoParams = mParams.get(cameraFront);
            } else {
                currentCamera = cameraBack;
                videoParams = mParams.get(cameraBack);
            }
    
            SurfaceHolder surface = mCameraPreviewSurface.get();
            if (surface == null) {
                Log.w(TAG, "Can't start capture: no surface registered.");
                previewParams = videoParams;
    
                ServiceEvent event = new ServiceEvent(ServiceEvent.EventType.VIDEO_EVENT);
                event.addEventInput(ServiceEvent.EventInput.VIDEO_START, true);
                setChanged();
                notifyObservers(event);
                return;
            }
    
            if (videoParams == null) {
                Log.w(TAG, "startCapture: no video parameters ");
                return;
            }
            Log.d(TAG, "startCapture " + videoParams.id + " " + videoParams.width + "x" + videoParams.height + " rot" + videoParams.rotation);
    
            final Camera preview;
            try {
                if (previewCamera != null) {
                    previewCamera.release();
                    previewCamera = null;
                }
                preview = Camera.open(videoParams.id);
                setCameraDisplayOrientation(videoParams.id, preview);
            } catch (Exception e) {
                Log.e(TAG, "Camera.open: " + e.getMessage());
                return;
            }
    
            try {
                surface.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
                preview.setPreviewDisplay(surface);
            } catch (IOException e) {
                Log.e(TAG, "setPreviewDisplay: " + e.getMessage());
                return;
            }
    
            Camera.Parameters parameters = preview.getParameters();
            parameters.setPreviewFormat(videoParams.format);
            parameters.setPreviewSize(videoParams.width, videoParams.height);
            parameters.setRotation(0);
    
            for (int[] fps : parameters.getSupportedPreviewFpsRange()) {
                if (videoParams.rate >= fps[Camera.Parameters.PREVIEW_FPS_MIN_INDEX] &&
                        videoParams.rate <= fps[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]) {
                    parameters.setPreviewFpsRange(fps[Camera.Parameters.PREVIEW_FPS_MIN_INDEX],
                            fps[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);
                }
            }
    
            try {
                preview.setParameters(parameters);
            } catch (RuntimeException e) {
                Log.e(TAG, "Error while settings preview parameters", e);
            }
    
            final int videoWidth = videoParams.width;
            final int heigth = videoParams.height;
            final int rotation = videoParams.rotation;
    
            preview.setPreviewCallbackWithBuffer(new Camera.PreviewCallback() {
                @Override
                public void onPreviewFrame(byte[] data, Camera camera) {
                    setVideoFrame(data, videoWidth, heigth, rotation);
                    preview.addCallbackBuffer(data);
                }
            });
    
            // enqueue first buffer
            int bufferSize = parameters.getPreviewSize().width * parameters.getPreviewSize().height * ImageFormat.getBitsPerPixel(parameters.getPreviewFormat()) / 8;
            preview.addCallbackBuffer(new byte[bufferSize]);
    
            preview.setErrorCallback(new Camera.ErrorCallback() {
                @Override
                public void onError(int error, Camera cam) {
                    Log.w(TAG, "Camera onError " + error);
                    if (preview == cam) {
                        stopCapture();
                    }
                }
            });
            try {
                preview.startPreview();
            } catch (RuntimeException e) {
                Log.e(TAG, "startPreview: " + e.getMessage());
                return;
            }
    
            previewCamera = preview;
            previewParams = videoParams;
    
            ServiceEvent event = new ServiceEvent(ServiceEvent.EventType.VIDEO_EVENT);
            event.addEventInput(ServiceEvent.EventInput.VIDEO_STARTED, true);
            event.addEventInput(ServiceEvent.EventInput.VIDEO_WIDTH, videoParams.rotWidth);
            event.addEventInput(ServiceEvent.EventInput.VIDEO_HEIGHT, videoParams.rotHeight);
            setChanged();
            notifyObservers(event);
        }
    
        @Override
        public void stopCapture() {
            Log.d(TAG, "stopCapture " + previewCamera);
            if (previewCamera != null) {
                final Camera preview = previewCamera;
                final VideoParams params = previewParams;
                previewCamera = null;
                try {
                    preview.setPreviewCallback(null);
                    preview.setErrorCallback(null);
                    preview.stopPreview();
                    preview.release();
                } catch (Exception e) {
                    Log.e(TAG, "stopCapture error" + e);
                }
    
                ServiceEvent event = new ServiceEvent(ServiceEvent.EventType.VIDEO_EVENT);
                event.addEventInput(ServiceEvent.EventInput.VIDEO_STARTED, false);
                event.addEventInput(ServiceEvent.EventInput.VIDEO_WIDTH, params.width);
                event.addEventInput(ServiceEvent.EventInput.VIDEO_HEIGHT, params.height);
                setChanged();
                notifyObservers(event);
            }
        }
    
        @Override
        public void addVideoSurface(String id, Object holder) {
            if (!(holder instanceof SurfaceHolder)) {
                return;
            }
    
            Log.w(TAG, "addVideoSurface " + id + holder.hashCode());
    
            Shm shm = videoInputs.get(id);
            WeakReference<SurfaceHolder> surfaceHolder = new WeakReference<>((SurfaceHolder) holder);
            videoSurfaces.put(id, surfaceHolder);
            if (shm != null && shm.window == 0) {
                shm.window = startVideo(shm.id, surfaceHolder.get().getSurface(), shm.w, shm.h);
            }
    
            if (shm == null || shm.window == 0) {
                Log.i(TAG, "DRingService.addVideoSurface() no window !");
    
                ServiceEvent event = new ServiceEvent(ServiceEvent.EventType.VIDEO_EVENT);
                event.addEventInput(ServiceEvent.EventInput.VIDEO_START, true);
                setChanged();
                notifyObservers(event);
                return;
            }
    
            ServiceEvent event = new ServiceEvent(ServiceEvent.EventType.VIDEO_EVENT);
            event.addEventInput(ServiceEvent.EventInput.VIDEO_CALL, shm.id);
            event.addEventInput(ServiceEvent.EventInput.VIDEO_STARTED, true);
            event.addEventInput(ServiceEvent.EventInput.VIDEO_WIDTH, shm.w);
            event.addEventInput(ServiceEvent.EventInput.VIDEO_HEIGHT, shm.h);
            setChanged();
            notifyObservers(event);
    
        }
    
        @Override
        public void addPreviewVideoSurface(Object holder) {
            if (!(holder instanceof SurfaceHolder)) {
                return;
            }
    
            Log.w(TAG, "addPreviewVideoSurface " + holder.hashCode());
    
            mCameraPreviewSurface = new WeakReference<>((SurfaceHolder) holder);
        }
    
        @Override
        public void removeVideoSurface(String id) {
            Log.i(TAG, "removeVideoSurface " + id);
            Shm shm = videoInputs.get(id);
            if (shm == null) {
                return;
            }
            if (shm.window != 0) {
                try {
                    stopVideo(shm.id, shm.window);
                } catch (Exception e) {
                    Log.e(TAG, "removeVideoSurface error" + e);
                }
    
                shm.window = 0;
            }
    
            ServiceEvent event = new ServiceEvent(ServiceEvent.EventType.VIDEO_EVENT);
            event.addEventInput(ServiceEvent.EventInput.VIDEO_CALL, shm.id);
            event.addEventInput(ServiceEvent.EventInput.VIDEO_STARTED, false);
            setChanged();
            notifyObservers(event);
        }
    
        @Override
        public void removePreviewVideoSurface() {
            Log.w(TAG, "removePreviewVideoSurface");
            mCameraPreviewSurface.clear();
        }
    
        @Override
        public void switchInput(String id) {
            Log.w(TAG, "switchInput " + id);
    
            final int camId;
            if (currentCamera == cameraBack) {
                camId = cameraFront;
            } else {
                camId = cameraBack;
            }
            currentCamera = camId;
    
            final String uri = "camera://" + camId;
            final StringMap map = mNativeParams.get(camId).toMap(mContext.getResources().getConfiguration().orientation);
            this.switchInput(id, uri, map);
        }
    
        @Override
        public void restartCamera(String id) {
            stopCapture();
            setPreviewSettings();
            final String uri = "camera://" + currentCamera;
            final StringMap map = mNativeParams.get(currentCamera).toMap(mContext.getResources().getConfiguration().orientation);
            this.switchInput(id, uri, map);
        }
    
        @Override
        public void setPreviewSettings() {
            Map<String, StringMap> camSettings = new HashMap<>();
            for (int i = 0; i < Camera.getNumberOfCameras(); i++) {
                if (mNativeParams.get(i) != null) {
                    camSettings.put(Integer.toString(i), mNativeParams.get(i).toMap(mContext.getResources().getConfiguration().orientation));
                    Log.w(TAG, "setPreviewSettings camera:" + Integer.toString(i));
                }
            }
            this.setPreviewSettings(camSettings);
        }
    
        @Override
        public int getCameraCount() {
            return Camera.getNumberOfCameras();
        }
    
        @Override
        public boolean isPreviewFromFrontCamera() {
            return Camera.getNumberOfCameras() == 1 || currentCamera == cameraFront;
        }
    
        private void setCameraDisplayOrientation(int camId, Camera camera) {
            Camera.CameraInfo info = new Camera.CameraInfo();
            Camera.getCameraInfo(camId, info);
            WindowManager windowManager = (WindowManager) mContext.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);
            Log.w(TAG, "setCameraDisplayOrientation " + Integer.toString(rotation) + " " + Integer.toString(result));
        }
    
        private int rotationToDegrees(int rotation) {
            switch (rotation) {
                case Surface.ROTATION_0:
                    return 0;
                case Surface.ROTATION_90:
                    return 90;
                case Surface.ROTATION_180:
                    return 180;
                case Surface.ROTATION_270:
                    return 270;
            }
            return 0;
        }
    
        private static class Shm {
            String id;
            int w, h;
            long window = 0;
        }
    
        private static class VideoParams {
            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 rotWidth;
            public int rotHeight;
            public int rate;
            public int rotation;
            public VideoParams(int id, int format, int width, int height, int rate) {
                this.id = id;
                this.format = format;
                this.width = width;
                this.height = height;
                this.rate = rate;
            }
        }
    
        private static class DeviceParams {
            Point size;
            long rate;
            Camera.CameraInfo infos;
    
            public StringMap toMap(int orientation) {
                StringMap map = new StringMap();
                boolean rotated = (size.x > size.y) == (orientation == Configuration.ORIENTATION_PORTRAIT);
                map.set("size", Integer.toString(rotated ? size.y : size.x) + "x" + Integer.toString(rotated ? size.x : size.y));
                map.set("rate", Long.toString(rate));
                return map;
            }
        }
    }