VideoSourceWrapper.java

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