Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ public class CameraConfiguration {
public int currentPipelineIndex = 0;
public Rotation2d camPitch = new Rotation2d();

public int streamIndex = 0; // 0 index means ports [1181, 1182], 1 means [1183, 1184], etc...

@JsonIgnore // this ignores the pipes as we serialize them to their own subfolder
public List<CVPipelineSettings> pipelineSettings = new ArrayList<>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,15 @@

package org.photonvision.vision.frame.consumer;

import edu.wpi.cscore.CameraServerJNI;
import edu.wpi.cscore.CvSource;
import edu.wpi.cscore.MjpegServer;
import edu.wpi.cscore.VideoEvent;
import edu.wpi.cscore.VideoListener;
import edu.wpi.cscore.VideoMode;
import edu.wpi.first.cameraserver.CameraServer;
import edu.wpi.first.networktables.NetworkTable;
import edu.wpi.first.networktables.NetworkTableInstance;
import java.util.ArrayList;
import org.opencv.core.Mat;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;
Expand All @@ -33,14 +38,63 @@ public class MJPGFrameConsumer {
private final MjpegServer mjpegServer;
private FrameDivisor divisor = FrameDivisor.NONE;

public MJPGFrameConsumer(String sourceName, int width, int height) {
// TODO h264?
@SuppressWarnings("FieldCanBeLocal")
private final VideoListener listener;

private final NetworkTable table;

public MJPGFrameConsumer(String sourceName, int width, int height, int port) {
this.cvSource = new CvSource(sourceName, VideoMode.PixelFormat.kMJPEG, width, height, 30);
this.mjpegServer = CameraServer.getInstance().startAutomaticCapture(cvSource);
this.table =
NetworkTableInstance.getDefault().getTable("/CameraPublisher").getSubTable(sourceName);

this.mjpegServer = new MjpegServer("serve_" + cvSource.getName(), port);
mjpegServer.setSource(cvSource);

listener =
new VideoListener(
event -> {
if (event.kind == VideoEvent.Kind.kNetworkInterfacesChanged) {
table.getEntry("source").setString("cv:");
table.getEntry("streams");
table.getEntry("connected").setBoolean(true);
table.getEntry("mode").setString(videoModeToString(cvSource.getVideoMode()));
table.getEntry("modes").setStringArray(getSourceModeValues(cvSource.getHandle()));
updateStreamValues();
}
},
0x4fff,
true);
}

private synchronized void updateStreamValues() {
// Get port
int port = mjpegServer.getPort();

// Generate values
var addresses = CameraServerJNI.getNetworkInterfaces();
ArrayList<String> values = new ArrayList<>(addresses.length + 1);
String listenAddress = CameraServerJNI.getMjpegServerListenAddress(mjpegServer.getHandle());
if (!listenAddress.isEmpty()) {
// If a listen address is specified, only use that
values.add(makeStreamValue(listenAddress, port));
} else {
// Otherwise generate for hostname and all interface addresses
values.add(makeStreamValue(CameraServerJNI.getHostname() + ".local", port));
for (String addr : addresses) {
if ("127.0.0.1".equals(addr)) {
continue; // ignore localhost
}
values.add(makeStreamValue(addr, port));
}
}

String[] streamAddresses = values.toArray(new String[0]);
table.getEntry("streams").setStringArray(streamAddresses);
}

public MJPGFrameConsumer(String name) {
this(name, 320, 240);
public MJPGFrameConsumer(String name, int port) {
this(name, 320, 240, port);
}

public void setFrameDivisor(FrameDivisor divisor) {
Expand Down Expand Up @@ -68,4 +122,45 @@ private Size getScaledSize(Size orig, FrameDivisor divisor) {
public int getCurrentStreamPort() {
return mjpegServer.getPort();
}

private static String makeStreamValue(String address, int port) {
return "mjpg:http://" + address + ":" + port + "/?action=stream";
}

private static String[] getSourceModeValues(int sourceHandle) {
VideoMode[] modes = CameraServerJNI.enumerateSourceVideoModes(sourceHandle);
String[] modeStrings = new String[modes.length];
for (int i = 0; i < modes.length; i++) {
modeStrings[i] = videoModeToString(modes[i]);
}
return modeStrings;
}

private static String videoModeToString(VideoMode mode) {
return mode.width
+ "x"
+ mode.height
+ " "
+ pixelFormatToString(mode.pixelFormat)
+ " "
+ mode.fps
+ " fps";
}

private static String pixelFormatToString(VideoMode.PixelFormat pixelFormat) {
switch (pixelFormat) {
case kMJPEG:
return "MJPEG";
case kYUYV:
return "YUYV";
case kRGB565:
return "RGB565";
case kBGR:
return "BGR";
case kGray:
return "Gray";
default:
return "Unknown";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,7 @@ public VisionModule(PipelineManager pipelineManager, VisionSource visionSource,

DataChangeService.getInstance().addSubscriber(new VisionModuleChangeSubscriber(this));

dashboardOutputStreamer =
new MJPGFrameConsumer(
visionSource.getSettables().getConfiguration().uniqueName + "-output");
dashboardInputStreamer =
new MJPGFrameConsumer(visionSource.getSettables().getConfiguration().uniqueName + "-input");

createStreams();
fpsLimitedResultConsumers.add(result -> dashboardInputStreamer.accept(result.inputFrame));
fpsLimitedResultConsumers.add(result -> dashboardOutputStreamer.accept(result.outputFrame));

Expand Down Expand Up @@ -140,6 +135,21 @@ public VisionModule(PipelineManager pipelineManager, VisionSource visionSource,
saveAndBroadcastAll();
}

private void createStreams() {
var camStreamIdx = visionSource.getSettables().getConfiguration().streamIndex;
// If idx = 0, we want (1181, 1182)
var inputStreamPort = 1181 + (camStreamIdx * 2);
var outputStreamPort = 1181 + (camStreamIdx * 2) + 1;

dashboardOutputStreamer =
new MJPGFrameConsumer(
visionSource.getSettables().getConfiguration().uniqueName + "-output",
outputStreamPort);
dashboardInputStreamer =
new MJPGFrameConsumer(
visionSource.getSettables().getConfiguration().uniqueName + "-input", inputStreamPort);
}

void setDriverMode(boolean isDriverMode) {
pipelineManager.setDriverMode(isDriverMode);
setVisionLEDs(!isDriverMode);
Expand Down Expand Up @@ -271,11 +281,7 @@ void setCameraNickname(String newName) {
// rename streams
fpsLimitedResultConsumers.clear();

dashboardOutputStreamer =
new MJPGFrameConsumer(
visionSource.getSettables().getConfiguration().uniqueName + "-output");
dashboardInputStreamer =
new MJPGFrameConsumer(visionSource.getSettables().getConfiguration().uniqueName + "-input");
createStreams();

fpsLimitedResultConsumers.add(result -> dashboardInputStreamer.accept(result.inputFrame));
fpsLimitedResultConsumers.add(result -> dashboardOutputStreamer.accept(result.outputFrame));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package org.photonvision.vision.processes;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import org.photonvision.common.configuration.CameraConfiguration;
Expand Down Expand Up @@ -57,10 +58,29 @@ public List<VisionModule> addSources(HashMap<VisionSource, CameraConfiguration>
for (var entry : visionSources.entrySet()) {
var visionSource = entry.getKey();
var pipelineManager = new PipelineManager(entry.getValue());

assignCameraIndex(visionSource.getSettables().getConfiguration());

var module = new VisionModule(pipelineManager, visionSource, visionModules.size());
visionModules.add(module);
addedModules.add(module);
}
return addedModules;
}

private void assignCameraIndex(CameraConfiguration config) {
// First, check if the index is less than the max
var optional =
visionModules.stream()
.max(
Comparator.comparingInt(
it -> it.visionSource.getSettables().getConfiguration().streamIndex));
int max =
optional
.map(it -> it.visionSource.getSettables().getConfiguration().streamIndex)
.orElse(-1);
if (config.streamIndex <= max) {
config.streamIndex = max + 1;
}
}
}