JavaCV使用ffmpeg实现录屏功能
今天突发奇想,想自己写一个录屏的软件,上次写了一个专门录音的Demo,但是要把声音和视频放到一起合成一个mp4文件,着实有一点艰难,所以就打算使用ffmpeg来写一个,而这篇博客中会顺便谈一谈我碰到的各种坑。
ffmpeg是一个c++程序,要想在java中使用ffmpeg,无非就是两种方式:直接在java程序中调用ffmpeg.exe,还有就是通过jni的方式。而在这里我就是使用jni的方式,但是我在这里直接使用javacv这个框架来实现就可以,用这个的好处就是你什么都不要干,直接导入几个重要的jar包就可以。
步骤
首先呢,下载javacv就可以
你也可以直接在我这里下载
然后下载好了,就要开始导包了,导入javacpp,javacv-platform,javacv,这三个一定要导,另外要能使用ffmpeg的API实现录屏就要再导入ffmpeg和videoinput。
导完包之后代码测试一下,这里发一个别人写的代码,可以实现录屏,但不能录音,代码里面需要修改一下存放文件的路径:
实现代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 | package com; import java.awt.AWTException; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Robot; import java.awt.Toolkit; 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.ffmpeg.global.avcodec; import org.bytedeco.ffmpeg.global.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(); } // 如果有录音设备则启动录音线程 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( "C:\\Users\\Administrator\\Desktop\\视频" , 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( "开始" ); } } } } class Constant{ public final static int WIDTH=Toolkit.getDefaultToolkit().getScreenSize().width; public final static int HEIGHT=Toolkit.getDefaultToolkit().getScreenSize().height; } |
到此这篇关于JavaCV使用ffmpeg实现录屏功能的文章就介绍到这了,更多相关JavaCV ffmpeg录屏内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
微信公众号搜索 “ 脚本之家 ” ,选择关注
程序猿的那些事、送书等活动等着你
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 reterry123@163.com 进行投诉反馈,一经查实,立即处理!
相关文章
Springboot+Mybatis中typeAliasesPackage正则扫描实现方式
这篇文章主要介绍了Springboot+Mybatis中typeAliasesPackage正则扫描实现方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教2021-07-07Spring @ExceptionHandler注解统一异常处理和获取方法名
这篇文章主要介绍了Spring注解之@ExceptionHandler 统一异常处理和获取方法名,在实际项目中,合理使用@ExceptionHandler能够提高代码的可维护性和用户体验,通过本文的解析和实践,读者可以更好地理解和掌握@ExceptionHandler的用法和原理2023-09-09
最新评论