1 /*
2  *  Copyright 2013 The WebRTC project authors. All Rights Reserved.
3  *
4  *  Use of this source code is governed by a BSD-style license
5  *  that can be found in the LICENSE file in the root of the source
6  *  tree. An additional intellectual property rights grant can be found
7  *  in the file PATENTS.  All contributing project authors may
8  *  be found in the AUTHORS file in the root of the source tree.
9  */
10 
11 package org.webrtc;
12 
13 import android.support.annotation.Nullable;
14 
15 /**
16  * Java wrapper of native AndroidVideoTrackSource.
17  */
18 public class VideoSource extends MediaSource {
19   /** Simple aspect ratio clas for use in constraining output format. */
20   public static class AspectRatio {
21     public static final AspectRatio UNDEFINED = new AspectRatio(/* width= */ 0, /* height= */ 0);
22 
23     public final int width;
24     public final int height;
25 
AspectRatio(int width, int height)26     public AspectRatio(int width, int height) {
27       this.width = width;
28       this.height = height;
29     }
30   }
31 
32   private final NativeAndroidVideoTrackSource nativeAndroidVideoTrackSource;
33   private final Object videoProcessorLock = new Object();
34   @Nullable private VideoProcessor videoProcessor;
35   private boolean isCapturerRunning;
36 
37   private final CapturerObserver capturerObserver = new CapturerObserver() {
38     @Override
39     public void onCapturerStarted(boolean success) {
40       nativeAndroidVideoTrackSource.setState(success);
41       synchronized (videoProcessorLock) {
42         isCapturerRunning = success;
43         if (videoProcessor != null) {
44           videoProcessor.onCapturerStarted(success);
45         }
46       }
47     }
48 
49     @Override
50     public void onCapturerStopped() {
51       nativeAndroidVideoTrackSource.setState(/* isLive= */ false);
52       synchronized (videoProcessorLock) {
53         isCapturerRunning = false;
54         if (videoProcessor != null) {
55           videoProcessor.onCapturerStopped();
56         }
57       }
58     }
59 
60     @Override
61     public void onFrameCaptured(VideoFrame frame) {
62       final VideoProcessor.FrameAdaptationParameters parameters =
63           nativeAndroidVideoTrackSource.adaptFrame(frame);
64       synchronized (videoProcessorLock) {
65         if (videoProcessor != null) {
66           videoProcessor.onFrameCaptured(frame, parameters);
67           return;
68         }
69       }
70 
71       VideoFrame adaptedFrame = VideoProcessor.applyFrameAdaptationParameters(frame, parameters);
72       if (adaptedFrame != null) {
73         nativeAndroidVideoTrackSource.onFrameCaptured(adaptedFrame);
74         adaptedFrame.release();
75       }
76     }
77   };
78 
VideoSource(long nativeSource)79   public VideoSource(long nativeSource) {
80     super(nativeSource);
81     this.nativeAndroidVideoTrackSource = new NativeAndroidVideoTrackSource(nativeSource);
82   }
83 
84   /**
85    * Calling this function will cause frames to be scaled down to the requested resolution. Also,
86    * frames will be cropped to match the requested aspect ratio, and frames will be dropped to match
87    * the requested fps. The requested aspect ratio is orientation agnostic and will be adjusted to
88    * maintain the input orientation, so it doesn't matter if e.g. 1280x720 or 720x1280 is requested.
89    */
adaptOutputFormat(int width, int height, int fps)90   public void adaptOutputFormat(int width, int height, int fps) {
91     final int maxSide = Math.max(width, height);
92     final int minSide = Math.min(width, height);
93     adaptOutputFormat(maxSide, minSide, minSide, maxSide, fps);
94   }
95 
96   /**
97    * Same as above, but allows setting two different target resolutions depending on incoming
98    * frame orientation. This gives more fine-grained control and can e.g. be used to force landscape
99    * video to be cropped to portrait video.
100    */
adaptOutputFormat( int landscapeWidth, int landscapeHeight, int portraitWidth, int portraitHeight, int fps)101   public void adaptOutputFormat(
102       int landscapeWidth, int landscapeHeight, int portraitWidth, int portraitHeight, int fps) {
103     adaptOutputFormat(new AspectRatio(landscapeWidth, landscapeHeight),
104         /* maxLandscapePixelCount= */ landscapeWidth * landscapeHeight,
105         new AspectRatio(portraitWidth, portraitHeight),
106         /* maxPortraitPixelCount= */ portraitWidth * portraitHeight, fps);
107   }
108 
109   /** Same as above, with even more control as each constraint is optional. */
adaptOutputFormat(AspectRatio targetLandscapeAspectRatio, @Nullable Integer maxLandscapePixelCount, AspectRatio targetPortraitAspectRatio, @Nullable Integer maxPortraitPixelCount, @Nullable Integer maxFps)110   public void adaptOutputFormat(AspectRatio targetLandscapeAspectRatio,
111       @Nullable Integer maxLandscapePixelCount, AspectRatio targetPortraitAspectRatio,
112       @Nullable Integer maxPortraitPixelCount, @Nullable Integer maxFps) {
113     nativeAndroidVideoTrackSource.adaptOutputFormat(targetLandscapeAspectRatio,
114         maxLandscapePixelCount, targetPortraitAspectRatio, maxPortraitPixelCount, maxFps);
115   }
116 
setIsScreencast(boolean isScreencast)117   public void setIsScreencast(boolean isScreencast) {
118     nativeAndroidVideoTrackSource.setIsScreencast(isScreencast);
119   }
120 
121   /**
122    * Hook for injecting a custom video processor before frames are passed onto WebRTC. The frames
123    * will be cropped and scaled depending on CPU and network conditions before they are passed to
124    * the video processor. Frames will be delivered to the video processor on the same thread they
125    * are passed to this object. The video processor is allowed to deliver the processed frames
126    * back on any thread.
127    */
setVideoProcessor(@ullable VideoProcessor newVideoProcessor)128   public void setVideoProcessor(@Nullable VideoProcessor newVideoProcessor) {
129     synchronized (videoProcessorLock) {
130       if (videoProcessor != null) {
131         videoProcessor.setSink(/* sink= */ null);
132         if (isCapturerRunning) {
133           videoProcessor.onCapturerStopped();
134         }
135       }
136       videoProcessor = newVideoProcessor;
137       if (newVideoProcessor != null) {
138         newVideoProcessor.setSink(
139             (frame)
140                 -> runWithReference(() -> nativeAndroidVideoTrackSource.onFrameCaptured(frame)));
141         if (isCapturerRunning) {
142           newVideoProcessor.onCapturerStarted(/* success= */ true);
143         }
144       }
145     }
146   }
147 
getCapturerObserver()148   public CapturerObserver getCapturerObserver() {
149     return capturerObserver;
150   }
151 
152   /** Returns a pointer to webrtc::VideoTrackSourceInterface. */
getNativeVideoTrackSource()153   long getNativeVideoTrackSource() {
154     return getNativeMediaSource();
155   }
156 
157   @Override
dispose()158   public void dispose() {
159     setVideoProcessor(/* newVideoProcessor= */ null);
160     super.dispose();
161   }
162 }
163