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            dispose(this.videoSource);
 91        }
 92    }
 93
 94    private static void dispose(final VideoSource videoSource) {
 95        try {
 96            videoSource.dispose();
 97        } catch (final IllegalStateException e) {
 98            Log.e(Config.LOGTAG, "unable to dispose video source", e);
 99        }
100    }
101
102    public ListenableFuture<Boolean> switchCamera() {
103        final SettableFuture<Boolean> future = SettableFuture.create();
104        this.cameraVideoCapturer.switchCamera(
105                new CameraVideoCapturer.CameraSwitchHandler() {
106                    @Override
107                    public void onCameraSwitchDone(final boolean isFrontCamera) {
108                        VideoSourceWrapper.this.isFrontCamera = isFrontCamera;
109                        future.set(isFrontCamera);
110                    }
111
112                    @Override
113                    public void onCameraSwitchError(final String message) {
114                        future.setException(
115                                new IllegalStateException(
116                                        String.format("Unable to switch camera %s", message)));
117                    }
118                });
119        return future;
120    }
121
122    public boolean isFrontCamera() {
123        return this.isFrontCamera;
124    }
125
126    public boolean isCameraSwitchable() {
127        return this.availableCameras.size() > 1;
128    }
129
130    public static class Factory {
131        final Context context;
132
133        public Factory(final Context context) {
134            this.context = context;
135        }
136
137        public VideoSourceWrapper create() {
138            final CameraEnumerator enumerator = new Camera2Enumerator(context);
139            final Set<String> deviceNames = ImmutableSet.copyOf(enumerator.getDeviceNames());
140            for (final String deviceName : deviceNames) {
141                if (isFrontFacing(enumerator, deviceName)) {
142                    final VideoSourceWrapper videoSourceWrapper =
143                            of(enumerator, deviceName, deviceNames);
144                    if (videoSourceWrapper == null) {
145                        return null;
146                    }
147                    videoSourceWrapper.isFrontCamera = true;
148                    return videoSourceWrapper;
149                }
150            }
151            if (deviceNames.size() == 0) {
152                return null;
153            } else {
154                return of(enumerator, Iterables.get(deviceNames, 0), deviceNames);
155            }
156        }
157
158        @Nullable
159        private VideoSourceWrapper of(
160                final CameraEnumerator enumerator,
161                final String deviceName,
162                final Set<String> availableCameras) {
163            final CameraVideoCapturer capturer = enumerator.createCapturer(deviceName, null);
164            if (capturer == null) {
165                return null;
166            }
167            final ArrayList<CameraEnumerationAndroid.CaptureFormat> choices =
168                    new ArrayList<>(enumerator.getSupportedFormats(deviceName));
169            Collections.sort(choices, (a, b) -> b.width - a.width);
170            for (final CameraEnumerationAndroid.CaptureFormat captureFormat : choices) {
171                if (captureFormat.width <= CAPTURING_RESOLUTION) {
172                    return new VideoSourceWrapper(capturer, captureFormat, availableCameras);
173                }
174            }
175            return null;
176        }
177
178        private static boolean isFrontFacing(
179                final CameraEnumerator cameraEnumerator, final String deviceName) {
180            try {
181                return cameraEnumerator.isFrontFacing(deviceName);
182            } catch (final NullPointerException e) {
183                return false;
184            }
185        }
186    }
187}