kongzy
2024-06-26 daf7acb4f107a427e4a83ba1eb26e5e6012cbdaf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
package com.gkhy.exam.common.utils;
 
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import com.gkhy.exam.common.config.FilePathConfig;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import net.bramp.ffmpeg.FFmpeg;
import net.bramp.ffmpeg.FFmpegExecutor;
import net.bramp.ffmpeg.FFprobe;
import net.bramp.ffmpeg.builder.FFmpegBuilder;
import net.bramp.ffmpeg.probe.FFmpegProbeResult;
import net.bramp.ffmpeg.probe.FFmpegStream;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
 
import javax.annotation.Resource;
import java.io.File;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
 
/**
 * @Author sirwsl
 * @Version 1.0
 */
@Slf4j
@Component
public class M3u8Utils {
 
    @Resource
    private FFmpeg ffmpeg;
 
    @Resource
    private FFprobe ffprobe;
 
    @Resource
    private FilePathConfig filePath;
 
 
 
    /**
     * 视频文件转 m3u8
     * 支持: .mp4 | .flv | .avi | .mov | .wmv | .wav
     * @param file 视频文件
     * @return 路径
     */
    public String mediaFileToM3u8(MultipartFile file){
        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());
        }
        long end = System.currentTimeMillis();
        log.info("临时文件上传成功......耗时:{} ms", end - start);
        String m3u8FilePath = localFileToM3u8(filePathName);
        log.info("视频转换已完成 !");
        return m3u8FilePath;
    }
 
    /**
     * 本地媒体资源转换
     * @param filePathName : 文件路径
     * @return :
     */
    @SneakyThrows
    public String localFileToM3u8(String filePathName) {
        long startTime = System.currentTimeMillis();
        final FFmpegProbeResult probe = ffprobe.probe(filePathName);
        final List<FFmpegStream> streams = probe.getStreams().stream().filter(fFmpegStream -> fFmpegStream.codec_type != null).collect(Collectors.toList());
        final Optional<FFmpegStream> audioStream = streams.stream().filter(fFmpegStream -> FFmpegStream.CodecType.AUDIO.equals(fFmpegStream.codec_type)).findFirst();
        final Optional<FFmpegStream> videoStream = streams.stream().filter(fFmpegStream -> FFmpegStream.CodecType.VIDEO.equals(fFmpegStream.codec_type)).findFirst();
 
        if (!audioStream.isPresent()) {
            log.error("未发现音频流");
        }
        if (!videoStream.isPresent()) {
            log.error("未发现视频流");
        }
        //m3u8文件 存储路径
        String filePath = generateFilePath(this.filePath.getBasePath());
        if (!FileUtil.exist(filePath)) {
            FileUtil.mkdir(filePath);
        }
        String mainName = getFileMainName(filePathName);
        String m3u8FileName = filePath + mainName + ".m3u8";
 
        //下面这一串参数别乱动,经过调优的,1G视频大概需要10秒左右,如果是大佬随意改
        //"-vsync", "2", "-c:v", "copy", "-c:a", "copy", "-tune", "fastdecode", "-hls_wrap", "0", "-hls_time", "10", "-hls_list_size", "0", "-threads", "12"
        FFmpegBuilder builder = new FFmpegBuilder()
                .setInput(filePathName)
                .overrideOutputFiles(true)
                .addOutput(m3u8FileName)//输出文件
                .setFormat(probe.getFormat().format_name) //"mp4"
                .setAudioBitRate(audioStream.map(fFmpegStream -> fFmpegStream.bit_rate).orElse(0L))
                .setAudioChannels(1)
                .setAudioCodec("aac")        // using the aac codec
                .setAudioSampleRate(audioStream.get().sample_rate)
                .setAudioBitRate(audioStream.get().bit_rate)
                .setStrict(FFmpegBuilder.Strict.STRICT)
                .setFormat("hls")
                .setPreset("ultrafast")
                .addExtraArgs("-vsync", "2", "-c:v", "copy", "-c:a", "copy", "-tune", "fastdecode", "-hls_wrap", "0", "-hls_time", "10", "-hls_list_size", "0", "-threads", "12")
                .done();
 
        FFmpegExecutor executor = new FFmpegExecutor(ffmpeg, ffprobe);
        // Run a one-pass encode
        executor.createJob(builder).run();
 
        File dest = new File(filePathName);
        if (dest.isFile() && dest.exists()) {
            dest.delete();
            System.gc();
            log.warn("临时文件 {}已删除", dest.getName());
        }
        long endTime = System.currentTimeMillis();
        log.info("文件:{} 转换完成!共耗时{} ms", dest.getName(), (endTime - startTime));
        return m3u8FileName;
    }
 
    public static String generateFilePath(String basePath){
        String temp = basePath;
        if(StringUtils.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("."));
    }
 
}