javaCV


目录

示例一:调用本地摄像头

参考地址:https://www.jianshu.com/p/9920d1636787

1.环境准备

   <dependency>
      <groupId>org.bytedeco</groupId>
      <artifactId>javacv-platform</artifactId>
      <version>1.5</version>
   </dependency>

2.调用本地摄像头并且显示在CanvasFrame里面

import org.bytedeco.javacv.CanvasFrame;
import org.bytedeco.javacv.FrameGrabber;
import org.bytedeco.javacv.OpenCVFrameGrabber;

import javax.swing.*;

/**
 * @author JHL
 * @version 1.0
 * @date 2022/8/25 14:50
 * @since : JDK 11
 */
public class T {


    public static void main(String[] args) {
        OpenCVFrameGrabber grabber = new OpenCVFrameGrabber(0);// 0就是本地摄像头
        try {
            grabber.start();   //开始获取摄像头数据
        } catch (FrameGrabber.Exception e) {
            e.printStackTrace();
        }

        CanvasFrame canvas = new CanvasFrame("JavaCV");//新建一个窗口
        canvas.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        while (true) {
            if (!canvas.isDisplayable())// 窗口状态控制线程
            {
                try {
                    grabber.stop();//停止抓取摄像头
                } catch (FrameGrabber.Exception e) {
                    e.printStackTrace();
                }
            }
            try {
                canvas.showImage(grabber.grab());//获取摄像头一帧视频图像并放到窗口上显示
            } catch (FrameGrabber.Exception e) {
                e.printStackTrace();
            }
            /*
            try {
                Thread.sleep(20);//可以加个间隔
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            */
        }
    }
}

示例二:javacv实现直播流

参考地址:https://www.jianshu.com/p/238e52bc16c4

pom依赖

    <!-- 需要注意,javacv主要是一组API为主,还需要加入对应的实现 -->
    <dependency>
        <groupId>org.bytedeco</groupId>
        <artifactId>javacv</artifactId>
        <version>1.5.6</version>
    </dependency>

    <!-- 用到了 ffmpeg 需要把 ffmpeg 的平台实现依赖引入 -->
    <dependency>
        <groupId>org.bytedeco</groupId>
        <artifactId>ffmpeg-platform</artifactId>
        <version>4.4-1.5.6</version>
    </dependency>

    <!--所有平台实现,依赖非常大,几百MB吧-->
    <!--<dependency>
        <groupId>org.bytedeco</groupId>
        <artifactId>javacv-platform</artifactId>
        <version>1.5.6</version>
    </dependency>-->

    <dependency>
        <groupId>org.openjfx</groupId>
        <artifactId>javafx-controls</artifactId>
        <version>17.0.2</version>
    </dependency>

    <dependency>
        <groupId>org.openjfx</groupId>
        <artifactId>javafx-base</artifactId>
        <version>17.0.2</version>
    </dependency>

    <dependency>
        <groupId>org.openjfx</groupId>
        <artifactId>javafx-graphics</artifactId>
        <version>17.0.2</version>
    </dependency>

    <dependency>
        <groupId>org.openjfx</groupId>
        <artifactId>javafx-fxml</artifactId>
        <version>17.0.2</version>
    </dependency>

    <dependency>
        <groupId>org.openjfx</groupId>
        <artifactId>javafx-swing</artifactId>
        <version>17.0.2</version>
    </dependency>

    <dependency>
        <groupId>org.openjfx</groupId>
        <artifactId>javafx-web</artifactId>
        <version>17.0.2</version>
    </dependency>

    <dependency>
        <groupId>org.openjfx</groupId>
        <artifactId>javafx-media</artifactId>
        <version>17.0.2</version>
    </dependency>

</dependencies>

测试类

import cn.hutool.core.io.FileUtil;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.javacv.*;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.TargetDataLine;
import java.io.File;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.ShortBuffer;
import java.util.Timer;
import java.util.TimerTask;

/**
 * @author JHL
 * @version 1.0
 * @date 2022/8/25 14:50
 * @since : JDK 11
 */
public class Test extends Application {


    private static final int frameRate = 24;// 录制的帧率
    private static boolean isStop = false;

    private static TargetDataLine line;

    @Override
    public void start(Stage primaryStage) throws Exception {
        primaryStage.setTitle("我的桌面录屏大师");
        ImageView imageVideo = new ImageView();// 用于软件录制显示
        imageVideo.setFitWidth(800);
        imageVideo.setFitHeight(600);
        Button button = new Button("停止录制");
        button.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                isStop = true;
                if (line != null) {// 马上停止声音录入
                    try {
                        line.close();
                    } catch (Exception e) {
                    }
                }
                Alert alert = new Alert(Alert.AlertType.INFORMATION);
                alert.setTitle("info");
                alert.setHeaderText("已经停止录制");
                alert.setOnCloseRequest(event1 -> alert.hide());
                alert.showAndWait();
            }
        });

        VBox box = new VBox();
        box.getChildren().addAll(button, imageVideo);
        primaryStage.setScene(new Scene(box));
        primaryStage.setHeight(600);
        primaryStage.setWidth(800);
        primaryStage.show();
        primaryStage.setOnCloseRequest(new EventHandler<WindowEvent>() {
            @Override
            public void handle(WindowEvent event) {// 退出时停止
                isStop = true;
                System.exit(0);
            }
        });


        // 帧记录
        // window 建议使用 FFmpegFrameGrabber("desktop") 进行屏幕捕捉
        FrameGrabber grabber = new FFmpegFrameGrabber("desktop");
        grabber.setFormat("gdigrab");
        grabber.setFrameRate(frameRate);// 帧获取间隔
        // 捕获指定区域,不设置则为全屏
        grabber.setImageHeight(600);
        grabber.setImageWidth(800);
        // grabber.setOption("offset_x", "200");
        // grabber.setOption("offset_y", "200");//必须设置了大小才能指定区域起点,参数可参考 FFmpeg 入参
        grabber.start();

        // 视频格式 flv/avi
        String videoFormat = "avi";
        File file = FileUtil.newFile("output." + videoFormat);
        if (file.exists()) {
            file.delete();
        }

        // 直播推流
        // final FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(
        //         "rtmp://10.8.4.191/live/livestream",
        //         grabber.getImageWidth(), grabber.getImageHeight(), 2);

        // 存到本地
        // final FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(
        //         "test.flv",
        //         grabber.getImageWidth(), grabber.getImageHeight(), 2);

        // 用于存储视频 , 调用stop后,需要释放,就会在指定位置输出文件,,这里我保存到D盘
        FFmpegFrameRecorder recorder = FFmpegFrameRecorder.createDefault(file, grabber.getImageWidth(), grabber.getImageHeight());
        recorder.setInterleaved(true);
        // https://trac.ffmpeg.org/wiki/StreamingGuide
        recorder.setVideoOption("tune", "zerolatency");// 加速
        // https://trac.ffmpeg.org/wiki/Encode/H.264
        recorder.setVideoOption("preset", "ultrafast");
        recorder.setFrameRate(frameRate);// 设置帧率,重要!
        // Key frame interval, in our case every 2 seconds -> 30 (fps) * 2 = 60
        recorder.setGopSize(frameRate * 2);
        recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);// 编码,使用编码能让视频占用内存更小,根据实际自行选择
        // https://trac.ffmpeg.org/wiki/Encode/H.264
        recorder.setVideoOption("crf", "28");
        // 2000 kb/s  720P
        recorder.setVideoBitrate(2000000);
        recorder.setFormat(videoFormat);


        // 添加音频录制
        // 不可变音频
        recorder.setAudioOption("crf", "0");
        // 最高音质
        recorder.setAudioQuality(0);
        // 192 Kbps
        recorder.setAudioBitrate(192000);
        recorder.setSampleRate(44100);
        recorder.setAudioChannels(2);
        recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);

        recorder.start();

        // 44100  16声道
        AudioFormat audioFormat = new AudioFormat(44100.0F, 16, 2, true, false);
        DataLine.Info dataLineInfo = new DataLine.Info(TargetDataLine.class, audioFormat);
        // 可以捕捉不同声道
        line = (TargetDataLine) AudioSystem.getLine(dataLineInfo);
        // 录制声音
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    line.open(audioFormat);
                    line.start();

                    final int sampleRate = (int) audioFormat.getSampleRate();
                    final int numChannels = audioFormat.getChannels();

                    // 缓冲区
                    final int audioBufferSize = sampleRate * numChannels;
                    final byte[] audioBytes = new byte[audioBufferSize];
                    Timer timer = new Timer();
                    timer.schedule(new TimerTask() {
                        @Override
                        public void run() {
                            try {
                                if (isStop) {// 停止录音
                                    line.stop();
                                    line.close();
                                    System.out.println("已经停止!");
                                    timer.cancel();
                                }

                                // 读取音频
                                // read会阻塞
                                int readLenth = 0;
                                while (readLenth == 0){
                                    readLenth = line.read(audioBytes, 0, line.available());
                                }
                                // audioFormat 定义了音频输入为16进制,需要将字节[]转为短字节[]
                                // FFmpegFrameRecorder.recordSamples 源码中的 AV_SAMPLE_FMT_S16
                                int rl = readLenth / 2;
                                short[] samples = new short[rl];

                                // short[] 转换为 ShortBuffer
                                ByteBuffer.wrap(audioBytes).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(samples);
                                ShortBuffer sBuff = ShortBuffer.wrap(samples, 0, rl);

                                // 记录
                                recorder.recordSamples(sampleRate, numChannels, sBuff);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    }, 1000, 1000 / frameRate);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    // 获取屏幕捕捉的一帧
                    Frame frame = null;
                    // 屏幕录制,由于已经对音频进行了记录,需要对记录时间进行调整即可
                    // 即上面调用了 recorder.recordSamples 需要重新分配时间,否则视频输出时长等于实际 的2倍
                    while ((frame = grabber.grab()) != null) {
                        if (isStop) {
                            try {
                                // 停止
                                recorder.stop();
                                grabber.stop();
                                // 释放内存,我们都知道c/c++需要手动释放资源
                                recorder.release();
                                grabber.release();
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                            break;
                        }

                        // 将这帧放到录制
                        recorder.record(frame);
                        Image convert = new JavaFXFrameConverter().convert(frame);
                        imageVideo.setImage(convert);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

什么是javaFX应用???

JavaFx类型应用启动引导类,解决启动报错

错误: 缺少 JavaFX 运行时组件, 需要使用该组件来运行此应用程序。

原因详解:https://www.cnblogs.com/hhddd-1024/p/16634369.html

/**
 * 用来引导 JavaFX应用解决控制台异常:错误: 缺少 JavaFX 运行时组件, 需要使用该组件来运行此应用程序。
 *
 * @author JHL
 * @version 1.0
 * @date 2022/8/25 18:03
 * @since : JDK 11
 */
public class JavaFXBootstrap {
    public static void main(String[] args) {
        Test.main(args);
    }
}

然后用docker起一个srs进行推流播放。

# 先启动
docker run -p 1935:1935 -p 1985:1985 -p 8080:8080 /
    ccr.ccs.tencentyun.com/ossrs/srs:4

最后消费者端即可拉流

示例三:javaCV 视频工具—截取视频缩略图、获取视频属性

https://www.jianshu.com/p/d691f0b68060

前言

通过javaCV 视频工具—截取视频缩略图、获取视频属性

依赖引入

<!--javaCV 视频工具-->
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>javacv-platform</artifactId>
            <version>1.5</version>
        </dependency>

实现

@Slf4j
public class VideoUtils {

    private static final String IMAGEMAT = "png";
    private static final String ROTATE = "rotate";

    /**
     * 默认截取视频的中间帧为封面
     */
    public static final int MOD = 2;

    /**
     * 视频缩略图后缀
     */
    private static final String VIDEO_THUMBNAIL_SUF = "th.png";

    /**
     * 视频缩略图前缀
     */
    private static final String VIDEO_THUMBNAIL_PRE = "video/thumbnail/";

    private static final String SYMBOL = ".";

    /**
     * 获取视频缩略图
     * @param filePath:视频路径
     * @param mod:视频长度/mod获取第几帧
     * @throws Exception
     */
    public static String randomGrabberFFmpegImage(String filePath, int mod) {
        String targetFilePath = "";
        try{
            FFmpegFrameGrabber ff = FFmpegFrameGrabber.createDefault(filePath);
            ff.start();
            //图片位置是否正确
            String rotate = ff.getVideoMetadata(ROTATE);
            //获取帧数
            int ffLength = ff.getLengthInFrames();
            Frame f;
            int i = 0;
            //设置截取帧数
            int index = ffLength / mod;
            while (i < ffLength) {
                f = ff.grabImage();
                if(i == index){
                    if (null != rotate && rotate.length() > 1) {
                        OpenCVFrameConverter.ToIplImage converter = new OpenCVFrameConverter.ToIplImage();
                        IplImage src = converter.convert(f);
                        f = converter.convert(rotate(src, Integer.parseInt(rotate)));
                    }
                    targetFilePath = getImagePath(filePath, i);
                    doExecuteFrame(f, targetFilePath);
                    break;
                }
                i++;
            }
            ff.stop();
        }catch (Exception e){
            log.error("获取视频缩略图异常:" + e.getMessage());
        }
        return targetFilePath;
    }

    /**
     * 随机生成生成缩略图存放路径
     * @param filePath:视频路径
     * @param index:第几帧
     * @return:缩略图的存放路径
     */
    private static String getImagePath(String filePath, int index){
        String fileName = FileUtils.getName(filePath);
        //去后缀
        fileName = fileName.substring(0, fileName.indexOf(SYMBOL));
        return TencentCosConfig.baseUrl + VIDEO_THUMBNAIL_PRE + DateUtils.datePath() + "/" + fileName + "_" + index +  VIDEO_THUMBNAIL_SUF;
    }

    /**
     * 旋转图片
     * @param src
     * @param angle
     * @return
     */
    public static IplImage rotate(IplImage src, int angle) {
        IplImage img = IplImage.create(src.height(), src.width(), src.depth(), src.nChannels());
        opencv_core.cvTranspose(src, img);
        opencv_core.cvFlip(img, img, angle);
        return img;
    }

    /**
     * 截取缩略图
     * @param f
     * @param targerFilePath:封面图片
     */
    public static void doExecuteFrame(Frame f, String targerFilePath) {
        COSClient cosClient = TencentCosUtils.initCosClient();

        if (null == f || null == f.image) {
            return;
        }
        Java2DFrameConverter converter = new Java2DFrameConverter();
        BufferedImage bi = converter.getBufferedImage(f);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        try {
            ImageIO.write(bi, IMAGEMAT, out);
            // 获取文件流
            InputStream bufferedImage = new ByteArrayInputStream(out.toByteArray());
            int length = out.size();
            ObjectMetadata objectMetadata = new ObjectMetadata();
            // 从输入流上传必须制定content length, 否则http客户端可能会缓存所有数据,存在内存OOM的情况
            objectMetadata.setContentLength(length);
            // 默认下载时根据cos路径key的后缀返回响应的contenttype, 上传时设置contenttype会覆盖默认值
            PutObjectRequest putObjectRequest = new PutObjectRequest(TencentCosConfig.bucket, targerFilePath, bufferedImage, objectMetadata);
            PutObjectResult putObjectResult = cosClient.putObject(putObjectRequest);
            log.info("腾讯COS上传视频缩略图成功:{}", putObjectResult.getETag());
            //关闭输入输出流
            bufferedImage.close();
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            cosClient.shutdown();
        }
    }

    /**
     * 根据视频长度随机生成随机数集合
     * @param baseNum:基础数字,此处为视频长度
     * @param length:随机数集合长度
     * @return:随机数集合
     */
    public static List<Integer> random(int baseNum, int length) {
        List<Integer> list = new ArrayList<Integer>(length);
        while (list.size() < length) {
            Integer next = (int) (Math.random() * baseNum);
            if (list.contains(next)) {
                continue;
            }
            list.add(next);
        }
        Collections.sort(list);
        return list;
    }

    /**
     * 获取视频时长 单位/秒
     * @param video
     * @return
     */
    public static long getVideoDuration(File video) {
        long duration = 0L;
        FFmpegFrameGrabber ff = new FFmpegFrameGrabber(video);
        try {
            ff.start();
            duration = ff.getLengthInTime() / (1000 * 1000);
            ff.stop();
        } catch (FrameGrabber.Exception e) {
            e.printStackTrace();
        }
        return duration;
    }

    /**
     * 获取视频时长 单位/秒
     * @param inputStream 输入流
     * @return
     */
    public static long getVideoDuration(InputStream inputStream) {
        long duration = 0L;
        FFmpegFrameGrabber ff = new FFmpegFrameGrabber(inputStream);
        try {
            ff.start();
            duration = ff.getLengthInTime() / (1000 * 1000);
            ff.stop();
        } catch (FrameGrabber.Exception e) {
            e.printStackTrace();
        }
        return duration;
    }

    /**
     * 转换视频文件为mp4
     * @param file
     * @return
     */
    public static String convertToMp4(File file) {
        FFmpegFrameGrabber frameGrabber = new FFmpegFrameGrabber(file);
        String fileName = null;
        Frame captured_frame = null;
        FFmpegFrameRecorder recorder = null;

        try {
            frameGrabber.start();
            fileName = file.getAbsolutePath() + "__.mp4";
            recorder = new FFmpegFrameRecorder(fileName, frameGrabber.getImageWidth(), frameGrabber.getImageHeight(), frameGrabber.getAudioChannels());
            recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264); //avcodec.AV_CODEC_ID_H264  //AV_CODEC_ID_MPEG4
            recorder.setFormat("mp4");
            recorder.setFrameRate(frameGrabber.getFrameRate());
            //recorder.setSampleFormat(frameGrabber.getSampleFormat()); //
            recorder.setSampleRate(frameGrabber.getSampleRate());

            recorder.setAudioChannels(frameGrabber.getAudioChannels());
            recorder.setFrameRate(frameGrabber.getFrameRate());
            recorder.start();
            while ((captured_frame = frameGrabber.grabFrame()) != null) {
                try {
                    recorder.setTimestamp(frameGrabber.getTimestamp());
                    recorder.record(captured_frame);

                } catch (FrameRecorder.Exception e) {
                    e.printStackTrace();
                }
            }
            recorder.stop();
            recorder.release();
            frameGrabber.stop();
        } catch (Exception | FrameRecorder.Exception e) {
            e.printStackTrace();
        }
        return fileName;
    }
}

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/tech/pnotes/283134.html

(0)
上一篇 2022年8月30日 17:03
下一篇 2022年8月30日 17:04

相关推荐

发表回复

登录后才能评论