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/283134.html

(0)
上一篇 2022年8月30日
下一篇 2022年8月30日

相关推荐

发表回复

登录后才能评论