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

news2024/9/23 3:33:55

前言

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JavaScript鼠标拖放(Drag and Drop)

&#x1f9d1;‍&#x1f393; 个人主页&#xff1a;《爱蹦跶的大A阿》 &#x1f525;当前正在更新专栏&#xff1a;《VUE》 、《JavaScript保姆级教程》、《krpano》、《krpano中文文档》 ​ ​ ✨ 前言 拖放是现代界面不可或缺的交互方式之一。本文将介绍如何用JavaScript…

Redis核心技术与实战【学习笔记】 - 19.Pika:基于SSD实现大容量“Redis”

前言 随着业务数据的增加&#xff08;比如电商业务中&#xff0c;随着用户规模和商品数量的增加&#xff09;&#xff0c;就需要 Redis 能保存更多的数据。你可能会想到使用 Redis 切片集群&#xff0c;把数据分散保存到不同的实例上。但是这样做的话&#xff0c;如果要保存的…

C# 程序不显示控制台

C# 程序不显示控制台。 C# 使用代码隐藏控制台。 文章目录 代码效果 代码 class Program {// 导入kernel32.dll库中的AllocConsole函数[DllImport("kernel32.dll")]static extern bool AllocConsole();// 导入kernel32.dll库中的FreeConsole函数[DllImport("k…

鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之Menu组件

鸿蒙&#xff08;HarmonyOS&#xff09;项目方舟框架&#xff08;ArkUI&#xff09;之Menu组件 一、操作环境 操作系统: Windows 10 专业版、IDE:DevEco Studio 3.1、SDK:HarmonyOS 3.1 二、Menu组件 TextClock组件通过文本将当前系统时间显示在设备上。支持不同时区的时间…

Mysql运维篇(四) MySQL常用命令

一路走来&#xff0c;所有遇到的人&#xff0c;帮助过我的、伤害过我的都是朋友&#xff0c;没有一个是敌人。如有侵权&#xff0c;请留言&#xff0c;我及时删除&#xff01; 一、MySQL命令速查表 https://www.cnblogs.com/pyng/p/15560059.html Mysql DBA运维命令大全 - 墨…

记录Git无法连接Github(443报错)的一种可能——代理问题

参考文章&#xff1a; Git安装配置与使用&#xff08;超级详细&#xff09;_git配置-CSDN博客 github代理报错_valueerror: unable to determine socks version from-CSDN博客 速通 如果在使用 git 时遇到了这样的报错&#xff1a; OpenSSL SSL_connect: SSL_ERROR_SYSCAL…

[office] excel表格怎么绘制股票的CCI指标- #媒体#学习方法#笔记

excel表格怎么绘制股票的CCI指标? excel表格怎么绘制股票的CCI指标&#xff1f;excel表格中想要绘制一个股票cci指标&#xff0c;该怎么绘制呢&#xff1f;下面我们就来看看详细的教程&#xff0c;需要的朋友可以参考下 CCI指标是一种在股票&#xff0c;贵金属&#xff0c;货…

嵌入式软件工程师面试题——嵌入式专题 (五十二)

说明&#xff1a; 面试群&#xff0c;群号&#xff1a; 228447240面试题来源于网络书籍&#xff0c;公司题目以及博主原创或修改&#xff08;题目大部分来源于各种公司&#xff09;&#xff1b;文中很多题目&#xff0c;或许大家直接编译器写完&#xff0c;1分钟就出结果了。但…

【新书推荐】5.2 位运算符

本节必须掌握的知识点&#xff1a; 位运算 示例十七 代码分析 汇编解析 5.2.1 位运算 位运算符如表5-2所示&#xff1a; 运算符 作用 示例 & 按位与 两个操作数同时为1&#xff0c;结果为1&#xff1b; | 按位或 两个操作数只要有一个为1&#xff0c;结果就为1&a…

Apollo配置中心之Server端

一、通知机制 二、架构思考 1、配置变更如何通知客户端&#xff1f; &#xff08;1&#xff09;如何建立长轮询&#xff1f; 2、客户端如何拉取数据&#xff1f; &#xff08;1&#xff09;如何拉取数据&#xff1f; 3、如何发现变更数据&#xff1f; &#xff08;1&…

全国医院及文体设施相关情况数据,shp+excel格式,多字段可查询,所见即所得

基本信息. 数据名称: 全国医院及文体设施相关情况数据 数据格式: shpexcel 数据几何类型: 面 数据坐标系: WGS84 数据来源&#xff1a;网络公开数据 数据字段&#xff1a; 序号字段名称字段说明1province省级名称2city城市名称3city_dm城市代码4tsgcss2020公共图书馆图…

vulhub中 Apache Airflow Celery 消息中间件命令执行漏洞复现(CVE-2020-11981)

Apache Airflow是一款开源的&#xff0c;分布式任务调度框架。在其1.10.10版本及以前&#xff0c;如果攻击者控制了Celery的消息中间件&#xff08;如Redis/RabbitMQ&#xff09;&#xff0c;将可以通过控制消息&#xff0c;在Worker进程中执行任意命令。 1.利用这个漏洞需要控…

[基础IO]文件描述符{重定向/perror/磁盘结构/inode/软硬链接}

文章目录 1. 再识重定向2.浅谈perror()3.初始文件系统4.软硬链接 1. 再识重定向 图解./sf > file.txt 2>&1 1中内容拷贝给2 使得2指向file 再学一个 把file的内容传给cat cat拿到后再给file2 2.浅谈perror() open()接口调用失败返回-1,并且错误码errno被适当的设置,…

Hive 主要内容一览

Hive架构 用户接口&#xff1a;Client CLI&#xff08;command-line interface&#xff09;、JDBC/ODBC(jdbc访问hive) 元数据&#xff1a;Metastore 元数据包括&#xff1a;表名、表所属的数据库&#xff08;默认是default&#xff09;、表的拥有者、列/分区字段、表的类型&am…

CTF(5)

一、[SWPUCTF 2021 新生赛]ez_caesar 1、题目 import base64 def caesar(plaintext):str_list list(plaintext)i 0while i < len(plaintext):if not str_list[i].isalpha():str_list[i] str_list[i]else:a "A" if str_list[i].isupper() else "a"…

vulhub中Adminer远程文件读取漏洞复现(CVE-2021-43008)

Adminer是一个PHP编写的开源数据库管理工具&#xff0c;支持MySQL、MariaDB、PostgreSQL、SQLite、MS SQL、Oracle、Elasticsearch、MongoDB等数据库。 在其版本1.12.0到4.6.2之间存在一处因为MySQL LOAD DATA LOCAL导致的文件读取漏洞。 参考链接&#xff1a; https://gith…

端到端实现高精地图重建(TopoNet解读和横评)

论文出处 [2304.05277] Graph-based Topology Reasoning for Driving Scenes (arxiv.org)https://arxiv.org/abs/2304.05277 TopoNet TopoNet的目标是从车辆上安装的多视角摄像头获取图像&#xff0c;感知实体并推理出驾驶场景的拓扑关系&#xff0c;实现端到端预测&#xf…

2017年苏州大学837复试机试C/C++

2017年苏州大学复试机试 要求 要求用C/C编程&#xff1b;对程序中必要的地方进行注释。上机规则 请在电脑桌面上新建一个文件夹文件夹名为考试姓名&#xff08;中文&#xff09;&#xff1b;考试完毕后&#xff0c;将所编写的文件放在上述文件中。 第一题&#xff08;20分&…

Node.js-1

Node.js 简介 定义&#xff1a;Node.js 是一个跨平台 JavaScript 运行环境&#xff0c;使开发者可以搭建服务器端的 JavaScript 应用程序 为什么 Node.js 能执行 JS 代码&#xff1a; Chrome 浏览器能执行 JS 代码&#xff0c;依靠的是内核中的 V8引擎&#xff08;即&#x…

react 使用react-seamless-scroll实现无缝滚动

文章目录 1. 实现无缝滚动效果2. react-seamless-scroll 无缝滚动案例介绍3. react 项目集成3.1 项目引入 cssSeamlessScroll 滚动组件3.2 完整代码3.2.1 newBet.tsx 代码3.2.2 index.module.scss 1. 实现无缝滚动效果 实现单步向下滚动点击更多展开&#xff0c;收起&#xff0…