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        if (surfaceTextureHelper == null) {
 61            throw new IllegalStateException("Could not create SurfaceTextureHelper");
 62        }
 63        this.videoSource = peerConnectionFactory.createVideoSource(false);
 64        this.cameraVideoCapturer.initialize(
 65                surfaceTextureHelper, context, this.videoSource.getCapturerObserver());
 66    }
 67
 68    public VideoSource getVideoSource() {
 69        final VideoSource videoSource = this.videoSource;
 70        if (videoSource == null) {
 71            throw new IllegalStateException("VideoSourceWrapper was not initialized");
 72        }
 73        return videoSource;
 74    }
 75
 76    public void startCapture() {
 77        final int frameRate = getFrameRate();
 78        Log.d(
 79                Config.LOGTAG,
 80                String.format(
 81                        "start capturing at %dx%d@%d",
 82                        captureFormat.width, captureFormat.height, frameRate));
 83        this.cameraVideoCapturer.startCapture(captureFormat.width, captureFormat.height, frameRate);
 84    }
 85
 86    public void stopCapture() throws InterruptedException {
 87        this.cameraVideoCapturer.stopCapture();
 88    }
 89
 90    public void dispose() {
 91        this.cameraVideoCapturer.dispose();
 92        if (this.videoSource != null) {
 93            dispose(this.videoSource);
 94        }
 95    }
 96
 97    private static void dispose(final VideoSource videoSource) {
 98        try {
 99            videoSource.dispose();
100        } catch (final IllegalStateException e) {
101            Log.e(Config.LOGTAG, "unable to dispose video source", e);
102        }
103    }
104
105    public ListenableFuture<Boolean> switchCamera() {
106        final SettableFuture<Boolean> future = SettableFuture.create();
107        this.cameraVideoCapturer.switchCamera(
108                new CameraVideoCapturer.CameraSwitchHandler() {
109                    @Override
110                    public void onCameraSwitchDone(final boolean isFrontCamera) {
111                        VideoSourceWrapper.this.isFrontCamera = isFrontCamera;
112                        future.set(isFrontCamera);
113                    }
114
115                    @Override
116                    public void onCameraSwitchError(final String message) {
117                        future.setException(
118                                new IllegalStateException(
119                                        String.format("Unable to switch camera %s", message)));
120                    }
121                });
122        return future;
123    }
124
125    public boolean isFrontCamera() {
126        return this.isFrontCamera;
127    }
128
129    public boolean isCameraSwitchable() {
130        return this.availableCameras.size() > 1;
131    }
132
133    public static class Factory {
134        final Context context;
135
136        public Factory(final Context context) {
137            this.context = context;
138        }
139
140        public VideoSourceWrapper create() {
141            final CameraEnumerator enumerator = new Camera2Enumerator(context);
142            final Set<String> deviceNames = ImmutableSet.copyOf(enumerator.getDeviceNames());
143            for (final String deviceName : deviceNames) {
144                if (isFrontFacing(enumerator, deviceName)) {
145                    final VideoSourceWrapper videoSourceWrapper =
146                            of(enumerator, deviceName, deviceNames);
147                    if (videoSourceWrapper == null) {
148                        return null;
149                    }
150                    videoSourceWrapper.isFrontCamera = true;
151                    return videoSourceWrapper;
152                }
153            }
154            if (deviceNames.size() == 0) {
155                return null;
156            } else {
157                return of(enumerator, Iterables.get(deviceNames, 0), deviceNames);
158            }
159        }
160
161        @Nullable
162        private VideoSourceWrapper of(
163                final CameraEnumerator enumerator,
164                final String deviceName,
165                final Set<String> availableCameras) {
166            final CameraVideoCapturer capturer = enumerator.createCapturer(deviceName, null);
167            if (capturer == null) {
168                return null;
169            }
170            final ArrayList<CameraEnumerationAndroid.CaptureFormat> choices =
171                    new ArrayList<>(enumerator.getSupportedFormats(deviceName));
172            Collections.sort(choices, (a, b) -> b.width - a.width);
173            for (final CameraEnumerationAndroid.CaptureFormat captureFormat : choices) {
174                if (captureFormat.width <= CAPTURING_RESOLUTION) {
175                    return new VideoSourceWrapper(capturer, captureFormat, availableCameras);
176                }
177            }
178            return null;
179        }
180
181        private static boolean isFrontFacing(
182                final CameraEnumerator cameraEnumerator, final String deviceName) {
183            try {
184                return cameraEnumerator.isFrontFacing(deviceName);
185            } catch (final NullPointerException e) {
186                return false;
187            }
188        }
189    }
190}