前言
近期接的一个公司内部安全终端需求,需要一个录屏功能,在调研了之后,使用 JavaCV 来实现录屏的功能。鉴于网上没有查找到扩展屏录制的相关代码,因此自己参考了官方文档后实现了录屏以及扩展屏录制的功能
基本原理看了下程序就是基于截屏,然后使用 ffmpeg 来合成视频。
JavaCV 就是一款开源的视觉处理库,封装了 FFmpeg、OpenCV、videoInput 和 ARToolKitPlus 等等计算机视觉的接口。
代码可在 github 查看:Fan-Yu-Feng/SpringBoot-demo: A SpringBoot Demo (github.com)
程序依赖
在 maven 的服务中添加以下的依赖。
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv-platform</artifactId>
<version>1.5.10</version>
</dependency>
<!-- 录屏需要的核心依赖 -->
<!-- https://mvnrepository.com/artifact/org.bytedeco.javacpp-presets/ffmpeg-platform -->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>ffmpeg-platform</artifactId>
<version>6.1.1-1.5.10</version>
</dependency>
录屏代码实现
/**
* @author fanyufeng
*
* 使用 javacv 进行录屏
*/
@Data
@Slf4j
public class VideoRecorderTest {
// 线程池 screenTimer
private ScheduledThreadPoolExecutor screenTimer;
private static boolean isStop = false;
// 获取屏幕尺寸
private static final int WIDTH = 2560;
private static final int HEIGHT = 1600;
private Rectangle rectangle;
// 视频类 FFmpegFrameRecorder
private Robot robot;
// 线程池 exec
private ScheduledThreadPoolExecutor exec;
private OpenCVFrameConverter.ToIplImage conveter;
// private BufferedImage screenCapture;
private TargetDataLine line;
private AudioFormat audioFormat;
private DataLine.Info dataLineInfo;
private boolean isHaveDevice = true;
private long startTime = 0;
private long videoTS = 0;
private long pauseTime = 0;
/* 录制的频率 */
private int frameRate = 1;
public VideoRecorderTest() {
}
// 定义时间格式,使用 "-" 拼接日期和时间
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss");
public static final String screenFilePath = "D:\\screenRecoding\\";
// String fileName = screenFilePath + DateUtil.formatAsDatetimeWithMs(new Date());
/**
* windows 多屏录制 demo
*/
@Test
public void recordTest() throws AWTException, InterruptedException, FFmpegFrameRecorder.Exception {
// 获取当前时间
LocalDateTime currentTime = LocalDateTime.now();
// 格式化当前时间为字符串
String fileName = screenFilePath + currentTime.format(formatter);
// 创建一个线程池
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice[] screens = ge.getScreenDevices();
Robot robot = new Robot();
screenTimer = new ScheduledThreadPoolExecutor(5);
isStop = false;
int i = 0;
for (GraphicsDevice screen : screens) {
DisplayMode displayMode = screen.getDisplayMode();
Rectangle screenRect = screen.getDefaultConfiguration().getBounds();
screenRect.height = displayMode.getHeight();
screenRect.width = displayMode.getWidth();
FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(fileName + i++ + ".mp4",
displayMode.getWidth(), displayMode.getHeight(), 0);
buildRecord(recorder);
screenTimer.scheduleAtFixedRate(() -> {
// 主动截屏屏幕偏红,需要重绘截图
BufferedImage screenCapture = robot.createScreenCapture(screenRect);
BufferedImage videoImg = new BufferedImage(displayMode.getWidth(), displayMode.getHeight(),
BufferedImage.TYPE_3BYTE_BGR);
// 声明一个 BufferedImage 用重绘截图
Graphics2D videoGraphics = videoImg.createGraphics();// 创建 videoImg 的 Graphics2D
videoGraphics.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE);
videoGraphics.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING,
RenderingHints.VALUE_COLOR_RENDER_SPEED);
videoGraphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
videoGraphics.drawImage(screenCapture, 0, 0, null); // 重绘截图
Java2DFrameConverter java2dConverter = new Java2DFrameConverter();
Frame frame = java2dConverter.convert(videoImg);
try {
// 检查偏移量
log.info("开始录制屏幕");
recorder.record(frame);
} catch (Exception e) {
log.error("录屏功能出现异常:", e);
} finally {
// 释放资源
// videoGraphics.dispose();
// videoGraphics = null;
videoImg.flush();
videoImg = null;
java2dConverter = null;
// screenCapture.flush();
// screenCapture = null;
}
if (isStop) {
try {
log.info("录制线程池结束运行");
recorder.stop();
recorder.release();
recorder.close();
Thread.sleep(1000);
screenTimer.shutdown();
} catch (Exception e) {
log.error("停止录屏功能出现异常:", e);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, 0, 1000 / frameRate, TimeUnit.MILLISECONDS);
}
// 线程睡眠 5S 录制 5S
Thread.sleep(1000 * 30);
log.info("执行完任务");
isStop = true;
Thread.sleep(1000 * 10);
// 等待任务执行完毕
}
private FFmpegFrameRecorder buildRecord(FFmpegFrameRecorder recorder) throws FFmpegFrameRecorder.Exception {
recorder.setVideoQuality(0);
recorder.setVideoOption("preset", "slow");
recorder.setVideoCodec(avcodec.AV_CODEC_ID_MPEG4); // 13
recorder.setFormat("mp4");
// recorder.setFormat("mov,mp4,m4a,3gp,3g2,mj2,h264,ogg,MPEG4");
recorder.setSampleRate(44100);
recorder.setFrameRate(frameRate);
recorder.setVideoOption("crf", "23");
// 2000 kb/s, 720P 视频的合理比特率范围 调整低了就改变画质,降低文件的大小
recorder.setVideoBitrate(50000); // 500 kbps
recorder.setPixelFormat(0); // yuv420p = 0
recorder.start();
return recorder;
}
}