Java通过FFmpeg录制屏幕
FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。采用LGPL或GPL许可证。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频/视频编解码库libavcodec,为了保证高可移植性和编解码质量,libavcodec里很多code都是从头开发的。
PS:有不少人不清楚“FFmpeg”应该怎么读。它读作“ef ef em peg”
maven依赖
<!-- https://mvnrepository.com/artifact/org.bytedeco/javacv --> <dependency> <groupId>org.bytedeco</groupId> <artifactId>javacv</artifactId> <version>1.4.4</version> </dependency> <!-- https://mvnrepository.com/artifact/org.bytedeco.javacpp-presets/ffmpeg-platform --> <dependency> <groupId>org.bytedeco.javacpp-presets</groupId> <artifactId>ffmpeg-platform</artifactId> <version>4.1-1.4.4</version> </dependency>
java代码
import java.awt.AWTException; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Robot; import java.awt.image.BufferedImage; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.ShortBuffer; import java.util.Scanner; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.DataLine; import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.TargetDataLine; import org.bytedeco.javacpp.avcodec; import org.bytedeco.javacpp.avutil; import org.bytedeco.javacv.FFmpegFrameRecorder; import org.bytedeco.javacv.Frame; import org.bytedeco.javacv.FrameRecorder.Exception; import org.bytedeco.javacv.Java2DFrameConverter; /** * 使用javacv进行录屏 * */ public class VideoRecord { //线程池 screenTimer private ScheduledThreadPoolExecutor screenTimer; //获取屏幕尺寸 private final Rectangle rectangle = new Rectangle(Constant.WIDTH, Constant.HEIGHT); // 截屏的大小 //视频类 FFmpegFrameRecorder private FFmpegFrameRecorder recorder; private Robot robot; //线程池 exec private ScheduledThreadPoolExecutor exec; 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 double frameRate = 5; public VideoRecord(String fileName, boolean isHaveDevice) { // TODO Auto-generated constructor stub recorder = new FFmpegFrameRecorder(fileName + ".mp4", Constant.WIDTH, Constant.HEIGHT); // recorder.setVideoCodec(avcodec.AV_CODEC_ID_H265); // 28 // recorder.setVideoCodec(avcodec.AV_CODEC_ID_FLV1); // 28 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.setVideoQuality(0); recorder.setVideoOption("crf", "23"); // 2000 kb/s, 720P视频的合理比特率范围 recorder.setVideoBitrate(1000000); /** * 权衡quality(视频质量)和encode speed(编码速度) values(值): ultrafast(终极快),superfast(超级快), * veryfast(非常快), faster(很快), fast(快), medium(中等), slow(慢), slower(很慢), * veryslow(非常慢) * ultrafast(终极快)提供最少的压缩(低编码器CPU)和最大的视频流大小;而veryslow(非常慢)提供最佳的压缩(高编码器CPU)的同时降低视频流的大小 * 参考:https://trac.ffmpeg.org/wiki/Encode/H.264 官方原文参考:-preset ultrafast as the * name implies provides for the fastest possible encoding. If some tradeoff * between quality and encode speed, go for the speed. This might be needed if * you are going to be transcoding multiple streams on one machine. */ recorder.setVideoOption("preset", "slow"); recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P); // yuv420p recorder.setAudioChannels(2); recorder.setAudioOption("crf", "0"); // Highest quality recorder.setAudioQuality(0); recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC); try { robot = new Robot(); } catch (AWTException e) { // TODO Auto-generated catch block e.printStackTrace(); } try { recorder.start(); } catch (Exception e) { // TODO Auto-generated catch block System.out.print("*******************************"); } this.isHaveDevice = isHaveDevice; } /** * 开始录制 */ public void start() { if (startTime == 0) { startTime = System.currentTimeMillis(); } if (pauseTime == 0) { pauseTime = System.currentTimeMillis(); } // 如果有录音设备则启动录音线程
//如果录制的视频没有声音 加上isHaveDevice=true;
if (isHaveDevice) {
new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub caputre(); } }).start(); } // 录屏 screenTimer = new ScheduledThreadPoolExecutor(1); screenTimer.scheduleAtFixedRate(new Runnable() { @Override public void run() { // 将screenshot对象写入图像文件 // try { // ImageIO.write(screenCapture, "JPEG", f); // videoGraphics.drawImage(screenCapture, 0, 0, null); // IplImage image = cvLoadImage(name); // 非常吃内存!! // // 创建一个 timestamp用来写入帧中 // videoTS = 1000 // * (System.currentTimeMillis() - startTime - (System.currentTimeMillis() - // pauseTime)); // // 检查偏移量 // if (videoTS > recorder.getTimestamp()) { // recorder.setTimestamp(videoTS); // } BufferedImage screenCapture = robot.createScreenCapture(rectangle); // 截屏 BufferedImage videoImg = new BufferedImage(Constant.WIDTH, Constant.HEIGHT, 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 { videoTS = 1000L * (System.currentTimeMillis() - startTime - (System.currentTimeMillis() - pauseTime)); // 检查偏移量 if (videoTS > recorder.getTimestamp()) { recorder.setTimestamp(videoTS); } recorder.record(frame); // 录制视频 } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } // 释放资源 videoGraphics.dispose(); videoGraphics = null; videoImg.flush(); videoImg = null; java2dConverter = null; screenCapture.flush(); screenCapture = null; } }, (int) (1000 / frameRate), (int) (1000 / frameRate), TimeUnit.MILLISECONDS); } /** * 抓取声音 */ public void caputre() { audioFormat = new AudioFormat(44100.0F, 16, 2, true, false); dataLineInfo = new DataLine.Info(TargetDataLine.class, audioFormat); try { line = (TargetDataLine) AudioSystem.getLine(dataLineInfo); } catch (LineUnavailableException e1) { // TODO Auto-generated catch block System.out.println("#################"); } try { line.open(audioFormat); } catch (LineUnavailableException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } line.start(); final int sampleRate = (int) audioFormat.getSampleRate(); final int numChannels = audioFormat.getChannels(); int audioBufferSize = sampleRate * numChannels; final byte[] audioBytes = new byte[audioBufferSize]; exec = new ScheduledThreadPoolExecutor(1); exec.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { int nBytesRead = line.read(audioBytes, 0, line.available()); int nSamplesRead = nBytesRead / 2; short[] samples = new short[nSamplesRead]; // Let's wrap our short[] into a ShortBuffer and // pass it to recordSamples ByteBuffer.wrap(audioBytes).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(samples); ShortBuffer sBuff = ShortBuffer.wrap(samples, 0, nSamplesRead); // recorder is instance of // org.bytedeco.javacv.FFmpegFrameRecorder recorder.recordSamples(sampleRate, numChannels, sBuff); // System.gc(); } catch (org.bytedeco.javacv.FrameRecorder.Exception e) { e.printStackTrace(); } } }, (int) (1000 / frameRate), (int) (1000 / frameRate), TimeUnit.MILLISECONDS); } /** * 停止 */ public void stop() { if (null != screenTimer) { screenTimer.shutdownNow(); } try { recorder.stop(); recorder.release(); recorder.close(); screenTimer = null; // screenCapture = null; if (isHaveDevice) { if (null != exec) { exec.shutdownNow(); } if (null != line) { line.stop(); line.close(); } dataLineInfo = null; audioFormat = null; } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } /** * 暂停 * * @throws Exception */ public void pause() throws Exception { screenTimer.shutdownNow(); screenTimer = null; if (isHaveDevice) { exec.shutdownNow(); exec = null; line.stop(); line.close(); dataLineInfo = null; audioFormat = null; line = null; } pauseTime = System.currentTimeMillis(); } public static void main(String[] args) throws Exception, AWTException { VideoRecord videoRecord = new VideoRecord("D:\\test", false); videoRecord.start(); while (true) { System.out.println("你要停止吗?请输入(stop),程序会停止。"); Scanner sc = new Scanner(System.in); if (sc.next().equalsIgnoreCase("stop")) { videoRecord.stop(); System.out.println("停止"); } if (sc.next().equalsIgnoreCase("pause")) { videoRecord.pause(); System.out.println("暂停"); } if (sc.next().equalsIgnoreCase("start")) { videoRecord.start(); System.out.println("开始"); } } } }
类constant
@Data public class Constant { public static int WIDTH = 800; public static int HEIGHT = 860; }
注意:本录屏功能的视频窗口为电脑屏幕的固定位置,录的视频内容可能不太"干净"
转载:https://blog.csdn.net/weixin_42802298/article/details/98101491