build(pom.xml): 更新项目版本并优化构建配置

- 将项目版本从 0.1.2 升级到 0.1.4
- 移除了 humble-video-all 依赖
- 添加了 maven-dependency-plugin 以复制依赖库到 target/lib 目录
- 更新了 maven-assembly-plugin 配置,设置最终名称为 sane-service-v${project.version}
- 添加了主类配置项,指定为 org.aohe.Main

refactor(webcam): 重构 WebCam 类

- 删除了 WebCam 类的主体逻辑
-保留了 convertToType 方法
- 添加了 RecordAndEncodeVideo 类,实现了屏幕录制功能

fix(show): 修复系统托盘图标加载问题

- 修改了 TrayFrameUtf8 类,使用 UrlResource 加载托盘图标
- 优化了资源加载路径
This commit is contained in:
JianGuo 2025-02-08 15:05:08 +08:00
parent 6cc8f1913b
commit a6bb46b8f5
4 changed files with 427 additions and 122 deletions

58
pom.xml
View File

@ -6,7 +6,7 @@
<groupId>org.aohe</groupId>
<artifactId>twain-service</artifactId>
<version>0.1.2</version>
<version>0.1.4</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
@ -69,16 +69,7 @@
<artifactId>webcam-capture-driver-native</artifactId>
<version>1.2.0-8</version>
</dependency>
<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>io.humble</groupId>
<artifactId>humble-video-all</artifactId>
<version>0.3.0</version>
</dependency>
<dependency>
<groupId>com.github.sarxos</groupId>
<artifactId>webcam-capture</artifactId>
@ -146,6 +137,7 @@
<!-- </build>-->
<build>
<finalName>sane-service-v${project.version}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
@ -156,16 +148,42 @@
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.4.1</version>
<configuration>
<!--打包时,包含所有依赖的 jar 包-->
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<finalName>${project.build.finalName}</finalName>
<archive>
<manifest>
<mainClass>org.aohe.Main</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
<!--生成 javadoc 文件-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>

View File

@ -1,16 +1,23 @@
package org.aohe.show;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.io.resource.UrlResource;
import com.github.sarxos.webcam.util.ImageUtils;
import lombok.extern.slf4j.Slf4j;
import org.aohe.constant.SocketEnum;
import org.aohe.web.SocketUtils;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
/**
@ -19,7 +26,7 @@ import java.nio.charset.StandardCharsets;
@Slf4j
public class TrayFrameUtf8 {
public static void initSystemTrayUTF8() {
public static void initSystemTrayUTF8() throws IOException {
SocketUtils.start(SocketEnum.SCAN_SOCKET);
//使用 JDialog 作为 JPopupMenu 载体
JDialog jDialog = new JDialog();
@ -83,8 +90,10 @@ public class TrayFrameUtf8 {
jPopupMenu.add(showMainFrame);
jPopupMenu.add(exit);
Image image = Toolkit.getDefaultToolkit().getImage(ResourceUtil.getResource("ah.png").getFile());
URL url = ResourceUtil.getResource("../res/ah.png");
String trayPng = "../res/ah.png";
log.info("file:{}", url.getFile());
Image image = Toolkit.getDefaultToolkit().getImage(url.getFile());
// 创建系统托盘图标
TrayIcon trayIcon = new TrayIcon(image, "Scan Program For AH");
// 自动调整系统托盘图标大小

View File

@ -0,0 +1,278 @@
//package org.aohe.webcam;
///*******************************************************************************
// * 版权所有 (c) 2014, Art Clarke. 保留所有权利
// *
// * 本文件是Humble-Video的一部分
// *
// * Humble-Video 是自由软件您可以根据自由软件基金会发布的GNU Affero通用公共许可证的条款重新分发和/或修改它
// * 许可证版本为3由您选择任何更新的版本
// *
// * Humble-Video 原样分发没有任何形式的担保甚至没有默示的适销性或特定用途适用性的保证
// * 有关详细信息请参阅GNU Affero通用公共许可证
// *
// * 您应该已经收到了一份GNU Affero通用公共许可证的副本如果没有请参见<http://www.gnu.org/licenses/>
// *******************************************************************************/
//import java.awt.*;
//import java.awt.image.BufferedImage;
//import java.io.IOException;
//import java.nio.ByteBuffer;
//
//import com.github.eduramiba.webcamcapture.drivers.NativeDriver;
//import com.github.sarxos.webcam.Webcam;
//import com.github.sarxos.webcam.WebcamResolution;
//import io.humble.ferry.Buffer;
//import org.aohe.constant.SocketEnum;
//import org.aohe.web.SocketUtils;
//import org.apache.commons.cli.CommandLine;
//import org.apache.commons.cli.CommandLineParser;
//import org.apache.commons.cli.HelpFormatter;
//import org.apache.commons.cli.OptionBuilder;
//import org.apache.commons.cli.Options;
//import org.apache.commons.cli.ParseException;
//
//import io.humble.video.Codec;
//import io.humble.video.Encoder;
//import io.humble.video.MediaAudio;
//import io.humble.video.MediaPacket;
//import io.humble.video.MediaPicture;
//import io.humble.video.Muxer;
//import io.humble.video.MuxerFormat;
//import io.humble.video.PixelFormat;
//import io.humble.video.Rational;
//import io.humble.video.awt.MediaPictureConverter;
//import io.humble.video.awt.MediaPictureConverterFactory;
//
///**
// * 将计算机屏幕内容录制到媒体文件中持续指定的时间
// * 这是一个演示程序旨在教授如何使用Humble API
// * <p>
// * 引入的概念
// * </p>
// * <ul>
// * <li>Muxer: {@link Muxer} 对象是可以写入媒体数据的容器</li>
// * <li>编码器: {@link Encoder} 对象可以将 {@link MediaAudio} {@link MediaPicture} 对象转换为 {@link MediaPacket} 对象
// * 以便将其写入 {@link Muxer} 对象</li>
// * </ul>
// *
// * <p>
// * 使用Maven运行此程序
// * </p>
// * <pre>
// * mvn install exec:java -Dexec.mainClass="io.humble.video.demos.RecordAndEncodeVideo" -Dexec.args="filename.mp4"
// * </pre>
// *
// * @author aclarke
// *
// */
//public class RecordAndEncodeVideo {
//
// /**
// * 录制屏幕内容
// *
// * @param filename 文件名用于保存录制的视频
// * @param formatName 视频格式名称如果未指定则根据文件名猜测
// * @param codecName 编解码器名称如果未指定则根据格式猜测
// * @param duration 录制的持续时间
// * @throws AWTException 如果创建Robot对象失败
// * @throws InterruptedException 如果线程被中断
// * @throws IOException 如果发生I/O错误
// */
// private static void recordScreen(String filename, String formatName,
// String codecName, int duration) throws AWTException, InterruptedException, IOException {
// SocketUtils.start(SocketEnum.CAM_SOCKET);
// Webcam.setDriver(new NativeDriver());
// Webcam webcam = Webcam.getDefault();
// Dimension[] viewSizes = webcam.getViewSizes();
// Dimension bigSize = WebcamResolution.VGA.getSize();
// for (Dimension d : viewSizes){
// //获取最大分辨率
// if(bigSize.getWidth()<d.getWidth()){
// bigSize = d;
// }
// }
// webcam.setViewSize(bigSize);
// webcam.open();
//
// final Rational framerate = Rational.make(1, 10);
//
// /** 创建muxer使用传递的文件名和格式名称如果有。 */
// final Muxer muxer = Muxer.make(filename, null, formatName);
//
// /** 决定要使用的编解码器类型muxer只能使用有限的编解码器集我们将选择第一个可用的编解码器
// * 或者如果用户提供了编解码器名称则强制使用该编解码器
// */
// final MuxerFormat format = muxer.getFormat();
// final Codec codec;
// if (codecName != null) {
// codec = Codec.findEncodingCodecByName(codecName);
// } else {
// codec = Codec.findEncodingCodec(Codec.ID.CODEC_ID_H264);
// }
//
// /**
// * 现在我们知道要使用的编解码器需要创建一个编码器
// */
// Encoder encoder = Encoder.make(codec);
//
// /**
// * 视频编码器至少需要知道以下信息
// * 宽度
// * 高度
// * 像素格式
// * 某些编码器还需要知道帧率旧的编码器可能有固定的帧率这里我们保持简单
// */
// encoder.setWidth(bigSize.width);
// encoder.setHeight(bigSize.height);
// // 我们将使用420P格式因为这是大多数现代视频格式所使用的
// final PixelFormat.Type pixelformat = PixelFormat.Type.PIX_FMT_YUV420P;
// encoder.setPixelFormat(pixelformat);
// encoder.setTimeBase(framerate);
//
// /** 某些格式需要全局而不是每流头文件因此需要告知编码器由于编码器与复用器分离
// * 没有简单的方法来确定这一点
// */
// if (format.getFlag(MuxerFormat.Flag.GLOBAL_HEADER))
// encoder.setFlag(Encoder.Flag.FLAG_GLOBAL_HEADER, true);
//
// /** 打开编码器。 */
// encoder.open(null, null);
//
// /** 将此流添加到复用器。 */
// muxer.addNewStream(encoder);
//
// /** 打开复用器以开始工作。 */
// muxer.open(null, null);
//
// /** 确保我们有正确的MediaPicture格式对象来编码数据
// * Java以及大多数屏幕图形程序使用某种变体的RGB图像编码大多数视频编解码器使用某种变体的YCrCb格式
// * 因此我们需要进行转换为此稍后我们将引入一个MediaPictureConverter对象
// */
// MediaPictureConverter converter = null;
// final MediaPicture picture = MediaPicture.make(
// encoder.getWidth(),
// encoder.getHeight(),
// pixelformat);
// picture.setTimeBase(framerate);
//
// /** 开始主循环,抓取屏幕快照并编码,然后写入结果包。 */
// final MediaPacket packet = MediaPacket.make();
// for (int i = 0; i < duration / framerate.getDouble(); i++) {
// /** 抓取屏幕并转换图像为TYPE_3BYTE_BGR */
// final BufferedImage screen = convertToType(webcam.getImage(), BufferedImage.TYPE_3BYTE_BGR);
//
// /** 图像很可能不是YUV420P格式因此我们将使用一些实用工具进行转换。 */
// if (converter == null)
// converter = MediaPictureConverterFactory.createConverter(screen, picture);
// converter.toPicture(picture, screen, i);
//
// do {
// encoder.encode(packet, picture);
// if (packet.isComplete()){
// Buffer data = packet.getData();
// SocketUtils.get(SocketEnum.CAM_SOCKET.getName())
// .broadcast(data.getByteBuffer(0, data.getBufferSize()));
// muxer.write(packet, false);
// }
// } while (packet.isComplete());
//
// /** 睡眠直到下一次抓取快照的时间。 */
// Thread.sleep((long) (1000 * framerate.getDouble()));
// }
//
// /** 编码器有时会缓存图片以进行关键帧优化因此需要刷新。与解码器一样约定是传递null输入直到输出不完整。 */
// do {
// encoder.encode(packet, null);
// if (packet.isComplete())
// muxer.write(packet, false);
// } while (packet.isComplete());
//
// /** 最后,清理资源。 */
// muxer.close();
// }
//
// @SuppressWarnings("static-access")
// public static void main(String[] args) throws InterruptedException, IOException, AWTException
// {
// final Options options = new Options();
// options.addOption("h", "help", false, "显示帮助");
// options.addOption("v", "version", false, "显示库版本");
// options.addOption(OptionBuilder.withArgName("format")
// .withLongOpt("format")
// .hasArg().
// withDescription("要使用的复用器格式。如果未指定,将根据文件名猜测")
// .create("f"));
// options.addOption(OptionBuilder.withArgName("codec")
// .withLongOpt("codec")
// .hasArg()
// .withDescription("编码视频时使用的编解码器;如果未指定,将根据格式猜测")
// .create("c"));
// options.addOption(OptionBuilder.withArgName("duration")
// .withLongOpt("duration")
// .hasArg()
// .withDescription("要录制的屏幕截图秒数默认为10秒")
// .create("d"));
// options.addOption(OptionBuilder.withArgName("snaps per second")
// .withLongOpt("snaps")
// .hasArg()
// .withDescription("每秒抓取的图片数量即帧率默认为5")
// .create("s"));
//
// final CommandLineParser parser = new org.apache.commons.cli.BasicParser();
// try {
// final CommandLine cmd = parser.parse(options, args);
// final String[] parsedArgs = cmd.getArgs();
// if (cmd.hasOption("version")) {
// // 查找正在运行的库版本
// final String version = io.humble.video_native.Version.getVersionInfo();
// System.out.println("Humble 版本: " + version);
// } else if (cmd.hasOption("help") || parsedArgs.length != 1) {
// final HelpFormatter formatter = new HelpFormatter();
// formatter.printHelp(RecordAndEncodeVideo.class.getCanonicalName() + " <filename>", options);
// } else {
// /**
// * 读取选项值及其默认值
// */
// final int duration = Integer.parseInt(cmd.getOptionValue("duration", "10"));
// if (duration <= 0)
// throw new IllegalArgumentException("duration 必须大于 0");
// final int snaps = Integer.parseInt(cmd.getOptionValue("snaps", "5"));
// if (snaps <= 0)
// throw new IllegalArgumentException("snaps 必须大于 0");
// final String codecname = cmd.getOptionValue("codec");
// final String formatname = cmd.getOptionValue("format");
// final String filename = cmd.getArgs()[0];
//
// recordScreen(filename, formatname, codecname, 1000);
// }
// } catch (ParseException e) {
// System.err.println("解析命令行时出现异常: " + e.getLocalizedMessage());
// }
// }
//
// /**
// * 将任意类型的 {@link BufferedImage} 转换为指定类型的 {@link BufferedImage}
// * 如果源图像已经是目标类型则返回源图像否则创建新图像并将源图像的内容复制到新图像中
// *
// * @param sourceImage 要转换的图像
// * @param targetType 目标 BufferedImage 类型
// * @return 指定目标类型的 BufferedImage
// * @see BufferedImage
// */
// public static BufferedImage convertToType(BufferedImage sourceImage,
// int targetType) {
// BufferedImage image;
//
// // 如果源图像是目标类型则返回源图像
// if (sourceImage.getType() == targetType)
// image = sourceImage;
//
// // 否则创建目标类型的新图像并绘制新图像
// else {
// image = new BufferedImage(sourceImage.getWidth(),
// sourceImage.getHeight(), targetType);
// image.getGraphics().drawImage(sourceImage, 0, 0, null);
// }
//
// return image;
// }
//}

View File

@ -1,99 +1,99 @@
package org.aohe.webcam;
import com.github.sarxos.webcam.Webcam;
import com.github.sarxos.webcam.WebcamResolution;
import io.humble.video.*;
import io.humble.video.awt.MediaPictureConverter;
import io.humble.video.awt.MediaPictureConverterFactory;
import org.aohe.constant.SocketEnum;
import org.aohe.web.SocketUtils;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
public class WebCam {
public static void main(String[] args) throws InterruptedException, IOException {
SocketUtils.start(SocketEnum.CAM_SOCKET);
Webcam webcam = Webcam.getDefault();
Dimension[] viewSizes = webcam.getViewSizes();
Dimension bigSize = WebcamResolution.VGA.getSize();
for (Dimension d : viewSizes){
//获取最大分辨率
if(bigSize.getWidth()<d.getWidth()){
bigSize = d;
}
}
webcam.setViewSize(bigSize);
webcam.open();
Rational framerate = Rational.make(1, 10); // 10 fps
int width = webcam.getViewSize().width;
int height = webcam.getViewSize().height;
Muxer muxer = Muxer.make("output.mp4", null, "mp4");
Codec codec = Codec.findEncodingCodec(Codec.ID.CODEC_ID_H264);
Encoder encoder = Encoder.make(codec);
encoder.setWidth(width);
encoder.setHeight(height);
encoder.setTimeBase(framerate);
encoder.setPixelFormat(PixelFormat.Type.PIX_FMT_YUV420P);
encoder.setFlag(Encoder.Flag.FLAG_GLOBAL_HEADER, true);
encoder.open(null, null);
MuxerStream muxerStream = muxer.addNewStream(encoder);
muxer.open(null, null);
MediaPicture picture = MediaPicture.make(
encoder.getWidth(),
encoder.getHeight(),
encoder.getPixelFormat());
picture.setTimeBase(framerate);
MediaPacket packet = MediaPacket.make();
System.out.println("BufferedImage Type: " + webcam.getImage().getType());
BufferedImage screen = convertToType(webcam.getImage(), BufferedImage.TYPE_3BYTE_BGR);
MediaPictureConverter converter = MediaPictureConverterFactory.createConverter(screen, picture);
while (true) {
System.out.println("frame 循环");
BufferedImage frame = convertToType(webcam.getImage(), BufferedImage.TYPE_3BYTE_BGR);
converter.toPicture(picture, frame, 0);
do {
System.out.println("packet 循环");
encoder.encode(packet, picture);
if (packet.isComplete()) {
System.out.println("packet size: " + packet.getSize());
SocketUtils.get(SocketEnum.CAM_SOCKET.getName())
.broadcast(packet.getData().getByteBuffer(0, packet.getSize()));
muxer.write(packet, false);
}
} while (packet.isComplete());
Thread.sleep(100);
}
}
public static BufferedImage convertToType(BufferedImage sourceImage,
int targetType) {
BufferedImage image;
// if the source image is already the target type, return the source image
if (sourceImage.getType() == targetType)
image = sourceImage;
// otherwise create a new image of the target type and draw the new
// image
else {
image = new BufferedImage(sourceImage.getWidth(),
sourceImage.getHeight(), targetType);
image.getGraphics().drawImage(sourceImage, 0, 0, null);
}
return image;
}
}
//package org.aohe.webcam;
//
//import com.github.sarxos.webcam.Webcam;
//import com.github.sarxos.webcam.WebcamResolution;
//import io.humble.video.*;
//import io.humble.video.awt.MediaPictureConverter;
//import io.humble.video.awt.MediaPictureConverterFactory;
//import org.aohe.constant.SocketEnum;
//import org.aohe.web.SocketUtils;
//
//import java.awt.*;
//import java.awt.image.BufferedImage;
//import java.io.IOException;
//
//public class WebCam {
//
// public static void main(String[] args) throws InterruptedException, IOException {
//
// SocketUtils.start(SocketEnum.CAM_SOCKET);
//
// Webcam webcam = Webcam.getDefault();
// Dimension[] viewSizes = webcam.getViewSizes();
// Dimension bigSize = WebcamResolution.VGA.getSize();
// for (Dimension d : viewSizes){
// //获取最大分辨率
// if(bigSize.getWidth()<d.getWidth()){
// bigSize = d;
// }
// }
// webcam.setViewSize(bigSize);
// webcam.open();
//
// Rational framerate = Rational.make(1, 10); // 10 fps
// int width = webcam.getViewSize().width;
// int height = webcam.getViewSize().height;
//
// Muxer muxer = Muxer.make("output.mp4", null, "mp4");
// Codec codec = Codec.findEncodingCodec(Codec.ID.CODEC_ID_H264);
// Encoder encoder = Encoder.make(codec);
// encoder.setWidth(width);
// encoder.setHeight(height);
// encoder.setTimeBase(framerate);
// encoder.setPixelFormat(PixelFormat.Type.PIX_FMT_YUV420P);
// encoder.setFlag(Encoder.Flag.FLAG_GLOBAL_HEADER, true);
// encoder.open(null, null);
//
// MuxerStream muxerStream = muxer.addNewStream(encoder);
// muxer.open(null, null);
//
// MediaPicture picture = MediaPicture.make(
// encoder.getWidth(),
// encoder.getHeight(),
// encoder.getPixelFormat());
// picture.setTimeBase(framerate);
//
// MediaPacket packet = MediaPacket.make();
//
// System.out.println("BufferedImage Type: " + webcam.getImage().getType());
// BufferedImage screen = convertToType(webcam.getImage(), BufferedImage.TYPE_3BYTE_BGR);
// MediaPictureConverter converter = MediaPictureConverterFactory.createConverter(screen, picture);
//
// while (true) {
// System.out.println("frame 循环");
// BufferedImage frame = convertToType(webcam.getImage(), BufferedImage.TYPE_3BYTE_BGR);
// converter.toPicture(picture, frame, 0);
//
// do {
// System.out.println("packet 循环");
// encoder.encode(packet, picture);
// if (packet.isComplete()) {
// System.out.println("packet size: " + packet.getSize());
// SocketUtils.get(SocketEnum.CAM_SOCKET.getName())
// .broadcast(packet.getData().getByteBuffer(0, packet.getSize()));
// muxer.write(packet, false);
// }
// } while (packet.isComplete());
//
// Thread.sleep(100);
// }
// }
//
// public static BufferedImage convertToType(BufferedImage sourceImage,
// int targetType) {
// BufferedImage image;
// // if the source image is already the target type, return the source image
// if (sourceImage.getType() == targetType)
// image = sourceImage;
// // otherwise create a new image of the target type and draw the new
// // image
// else {
// image = new BufferedImage(sourceImage.getWidth(),
// sourceImage.getHeight(), targetType);
// image.getGraphics().drawImage(sourceImage, 0, 0, null);
// }
//
// return image;
// }
//}
//