001// Copyright (c) FIRST and other WPILib contributors.
002// Open Source Software; you can modify and/or share it under the terms of
003// the WPILib BSD license file in the root directory of this project.
004
005package edu.wpi.first.cscore;
006
007import edu.wpi.first.util.PixelFormat;
008
009/**
010 * A source for video that provides a sequence of frames. Each frame may consist of multiple images
011 * (e.g. from a stereo or depth camera); these are called channels.
012 */
013public class VideoSource implements AutoCloseable {
014  /** Video source kind. */
015  public enum Kind {
016    /** Unknown video source. */
017    kUnknown(0),
018    /** USB video source. */
019    kUsb(1),
020    /** HTTP video source. */
021    kHttp(2),
022    /** CV video source. */
023    kCv(4),
024    /** Raw video source. */
025    kRaw(8);
026
027    private final int value;
028
029    Kind(int value) {
030      this.value = value;
031    }
032
033    /**
034     * Returns the Kind value.
035     *
036     * @return The Kind value.
037     */
038    public int getValue() {
039      return value;
040    }
041  }
042
043  /** Connection strategy. */
044  public enum ConnectionStrategy {
045    /**
046     * Automatically connect or disconnect based on whether any sinks are connected to this source.
047     * This is the default behavior.
048     */
049    kAutoManage(0),
050
051    /** Try to keep the connection open regardless of whether any sinks are connected. */
052    kKeepOpen(1),
053
054    /**
055     * Never open the connection. If this is set when the connection is open, close the connection.
056     */
057    kForceClose(2);
058
059    private final int value;
060
061    ConnectionStrategy(int value) {
062      this.value = value;
063    }
064
065    /**
066     * Returns the ConnectionStrategy value.
067     *
068     * @return The ConnectionStrategy value.
069     */
070    public int getValue() {
071      return value;
072    }
073  }
074
075  /**
076   * Convert from the numerical representation of kind to an enum type.
077   *
078   * @param kind The numerical representation of kind
079   * @return The kind
080   */
081  public static Kind getKindFromInt(int kind) {
082    return switch (kind) {
083      case 1 -> Kind.kUsb;
084      case 2 -> Kind.kHttp;
085      case 4 -> Kind.kCv;
086      case 8 -> Kind.kRaw;
087      default -> Kind.kUnknown;
088    };
089  }
090
091  /**
092   * Constructs a VideoSource.
093   *
094   * @param handle The video source handle.
095   */
096  protected VideoSource(int handle) {
097    m_handle = handle;
098  }
099
100  @Override
101  public synchronized void close() {
102    if (m_handle != 0) {
103      CameraServerJNI.releaseSource(m_handle);
104    }
105    m_handle = 0;
106  }
107
108  /**
109   * Returns true if the VideoSource is valid.
110   *
111   * @return True if the VideoSource is valid.
112   */
113  public boolean isValid() {
114    return m_handle != 0;
115  }
116
117  /**
118   * Returns the video source handle.
119   *
120   * @return The video source handle.
121   */
122  public int getHandle() {
123    return m_handle;
124  }
125
126  @Override
127  public boolean equals(Object other) {
128    if (this == other) {
129      return true;
130    }
131    if (other == null) {
132      return false;
133    }
134    return other instanceof VideoSource source && m_handle == source.m_handle;
135  }
136
137  @Override
138  public int hashCode() {
139    return m_handle;
140  }
141
142  /**
143   * Get the kind of the source.
144   *
145   * @return The kind of the source.
146   */
147  public Kind getKind() {
148    return getKindFromInt(CameraServerJNI.getSourceKind(m_handle));
149  }
150
151  /**
152   * Get the name of the source. The name is an arbitrary identifier provided when the source is
153   * created, and should be unique.
154   *
155   * @return The name of the source.
156   */
157  public String getName() {
158    return CameraServerJNI.getSourceName(m_handle);
159  }
160
161  /**
162   * Get the source description. This is source-kind specific.
163   *
164   * @return The source description.
165   */
166  public String getDescription() {
167    return CameraServerJNI.getSourceDescription(m_handle);
168  }
169
170  /**
171   * Get the last time a frame was captured.
172   *
173   * @return Time in 1 us increments.
174   */
175  public long getLastFrameTime() {
176    return CameraServerJNI.getSourceLastFrameTime(m_handle);
177  }
178
179  /**
180   * Sets the connection strategy. By default, the source will automatically connect or disconnect
181   * based on whether any sinks are connected.
182   *
183   * <p>This function is non-blocking; look for either a connection open or close event or call
184   * {@link #isConnected()} to determine the connection state.
185   *
186   * @param strategy connection strategy (auto, keep open, or force close)
187   */
188  public void setConnectionStrategy(ConnectionStrategy strategy) {
189    CameraServerJNI.setSourceConnectionStrategy(m_handle, strategy.getValue());
190  }
191
192  /**
193   * Returns true if the source currently connected to whatever is providing the images.
194   *
195   * @return True if the source currently connected to whatever is providing the images.
196   */
197  public boolean isConnected() {
198    return CameraServerJNI.isSourceConnected(m_handle);
199  }
200
201  /**
202   * Gets source enable status. This is determined with a combination of connection strategy and the
203   * number of sinks connected.
204   *
205   * @return True if enabled, false otherwise.
206   */
207  public boolean isEnabled() {
208    return CameraServerJNI.isSourceEnabled(m_handle);
209  }
210
211  /**
212   * Get a property.
213   *
214   * @param name Property name
215   * @return Property contents (of kind Property::kNone if no property with the given name exists)
216   */
217  public VideoProperty getProperty(String name) {
218    return new VideoProperty(CameraServerJNI.getSourceProperty(m_handle, name));
219  }
220
221  /**
222   * Enumerate all properties of this source.
223   *
224   * @return Array of video properties.
225   */
226  public VideoProperty[] enumerateProperties() {
227    int[] handles = CameraServerJNI.enumerateSourceProperties(m_handle);
228    VideoProperty[] rv = new VideoProperty[handles.length];
229    for (int i = 0; i < handles.length; i++) {
230      rv[i] = new VideoProperty(handles[i]);
231    }
232    return rv;
233  }
234
235  /**
236   * Get the current video mode.
237   *
238   * @return The current video mode.
239   */
240  public VideoMode getVideoMode() {
241    return CameraServerJNI.getSourceVideoMode(m_handle);
242  }
243
244  /**
245   * Set the video mode.
246   *
247   * @param mode Video mode
248   * @return True if set successfully.
249   */
250  public boolean setVideoMode(VideoMode mode) {
251    return CameraServerJNI.setSourceVideoMode(
252        m_handle, mode.pixelFormat.getValue(), mode.width, mode.height, mode.fps);
253  }
254
255  /**
256   * Set the video mode.
257   *
258   * @param pixelFormat desired pixel format
259   * @param width desired width
260   * @param height desired height
261   * @param fps desired FPS
262   * @return True if set successfully
263   */
264  public boolean setVideoMode(PixelFormat pixelFormat, int width, int height, int fps) {
265    return CameraServerJNI.setSourceVideoMode(m_handle, pixelFormat.getValue(), width, height, fps);
266  }
267
268  /**
269   * Set the pixel format.
270   *
271   * @param pixelFormat desired pixel format
272   * @return True if set successfully
273   */
274  public boolean setPixelFormat(PixelFormat pixelFormat) {
275    return CameraServerJNI.setSourcePixelFormat(m_handle, pixelFormat.getValue());
276  }
277
278  /**
279   * Set the resolution.
280   *
281   * @param width desired width
282   * @param height desired height
283   * @return True if set successfully
284   */
285  public boolean setResolution(int width, int height) {
286    return CameraServerJNI.setSourceResolution(m_handle, width, height);
287  }
288
289  /**
290   * Set the frames per second (FPS).
291   *
292   * @param fps desired FPS
293   * @return True if set successfully
294   */
295  public boolean setFPS(int fps) {
296    return CameraServerJNI.setSourceFPS(m_handle, fps);
297  }
298
299  /**
300   * Set video mode and properties from a JSON configuration string.
301   *
302   * <p>The format of the JSON input is:
303   *
304   * <pre>
305   * {
306   *     "pixel format": "MJPEG", "YUYV", etc
307   *     "width": video mode width
308   *     "height": video mode height
309   *     "fps": video mode fps
310   *     "brightness": percentage brightness
311   *     "white balance": "auto", "hold", or value
312   *     "exposure": "auto", "hold", or value
313   *     "properties": [
314   *         {
315   *             "name": property name
316   *             "value": property value
317   *         }
318   *     ]
319   * }
320   * </pre>
321   *
322   * @param config configuration
323   * @return True if set successfully
324   */
325  public boolean setConfigJson(String config) {
326    return CameraServerJNI.setSourceConfigJson(m_handle, config);
327  }
328
329  /**
330   * Get a JSON configuration string.
331   *
332   * @return JSON configuration string
333   */
334  public String getConfigJson() {
335    return CameraServerJNI.getSourceConfigJson(m_handle);
336  }
337
338  /**
339   * Get the actual FPS.
340   *
341   * <p>CameraServerJNI#setTelemetryPeriod() must be called for this to be valid (throws
342   * VisionException if telemetry is not enabled).
343   *
344   * @return Actual FPS averaged over the telemetry period.
345   */
346  public double getActualFPS() {
347    return CameraServerJNI.getTelemetryAverageValue(
348        m_handle, CameraServerJNI.TelemetryKind.kSourceFramesReceived);
349  }
350
351  /**
352   * Get the data rate (in bytes per second).
353   *
354   * <p>CameraServerJNI#setTelemetryPeriod() must be called for this to be valid (throws
355   * VisionException if telemetry is not enabled).
356   *
357   * @return Data rate averaged over the telemetry period.
358   */
359  public double getActualDataRate() {
360    return CameraServerJNI.getTelemetryAverageValue(
361        m_handle, CameraServerJNI.TelemetryKind.kSourceBytesReceived);
362  }
363
364  /**
365   * Enumerate all known video modes for this source.
366   *
367   * @return Vector of video modes.
368   */
369  public VideoMode[] enumerateVideoModes() {
370    return CameraServerJNI.enumerateSourceVideoModes(m_handle);
371  }
372
373  /**
374   * Enumerate all sinks connected to this source.
375   *
376   * @return Vector of sinks.
377   */
378  public VideoSink[] enumerateSinks() {
379    int[] handles = CameraServerJNI.enumerateSourceSinks(m_handle);
380    VideoSink[] rv = new VideoSink[handles.length];
381    for (int i = 0; i < handles.length; i++) {
382      rv[i] = new VideoSink(handles[i]);
383    }
384    return rv;
385  }
386
387  /**
388   * Enumerate all existing sources.
389   *
390   * @return Vector of sources.
391   */
392  public static VideoSource[] enumerateSources() {
393    int[] handles = CameraServerJNI.enumerateSources();
394    VideoSource[] rv = new VideoSource[handles.length];
395    for (int i = 0; i < handles.length; i++) {
396      rv[i] = new VideoSource(handles[i]);
397    }
398    return rv;
399  }
400
401  /** Video source handle. */
402  protected int m_handle;
403}