VideoSourceWrapper.java

  1package eu.siacs.conversations.xmpp.jingle;
  2
  3import android.content.Context;
  4import android.util.Log;
  5
  6import com.google.common.collect.ImmutableSet;
  7import com.google.common.collect.Iterables;
  8import com.google.common.util.concurrent.ListenableFuture;
  9import com.google.common.util.concurrent.SettableFuture;
 10
 11import org.webrtc.Camera2Enumerator;
 12import org.webrtc.CameraEnumerationAndroid;
 13import org.webrtc.CameraEnumerator;
 14import org.webrtc.CameraVideoCapturer;
 15import org.webrtc.EglBase;
 16import org.webrtc.PeerConnectionFactory;
 17import org.webrtc.SurfaceTextureHelper;
 18import org.webrtc.VideoSource;
 19
 20import java.util.ArrayList;
 21import java.util.Collections;
 22import java.util.Set;
 23
 24import javax.annotation.Nullable;
 25
 26import eu.siacs.conversations.Config;
 27
 28class VideoSourceWrapper {
 29
 30    private static final int CAPTURING_RESOLUTION = 1920;
 31    private static final int CAPTURING_MAX_FRAME_RATE = 30;
 32
 33    private final CameraVideoCapturer cameraVideoCapturer;
 34    private final CameraEnumerationAndroid.CaptureFormat captureFormat;
 35    private final Set<String> availableCameras;
 36    private boolean isFrontCamera = false;
 37    private VideoSource videoSource;
 38
 39    VideoSourceWrapper(
 40            CameraVideoCapturer cameraVideoCapturer,
 41            CameraEnumerationAndroid.CaptureFormat captureFormat,
 42            Set<String> cameras) {
 43        this.cameraVideoCapturer = cameraVideoCapturer;
 44        this.captureFormat = captureFormat;
 45        this.availableCameras = cameras;
 46    }
 47
 48    private int getFrameRate() {
 49        return Math.max(
 50                captureFormat.framerate.min,
 51                Math.min(CAPTURING_MAX_FRAME_RATE, captureFormat.framerate.max));
 52    }
 53
 54    public void initialize(
 55            final PeerConnectionFactory peerConnectionFactory,
 56            final Context context,
 57            final EglBase.Context eglBaseContext) {
 58        final SurfaceTextureHelper surfaceTextureHelper =
 59                SurfaceTextureHelper.create("webrtc", eglBaseContext);
 60        this.videoSource = peerConnectionFactory.createVideoSource(false);
 61        this.cameraVideoCapturer.initialize(
 62                surfaceTextureHelper, context, this.videoSource.getCapturerObserver());
 63    }
 64
 65    public VideoSource getVideoSource() {
 66        final VideoSource videoSource = this.videoSource;
 67        if (videoSource == null) {
 68            throw new IllegalStateException("VideoSourceWrapper was not initialized");
 69        }
 70        return videoSource;
 71    }
 72
 73    public void startCapture() {
 74        final int frameRate = getFrameRate();
 75        Log.d(
 76                Config.LOGTAG,
 77                String.format(
 78                        "start capturing at %dx%d@%d",
 79                        captureFormat.width, captureFormat.height, frameRate));
 80        this.cameraVideoCapturer.startCapture(captureFormat.width, captureFormat.height, frameRate);
 81    }
 82
 83    public void stopCapture() throws InterruptedException {
 84        this.cameraVideoCapturer.stopCapture();
 85    }
 86
 87    public void dispose() {
 88        this.cameraVideoCapturer.dispose();
 89        if (this.videoSource != null) {
 90            this.videoSource.dispose();
 91        }
 92    }
 93
 94    public ListenableFuture<Boolean> switchCamera() {
 95        final SettableFuture<Boolean> future = SettableFuture.create();
 96        this.cameraVideoCapturer.switchCamera(
 97                new CameraVideoCapturer.CameraSwitchHandler() {
 98                    @Override
 99                    public void onCameraSwitchDone(final boolean isFrontCamera) {
100                        VideoSourceWrapper.this.isFrontCamera = isFrontCamera;
101                        future.set(isFrontCamera);
102                    }
103
104                    @Override
105                    public void onCameraSwitchError(final String message) {
106                        future.setException(
107                                new IllegalStateException(
108                                        String.format("Unable to switch camera %s", message)));
109                    }
110                });
111        return future;
112    }
113
114    public boolean isFrontCamera() {
115        return this.isFrontCamera;
116    }
117
118    public boolean isCameraSwitchable() {
119        return this.availableCameras.size() > 1;
120    }
121
122    public static class Factory {
123        final Context context;
124
125        public Factory(final Context context) {
126            this.context = context;
127        }
128
129        public VideoSourceWrapper create() {
130            final CameraEnumerator enumerator = new Camera2Enumerator(context);
131            final Set<String> deviceNames = ImmutableSet.copyOf(enumerator.getDeviceNames());
132            for (final String deviceName : deviceNames) {
133                if (isFrontFacing(enumerator, deviceName)) {
134                    final VideoSourceWrapper videoSourceWrapper =
135                            of(enumerator, deviceName, deviceNames);
136                    if (videoSourceWrapper == null) {
137                        return null;
138                    }
139                    videoSourceWrapper.isFrontCamera = true;
140                    return videoSourceWrapper;
141                }
142            }
143            if (deviceNames.size() == 0) {
144                return null;
145            } else {
146                return of(enumerator, Iterables.get(deviceNames, 0), deviceNames);
147            }
148        }
149
150        @Nullable
151        private VideoSourceWrapper of(
152                final CameraEnumerator enumerator,
153                final String deviceName,
154                final Set<String> availableCameras) {
155            final CameraVideoCapturer capturer = enumerator.createCapturer(deviceName, null);
156            if (capturer == null) {
157                return null;
158            }
159            final ArrayList<CameraEnumerationAndroid.CaptureFormat> choices =
160                    new ArrayList<>(enumerator.getSupportedFormats(deviceName));
161            Collections.sort(choices, (a, b) -> b.width - a.width);
162            for (final CameraEnumerationAndroid.CaptureFormat captureFormat : choices) {
163                if (captureFormat.width <= CAPTURING_RESOLUTION) {
164                    return new VideoSourceWrapper(capturer, captureFormat, availableCameras);
165                }
166            }
167            return null;
168        }
169
170        private static boolean isFrontFacing(
171                final CameraEnumerator cameraEnumerator, final String deviceName) {
172            try {
173                return cameraEnumerator.isFrontFacing(deviceName);
174            } catch (final NullPointerException e) {
175                return false;
176            }
177        }
178    }
179}