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(); + } + } + } +}