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

前言

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java基础 反射详解

目录 简介 反射的基本使用 获取 Class 对象的四种方式 基本使用示例 常用方法 生产中的常用方式 获取注解 SpringIoc容器的制作 反射 抽象工厂模式 双亲委派 反射缺点 前言-与正文无关 生活远不止眼前的苦劳与奔波&#xff0c;它还充满了无数值得我们去体验和珍…

Vim工具使用全攻略:从入门到精通

引言 在软件开发的世界里&#xff0c;Vim不仅仅是一个文本编辑器&#xff0c;它是一个让你的编程效率倍增的神器。然而&#xff0c;对于新手来说&#xff0c;Vim的学习曲线似乎有些陡峭。本文将手把手教你如何从Vim的新手逐渐变为高手&#xff0c;深入理解Vim的操作模式&#…

【C++】拷贝构造函数和赋值运算符重载详解

目录 拷贝构造函数 概念 特征 赋值运算符重载 运算符重载 赋值运算符重载 ​编辑前置和后置重载 ⭐拷贝构造函数 ⭐概念 拷贝构造函数&#xff1a;只有单个形参&#xff0c;该形参是对本类类型对象的引用(一般常用const修饰)&#xff0c;在用已存 在的类类型对象创建新…

不同核函数高斯过程回归算法与不同因子输入情况下对长江流域蒸散发量应用研究_杨梓涵_2023

不同核函数高斯过程回归算法与不同因子输入情况下对长江流域蒸散发量应用研究_杨梓涵_2023 摘要关键词 0 引言1 材料与方法1.1 数据资料1.2 参考作物腾发量( ET0 ) 计算方法1.2.1 FAO&#xff0d;56 Penman&#xff0d;Monteith 模型1.2.2 Hargreaves&#xff0d;Samani 模型1.…

ctfshow web-76

开启环境: c?><?php $anew DirectoryIterator("glob:///*"); foreach($a as $f) {echo($f->__toString(). );} exit(0); ?> cinclude("/flagc.txt");exit(); c?><?php $anew DirectoryIterator("glob:///*"); foreach($a…

WebAssembly002 FFmpegWasmLocalServer项目

项目介绍 https://github.com/incubated-geek-cc/FFmpegWasmLocalServer.git可将音频或视频文件转换为其他可选的多媒体格式&#xff0c;并导出转码的结果 $ bash run.sh FFmpeg App is listening on port 3000!运行效果 相关依赖 Error: Cannot find module ‘express’ …

政安晨:示例演绎Python的函数与获取帮助的方法

调用函数和定义我们自己的函数&#xff0c;并使用Python内置的文档&#xff0c;是成为一位Pythoner的开始。 通过我的上篇文章&#xff0c;相信您已经看过并使用了print和abs等函数。但是Python还有许多其他函数&#xff0c;并且定义自己的函数是Python编程的重要部分。 在本…

花瓣网美女图片爬取

爬虫基础案例01 花瓣网美女图片 网站url&#xff1a;https://huaban.com 图片爬取 import requests import json import os res requests.get(url "https://api.huaban.com/search/file?text%E7%BE%8E%E5%A5%B3&sortall&limit40&page1&positionsear…

FreeRTOS动态 / 静态创建和删除任务

本篇文章记录我学习FreeRTOS的动态 / 静态创建和删除任务的知识。希望我的分享能给你带来不一样的收获&#xff01;文中涉及FreeRTOS创建和删除任务的API函数&#xff0c;建议读者参考以下文章&#xff1a; FreeRTOS任务相关的API函数-CSDN博客 目录 ​编辑 一、FreeRTOS动态创…

离线数仓-数据治理

目录 一、前言 1.1 数据治理概念 1.2 数据治理目标 1.3 数据治理要解决的问题 1.3.1 合规性 元数据合规性 数据质量合规性 数据安全合规性 1.3.2 成本 存储资源成本 计算资源成本 二、数据仓库发展阶段 2.1 初始期 2.2 扩张期 2.3 缓慢发展期 2.4 变革期 三、…

前后端数据校验

前端校验内容 前端开发中的必要校验&#xff0c;可以保证用户输入的数据的准确性、合法性和安全性。同时&#xff0c;这些校验也有助于提供良好的用户体验和防止不必要的错误提交到后端。 1、必填字段校验&#xff1a; 对于必填的字段&#xff0c;需确保用户输入了有效的数据…

Node.js版本管理工具之_Volta

Node.js包管理工具之_Volta 文章目录 Node.js包管理工具之_Volta1. 官网1. 官网介绍2. 特点1. 快( Fast)2. 可靠(Reliable)3. 普遍( Universal) 2. 下载与安装1. 下载2. 安装3. 查看 3. 使用1. 查看已安装的工具包2. 安装指定的node版本3.切换项目中使用的版本 1. 官网 1. 官网…