使用javacv中的ffmpeg实现录屏

今天突发奇想,想自己写一个录屏的软件,上次写了一个专门录音的Demo,但是要把声音和视频放到一起合成一个mp4文件,着实有一点艰难,所以就打算使用ffmpeg来写一个,而这篇博客中会顺便谈一谈我碰到的各种坑。
ffmpeg是一个c++程序,要想在java中使用ffmpeg,无非就是两种方式:直接在java程序中调用ffmpeg.exe,还有就是通过jni的方式。而在这里我就是使用jni的方式,但是我在这里直接使用javacv这个框架来实现就可以,用这个的好处就是你什么都不要干,直接导入几个重要的jar包就可以。

步骤:
首先呢,下载javacv就可以:github下载
你也可以直接在我这里下载
然后下载好了,就要开始导包了,导入javacpp,javacv-platform,javacv,这三个一定要导,另外要能使用ffmpeg的API实现录屏就要再导入ffmpeg和videoinput。
在这里插入图片描述
导完包之后代码测试一下,这里发一个别人写的代码,可以实现录屏,但不能录音,代码里面需要修改一下存放文件的路径:

 

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;

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/28233.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

HarmonyOS学习路之开发篇—Java UI框架(StackLayout)

StackLayout StackLayout直接在屏幕上开辟出一块空白的区域,添加到这个布局中的视图都是以层叠的方式显示,而它会把这些视图默认放到这块区域的左上角,第一个添加到布局中的视图显示在最底层,最后一个被放在最顶层。上一层的视图…

数据湖仓一体化架构:探究新一代数据处理的可能性

一、引言 随着大数据的快速发展,企业不断寻求高效、灵活和经济的方法来处理和管理海量数据。在这种背景下,数据湖和数据仓库这两种不同的架构模式各自展现出其独特的优势。而数据湖仓一体化架构,是对这两种模式优势的综合,为企业…

wenda+fess问答系统

1 安装conda 2 创建环境 conda activate --name wenda python3.8 3 安装依赖工具包 pip install -r requirements/requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple pip install torch BeautifulSoup4 torchvision torchaudio pdfminer.six -i https://pypi.t…

【乐观锁与悲观锁】—— 每天一点小知识

💧 乐观锁与悲观锁 \color{#FF1493}{乐观锁与悲观锁} 乐观锁与悲观锁💧 🌷 仰望天空,妳我亦是行人.✨ 🦄 个人主页——微风撞见云的博客🎐 🐳 《数据结构与算法》专栏的文章图文并茂&…

美团买菜基于 Flink 的实时数仓建设

摘要:本文整理自美团买菜实时数仓技术负责人严书,在 Flink Forward Asia 2022 实时湖仓专场的分享。本篇内容主要分为四个部分: 背景介绍 技术愿景和架构设计 典型场景、挑战与应对 未来规划 点击查看原文视频 & 演讲PPT 一、背景介绍…

设置全局loading

为什么要设置全局loading? 在项目开发过程中,请求接口的时候延迟没有数据,页面感觉狠卡顿,这个时候就要用loading来做一个延迟界面。 但是每个界面都写loading的话就会很复杂,所以今天给大家带来了一个全局loading的…

JMeter+Ant+jenkins搭建接口自动化测试环境

目录 前言: 1.ant简介 2. 构建ant环境 3.JMeter与Ant集成 4. 报告优化 5.jenkins持续集成 前言: JMeter是一个开源的性能测试工具,可以用于测试Web应用程序或API接口的性能,支持多种通信协议和数据格式。Ant是一个构建工具&…

基于卫星星历计算卫星在CGCS2000大地坐标系中的坐标

目录 一、北斗系统概述 1.空间星座 2.坐标系统 3.时间系统 二、实验目的 三、实验内容 四、实验过程 五、实验结果 一、北斗系统概述 1.空间星座 北斗卫星导航系统简称北斗系统,英文缩写为 BDS,其空间星座由 5 颗地球静止轨道(GEO&…

学会使用这些Lumion照片级渲染技巧,秒出大片

Lumion 是一种渲染软件,可帮助建筑师以清晰、感性的方式传达他们的设计。十年来,人们发现 Lumion 的每个新版本都有新的功能、工作流程和控制方法。他们可以在 Revit、SketchUp 或其他 BIM 程序等软件中建模,并将模型导入 Lumion 进行渲染&am…

基于stm32作品设计:多功能氛围灯、手机APP无线控制ws2812,MCU无线升级程序

文章目录 一、作品背景二、功能设计与实现过程三、实现基础功能(一)、首先是要选材(二)、原理图设计(二)、第一版本PCB设计(三)、焊接PCB板(四)编写单片机程序…

作为自动化测试工程师,这4个自动化测试阶段你真的知道吗?

目录:导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜) 前言 阶段一&#xff1…

Kubernetes集群添加新集群节点

Kubernetes集群添加新集群节点 添加worker节点 参考文档https://gitee.com/open-hand/kubeadm-ha/blob/release-1.21/docs/02/%E6%B7%BB%E5%8A%A0%20worker%20%E8%8A%82%E7%82%B9.md 添加工作节点与集群安装时初始化工作节点一样,可以在主节点上执行,也可以在要加…

第四章 完型填空

第四章 完型填空 第一节 真题 2020-完型填空- Section I Use of English Directions: Read the following text. Choose the best word (s) for each numbered blank and mark A, B, C or D on the ANSWER SHEET. (10 points) Being a good parent is, of cour…

springboot 连接 kafka集群(kafka版本 2.13-3.4.0)

springboot 连接 kafka集群 一、环境搭建1.1 springboot 环境1.2 kafka 依赖 二、 kafka 配置类2.1 发布者2.1.1 配置2.1.2 构建发布者类2.1.3 发布消息 2.2 消费者2.2.1 配置2.2.2 构建消费者类2.2.3 进行消息消费 一、环境搭建 1.1 springboot 环境 JDK 11 Maven 3.8.x spr…

CSDN问答机器人

文章目录 前言一、背景二、总体流程三、构建知识库四、粗排五、精排六、Prompt总结相关博客 前言 先看结果: 已经连续很多周获得了第二名(万年老二), 上周终于拿了一回第一, 希望继续保持. 😁 这是今天的榜单, 采纳的数量相对较少, 之前基本上维持在100 重点说明…

SpringBoot项目实战:自定义异常和统一参数验证(附源码)

你好,我是田哥 在实际开发过程中,不可避免的是需要处理各种异常,异常处理方法随处可见,所以代码中就会出现大量的try {...} catch {...} finally {...} 代码块,不仅会造成大量的冗余代码,而且还影响代码的可…

母婴商家怎么建立自己的品牌,母婴产品传播渠道总结

随着互联网的发展逐渐深入我们的生活,线上传播的模式也越来越被大家熟知。越来越多的行业开始重视线上传播。那么母婴商家怎么建立自己的品牌,母婴产品传播渠道总结。 其实,母婴产品线上用户群体众多,且母婴产品用户目的明确&…

深入解析IT专业分类、方向及就业前景:高考毕业生如何选择适合自己的IT专业?重点探索近年来人工智能专业发展及人才需求

目录 一、IT专业的就业前景和发展趋势二、了解IT专业的分类和方向三、你对本专业的看法和感想四、本专业对人能力素养的要求五、建议和思考其它资料下载 当今社会,信息技术行业以其迅猛的发展和无限的潜力成为了吸引无数年轻人的热门选择。特别是对于高考毕业生来说…

你的企业还没搭建这个帮助中心网页,那你太落后了!

作为现代企业,拥有一个完善的帮助中心网页已经成为了不可或缺的一部分。帮助中心网页不仅可以提供给用户有关产品或服务的详细信息,还可以解答用户的疑问和提供技术支持,使用户在使用产品或服务时遇到问题可以很快地得到解决。因此&#xff0…

论文阅读和分析:Binary CorNET Accelerator for HR Estimation From Wrist-PPG

主要贡献: 一种完全二值化网络(bCorNET)拓扑结构及其相应的算法-架构映射和高效实现。对CorNET进行量化后,减少计算量,又能实现减轻运动伪影的效果。 该框架在22个IEEE SPC受试者上的MAE为6.675.49 bpm。该设计采用ST65 nm技术框架&#xff…