diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml
new file mode 100644
index 0000000..2b63946
--- /dev/null
+++ b/.idea/uiDesigner.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index d48f0ad..cb9742a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -8,10 +8,39 @@
onnx-inference4j-play
1.0-SNAPSHOT
+
+
- 8
- 8
UTF-8
+ 1.8
+ 1.8
+ 8.0.202
+
+
+
+ com.microsoft.onnxruntime
+ onnxruntime_gpu
+ 1.17.0
+
+
+ org.bytedeco
+ javacv-platform
+ 1.5.7
+
+
+
+ com.formdev
+ flatlaf
+ 2.3
+
+
+ org.bytedeco
+ ffmpeg-platform
+ 5.0-1.5.7
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/com/ly/Main.java b/src/main/java/com/ly/Main.java
deleted file mode 100644
index 21219f0..0000000
--- a/src/main/java/com/ly/Main.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.ly;
-
-//TIP 要运行代码,请按 或
-// 点击装订区域中的 图标。
-public class Main {
- public static void main(String[] args) {
- //TIP 当文本光标位于高亮显示的文本处时按
- // 查看 IntelliJ IDEA 建议如何修正。
- System.out.printf("Hello and welcome!");
-
- for (int i = 1; i <= 5; i++) {
- //TIP 按 开始调试代码。我们已经设置了一个 断点
- // 但您始终可以通过按 添加更多断点。
- System.out.println("i = " + i);
- }
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/ly/VideoInferenceApp.java b/src/main/java/com/ly/VideoInferenceApp.java
new file mode 100644
index 0000000..d693bc9
--- /dev/null
+++ b/src/main/java/com/ly/VideoInferenceApp.java
@@ -0,0 +1,182 @@
+package com.ly;
+
+import com.formdev.flatlaf.FlatLightLaf;
+import com.ly.layout.VideoPanel;
+import com.ly.model_load.ModelManager;
+import com.ly.play.VideoPlayer;
+
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.filechooser.FileSystemView;
+import java.awt.*;
+import java.io.File;
+
+public class VideoInferenceApp extends JFrame {
+
+ private VideoPanel videoPanel; // 视频显示面板
+ private JPanel controlPanel; // 操作按钮区域
+ private JTextField streamUrlField; // 流地址输入框
+
+ private VideoPlayer videoPlayer;
+ private ModelManager modelManager;
+
+ public VideoInferenceApp() {
+ // 设置窗口标题
+ super("Video Inference Player");
+
+ // 初始化UI组件
+ initializeUI();
+ }
+
+ private void initializeUI() {
+ // 设置 FlatLaf 主题
+ try {
+ UIManager.setLookAndFeel(new FlatLightLaf());
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ // 设置布局管理器
+ this.setLayout(new BorderLayout());
+
+ // 视频显示区域
+ videoPanel = new VideoPanel();
+ videoPanel.setBackground(Color.BLACK);
+
+ // 初始化 VideoPlayer
+ videoPlayer = new VideoPlayer(videoPanel);
+
+ // 模型列表区域
+ modelManager = new ModelManager();
+ modelManager.setPreferredSize(new Dimension(250, 0)); // 设置模型列表区域的宽度
+
+ // 使用 JSplitPane 分割视频区域和模型列表区域
+ JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, videoPanel, modelManager);
+ splitPane.setResizeWeight(0.8); // 视频区域初始占据80%的空间
+ splitPane.setDividerLocation(0.8);
+ this.add(splitPane, BorderLayout.CENTER);
+
+ // 操作按钮区域
+ controlPanel = new JPanel();
+ controlPanel.setLayout(new FlowLayout(FlowLayout.CENTER, 10, 5));
+
+ // 创建控制按钮
+ JButton playButton = new JButton("播放");
+ JButton pauseButton = new JButton("暂停");
+ JButton replayButton = new JButton("重播");
+ JButton fastForward5sButton = new JButton("快进5秒");
+ JButton rewind5sButton = new JButton("后退5秒");
+
+ // 设置按钮的统一高度
+ Dimension buttonSize = new Dimension(100, 30);
+ playButton.setPreferredSize(buttonSize);
+ pauseButton.setPreferredSize(buttonSize);
+ replayButton.setPreferredSize(buttonSize);
+ fastForward5sButton.setPreferredSize(buttonSize);
+ rewind5sButton.setPreferredSize(buttonSize);
+
+ // 添加按钮到控制面板
+ controlPanel.add(playButton);
+ controlPanel.add(pauseButton);
+ controlPanel.add(replayButton);
+ controlPanel.add(rewind5sButton);
+ controlPanel.add(fastForward5sButton);
+
+ this.add(controlPanel, BorderLayout.SOUTH);
+
+ // 顶部部分 - 视频和模型加载,流地址
+ JPanel topPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 5));
+
+ // 视频文件选择按钮
+ JButton loadVideoButton = new JButton("选择视频文件");
+ loadVideoButton.setPreferredSize(new Dimension(150, 30));
+
+ // 模型文件选择按钮
+ JButton loadModelButton = new JButton("选择模型");
+ loadModelButton.setPreferredSize(new Dimension(150, 30));
+
+ // 流地址输入框
+ streamUrlField = new JTextField(15); // 流地址输入框
+
+ // 开始播放按钮
+ JButton startPlayButton = new JButton("开始播放");
+ startPlayButton.setPreferredSize(new Dimension(100, 30));
+
+ // 将按钮和输入框添加到顶部面板
+ topPanel.add(loadVideoButton);
+ topPanel.add(loadModelButton);
+ topPanel.add(new JLabel("流地址:"));
+ topPanel.add(streamUrlField);
+ topPanel.add(startPlayButton);
+
+ this.add(topPanel, BorderLayout.NORTH);
+
+ // 设置窗口属性
+ this.setSize(1280, 720);
+ this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ this.setLocationRelativeTo(null); // 居中显示
+ this.setVisible(true);
+
+ // 添加视频加载按钮的行为
+ loadVideoButton.addActionListener(e -> selectVideoFile());
+
+ // 添加模型加载按钮的行为
+ loadModelButton.addActionListener(e -> modelManager.loadModel(this));
+
+ // 播放按钮
+ playButton.addActionListener(e -> videoPlayer.playVideo());
+
+ // 暂停按钮
+ pauseButton.addActionListener(e -> videoPlayer.pauseVideo());
+
+ // 重播按钮
+ replayButton.addActionListener(e -> videoPlayer.replayVideo());
+
+ // 后退5秒
+ rewind5sButton.addActionListener(e -> videoPlayer.rewind(5000));
+
+ // 快进5秒
+ fastForward5sButton.addActionListener(e -> videoPlayer.fastForward(5000));
+
+ // 开始播放按钮的行为
+ startPlayButton.addActionListener(e -> {
+ String streamUrl = streamUrlField.getText().trim();
+ if (!streamUrl.isEmpty()) {
+ try {
+ videoPlayer.loadVideo(streamUrl);
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ JOptionPane.showMessageDialog(this, "无法播放流地址: " + ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);
+ }
+ } else {
+ JOptionPane.showMessageDialog(this, "请输入流地址或摄像头索引。", "提示", JOptionPane.WARNING_MESSAGE);
+ }
+ });
+ }
+
+ // 选择视频文件
+ private void selectVideoFile() {
+ File desktopDir = FileSystemView.getFileSystemView().getHomeDirectory();
+ JFileChooser fileChooser = new JFileChooser(desktopDir);
+ fileChooser.setDialogTitle("选择视频文件");
+ // 设置视频文件过滤器,支持常见的视频格式
+ FileNameExtensionFilter videoFilter = new FileNameExtensionFilter(
+ "视频文件 (*.mp4;*.avi;*.mkv;*.mov;*.flv;*.wmv)", "mp4", "avi", "mkv", "mov", "flv", "wmv");
+ fileChooser.setFileFilter(videoFilter);
+
+ int returnValue = fileChooser.showOpenDialog(this);
+ if (returnValue == JFileChooser.APPROVE_OPTION) {
+ File selectedFile = fileChooser.getSelectedFile();
+ try {
+ videoPlayer.loadVideo(selectedFile.getAbsolutePath());
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ JOptionPane.showMessageDialog(this, "加载视频失败: " + ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);
+ }
+ }
+ }
+
+ public static void main(String[] args) {
+ SwingUtilities.invokeLater(VideoInferenceApp::new);
+ }
+}
diff --git a/src/main/java/com/ly/file/FileEditor.java b/src/main/java/com/ly/file/FileEditor.java
new file mode 100644
index 0000000..798e2ef
--- /dev/null
+++ b/src/main/java/com/ly/file/FileEditor.java
@@ -0,0 +1,48 @@
+package com.ly.file;
+
+import javax.swing.*;
+import java.awt.*;
+import java.io.*;
+import javax.swing.filechooser.FileSystemView;
+
+public class FileEditor {
+ public static void openFileEditor(String filePath) {
+ File file = new File(filePath);
+ if (!file.exists()) {
+ JOptionPane.showMessageDialog(null, "文件不存在:" + filePath, "错误", JOptionPane.ERROR_MESSAGE);
+ return;
+ }
+
+ // 仅对文本文件进行编辑
+ if (file.getName().endsWith(".txt")) {
+ JTextArea textArea = new JTextArea();
+ JScrollPane scrollPane = new JScrollPane(textArea);
+ scrollPane.setPreferredSize(new Dimension(600, 400));
+
+ // 读取文件内容
+ try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
+ textArea.read(reader, null);
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ JOptionPane.showMessageDialog(null, "无法读取文件:" + ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);
+ return;
+ }
+
+ int result = JOptionPane.showConfirmDialog(null, scrollPane, "编辑文件:" + file.getName(),
+ JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE);
+
+ if (result == JOptionPane.OK_OPTION) {
+ // 保存文件内容
+ try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
+ textArea.write(writer);
+ JOptionPane.showMessageDialog(null, "文件已保存。", "信息", JOptionPane.INFORMATION_MESSAGE);
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ JOptionPane.showMessageDialog(null, "无法保存文件:" + ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);
+ }
+ }
+ } else {
+ JOptionPane.showMessageDialog(null, "无法编辑非文本文件。", "提示", JOptionPane.WARNING_MESSAGE);
+ }
+ }
+}
diff --git a/src/main/java/com/ly/layout/VideoPanel.java b/src/main/java/com/ly/layout/VideoPanel.java
new file mode 100644
index 0000000..f4b86ca
--- /dev/null
+++ b/src/main/java/com/ly/layout/VideoPanel.java
@@ -0,0 +1,53 @@
+package com.ly.layout;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+
+public class VideoPanel extends JPanel {
+ private BufferedImage image;
+
+ public void updateImage(BufferedImage img) {
+ this.image = img;
+ repaint();
+ }
+
+ public void clearImage() {
+ this.image = null;
+ repaint();
+ }
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ super.paintComponent(g);
+ if (image != null) {
+ Graphics2D g2d = (Graphics2D) g.create();
+
+ // 启用抗锯齿和高质量渲染
+ g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+ g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+
+ // 缩放图片以适应面板大小
+ int panelWidth = this.getWidth();
+ int panelHeight = this.getHeight();
+ double imgAspect = (double) image.getWidth() / image.getHeight();
+ double panelAspect = (double) panelWidth / panelHeight;
+
+ int drawWidth, drawHeight;
+ if (imgAspect > panelAspect) {
+ drawWidth = panelWidth;
+ drawHeight = (int) (panelWidth / imgAspect);
+ } else {
+ drawHeight = panelHeight;
+ drawWidth = (int) (panelHeight * imgAspect);
+ }
+
+ g2d.drawImage(image, (panelWidth - drawWidth) / 2, (panelHeight - drawHeight) / 2,
+ drawWidth, drawHeight, null);
+
+ g2d.dispose();
+ }
+ }
+
+}
diff --git a/src/main/java/com/ly/model_load/ModelManager.java b/src/main/java/com/ly/model_load/ModelManager.java
new file mode 100644
index 0000000..f80eb6d
--- /dev/null
+++ b/src/main/java/com/ly/model_load/ModelManager.java
@@ -0,0 +1,82 @@
+package com.ly.model_load;
+
+
+
+import com.ly.file.FileEditor;
+
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.filechooser.FileSystemView;
+import javax.swing.table.DefaultTableModel;
+import java.awt.*;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.io.File;
+
+public class ModelManager extends JPanel {
+ private DefaultListModel modelListModel;
+ private JList modelList;
+
+ public ModelManager() {
+ setLayout(new BorderLayout());
+ modelListModel = new DefaultListModel<>();
+ modelList = new JList<>(modelListModel);
+ JScrollPane modelScrollPane = new JScrollPane(modelList);
+ add(modelScrollPane, BorderLayout.CENTER);
+
+ // 添加双击事件,编辑标签文件
+ modelList.addMouseListener(new MouseAdapter() {
+ public void mouseClicked(MouseEvent e) {
+ if (e.getClickCount() == 2) {
+ int index = modelList.locationToIndex(e.getPoint());
+ if (index >= 0) {
+ String item = modelListModel.getElementAt(index);
+ // 解析标签文件路径
+ String[] parts = item.split("\n");
+ if (parts.length >= 2) {
+ String labelFilePath = parts[1].replace("标签文件: ", "").trim();
+ FileEditor.openFileEditor(labelFilePath);
+ }
+ }
+ }
+ }
+ });
+ }
+
+ // 加载模型
+ public void loadModel(JFrame parent) {
+ // 获取桌面目录
+ File desktopDir = FileSystemView.getFileSystemView().getHomeDirectory();
+ JFileChooser fileChooser = new JFileChooser(desktopDir);
+ fileChooser.setDialogTitle("选择模型文件");
+
+ // 设置模型文件过滤器,只显示 .onnx 文件
+ FileNameExtensionFilter modelFilter = new FileNameExtensionFilter("ONNX模型文件 (*.onnx)", "onnx");
+ fileChooser.setFileFilter(modelFilter);
+
+ int returnValue = fileChooser.showOpenDialog(parent);
+ if (returnValue == JFileChooser.APPROVE_OPTION) {
+ File modelFile = fileChooser.getSelectedFile();
+
+ // 选择对应的标签文件
+ fileChooser.setDialogTitle("选择标签文件");
+
+ // 设置标签文件过滤器,只显示 .txt 文件
+ FileNameExtensionFilter labelFilter = new FileNameExtensionFilter("标签文件 (*.txt)", "txt");
+ fileChooser.setFileFilter(labelFilter);
+
+ returnValue = fileChooser.showOpenDialog(parent);
+ if (returnValue == JFileChooser.APPROVE_OPTION) {
+ File labelFile = fileChooser.getSelectedFile();
+
+ // 将模型和标签文件添加到列表中
+ String item = "模型文件: " + modelFile.getAbsolutePath() + "\n标签文件: " + labelFile.getAbsolutePath();
+ modelListModel.addElement(item);
+ } else {
+ JOptionPane.showMessageDialog(parent, "未选择标签文件。", "提示", JOptionPane.WARNING_MESSAGE);
+ }
+ } else {
+ JOptionPane.showMessageDialog(parent, "未选择模型文件。", "提示", JOptionPane.WARNING_MESSAGE);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/ly/play/VideoPlayer.java b/src/main/java/com/ly/play/VideoPlayer.java
new file mode 100644
index 0000000..1e91524
--- /dev/null
+++ b/src/main/java/com/ly/play/VideoPlayer.java
@@ -0,0 +1,280 @@
+package com.ly.play;
+
+import com.ly.layout.VideoPanel;
+import org.bytedeco.javacv.*;
+
+import javax.swing.*;
+import java.awt.image.BufferedImage;
+
+public class VideoPlayer {
+ private FrameGrabber grabber;
+ private Java2DFrameConverter converter = new Java2DFrameConverter();
+ private boolean isPlaying = false;
+ private boolean isPaused = false;
+ private Thread videoThread;
+ private VideoPanel videoPanel;
+
+ private long videoDuration = 0; // 毫秒
+ private long currentTimestamp = 0; // 毫秒
+
+ public VideoPlayer(VideoPanel videoPanel) {
+ this.videoPanel = videoPanel;
+ }
+
+ // 加载视频或流
+ // 加载视频或流
+ public void loadVideo(String videoFilePathOrStreamUrl) throws Exception {
+ stopVideo();
+
+
+ if (videoFilePathOrStreamUrl.equals("0")) {
+ int cameraIndex = Integer.parseInt(videoFilePathOrStreamUrl);
+ grabber = new OpenCVFrameGrabber(cameraIndex);
+ grabber.start();
+ videoDuration = 0; // 摄像头没有固定的时长
+ playVideo();
+ } else {
+ // 输入不是数字,尝试使用 FFmpegFrameGrabber 打开流或视频文件
+ grabber = new FFmpegFrameGrabber(videoFilePathOrStreamUrl);
+ grabber.start();
+ videoDuration = grabber.getLengthInTime() / 1000; // 转换为毫秒
+ }
+
+
+ // 显示第一帧
+ Frame frame;
+ if (grabber instanceof OpenCVFrameGrabber) {
+ frame = grabber.grab();
+ } else {
+ frame = grabber.grab();
+ }
+ if (frame != null && frame.image != null) {
+ BufferedImage bufferedImage = converter.getBufferedImage(frame);
+ videoPanel.updateImage(bufferedImage);
+ currentTimestamp = 0;
+ }
+
+ // 重置到视频开始位置
+ if (grabber instanceof FFmpegFrameGrabber) {
+ grabber.setTimestamp(0);
+ }
+ currentTimestamp = 0;
+ }
+
+ public void playVideo() {
+ if (grabber == null) {
+ JOptionPane.showMessageDialog(null, "请先加载视频文件或流。", "提示", JOptionPane.WARNING_MESSAGE);
+ return;
+ }
+
+ if (isPlaying) {
+ if (isPaused) {
+ isPaused = false; // 恢复播放
+ }
+ return;
+ }
+
+ isPlaying = true;
+ isPaused = false;
+
+ videoThread = new Thread(() -> {
+ try {
+ if (grabber instanceof OpenCVFrameGrabber) {
+ // 摄像头捕获
+ while (isPlaying) {
+ if (isPaused) {
+ Thread.sleep(100);
+ continue;
+ }
+
+ Frame frame = grabber.grab();
+ if (frame == null) {
+ isPlaying = false;
+ break;
+ }
+
+ BufferedImage bufferedImage = converter.getBufferedImage(frame);
+ if (bufferedImage != null) {
+ videoPanel.updateImage(bufferedImage);
+ }
+ }
+ } else {
+ // 视频文件或流
+ double frameRate = grabber.getFrameRate();
+ if (frameRate <= 0 || Double.isNaN(frameRate)) {
+ frameRate = 25; // 默认帧率
+ }
+ long frameInterval = (long) (1000 / frameRate); // 每帧间隔时间(毫秒)
+ long startTime = System.currentTimeMillis();
+ long frameCount = 0;
+
+ while (isPlaying) {
+ if (isPaused) {
+ Thread.sleep(100);
+ startTime += 100; // 调整开始时间以考虑暂停时间
+ continue;
+ }
+
+ Frame frame = grabber.grab();
+ if (frame == null) {
+ // 视频播放结束
+ isPlaying = false;
+ break;
+ }
+
+ BufferedImage bufferedImage = converter.getBufferedImage(frame);
+ if (bufferedImage != null) {
+ videoPanel.updateImage(bufferedImage);
+
+ // 更新当前帧时间戳
+ frameCount++;
+ long expectedTime = frameCount * frameInterval;
+ long actualTime = System.currentTimeMillis() - startTime;
+
+ currentTimestamp = grabber.getTimestamp() / 1000;
+
+ // 如果实际时间落后于预期时间,进行调整
+ if (actualTime < expectedTime) {
+ Thread.sleep(expectedTime - actualTime);
+ }
+ }
+ }
+ }
+
+ // 视频播放完毕后,停止播放
+ isPlaying = false;
+
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ }
+ });
+ videoThread.start();
+ }
+
+ // 暂停视频
+ public void pauseVideo() {
+ if (!isPlaying) {
+ return;
+ }
+ isPaused = true;
+ }
+
+ // 重播视频
+ public void replayVideo() {
+ try {
+ if (grabber instanceof FFmpegFrameGrabber) {
+ grabber.setTimestamp(0); // 重置到视频开始位置
+ grabber.flush(); // 清除缓存
+ currentTimestamp = 0;
+
+ // 显示第一帧
+ Frame frame = grabber.grab();
+ if (frame != null && frame.image != null) {
+ BufferedImage bufferedImage = converter.getBufferedImage(frame);
+ videoPanel.updateImage(bufferedImage);
+ }
+
+ playVideo(); // 开始播放
+ } else if (grabber instanceof OpenCVFrameGrabber) {
+ // 对于摄像头,重播相当于重新开始播放
+ playVideo();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ JOptionPane.showMessageDialog(null, "重播失败: " + e.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);
+ }
+ }
+
+ // 停止视频
+ public void stopVideo() {
+ isPlaying = false;
+ isPaused = false;
+
+ if (videoThread != null && videoThread.isAlive()) {
+ try {
+ videoThread.join();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ if (grabber != null) {
+ try {
+ grabber.stop();
+ grabber.release();
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ }
+ grabber = null;
+ }
+ }
+
+ // 快进或后退
+ public void seekTo(long seekTime) {
+ if (grabber == null) return;
+ if (!(grabber instanceof FFmpegFrameGrabber)) {
+ JOptionPane.showMessageDialog(null, "此操作仅支持视频文件和流。", "提示", JOptionPane.WARNING_MESSAGE);
+ return;
+ }
+ try {
+ isPaused = false; // 取消暂停
+ isPlaying = false; // 停止当前播放线程
+ if (videoThread != null && videoThread.isAlive()) {
+ videoThread.join();
+ }
+
+ grabber.setTimestamp(seekTime * 1000); // 转换为微秒
+ grabber.flush(); // 清除缓存
+
+ Frame frame;
+ do {
+ frame = grabber.grab();
+ if (frame == null) {
+ break;
+ }
+ } while (frame.image == null); // 跳过没有图像的帧
+
+ if (frame != null && frame.image != null) {
+ BufferedImage bufferedImage = converter.getBufferedImage(frame);
+ videoPanel.updateImage(bufferedImage);
+
+ // 更新当前帧时间戳
+ currentTimestamp = grabber.getTimestamp() / 1000;
+ }
+
+ // 重新开始播放
+ playVideo();
+
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ }
+ }
+
+ // 快进
+ public void fastForward(long milliseconds) {
+ if (!(grabber instanceof FFmpegFrameGrabber)) {
+ JOptionPane.showMessageDialog(null, "此操作仅支持视频文件和流。", "提示", JOptionPane.WARNING_MESSAGE);
+ return;
+ }
+ long newTime = Math.min(currentTimestamp + milliseconds, videoDuration);
+ seekTo(newTime);
+ }
+
+ // 后退
+ public void rewind(long milliseconds) {
+ if (!(grabber instanceof FFmpegFrameGrabber)) {
+ JOptionPane.showMessageDialog(null, "此操作仅支持视频文件和流。", "提示", JOptionPane.WARNING_MESSAGE);
+ return;
+ }
+ long newTime = Math.max(currentTimestamp - milliseconds, 0);
+ seekTo(newTime);
+ }
+
+ public long getVideoDuration() {
+ return videoDuration;
+ }
+
+ public FrameGrabber getGrabber() {
+ return grabber;
+ }
+}
diff --git a/src/main/java/com/ly/utils/CameraDeviceLister.java b/src/main/java/com/ly/utils/CameraDeviceLister.java
new file mode 100644
index 0000000..8f59cd1
--- /dev/null
+++ b/src/main/java/com/ly/utils/CameraDeviceLister.java
@@ -0,0 +1,13 @@
+package com.ly.utils;
+
+import org.bytedeco.javacv.FrameGrabber;
+import org.bytedeco.javacv.VideoInputFrameGrabber;
+
+public class CameraDeviceLister {
+ public static void main(String[] args) throws FrameGrabber.Exception {
+ String[] deviceDescriptions = VideoInputFrameGrabber.getDeviceDescriptions();
+ for (String deviceDescription : deviceDescriptions) {
+ System.out.println("摄像头索引 " + ": " + deviceDescription);
+ }
+ }
+}
diff --git a/src/main/java/com/ly/utils/RTSPStreamer.java b/src/main/java/com/ly/utils/RTSPStreamer.java
new file mode 100644
index 0000000..2cb6845
--- /dev/null
+++ b/src/main/java/com/ly/utils/RTSPStreamer.java
@@ -0,0 +1,57 @@
+package com.ly.utils;
+
+import org.bytedeco.ffmpeg.global.avcodec;
+import org.bytedeco.javacv.*;
+
+public class RTSPStreamer {
+
+ public static void main(String[] args) {
+ String inputFile = "C:\\Users\\ly\\Desktop\\屏幕录制 2024-09-20 225443.mp4"; // 替换为您的本地视频文件路径
+ String rtspUrl = "rtsp://localhost:8554/live"; // 替换为您的 RTSP 服务器地址
+
+ FFmpegFrameGrabber grabber = null;
+ FFmpegFrameRecorder recorder = null;
+
+ try {
+ // 初始化 FFmpegFrameGrabber 以从本地视频文件读取
+ grabber = new FFmpegFrameGrabber(inputFile);
+ grabber.start();
+
+ // 初始化 FFmpegFrameRecorder 以推流到 RTSP 服务器
+ recorder = new FFmpegFrameRecorder(rtspUrl, grabber.getImageWidth(), grabber.getImageHeight(), grabber.getAudioChannels());
+ recorder.setFormat("rtsp");
+ recorder.setFrameRate(grabber.getFrameRate());
+ recorder.setVideoBitrate(grabber.getVideoBitrate());
+ recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264); // 设置视频编码格式
+ recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC); // 设置音频编码格式
+
+ // 设置 RTSP 传输选项(如果需要)
+ recorder.setOption("rtsp_transport", "tcp");
+
+ recorder.start();
+
+ Frame frame;
+ while ((frame = grabber.grab()) != null) {
+ recorder.record(frame);
+ }
+
+ System.out.println("推流完成。");
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ } finally {
+ try {
+ if (recorder != null) {
+ recorder.stop();
+ recorder.release();
+ }
+ if (grabber != null) {
+ grabber.stop();
+ grabber.release();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+}