springboot 通过javaCV 实现mp4转m3u8 上传oss
创始人
2024-05-20 20:54:00
0

配置依赖

 	110.6.25.7.153.13.2org.springframework.bootspring-boot-starter-weborg.springframework.bootspring-boot-configuration-processortrueorg.projectlomboklomboktrueorg.bytedecojavacv-platform1.5.8




org.bytedecoffmpeg-platform-gpl5.1.2-1.5.8net.bramp.ffmpegffmpeg${ffmpeg.version}cn.hutoolhutool-all${hutool.version}com.aliyun.ossaliyun-sdk-oss${aliyun-sdk-oss.version}

注意: ffmpeg-platformjavacv-platform 版本要对应

application.yml 项目配置


spring:servlet:multipart:max-file-size: 200MBmax-request-size: 500MB#m3u8视频转换配置
m3u8:convertor:base-path: /file/m3u8/temp-path: /file/temp/big-path: /file/big/proxy: m3u8/ali:oss:#oss end-pointend-point: #oss access-key-idaccess-key-id: #oss access-key-secretaccess-key-secret: #oss bucket-namebucket-name: #访问地址 可以与 ali-url  my-host-url 一致 例如 https://12312312.oss-cn-shenzhen.aliyuncs.com/url: #访问地址 可以与 my-host-url  url 一致 例如 https://12312312.oss-cn-shenzhen.aliyuncs.com/ali-url: get-file-url: ${aliyun.oss.url}${aliyun.oss.fileDir}#访问地址 可以与 ali-url  url 一致 例如 https://12312312.oss-cn-shenzhen.aliyuncs.com/my-host-url: 

configproperties配置相关类

SpringAsyncConfig 配置信息


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;/*** 线程池配置*/
@Configuration
@EnableAsync
public class SpringAsyncConfig {/*** oss async* @return*/@Bean("ossUploadTreadPool")public ThreadPoolTaskExecutor  asyncServiceExecutorForOss() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();// 设置核心线程数,采用IO密集 h/(1-拥塞)executor.setCorePoolSize(8);// 设置最大线程数,由于minIO连接数量有限,此处尽力设计大点executor.setMaxPoolSize(120);// 设置线程活跃时间(秒)executor.setKeepAliveSeconds(30);// 设置默认线程名称executor.setThreadNamePrefix("ossUploadTask-");// 等待所有任务结束后再关闭线程池executor.setWaitForTasksToCompleteOnShutdown(true);//执行初始化executor.initialize();return executor;}
}

FilePath 配置信息


import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;@Data
@Component
@ConfigurationProperties(prefix = "m3u8.convertor")
public class FilePath {/*** 文件上传临时路径 (本地文件转换不需要)*/private String tempPath = "/file/tmp/";/*** m3u8文件转换后,储存的根路径*/private String basePath = "/file/m3u8/";/*** m3u8文件转换后,储存的根路径*/private String bigPath = "/file/big/";private String proxy = "m3u8/";}

AliOssProperties 配置信息


import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;@Component
@ConfigurationProperties(prefix = "ali.oss")
@Data
public class AliOssProperties {/*** OSS配置信息*/private String endpoint;private String accessKeyId;private String accessKeySecret;private String bucketName;private String myHostUrl;private String url;private String aliUrl;
}

component 包 相关组件类

OssComponent 组件

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.CompleteMultipartUploadRequest;
import com.aliyun.oss.model.CompleteMultipartUploadResult;
import com.aliyun.oss.model.InitiateMultipartUploadRequest;
import com.aliyun.oss.model.InitiateMultipartUploadResult;
import com.aliyun.oss.model.ObjectMetadata;
import com.aliyun.oss.model.PartETag;
import com.aliyun.oss.model.PutObjectResult;
import com.aliyun.oss.model.UploadPartRequest;
import com.aliyun.oss.model.UploadPartResult;
import com.laowei.ffmpegm3u8demo.config.AliOssProperties;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;import javax.annotation.Resource;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.UUID;/*** 阿里云 OSS 工具类** */
@Component
@Slf4j
@Getter
public class OssComponent{@Resourceprivate AliOssProperties aliOssProperties;/* -----------------对外功能---------------- *//*** 本地文件切片上传** @param objectName:文件名* @param path           : 本地完整路径,xxx/xxx.txt* @return :异常*/public String uploadSlice(String objectName, String localPath,String path) throws IOException {OSS ossClient = new OSSClientBuilder().build(aliOssProperties.getEndpoint(), aliOssProperties.getAccessKeyId(), aliOssProperties.getAccessKeySecret());String keyPath = path+objectName;// 创建InitiateMultipartUploadRequest对象。InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(aliOssProperties.getBucketName(), keyPath);// 如果需要在初始化分片时设置请求头,请参考以下示例代码。ObjectMetadata metadata = new ObjectMetadata();// 指定该Object的网页缓存行为。metadata.setCacheControl("no-cache");// 指定该Object被下载时的名称。metadata.setContentDisposition("attachment;filename=" + objectName);// 指定初始化分片上传时是否覆盖同名Object。此处设置为true,表示禁止覆盖同名Object。metadata.setHeader("x-oss-forbid-overwrite", "true");// 初始化分片。InitiateMultipartUploadResult result = ossClient.initiateMultipartUpload(request);// 返回uploadId,它是分片上传事件的唯一标识。您可以根据该uploadId发起相关的操作,例如取消分片上传、查询分片上传等。String uploadId = result.getUploadId();List partETags = new ArrayList<>();// 每个分片的大小,用于计算文件有多少个分片。单位为字节。final long partSize = 5 * 1024 * 1024L;   //1 MB。// 填写本地文件的完整路径。如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件。final File sampleFile = new File(localPath);long fileLength = sampleFile.length();int partCount = (int) (fileLength / partSize);if (fileLength % partSize != 0) {partCount++;}// 遍历分片上传。for (int i = 0; i < partCount; i++) {long startPos = i * partSize;long curPartSize = (i + 1 == partCount) ? (fileLength - startPos) : partSize;try (InputStream inStream = new FileInputStream(sampleFile)) {// 跳过已经上传的分片。long skip = inStream.skip(startPos);UploadPartRequest uploadPartRequest = new UploadPartRequest();uploadPartRequest.setBucketName(aliOssProperties.getBucketName());uploadPartRequest.setKey(keyPath);uploadPartRequest.setUploadId(uploadId);uploadPartRequest.setInputStream(inStream);// 设置分片大小。除了最后一个分片没有大小限制,其他的分片最小为100 KB。uploadPartRequest.setPartSize(curPartSize);// 设置分片号。每一个上传的分片都有一个分片号,取值范围是1~10000,如果超出此范围,OSS将返回InvalidArgument错误码。uploadPartRequest.setPartNumber(i + 1);// 每个分片不需要按顺序上传,甚至可以在不同客户端上传,OSS会按照分片号排序组成完整的文件。UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);// 每次上传分片之后,OSS的返回结果包含PartETag。PartETag将被保存在partETags中。partETags.add(uploadPartResult.getPartETag());}catch (Exception e){log.error("OSS切片上传异常,e:{}",e.getMessage());}}// 创建CompleteMultipartUploadRequest对象。// 在执行完成分片上传操作时,需要提供所有有效的partETags。OSS收到提交的partETags后,会逐一验证每个分片的有效性。当所有的数据分片验证通过后,OSS将把这些分片组合成一个完整的文件。CompleteMultipartUploadRequest completeMultipartUploadRequest =new CompleteMultipartUploadRequest(aliOssProperties.getBucketName(),keyPath , uploadId, partETags);// 完成分片上传。CompleteMultipartUploadResult completeMultipartUploadResult = ossClient.completeMultipartUpload(completeMultipartUploadRequest);log.info(completeMultipartUploadResult.getETag());// 关闭OSSClient。ossClient.shutdown();return path+objectName;}/*** 单个文件上传** @param file 文件* @return 返回完整URL地址*/public String uploadFile(String fileDir, MultipartFile file) {String fileUrl = upload2Oss(fileDir, file);String str = getFileUrl(fileDir, fileUrl);return str.trim();}/*** 单个文件上传(指定文件名(带后缀))** @param inputStream 文件* @param fileName    文件名(带后缀)* @return 返回完整URL地址*/public String uploadFile(String fileDir, InputStream inputStream, String fileName) {try {this.uploadFile2Oss(fileDir, inputStream, fileName);String url = getFileUrl(fileDir, fileName);if (url != null && url.length() > 0) {return url;}} catch (Exception e) {throw new RuntimeException("获取路径失败");}return "";}/*** 多文件上传** @param fileList 文件列表* @return 返回完整URL,逗号分隔*/public String uploadFile(String fileDir, List fileList) {String fileUrl;String str;StringBuilder photoUrl = new StringBuilder();for (int i = 0; i < fileList.size(); i++) {fileUrl = upload2Oss(fileDir, fileList.get(i));str = getFileUrl(fileDir, fileUrl);if (i == 0) {photoUrl = new StringBuilder(str);} else {photoUrl.append(",").append(str);}}return photoUrl.toString().trim();}public boolean deleteFile(String fileDir, String fileName) {OSS ossClient = new OSSClientBuilder().build(aliOssProperties.getEndpoint(), aliOssProperties.getAccessKeyId(), aliOssProperties.getAccessKeySecret());// 删除文件ossClient.deleteObject(aliOssProperties.getBucketName(), fileDir + fileName);// 判断文件是否存在boolean found = ossClient.doesObjectExist(aliOssProperties.getBucketName(), fileDir + fileName);// 如果文件存在则删除失败return !found;}/*** 通过文件名获取文完整件路径** @param fileUrl 文件名* @return 完整URL路径*/public String getFileUrl(String fileDir, String fileUrl) {if (fileUrl != null && fileUrl.length() > 0) {String[] split = fileUrl.replaceAll("\\\\","/").split("/");String url = aliOssProperties.getMyHostUrl() + fileDir + split[split.length - 1];return Objects.requireNonNull(url);}return null;}public File getFile(String url) {//对本地文件命名String fileName = url.substring(url.lastIndexOf("."));File file = null;try {file = File.createTempFile("net_url", fileName);} catch (Exception e) {log.error("创建默认文件夹net_url失败!原因e:{}", e.getMessage());}if (file != null) {try (InputStream inStream = new URL(url).openStream();OutputStream os = new FileOutputStream(file)) {int bytesRead;byte[] buffer = new byte[8192];while ((bytesRead = inStream.read(buffer, 0, 8192)) != -1) {os.write(buffer, 0, bytesRead);}} catch (Exception e) {e.printStackTrace();}}return file;}/* -----------内部辅助功能------------------------ *//*** 获取去掉参数的完整路径** @param url URL* @return 去掉参数的URL*/private String getShortUrl(String url) {String[] imgUrls = url.split("\\?");return imgUrls[0].trim();}/*** 获得url真实外网链接* 不提供使用,因为会产生公网OOS流量下行费用** @param key 文件名* @return URL*/@Deprecatedprivate String getUrl(String key) {OSS ossClient = new OSSClientBuilder().build(aliOssProperties.getEndpoint(), aliOssProperties.getAccessKeyId(), aliOssProperties.getAccessKeySecret());// 设置URL过期时间为20年  3600l* 1000*24*365*20Date expiration = new Date(System.currentTimeMillis() + 3600L * 1000 * 24 * 365 * 20);URL url = ossClient.generatePresignedUrl(aliOssProperties.getBucketName(), key, expiration);if (url != null) {String replaceUrl = url.toString().replace(aliOssProperties.getAliUrl(), aliOssProperties.getUrl());return getShortUrl(replaceUrl);}ossClient.shutdown();return null;}/*** 上传文件** @param file 文件* @return 文件名*/private String upload2Oss(String fileDir, MultipartFile file) {// 2、重命名文件String fileName = Objects.requireNonNull(file.getOriginalFilename(), "文件名不能为空");// 文件后缀String suffix = fileName.substring(fileName.lastIndexOf(".")).toLowerCase(Locale.ENGLISH);String uuid = UUID.randomUUID().toString();String name = uuid + suffix;try {InputStream inputStream = file.getInputStream();this.uploadFile2Oss(fileDir, inputStream, name);return name;} catch (Exception e) {throw new RuntimeException("上传失败");}}/*** 上传文件(指定文件名)** @param inputStream 输入流* @param fileName    文件名*/private void uploadFile2Oss(String fileDir, InputStream inputStream, String fileName) {OSS ossClient = new OSSClientBuilder().build(aliOssProperties.getEndpoint(), aliOssProperties.getAccessKeyId(), aliOssProperties.getAccessKeySecret());String ret;try {//创建上传Object的MetadataObjectMetadata objectMetadata = new ObjectMetadata();objectMetadata.setContentLength(inputStream.available());objectMetadata.setCacheControl("no-cache");objectMetadata.setHeader("Pragma", "no-cache");objectMetadata.setContentType(getContentType(fileName.substring(fileName.lastIndexOf("."))));objectMetadata.setContentDisposition("inline;filename=" + fileName);//上传文件PutObjectResult putResult = ossClient.putObject(aliOssProperties.getBucketName(), fileDir + fileName, inputStream, objectMetadata);ret = putResult.getETag();if (StringUtils.isEmpty(ret)) {log.error("上传失败,文件ETag为空");}ossClient.shutdown();} catch (IOException e) {log.error(e.getMessage(), e);} finally {try {inputStream.close();} catch (IOException e) {e.printStackTrace();}}}/*** 请求类型** @param filenameExtension :* @return :*/private static String getContentType(String filenameExtension) {if (FileNameSuffixEnum.BMP.getSuffix().equalsIgnoreCase(filenameExtension)) {return "image/bmp";}if (FileNameSuffixEnum.GIF.getSuffix().equalsIgnoreCase(filenameExtension)) {return "image/gif";}if (FileNameSuffixEnum.JPEG.getSuffix().equalsIgnoreCase(filenameExtension) ||FileNameSuffixEnum.JPG.getSuffix().equalsIgnoreCase(filenameExtension) ||FileNameSuffixEnum.PNG.getSuffix().equalsIgnoreCase(filenameExtension)) {return "image/jpeg";}if (FileNameSuffixEnum.HTML.getSuffix().equalsIgnoreCase(filenameExtension)) {return "text/html";}if (FileNameSuffixEnum.TXT.getSuffix().equalsIgnoreCase(filenameExtension)) {return "text/plain";}if (FileNameSuffixEnum.VSD.getSuffix().equalsIgnoreCase(filenameExtension)) {return "application/vnd.visio";}if (FileNameSuffixEnum.PPTX.getSuffix().equalsIgnoreCase(filenameExtension) ||FileNameSuffixEnum.PPT.getSuffix().equalsIgnoreCase(filenameExtension)) {return "application/vnd.ms-powerpoint";}if (FileNameSuffixEnum.DOCX.getSuffix().equalsIgnoreCase(filenameExtension) ||FileNameSuffixEnum.DOC.getSuffix().equalsIgnoreCase(filenameExtension)) {return "application/msword";}if (FileNameSuffixEnum.XML.getSuffix().equalsIgnoreCase(filenameExtension)) {return "text/xml";}if (FileNameSuffixEnum.PDF.getSuffix().equalsIgnoreCase(filenameExtension)) {return "application/pdf";}return "image/jpeg";}}@Getter
enum FileNameSuffixEnum {/*** 文件后缀名*/BMP(".bmp", "bmp文件"),GIF(".gif", "gif文件"),JPEG(".jpeg", "jpeg文件"),JPG(".jpg", "jpg文件"),PNG(".png", "png文件"),HTML(".html", "HTML文件"),TXT(".txt", "txt文件"),VSD(".vsd", "vsd文件"),PPTX(".pptx", "PPTX文件"),DOCX(".docx", "DOCX文件"),PPT(".ppt", "PPT文件"),DOC(".doc", "DOC文件"),XML(".xml", "XML文件"),PDF(".pdf", "PDF文件");/*** 后缀名*/private final String suffix;/*** 描述*/private final String description;FileNameSuffixEnum(String suffix, String description) {this.suffix = suffix;this.description = description;}
}

M3u8Component 组件

import javax.annotation.Resource;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.Charset;
import java.security.SecureRandom;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;@Slf4j
@Component
public class M3u8Component {@Resourceprivate FilePath filePath;public String mediaFileToJavaM3u8(MultipartFile file) throws Exception{if (file.isEmpty()) {throw new RuntimeException("未发现文件");}log.info("开始解析视频");long start = System.currentTimeMillis();//临时目录创建String path = new File(System.getProperty("user.dir")).getAbsolutePath();String tempFilePath = path+ filePath.getTempPath();if (!FileUtil.exist(tempFilePath)) {FileUtil.mkdir(tempFilePath);}String filePathName = tempFilePath + file.getOriginalFilename();File dest = new File(filePathName);try {file.transferTo(dest);}catch (Exception e){log.error("视频转m3u8格式存在异常,异常原因e:{}",e.getMessage());}//m3u8文件 存储路径String filePath = m3u8Util.generateFilePath(this.filePath.getBasePath());if (!FileUtil.exist(filePath)) {FileUtil.mkdir(filePath);}long end = System.currentTimeMillis();log.info("临时文件上传成功......耗时:{} ms", end - start);String m3u8FilePath = FfmpegUtil.mp4ToM3u8(filePathName,filePath);log.info("视频转换已完成 !");return m3u8FilePath;}
}

工具包

m3u8Util 工具类


import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;import java.time.LocalDateTime;public class m3u8Util {/***@Description 根据基础路径,生成文件存储路径*@param basePath 基础路径(根路径)*@Return */public static String generateFilePath(String basePath){String temp = basePath;if(StrUtil.isNotBlank(basePath)){if(basePath.endsWith("/")){temp = basePath.substring(0,basePath.lastIndexOf("/"));}}return temp+"/"+generateDateDir()+"/";}/***@Description 根据当前时间,生成下级存储目录*@Return*/public static String generateDateDir(){LocalDateTime now = LocalDateTime.now();return DateUtil.format(now, "yyyyMMdd/HH/mm/ss");}/***@Description 根据文件全路径,获取文件主名称*@param fullPath 文件全路径(包含文件名)*@Return*/public static String getFileMainName(String fullPath){String fileName = FileUtil.getName(fullPath);return fileName.substring(0,fileName.lastIndexOf("."));}}

FfmpegUtil 工具类


import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.FileUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.bytedeco.ffmpeg.avcodec.AVPacket;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.ffmpeg.global.avutil;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.FFmpegFrameRecorder;
import org.bytedeco.javacv.FFmpegLogCallback;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.FrameRecorder;import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.UUID;/*** javacv ffmpeg 工具类*/
@Slf4j
public class FfmpegUtil {/*** mp4, m3u8** @param filePathName       需要转换文件* @param toFilePath 需要转换的文件路径*/public static String mp4ToM3u8(String filePathName, String toFilePath) throws Exception {avutil.av_log_set_level(avutil.AV_LOG_INFO);FFmpegLogCallback.set();boolean isStart = true;// 该变量建议设置为全局控制变量,用于控制录制结束//加载文件FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(filePathName);//grabber.setAudioChannels(1);grabber.start();String fileName = UUID.randomUUID().toString().replaceAll("-", "");File tempFile3 = new File(toFilePath, fileName + ".m3u8");String prefixName = toFilePath + File.separator + fileName;//生成加密keyString secureFileName = prefixName + ".key";byte[] secureRandom = getSecureRandom();FileUtil.writeBytes(secureRandom,secureFileName);String toHex = Convert.toHex(secureRandom);String keyInfoPath = toFilePath + File.separator +"key.keyinfo";//写入加密文件writeKeyInfo(keyInfoPath,fileName + ".key",secureFileName,toHex);FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(tempFile3, grabber.getImageWidth(), grabber.getImageHeight(), grabber.getAudioChannels());//格式方式recorder.setFormat("hls");//关于hls_wrap的说明,hls_wrap表示重复覆盖之前ts切片,这是一个过时配置,ffmpeg官方推荐使用hls_list_size 和hls_flags delete_segments代替hls_wrap//设置单个ts切片的时间长度(以秒为单位)。默认值为2秒recorder.setOption("hls_time", "10");//不根据gop间隔进行切片,强制使用hls_time时间进行切割ts分片recorder.setOption("hls_flags", "split_by_time");//设置播放列表条目的最大数量。如果设置为0,则列表文件将包含所有片段,默认值为5// 当切片的时间不受控制时,切片数量太小,就会有卡顿的现象recorder.setOption("hls_list_size", "0");//自动删除切片,如果切片数量大于hls_list_size的数量,则会开始自动删除之前的ts切片,只保留hls_list_size个数量的切片recorder.setOption("hls_flags", "delete_segments");//ts切片自动删除阈值,默认值为1,表示早于hls_list_size+1的切片将被删除recorder.setOption("hls_delete_threshold", "1");/*hls的切片类型:* 'mpegts':以MPEG-2传输流格式输出ts切片文件,可以与所有HLS版本兼容。* 'fmp4':以Fragmented MP4(简称:fmp4)格式输出切片文件,类似于MPEG-DASH,fmp4文件可用于HLS version 7和更高版本。*/recorder.setOption("hls_segment_type", "mpegts");//指定ts切片生成名称规则,按数字序号生成切片,例如'file%03d.ts',就会生成file000.ts,file001.ts,file002.ts等切片文件//recorder.setOption("hls_segment_filename", toFilePath + "-%03d.ts");recorder.setOption("hls_segment_filename", toFilePath + File.separator + fileName + "-%5d.ts");//加密recorder.setOption("hls_key_info_file", keyInfoPath);// 设置第一个切片的编号
//        recorder.setOption("start_number", String.valueOf(tsCont));
//        recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);// 转码log.info("{} | 启动Hls转码录制器……", toFilePath);//      设置零延迟//recorder.setVideoOption("tune", "zerolatency");recorder.setVideoOption("tune", "fastdecode");// 快速recorder.setVideoOption("preset", "ultrafast");
//        recorder.setVideoOption("crf", "26");recorder.setVideoOption("threads", "12");recorder.setVideoOption("vsync", "2");recorder.setFrameRate(grabber.getFrameRate());// 设置帧率
//        recorder.setGopSize(25);// 设置gop,与帧率相同,相当于间隔1秒chan's一个关键帧
//		recorder.setVideoBitrate(100 * 1000);// 码率500kb/srecorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
//        recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);//如果想截取规定时间段视频
//        recorder.start();
//        Frame frame;
//        while ((frame = grabber.grabImage()) != null) {
//            try {
//                recorder.record(frame);
//            } catch (FrameRecorder.Exception e) {
//                log.error("转码异常:{}", e);
//            }
//        }recorder.start(grabber.getFormatContext());AVPacket packet;while ((packet = grabber.grabPacket()) != null) {try {recorder.recordPacket(packet);} catch (FrameRecorder.Exception e) {log.error("转码异常:{}", e);}}recorder.setTimestamp(grabber.getTimestamp());recorder.stop();recorder.release();grabber.stop();grabber.release();File dest = new File(filePathName);if (dest.isFile() && dest.exists()) {dest.delete();log.warn("临时文件 {}已删除", dest.getName());}log.info("转码m3u8:{}", tempFile3.getAbsolutePath());return tempFile3.getAbsolutePath();}/*** 安全安全随机** @return {@link byte[]}*/public static byte[] getSecureRandom(){byte[] bytes = new byte[16];new SecureRandom().nextBytes(bytes);return bytes;}/*** 写入关键文件数据** @param keyInfoPath 路径* @param decrypt    解密* @param encrypt    加密*/public static void writeKeyInfo(String keyInfoPath,String decrypt,String encrypt,String IV) throws IOException {try (BufferedWriter writer = new BufferedWriter(new FileWriter(keyInfoPath));){writer.write(decrypt);writer.newLine();writer.write(encrypt);writer.newLine();if(StringUtils.isNotBlank(IV)){writer.write(IV);}writer.flush();} catch (IOException e) {throw new RuntimeException(e);}}
}

创建service包 并创建 FileService 服务

/*** 文件服务**/
public interface FileService {/*** 上传java video2 m3u8** @param file 文件* @return {@link String}* @throws Exception 异常*/String uploadJavaVideo2M3u8(MultipartFile file) throws Exception;
}
/*** 文件服务impl**/@Slf4j
@Service
public class FileServiceImpl implements FileService {@Autowiredprivate M3u8Component m3U8ComponentTemplate;@Autowiredprivate AliOssProperties aliOssProperties;@Autowiredprivate OssComponent ossComponent;@Autowiredprivate FilePath filePath;@Resource(name = "ossUploadTreadPool")private ThreadPoolTaskExecutor poolTaskExecutor;private static final String projectUrl = System.getProperty("user.dir").replaceAll("\\\\", "/");@Overridepublic String uploadJavaVideo2M3u8(MultipartFile file) throws Exception {String path = m3U8ComponentTemplate.mediaFileToJavaM3u8(file);return uploadJava2M3u8(path);}public String uploadJava2M3u8(String path) throws Exception {File pathFile = new File(path);String realPath = pathFile.getParent();log.info("视频解析后的 realPath {}", realPath);String name = pathFile.getName();log.info("解析后视频 name {}", name);return uploadFile(path, realPath, name);}/*** 上传文件** @param path     路径* @param realPath 真正路径* @param name     名字* @return {@link String}* @throws Exception 异常*/public String uploadFile(String path, String realPath, String name) throws Exception {File allFile = new File(realPath);File[] files = allFile.listFiles();if (null == files || files.length == 0) {return null;}String patch = DateUtil.format(LocalDateTime.now(), "yyyy/MM/") + name.substring(0, name.lastIndexOf(".")) + "/";log.info("uploadfile--->path:{}", patch);List errorFile = new ArrayList<>();long start = System.currentTimeMillis();//String fileName = UUID.randomUUID().toString().replaceAll("-","");//替换m3u8文件中的路径FileUtil.replaceTextContent(path, name.substring(0, name.lastIndexOf(".")),aliOssProperties.getMyHostUrl() + filePath.getProxy() + patch +name.substring(0, name.lastIndexOf(".")));//开始上传CountDownLatch countDownLatch = new CountDownLatch(files.length);Arrays.stream(files).forEach(li -> poolTaskExecutor.execute(() -> {try (FileInputStream fileInputStream = new FileInputStream(li)) {//minioComponent.FileUploaderExist("m3u8", patch + li.getName(), fileInputStream);ossComponent.uploadFile(filePath.getProxy() + patch, fileInputStream, li.getName());log.info("文件:{} 正在上传", li.getName());} catch (Exception e) {errorFile.add(li);e.printStackTrace();} finally {countDownLatch.countDown();}}));countDownLatch.await();long end = System.currentTimeMillis();log.info("解析文件上传成功,共计:{} 个文件,失败:{},共耗时: {}ms", files.length, errorFile.size(), end - start);//  try {//      minioComponent.mkBucket("m3u8");//  } catch (Exception e) {//      log.error("创建Bucket失败!");//  }//异步移除所有文件poolTaskExecutor.execute(() -> {FileUtil.deleteFile(projectUrl + filePath.getTempPath());});if (CollectionUtils.isEmpty(errorFile)) {return aliOssProperties.getMyHostUrl() + filePath.getProxy() + patch + name;}return "";}
}

Result 通用返回类


import lombok.Data;
import lombok.experimental.Accessors;@Data
@Accessors(chain = true)
public class Result {public String code ;public String msg;public Object data;public static Result success(String msg,String data){return new Result().setCode("200").setData(data).setMsg(msg);}public static Result success(String msg){return new Result().setCode("200").setMsg(msg);}public static Result fileBuild(){return new Result().setCode("101");}public static Result fileSuccess(String data){return new Result().setCode("201").setData(data);}public static Result error(String msg){return new Result().setCode("500").setMsg(msg);}public static Result error(){return new Result().setCode("500").setMsg("服务器出错");}public static Result fileOver() {return new Result().setCode("202");}
}

创建测试 TestController


@Slf4j
@RestController
@RequestMapping("/")
public class TestController {@Autowiredprivate FileService fileService;@PostMapping("/uploadJavaVideo")public Result uploadJavaVideo(@RequestPart("file") MultipartFile file) {try {String path = fileService.uploadJavaVideo2M3u8(file);if (StringUtils.isNotBlank(path)) {return Result.success("上传成功",path);}}catch (Exception e){log.error("视频上传转码异常,异常原因e:{}",e);}return Result.error("上传失败");}
}

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
【PdgCntEditor】解... 一、问题背景 大部分的图书对应的PDF,目录中的页码并非PDF中直接索引的页码...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
修复 爱普生 EPSON L4... L4151 L4153 L4156 L4158 L4163 L4165 L4166 L4168 L4...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...