云端录制直播流视频,上传云盘

前言

哪一天我心血来潮,想把我儿子学校的摄像头视频流录制下来,并保存到云盘上,这样我就可以在有空的时候看看我儿子在学校干嘛。想到么就干,当时花了一些时间开发了一个后端服务,通过数据库配置录制参数,以后的设想是能够通过页面去配置,能够自动捕获直播视频流,这还得要求自己先学会vue,所以还得缓缓。

实现

技术栈:Spring Boot、Webflux、r2dbc、javacv

架构图:
在这里插入图片描述
流程很简单,主要还是要用到JavaCV从视频流里捕获视频,先报错到本地,然后有一个定时任务会定时去检测目录内是否有新生成的文件,有就上传到配置的云盘(百度云)。

1、创建pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.6.4</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>
	<groupId>net.178le</groupId>
	<artifactId>video-cloud-record</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>video-cloud-record</name>
	<description>视频云录制</description>
	<properties>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-webflux</artifactId>
		</dependency>

		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-r2dbc</artifactId>
		</dependency>

		<dependency>
			<groupId>dev.miku</groupId>
			<artifactId>r2dbc-mysql</artifactId>
		</dependency>

		<dependency>
			<groupId>cn.hutool</groupId>
			<artifactId>hutool-all</artifactId>
			<version>5.7.22</version>
		</dependency>

		<dependency>
			<groupId>org.bytedeco</groupId>
			<artifactId>javacv-platform</artifactId>
			<version>1.4.4</version>
		</dependency>
		
		 <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpcore</artifactId>
            <version>4.4.10</version>
        </dependency>
        
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.6</version>
        </dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>io.projectreactor</groupId>
			<artifactId>reactor-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<finalName>video-cloud-record</finalName>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<excludes>
						<exclude>
							<groupId>org.projectlombok</groupId>
							<artifactId>lombok</artifactId>
						</exclude>
					</excludes>
				</configuration>
			</plugin>
		</plugins>
	</build>

</project>

2、定时异常信息

package net.video.record.config;

import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import lombok.extern.slf4j.Slf4j;

/**
 * @desc 全局异常捕捉并转换异常
 */
@Slf4j	
@RestControllerAdvice(basePackages = "net.video.record")
public class GlobalExceptionHandler {


    @ExceptionHandler(Exception.class)
    public Result<String> handleException(Exception e) {
    	log.error("{}", e);
        return Result.error("", e.getMessage());
    }

}

3、统一结果集

package net.video.record.config;

import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class Result<T> {

	private String code;
	
	private T data;
	
	private String msg;
	
	public static <T> Result<T> ok(T data) {
		return new Result<T>("0", data, "");
	}
	
	public static <T> Result<T> error(String code, String msg) {
		code = StrUtil.isEmpty(code)? "500" : code;
		return new Result<T>(code, null, msg);
	}
}

4、定义两个Model

TaskList 用来保存用户相关的录制任务

package net.video.record.entity.model;

import java.time.LocalDateTime;
import java.util.Date;

import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Table;

import lombok.Data;

@Data
@Table("task_list")
public class TaskList {
	
	@Id
	private Integer id;
	
	private String name;
	
	private String streamUrl;
	
	private Integer userId;
	
	private Integer status;
	
	private Integer delFlag;
	
	private LocalDateTime createTime;
	
	private LocalDateTime modifyTime;
	
	private String runRule;
	
	private LocalDateTime lastRunTime;

	private Integer recordTime;
	
	private Integer segTime;

}

User 定义用户信息,保存了用过相关的录制参数

package net.video.record.entity.model;

import java.time.LocalDateTime;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Table;

import lombok.Data;
import lombok.experimental.Accessors;

@Data
@Accessors(chain = true)
@Table("user")
public class User {
	
	public static Map<Integer, User> userMap = new ConcurrentHashMap<Integer, User>();
	
	@Id
	private Integer id;
	
	private String userName;
	
	private String password;
	
	private String bdAccessToken;
	
	private String bdRefreshToken;
	
	private LocalDateTime createTime;
	
	private LocalDateTime modifyTime;

}

5、几个VO

TaskReq 任务请求参数

package net.video.record.entity.vo;

import lombok.Data;
import lombok.experimental.Accessors;

@Data
@Accessors(chain = true)
public class TaskReq {

	private Integer taskId;
}

UserReq

package net.video.record.entity.vo;

import lombok.Data;

@Data
public class UserReq {

	private String userName;
	
	private String password;
}

UserRes

package net.video.record.entity.vo;

import java.time.LocalDateTime;

import com.fasterxml.jackson.annotation.JsonFormat;

import lombok.Data;

@Data
public class UserRes {
	
	private Integer id;
	
	private String userName;
	
	private String password;
	
	@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
	private LocalDateTime createTime;
	
	@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
	private LocalDateTime modifyTime;
}

6、把网盘接口封装一下

我封装的是百度网盘,可以去网盘开放平台查看文档,这里贴出主要的上传代码。

public String upload(BdFileUpload req, TaskList task) {
		User user = User.userMap.get(task.getUserId());
		if (user == null) {
			throw new RuntimeException("用户信息不存在");
		}
		
		//大于4m的话分片,这里先不处理分片
		File file = req.getFile();
		req.setAccess_token(user.getBdAccessToken());
		List<String> fileMd5 = Arrays.asList(SecureUtil.md5(file));
		PreCreateReq preCreateReq = new PreCreateReq().setAccess_token(req.getAccess_token())
				.setAutoinit(1).setIsdir(0).setRtype(1)
				.setPath("/apps/直播云存储/" + task.getId() + "/" + DateUtil.today() + "/" + file.getName())
				.setSize(String.valueOf(file.length()))
				.setBlock_list(JSONUtil.toJsonStr(fileMd5));
		PreCreateRes preCreate = preCreate(preCreateReq);
		
		for (int i = 0; i < fileMd5.size(); i++) {
			SegUploadReq segUploadReq = new SegUploadReq()
					.setAccess_token(req.getAccess_token())
					.setPath(preCreate.getPath())
					.setUploadid(preCreate.getUploadid())
					.setPartseq(i)
					.setFile(req.getFile());
			SegUploadRes segUploadRes = SegUpload(segUploadReq);
		}
		CreateFileReq createFileReq = new CreateFileReq().setAccess_token(req.getAccess_token())
				.setBlock_list(JSONUtil.toJsonStr(fileMd5))
				.setPath(preCreateReq.getPath())
				.setSize(preCreateReq.getSize())
				.setIsdir(preCreateReq.getIsdir())
				.setRtype(preCreateReq.getRtype())
				.setUploadid(preCreate.getUploadid());
		CreateFileRes createFile = createFile(createFileReq);
		
		return createFile.getServer_filename();
	}

7、视频流录制部分

/**
	 * 录制视频
	 * @param inputFile 该地址可以是网络直播/录播地址,也可以是远程/本地文件路径
	 * @param outputFile 该地址只能是文件地址,如果使用该方法推送流媒体服务器会报错,原因是没有设置编码格式
	 * @param audioChannel 是否录制音频 1录制
	 * @param time 录制时间
	 * @throws Exception
	 * @throws org.bytedeco.javacv.FrameRecorder.Exception
	 */
	public void frameRecord(String inputFile, String outputFile, int audioChannel, int time)
			throws Exception, org.bytedeco.javacv.FrameRecorder.Exception {
		// 获取视频源
		FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(inputFile);
		// 流媒体输出地址,分辨率(长,高),是否录制音频(0:不录制/1:录制)
		FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(outputFile, 1280, 720, audioChannel);
		recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
		recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
		//设置分片
		recorder.setFormat("segment");
		//生成模式 实时
		recorder.setOption("segment_list_flags", "live");
		//分片时长 60s
		recorder.setOption("segment_time", "60");
		//锁定分片时长
		recorder.setOption("segment_atclocktime", "1");
		//用来严格控制分片时长
		recorder.setOption("break_non_keyframes", "1");
		//设置日志级别
		avutil.av_log_set_level(avutil.AV_LOG_ERROR);
		// 开始取视频源
		try {
			grabber.start();
			recorder.start();
			Frame frame = null;
			Date startDate = new Date();
			while ((frame = grabber.grabFrame()) != null 
					&& DateUtil.between(startDate, new Date(), DateUnit.SECOND) <= time * 60) {
				recorder.record(frame);
			}
			recorder.stop();
			grabber.stop();
		} finally {
			if (grabber != null) {
				grabber.stop();
			}
		}
	}

总结

这里我只贴出了部分代码,如果有想要了解具体实现的,也可以留言跟我交流。这个系统我也只是快速实现了一下,只达到能用的程度,其中对javacv、webflux进行了一定学习研究,后续的完善,还要看我哪天再次心血来潮。


作者其他文章推荐:
基于Spring Boot 3.1.0 系列文章

  1. Spring Boot 源码阅读初始化环境搭建
  2. Spring Boot 框架整体启动流程详解
  3. Spring Boot 系统初始化器详解
  4. Spring Boot 监听器详解
  5. Spring Boot banner详解
  6. Spring Boot 属性配置解析
  7. Spring Boot 属性加载原理解析
  8. Spring Boot 异常报告器解析
  9. Spring Boot 3.x 自动配置详解

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

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

相关文章

Vue引入Axios

1.命令安装axios和vue-axios npm install axios --save npm install vue-axios --save 2.package.json查看版本 3.在main.js中引用 import axios from axios; import VueAxios from vue-axios; Vue.use(VueAxios,axios) 4.如何使用 &#xff08;初始化方法&#xff09; 将下列代…

微信小程序实现时间轴效果

目录 引言时间轴效果的应用场景微信小程序的优势时间轴效果的设计思路时间轴界面布局数据结构设计实现时间轴效果WXML结构设计WXSS样式设计JavaScript逻辑实现说明引言 时间轴效果的应用场景 时间轴效果作为一种独特且直观的信息展示形式,已经被广泛应用于各种场景中,提供了…

2023年最受欢迎的4款绘图软件全面评测!

无论你是一个专业的插画家&#xff0c;还是一个有创造力的人&#xff0c;想要随时记录生活的灵感&#xff0c;现在你只需要拿起平板电脑或打开电脑浏览器来描述你脑海中的图片。在这篇文章中&#xff0c;我们选择了四个强大、方便和易于使用的绘图软件&#xff0c;其中一个必须…

【算法】{画决策树 + dfs + 递归 + 回溯 + 剪枝} 解决排列、子集问题(C++)

文章目录 1. 前言2. 算法例题46.全排列78.子集 1. 前言 dfs问题 我们已经学过&#xff0c;对于排列、子集类的问题&#xff0c;一般可以想到暴力枚举&#xff0c;但此类问题用暴力解法 一般都会超时&#xff0c;时间开销过大。对于该种问题&#xff0c;重点在于尽可能详细的 画…

【python】pyqt6信号与槽的代码与designer设置方法

pyqt6信号与槽的作用 整体过程 对象&#xff08;控件&#xff09;接收到一个信号&#xff08;类似clicked这样的函数&#xff09;&#xff0c;就会对接受者&#xff08;自身或者其他控件&#xff09;发出一个我被点击了的信息&#xff0c;然后这个接受者就会执行槽&#xff0…

计算机网络_1.6.3 计算机网络体系结构分层思想举例

1.6.3 计算机网络体系结构分层思想举例 1、实例引入&#xff08;用户在主机中使用浏览器访问web服务器&#xff09;2、从五层原理体系结构的角度研究该实例3、练习题 笔记来源&#xff1a; B站 《深入浅出计算机网络》课程 本节通过一个常见的网络应用实例&#xff0c;来介绍计…

代码混淆技术综述与优化方法

摘要 本文介绍了代码混淆的概念和目的&#xff0c;并提供了Python代码混淆的宏观思路。同时&#xff0c;还介绍了一种在线网站混淆Python代码的方法&#xff0c;并给出了混淆前后的示例代码。 引言 在当今信息时代&#xff0c;软件代码的保护显得尤为重要。代码混淆是一种常…

ReentrantLock相较于synchronized有哪些区别(一)?

ReentrantLock特点 相对于 synchronized 它具备如下特点 可中断 可以设置超时时间 可以设置为公平锁 支持多个条件变量 与 synchronized 一样&#xff0c;都支持可重入 基本使用语法如下&#xff1a; public class Test {public static void main(String[] args) {Reentran…

第十一章[文件系统]:11.2:文件的复制/删除/移动

一,相关文档: os模块: os --- 多种操作系统接口 — Python 3.12.1 文档源代码: Lib/os.py 本模块提供了一种使用与操作系统相关的功能的便捷式途径。 如果你只是想读写一个文件,请参阅 open() ,如果你想操作文件路径,请参阅 os.path 模块,如果你想读取通过命令行给出的所…

Java GC-常见垃圾回收器

目录 前言一、垃圾回收器分类二、垃圾回收器介绍1、Serial 收集器2、ParNew 收集器3、Parallel Scavenge 收集器4、Serial Old 收集器5、Parallel Old 收集器6、CMS 收集器&#xff08;多线程标记清除算法&#xff09;7、G1 收集器 三、项目中垃圾收集器选型 前言 Java的垃圾回…

蓝桥杯每日一题-----数位dp

前言 今天浅谈一下数位dp的板子&#xff0c;我最初接触到数位dp的时候&#xff0c;感觉数位dp老难了&#xff0c;一直不敢写&#xff0c;最近重新看了一些数位dp&#xff0c;发现没有想象中那么难&#xff0c;把板子搞会了&#xff0c;变通也会变的灵活的多&#xff01; 引入…

如何以管理员身份删除node_modules文件

今天拉项目&#xff0c;然后需要安装依赖&#xff0c;但是一直报错&#xff0c;如下&#xff1a; 去搜这个问题会让把node_modules文件先删掉 再去安装依赖。我在删除的过程中会说请以管理员身份来删除。 那么windows如何以管理员身份删除node_modules文件呢&#xff1f; wi…

Python绘制热力图

最近投SCI论文的时候&#xff0c;有些实验结果需要热力图展示&#xff0c;所以专门试了一下如何用python绘制热力图&#xff0c;发现简单好用&#xff0c;下面分享给大家具体方法。 一、安装python库 需要安装pandas、seaborn、matplotlib安装包依赖&#xff0c;均用pip一键安…

深入了解 Ansible:全面掌握自动化 IT 环境的利器

本文以详尽的篇幅介绍了 Ansible 的方方面面&#xff0c;旨在帮助读者从入门到精通。无论您是初学者还是有一定经验的 Ansible 用户&#xff0c;都可以在本文中找到对应的内容&#xff0c;加深对 Ansible 的理解和应用。愿本文能成为您在 Ansible 自动化旅程中的良师益友&#…

vue学习91-105

vue的基本认知p91 创建一个空仓库p93 vue 路由 vuex版本 2 3 3 3 4 4 npm的vuex装包npm install vuex --save vuex里有仓库,仓库放vuex核心代码&#xff0c;所有组件都能访问到 const store new Vuex.Store()//访问stored this.$store如何提供$访问vuex的数据p94 核心概念-…

GNSS模块的惯导技术:引领定位科技的前沿

全球导航卫星系统&#xff08;GNSS&#xff09;模块的惯导技术是一项颇具前瞻性的科技&#xff0c;它结合了全球定位系统和惯性导航技术&#xff0c;为各个领域的定位需求提供了更为精准和可靠的解决方案。本文将深入探讨GNSS模块的惯导技术&#xff0c;以及它如何在多个领域中…

DATAX改造支持geometry类型数据同步

数据库使用postgresql安装了postgis插件存储了geometry空间数据&#xff0c;想使用datax做数据同步&#xff0c;但datax本身不支持geometry类型数据&#xff0c;如何改造呢&#xff1f; 1.首先下载已改造支持geometry类型的datax引擎&#xff0c;下载地址 https://download.c…

微信小程序之本地生活案例的实现

学习的最大理由是想摆脱平庸&#xff0c;早一天就多一份人生的精彩&#xff1b;迟一天就多一天平庸的困扰。各位小伙伴&#xff0c;如果您&#xff1a; 想系统/深入学习某技术知识点… 一个人摸索学习很难坚持&#xff0c;想组团高效学习… 想写博客但无从下手&#xff0c;急需…

关于《人工智能法案》你应该知道的真相

什么是《人工智能法案》 《人工智能法案》是欧盟的一项法规&#xff0c;旨在为人工智能引入一个共同的监管和法律框架&#xff0c;保障人类的安全、健康和基本权利&#xff0c;同时促进人工智能的创新和发展。该法案根据人工智能应用可能造成的风险&#xff0c;对其进行了不同…

AcWing算法学习笔记:动态规划(背包 + 线性dp + 区间dp + 计数dp + 状态压缩dp + 树形dp + 记忆化搜索)

动态规划 一、背包问题① 01背包朴素版② 01背包优化版③ 完全背包朴素版④ 完全背包消k版⑤ 完全背包消k优化版⑥ 多重背包朴素版⑦多重背包二进制优化版⑧ 分组背包朴素版⑨ 分组背包优化版 二、线性dp① 数字三角形② 最长上升子序列③ 最长上升子序列打印序列版④ 最长上升…