(二)ffmpeg的相关命令,以及JAVA操作ffmpeg

一、常用查看指令

1.查看FFmpeg支持的编码器
ffmpeg configure -encoders

2.查看FFmpeg支持的编码器
ffmpeg configure -decoders

3.查看ffmpeg支持的通信协议
ffmpeg configure -protocols

4.查看FFmpeg所支持的音视频编码格式、文件封装格式与流媒体传输协议
ffmpeg configure --help

二、常用操作视频命令

        1.视频压缩
ffmpeg -i ahaha.mp4 -vcodec h264 -vf scale=640:-2 -threads 4 2020_conv.mp4

ffmpeg -i ahaha.mp4 -strict -2 -vcodec h264 1579251906_output.mp4

-i ahaha.mp4
输入文件,源文件

xy_conv.mp4
输出文件,目标文件

-vf scale=640:-2  
改变视频分辨率,缩放到640px宽,高度的-2是考虑到libx264要求高度是偶数,所以设置成-2,让软件自动计算得出一个接近等比例的偶数高

-threads 4
4核运算


相关参数

-s 1280x720 
设置输出文件的分辨率,w*h。

-b:v 
输出文件的码率,一般500k左右即可,人眼看不到明显的闪烁,这个是与视频大小最直接相关的。

-preset
指定输出的视频质量,会影响文件的生成速度,有以下几个可用的值 ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow。
与 veryslow相比,placebo以极高的编码时间为代价,只换取了大概1%的视频质量提升。这是一种收益递减准则:slow 与 medium相比提升了5%~10%;slower 与 slow相比提升了5%;veryslow 与 slower相比提升了3%。
针对特定类型的源内容(比如电影、动画等),还可以使用-tune参数进行特别的优化。

-an
去除音频流。

-vn
去除视频流。

-c:a
指定音频编码器。

-c:v
指定视频编码器,libx264,libx265,H.262,H.264,H.265。
libx264:最流行的开源 H.264 编码器。
NVENC:基于 NVIDIA GPU 的 H.264 编码器。
libx265:开源的 HEVC 编码器。
libvpx:谷歌的 VP8 和 VP9 编码器。
libaom:AV1 编码器。

-vcodec copy
表示不重新编码,在格式未改变的情况采用。

-re 
以源文件固有帧率发送数据。

-minrate 964K -maxrate 3856K -bufsize 2000K 
指定码率最小为964K,最大为3856K,缓冲区大小为 2000K。

-y
不经过确认,输出时直接覆盖同名文件。

-crf
参数来控制转码,取值范围为 0~51,其中0为无损模式,18~28是一个合理的范围,数值越大,画质越差。

        2.视频拼接
1.将4个视频拼接成一个很长的视频(无声音)
ffmpeg -i 0.mp4 -i 1.mp4 -i 2.mp4 -i 3.mp4 -filter_complex '[0:0][1:0] [2:0][3:0] concat=n=4:v=1 [v]' -map '[v]' output.mp4

2.将4个视频拼接成一个很长的视频(有声音)
ffmpeg -i 1.mp4 -i 2.mp4 -i 3.mp4 -filter_complex '[0:0][0:1] [1:0][1:1] [2:0][2:1] concat=n=3:v=1:a=1 [v][a]' -map '[v]' -map '[a]’  output.mp4

[0:0][0:1] [1:0][1:1] [2:0][2:1] 
分别表示第1个输入文件的视频、音频,第2个输入文件的视频、音频,第3个输入文件的视频、音频。

concat=n=3:v=1:a=1 
表示有3个输入文件,输出一条视频流和一条音频流。

[v][a] 
得到的视频流和音频流的名字,注意在 bash 等 shell 中需要用引号,防止通配符扩展。


3.横向拼接视频2个
  
ffmpeg -i 0.mp4 -i 1.mp4 -filter_complex "[0:v]pad=iw*2:ih*1[a];[a][1:v]overlay=w" out.mp4

pad
将合成的视频宽高,这里iw代表第1个视频的宽,iw*2代表合成后的视频宽度加倍,ih为第1个视频的高,合成的两个视频最好分辨率一致。

overlay
覆盖,[a][1:v]overlay=w,后面代表是覆盖位置w:0。

4.竖向拼接视频2个
ffmpeg -i 0.mp4 -i 1.mp4 -filter_complex "[0:v]pad=iw:ih*2[a];[a][1:v]overlay=0:h" out_2.mp4


5.横向拼接视频3个

ffmpeg -i 0.mp4 -i 1.mp4 -i 2.mp4 -filter_complex "[0:v]pad=iw*3:ih*1[a];[a][1:v]overlay=w[b];[b][2:v]overlay=2.0*w" out_v3.mp4


6.竖向拼接视频3个

ffmpeg -i 0.mp4 -i 1.mp4 -i 2.mp4 -filter_complex "[0:v]pad=iw:ih*3[a];[a][1:v]overlay=0:h[b];[b][2:v]overlay=0:2.0*h" out_v4.mp4

7.竖向拼接视频4个品字形
ffmpeg -i 0.mp4 -i 1.mp4 -i 2.mp4 -i 3.mp4 -filter_complex "[0:v]pad=iw*2:ih*2[a];[a][1:v]overlay=w[b];[b][2:v]overlay=0:h[c];[c][3:v]overlay=w:h" out.mp4



       3.截取视频第一帧或者某一帧
# input seeking
ffmpeg -ss 00:1:05 -i gemfield.mp4 -frames:v 1 out.jpg


# output seeking
ffmpeg -i gemfield.mp4 -ss 00:1:05 -frames:v 1 out1.jpg

-frame:v 1,在video stream上截取1帧。
input seeking使用的是key frames,所以速度很快;而output seeking是逐帧decode,直到1分05秒,所以速度很慢。

ffmpeg截取视频帧有2种 seeking 方式,对应有2种 coding 模式:transcoding 和 stream copying(ffmpeg -c copy)。

transcoding 模式:需要 decoding + encoding 的模式,即先 decoding 再encoding。

stream copying 模式:不需要decoding + encoding的模式,由命令行选项-codec加上参数copy来指定(-c:v copy )。在这种模式下,ffmpeg在video stream上就会忽略 decoding 和 encoding步骤。


查看视频总帧数
ffprobe -v error -count_frames -select_streams v:0 -show_entries stream=nb_frames -of default=nokey=1:noprint_wrappers=1 gemfield.mp4

       4.图片转视频

ffmpeg -f image2 -i 'in%6d.jpg' -vcodec libx264 -r 25 -b 200k test.mp4
-r 25 表示每秒播放25帧
-b 200k 指定码率为200k
	
图片的文件名为"in000000.jpg",从0开始依次递增。

       4.图片格式转化

1.webp转换成jpg
ffmpeg -i in.webp out.jpg

2.webp转换成png
ffmpeg -i in.webp out.png

3.jpg转换成png
ffmpeg -i in.jpg out.png

4.jpg转换成webp
ffmpeg -i in.jpg out.webp

5.png转换成webp
ffmpeg -i in.png out.webp

6.png转换成jpg
ffmpeg -i in.png out.jpg

三、java操作ffmpeg录制视频

        1.相关代码
/**
 *
 * @Author: xy丶
 * @create: 2021/8/27 16:11
 */
@Component
public class RtspToMP4 {

    public class In implements Runnable{
        private InputStream inputStream;

        public In(InputStream inputStream) {
            this.inputStream = inputStream;
        }
        @Override
        public void run() {
            try {
                //转成字符输入流
                InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "UTF-8");
                int len = -1;
                char[] c = new char[1024];
                //读取进程输入流中的内容
                while ((len = inputStreamReader.read(c)) != -1) {
                    String s = new String(c, 0, len);
                    System.out.print(s);
                }
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public Process startRecord(String ffmpegPath,String streamUrl, String FilePath){
        ProcessBuilder processBuilder = new ProcessBuilder();
        //定义命令内容
        List<String> command = new ArrayList<>();
        command.add(ffmpegPath);
        command.add("-rtsp_transport");
        command.add("tcp");
        command.add("-y");
        command.add("-i");
        command.add(streamUrl);
        command.add("-c");
        command.add("copy");
        command.add("-f");
        command.add("mp4");
        command.add(FilePath);
        processBuilder.command(command);
        System.out.println("脚本:" + command.toString());
        //将标准输入流和错误输入流合并,通过标准输入流读取信息
        processBuilder.redirectErrorStream(true);
        try {
            //启动进程
            Process process = processBuilder.start();
            System.out.println("开始时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(System.currentTimeMillis())));
            //获取输入流
            InputStream inputStream = process.getInputStream();
            Thread inThread = new Thread(new In(inputStream));
            inThread.start();
           return process;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 停止录制
     * @param process
     * @return
     */
    public boolean stopRecord(Process process) {
        try {
            OutputStream os = process.getOutputStream();
            os.write("q".getBytes());
            // 一定要刷新
            os.flush();
            os.close();
        } catch (Exception err) {
            err.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 音视频合并,视频结束,音频结束 -- (cmd(windows): ffmpeg.exe -i test2.mp3 -i test1.mp4 -t 10 -y newVideo.mp4)
     *
     * @param ffmpegPath      ffmpeg.exe文件路径,可在rest或者admin中进行配置,使用配置文件进行读取
     * @param audioInputPath  音频文件路径(输入)
     * @param videoInputPath  视频文件路径(输入)
     * @param time            文件时长
     * @param videoOutputPath 转换完成的文件路径(输出)
     * @throws IOException
     */
    public static void audioVideoMerge(String ffmpegPath, String audioInputPath, String videoInputPath, double time, String videoOutputPath) throws IOException {
        // 构建命令
        List<String> command = Lists.newArrayList();
        command.add(ffmpegPath);
        command.add("-i");
        command.add(audioInputPath);
        command.add("-i");
        command.add(videoInputPath);
        command.add("-t");
        command.add(String.valueOf(time));
        command.add("-y");
        command.add(videoOutputPath);
        // 执行操作
        ProcessBuilder builder = new ProcessBuilder(command);
        Process process = builder.start();
        InputStream errorStream = process.getErrorStream();
        InputStreamReader isr = new InputStreamReader(errorStream);
        BufferedReader br = new BufferedReader(isr);
        String line = "";
        while ((line = br.readLine()) != null) {
        }
        if (br != null) {
            br.close();
        }
        if (isr != null) {
            isr.close();
        }
        if (errorStream != null) {
            errorStream.close();
        }
    }


/*    public void testAudioVideoMerge() {
        FfmpegProperties ffmpegProperties = SpringContextHolder.getBean(FfmpegProperties.class);
        try {
            FfmpegUtil.audioVideoMerge(ffmpegProperties.getFfmpegFile(), "D:\\tools\\ffmpeg\\bin\\test2.mp3", "D:\\tools\\ffmpeg\\bin\\test1.mp4", 10, "D:\\tools\\ffmpeg\\bin\\newVideo.mp4");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }*/

}
        2.开始录制
   private Map<Integer,Process> map=new HashMap<>();

    /**
     * 开始录制
     * @param id
     * @param FileName
     * @return
     */
    @GetMapping(value = "/startRecord")
    public Result<Object> Start(Integer id, String FileName) {
        String ffmpegPath="E:\\install\\ffmpeg\\bin\\ffmpeg.exe";
        //rtsp://127.0.0.1:554/rtp/34020000001110000001_34020000001320000002   rtsp://127.0.0.1:554/rtp/44010200492000000001_34020000001320000001
        String streamUrl="rtsp://127.0.0.1:554/rtp/44010200492000000001_34020000001320000001";
        String FilePath="E:\\ffmp4\\"+FileName;
        Process process = rtspToMP4.startRecord(ffmpegPath, streamUrl, FilePath);
        if(null!=process){
            map.put(id,process);
            boolean interceptPhoto = VedioUtils.interceptPhoto(ffmpegPath, streamUrl, "E:\\ffmp4\\rtsp新脚本.jpg");
            if (!interceptPhoto){
                return Results.newFailedResult(ErrorCodeEnum.UNKNOWN);
            }
            return Results.newSuccessResult("操作成功");
        }
        return Results.newFailedResult(ErrorCodeEnum.UNKNOWN);
    }
        3.结束录制
    /**
     * 结束录制
     * @param id
     * @return
     */
    @GetMapping(value = "/stop")
    public Result<Object> stop(Integer id) {
        if(map.containsKey(id)){
            Process process = map.get(id);
            if(null!=process){
                rtspToMP4.stopRecord(process);
                return Results.newSuccessResult("操作成功");
            }
        }
        return Results.newFailedResult(ErrorCodeEnum.UNKNOWN);
    }
        4.合并视频
    /**
     * @Description:合并视频
     * @Author: xy丶
     */
    @GetMapping("/mergeVideo")
    public void mergeVideo( HttpServletResponse response){
        List<String> paths = Lists.newArrayList();
        paths.add("E:\\excel\\test.mp4");
        paths.add("E:\\excel\\test1.mp4");
        paths.add("E:\\excel\\test2.mp4");
        paths.add("E:\\excel\\test3.mp4");
        CMDExecteUtil excutor = new CMDExecteUtil();
        //String cmd = VedioUtils.mergeCmd[paths.size() - 1];
        String cmd = "ffmpeg %s -filter_complex \"nullsrc=size=640x480[base];[0:v]setpts=PTS-STARTPTS,scale=320x240[1];[1:v]setpts=PTS-STARTPTS,scale=320x240[2];[2:v]setpts=PTS-STARTPTS,scale=320x240[3];[3:v]setpts=PTS-STARTPTS,scale=320x240[4];[base][1]overlay=shortest=1[tmp1];[tmp1][2]overlay=shortest=1:x=320[tmp2];[tmp2][3]overlay=shortest=1:y=240[tmp3];[tmp3][4]overlay=shortest=1:x=320:y=240\" %s";
        StringBuilder sb = new StringBuilder();
        for (String s : paths) {
            if (StringUtils.isEmpty(sb)) {
                sb.append("-i ").append(s);
            } else {
                sb.append(" ").append("-i ").append(s);
            }
        }
        // "ffmpeg %s -filter_complex \"nullsrc=size=640x480[base];[0:v]setpts=PTS-STARTPTS,scale=320x240[1];[1:v]setpts=PTS-STARTPTS,scale=320x240[2];[2:v]setpts=PTS-STARTPTS,scale=320x240[3];[3:v]setpts=PTS-STARTPTS,scale=320x240[4];[base][1]overlay=shortest=1[tmp1];[tmp1][2]overlay=shortest=1:x=320[tmp2];[tmp2][3]overlay=shortest=1:y=240[tmp3];[tmp3][4]overlay=shortest=1:x=320:y=240\" %s",
        String tmpCMD = String.format(cmd, sb.toString(), "E:\\excel\\mergeVideo.mp4");
        excutor.exec(tmpCMD);
    }
public class CMDExecteUtil {

	Logger logger = LoggerFactory.getLogger(CMDExecteUtil.class);

	/**
	 * 执行命令
	 * @param command 命令语句
	 * @return
	 */
	public String exec(String command) {
		try {
			//创建子进程执行命令语句
			Process p = Runtime.getRuntime().exec(command);
			StreamCaptureThread errorStream = new StreamCaptureThread(p.getErrorStream());
			StreamCaptureThread outputStream = new StreamCaptureThread(p.getInputStream());
			new Thread(errorStream).start();
			new Thread(outputStream).start();
			//等待执行完毕
			p.waitFor();
			String result = command + "\n" + outputStream.output.toString() + errorStream.output.toString();
			logger.info(result);

			if (!StringUtils.isEmpty(errorStream.output.toString())) {
				return errorStream.output.toString();
			}

		} catch (Exception e) {
			logger.error(e.getMessage());
		}
		return null;
	}

	private class StreamCaptureThread implements Runnable {
		InputStream stream;
		StringBuilder output;

		public StreamCaptureThread(InputStream stream) {
			this.stream = stream;
			this.output = new StringBuilder();
		}

		@Override
        public void run() {
			try {
				try {
					BufferedReader br = new BufferedReader(new InputStreamReader(this.stream));
					String line = br.readLine();
					while (line != null) {
						if (line.trim().length() > 0) {
							output.append(line).append("\n");
						}
						line = br.readLine();
					}
				} finally {
					if (stream != null) {
						stream.close();
					}
				}
			} catch (IOException ex) {
				ex.printStackTrace(System.err);
			}
		}
	}
}
       5.其他操作

/**
 * @author xy丶
 * @date 2023/10/11
 */
@Service
@Slf4j
public class FFmpegServiceImpl {
    //九宫格命令
    private String[] mergeCmd = {
            "ffmpeg %s -filter_complex \"nullsrc=size=640x480[base];[0:v]setpts=PTS-STARTPTS,scale=640x480[1];[base][1]overlay=shortest=1\" %s",
            "ffmpeg -i 1.mp4 -i 2.mp4 -filter_complex \"nullsrc=size=640x480[base];[0:v]setpts=PTS-STARTPTS,scale=320x480[1];[1:v]setpts=PTS-STARTPTS,scale=320x480[2];[base][1]overlay=shortest=1[tmp1];[tmp1][2]overlay=shortest=1:x=320\" mergeOut.mp4",
            "ffmpeg -i 1.mp4 -i 2.mp4 -i 3.mp4 -filter_complex \"nullsrc=size=640x480[base];[0:v]setpts=PTS-STARTPTS,scale=320x240[1];[1:v]setpts=PTS-STARTPTS,scale=320x240[2];[2:v]setpts=PTS-STARTPTS,scale=320x240[3];[base][1]overlay=shortest=1[tmp1];[tmp1][2]overlay=shortest=1:y=240[tmp2];[tmp2][3]overlay=shortest=1:x=320\" mergeOut.mp4",
            "ffmpeg -i 1.mp4 -i 2.mp4 -i 3.mp4 -i 3.mp4 -i 3.mp4 -filter_complex \"nullsrc=size=640x480[base];[0:v]setpts=PTS-STARTPTS,scale=320x240[1];[1:v]setpts=PTS-STARTPTS,scale=320x240[2];[2:v]setpts=PTS-STARTPTS,scale=320x240[3];[3:v]setpts=PTS-STARTPTS,scale=320x240[4];[base][1]overlay=shortest=1[tmp1];[tmp1][2]overlay=shortest=1:x=320[tmp2];[tmp2][3]overlay=shortest=1:y=240[tmp3];[tmp3][4]overlay=shortest=1:x=320:y=240\" mergeOut.mp4",
            "ffmpeg -i 1.mp4 -i 2.mp4 -i 3.mp4 -i 4.mp4 -i 5.mp4 -filter_complex \"nullsrc=size=690x480[base];[0:v]setpts=PTS-STARTPTS,scale=230x240[1];[1:v]setpts=PTS-STARTPTS,scale=230x240[2];[2:v]setpts=PTS-STARTPTS,scale=230x240[3];[3:v]setpts=PTS-STARTPTS,scale=230x240[4];[4:v]setpts=PTS-STARTPTS,scale=230x240[5];[base][1]overlay=shortest=1[tmp1];[tmp1][2]overlay=shortest=1:x=230[tmp2];[tmp2][3]overlay=shortest=1:x=460[tmp3];[tmp3][4]overlay=shortest=1:y=240[tmp4];[tmp4][5]overlay=shortest=1:x=230:y=240\" mergeOut.mp4",
            "ffmpeg -i 1.mp4 -i 2.mp4 -i 3.mp4 -i 4.mp4 -i 5.mp4 -i 6.mp4 -filter_complex \"nullsrc=size=690x480[base];[0:v]setpts=PTS-STARTPTS,scale=230x240[1];[1:v]setpts=PTS-STARTPTS,scale=230x240[2];[2:v]setpts=PTS-STARTPTS,scale=230x240[3];[3:v]setpts=PTS-STARTPTS,scale=230x240[4];[4:v]setpts=PTS-STARTPTS,scale=230x240[5];[5:v]setpts=PTS-STARTPTS,scale=230x240[6];[base][1]overlay=shortest=1[tmp1];[tmp1][2]overlay=shortest=1:x=230[tmp2];[tmp2][3]overlay=shortest=1:x=460[tmp3];[tmp3][4]overlay=shortest=1:y=240[tmp4];[tmp4][5]overlay=shortest=1:x=230:y=240[tmp5];[tmp5][6]overlay=shortest=1:x=460:y=240\" mergeOut.mp4",
            "ffmpeg -i 1.mp4 -i 2.mp4 -i 3.mp4 -i 4.mp4 -i 5.mp4 -i 6.mp4 -i 7.mp4 -filter_complex \"nullsrc=size=690x480[base];[0:v]setpts=PTS-STARTPTS,scale=230x160[1];[1:v]setpts=PTS-STARTPTS,scale=230x160[2];[2:v]setpts=PTS-STARTPTS,scale=230x160[3];[3:v]setpts=PTS-STARTPTS,scale=230x160[4];[4:v]setpts=PTS-STARTPTS,scale=230x160[5];[5:v]setpts=PTS-STARTPTS,scale=230x160[6];[6:v]setpts=PTS-STARTPTS,scale=230x160[7];[base][1]overlay=shortest=1[tmp1];[tmp1][2]overlay=shortest=1:x=230[tmp2];[tmp2][3]overlay=shortest=1:x=460[tmp3];[tmp3][4]overlay=shortest=1:y=160[tmp4];[tmp4][5]overlay=shortest=1:x=230:y=160[tmp5];[tmp5][6]overlay=shortest=1:x=460:y=160[tmp6];[tmp6][7]overlay=shortest=1:y=320\" mergeOut.mp4",
            "ffmpeg -i 1.mp4 -i 2.mp4 -i 3.mp4 -i 4.mp4 -i 5.mp4 -i 6.mp4 -i 7.mp4  -i 8.mp4 -filter_complex \"nullsrc=size=690x480[base];[0:v]setpts=PTS-STARTPTS,scale=230x160[1];[1:v]setpts=PTS-STARTPTS,scale=230x160[2];[2:v]setpts=PTS-STARTPTS,scale=230x160[3];[3:v]setpts=PTS-STARTPTS,scale=230x160[4];[4:v]setpts=PTS-STARTPTS,scale=230x160[5];[5:v]setpts=PTS-STARTPTS,scale=230x160[6];[6:v]setpts=PTS-STARTPTS,scale=230x160[7];[7:v]setpts=PTS-STARTPTS,scale=230x160[8];[base][1]overlay=shortest=1[tmp1];[tmp1][2]overlay=shortest=1:x=230[tmp2];[tmp2][3]overlay=shortest=1:x=460[tmp3];[tmp3][4]overlay=shortest=1:y=160[tmp4];[tmp4][5]overlay=shortest=1:x=230:y=160[tmp5];[tmp5][6]overlay=shortest=1:x=460:y=160[tmp6];[tmp6][7]overlay=shortest=1:y=320[tmp7];[tmp7][8]overlay=shortest=1:x=230:y=320\" mergeOut.mp4",
            "ffmpeg -i 1.mp4 -i 2.mp4 -i 3.mp4 -i 4.mp4 -i 5.mp4 -i 6.mp4 -i 7.mp4  -i 8.mp4 -i 9.mp4 -filter_complex \"nullsrc=size=690x480[base];[0:v]setpts=PTS-STARTPTS,scale=230x160[1];[1:v]setpts=PTS-STARTPTS,scale=230x160[2];[2:v]setpts=PTS-STARTPTS,scale=230x160[3];[3:v]setpts=PTS-STARTPTS,scale=230x160[4];[4:v]setpts=PTS-STARTPTS,scale=230x160[5];[5:v]setpts=PTS-STARTPTS,scale=230x160[6];[6:v]setpts=PTS-STARTPTS,scale=230x160[7];[7:v]setpts=PTS-STARTPTS,scale=230x160[8];[8:v]setpts=PTS-STARTPTS,scale=230x160[9];[base][1]overlay=shortest=1[tmp1];[tmp1][2]overlay=shortest=1:x=230[tmp2];[tmp2][3]overlay=shortest=1:x=460[tmp3];[tmp3][4]overlay=shortest=1:y=160[tmp4];[tmp4][5]overlay=shortest=1:x=230:y=160[tmp5];[tmp5][6]overlay=shortest=1:x=460:y=160[tmp6];[tmp6][7]overlay=shortest=1:y=320[tmp7];[tmp7][8]overlay=shortest=1:x=230:y=320[tmp8];[tmp8][9]overlay=shortest=1:x=460:y=320\" mergeOut.mp4",
    };

    //剪切命令
    private String shearCmd = "ffmpeg -i %s -ss 0 -c copy -t %s  -codec copy %s";
    //拼接命令
    private String jointCmd = "ffmpeg -f concat -safe 0 -i %s -c copy %s";
    //提取一帧转为图片
    private String cropCmd ="ffmpeg -i %s -frames:v 1 %s";

    //剪辑固定时长视频
    public void shearVideo(String source, Long dur, String outputName) {
        dur = dur / 1000;
        outputName = outputName.replace(":", "");
        String outputPath = source + outputName;
        String tmpCMD = String.format(shearCmd, source + "tmp.mp4", dur, outputPath);
        CMDExecteUtil excutor = new CMDExecteUtil();
        excutor.exec(tmpCMD);
    }

    //拼接多个视频
    public void jointVideo(String filePath, String outPath) {
        String tmpCMD = String.format(jointCmd, filePath, outPath);
        CMDExecteUtil excutor = new CMDExecteUtil();
        excutor.exec(tmpCMD);
    }

    //合并多个视频
    public void mergeVideo(List<String> paths, String outPutPath) {
        String cmd = mergeCmd[paths.size() - 1];
        String viPath = "";
        StringBuilder sb = new StringBuilder();
        for (String s : paths) {
            if (StringUtils.isEmpty(sb)) {
                sb.append("-i ").append(s);
            } else {
                sb.append(" ").append("-i ").append(s);
            }
        }
        String tmpCMD = String.format(cmd, sb.toString(), outPutPath + "merge.mp4");
        CMDExecteUtil excutor = new CMDExecteUtil();
        excutor.exec(tmpCMD);
    }

    //截取一帧
    public void cropImage(String streamPath,String outPath){
        String tmpCMD = String.format(cropCmd, streamPath, outPath);
        log.info("FFMPEG---"+tmpCMD);
        CMDExecteUtil excutor = new CMDExecteUtil();
        excutor.exec(tmpCMD);
    }
}

结尾:干活满满,喜欢就点个赞收藏吧

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

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

相关文章

Vulnhub - Hacker_Kid

希望和各位大佬一起学习&#xff0c;如果文章内容有错请多多指正&#xff0c;谢谢&#xff01; 个人博客链接&#xff1a;CH4SER的个人BLOG – Welcome To Ch4sers Blog Hacker_Kid 靶机下载地址&#xff1a;https://www.vulnhub.com/entry/hacker-kid-101,719/ WP 参考&…

什么样的人适合学习网络安全?

有很多想要转行网络安全或者选择网络安全专业的人在进行决定之前一定会有的问题&#xff1a;什么样的人适合学习网络安全&#xff1f;我适不适合学习网络安全&#xff1f; 会产生这样的疑惑并不奇怪&#xff0c;毕竟网络安全这个专业在2017年才调整为国家一级学科&#xff0c;…

在Ubuntu上使用Script命令捕获命令与其输出

在Ubuntu上使用Script命令捕获命令与其输出 起初&#xff0c;是为了记录软件的安装过程&#xff0c;就在想有没有简单高效的记录方法&#xff0c;之后就找到了script命令。 使用 script命令&#xff0c;可以很容易地记录下你在终端里所有的操作与输出&#xff0c;非常适合用来…

CMake笔记之PROJECT_SOURCE_DIR、CMAKE_SOURCE_DIR、CMAKE_CURRENT_BINARY_DIR对比

CMake笔记之PROJECT_SOURCE_DIR、CMAKE_SOURCE_DIR、CMAKE_CURRENT_BINARY_DIR对比 —— 杭州 2024-03-19 夜 code review! 文章目录 CMake笔记之PROJECT_SOURCE_DIR、CMAKE_SOURCE_DIR、CMAKE_CURRENT_BINARY_DIR对比1.三者区别2.具体示例说明3.CMAKE_SOURCE_DIR 和 PROJECT_S…

深度解析 JavaScript 的 structuredClone() 方法

您是否知道&#xff0c;现在 JavaScript 中有一种原生的方式可以深拷贝对象&#xff1f; 没错&#xff0c;这个内置于 JavaScript 运行时的structuredClone函数就是这样&#xff1a; const calendarEvent {title: "Builder.io大会",date: new Date(123),attendees…

(一)Linux+Windows下安装ffmpeg

一丶前言 FFmpeg是一个开源的音视频处理工具集&#xff0c;由多个命令行工具组成。它可以在跨平台的环境中处理、转换、编辑和流媒体处理音视频文件。 FFmpeg支持多种常见的音视频格式和编解码器&#xff0c;可以对音视频文件进行编码、解码、转码、剪辑、合并等操作。它具有广…

python3入门机器学习,知识点全面总结与代码实操示例

目录 写在前面的话 一、机器学习的基本任务与方法分类 机器学习的概念和定义 基本任务 二分类任务&#xff08;Binary Classification&#xff09;&#xff1a; 多分类任务&#xff08;Multi-class Classification&#xff09;&#xff1a; 多标签分类任务&#xff08;Mu…

SpringAOP(控制反转)【看这一片文章就够了】

目录 一、SpringAOP 1.概述 1 什么是AOP 2 AOP的作用 3 AOP的底层 4 AOP相关概念 5 AOP开发前要明确的事情 2. AOP入门 小结&#xff1a; 3. AOP详解-通知类型 1 通知类型 2 通知方法里获取目标方法信息(备用) 3 小结 4. AOP详解-切入点表达式 1 execution 2 an…

opencv 傅里叶变换(低通滤波 + 高通滤波)

文章目录 1、傅里叶变换2、通过numpy实现3、高通滤波器5、通过opencv实现傅里叶变换6、低通滤波器 1、傅里叶变换 时域分析&#xff1a;以时间作为参照物&#xff0c;世间万物都是随着时间变化而变化&#xff0c;并且不会停止 频域分析&#xff1a;认为世间万物都是静止的&…

将html网页展示的图表,下载到PPT文档内,以图片的形式展示在PPT内

使用到的工具有&#xff1a; 开发工具&#xff1a;IDEA 报表开发工具&#xff1a;帆软10.0.19 1、针对帆软报表[普通报表]的设置 1.1首先选中在帆软里制作好的报表&#xff0c;选择模板web属性 1.2.选择数据分析模式&#xff0c;添加一个事件设置&#xff0c;该事件应该设置“…

使用甘特图实现高效时间规划

甘特图虽然看似简单,却蕴含着规划时间的奥秘。它将复杂的工序分解成逻辑严密的任务链条,每个短小的条形图块都清晰地道出一个任务的起始、持续和终止。就像指挥家挥舞手中的棒,每个动作都精确拍着节奏,确保各个乐手分工合作、行云流水。择一个好用的甘特图制作工具,会让你事半功…

HCIP作业

实验要求&#xff1a; 1、R6为ISP&#xff0c;接口IP地址均为公有地址&#xff0c;该设备只能配置IP地址&#xff0c;之后不能再对其进行任何配置&#xff1b; 2、R1-R5为局域网&#xff0c;私有IP地址192.168.1.0/24&#xff0c;请合理分配&#xff1b; 3、R1、R2、R4&#x…

空间解析几何之直线与平面:推导直线与直线、直线与平面交点

空间解析几何——直线与平面 三维空间中的直线和平面与二维空间中的性质有一定的类似之处&#xff0c;但是其相交关系的求解方式有所差异。本文回顾了三维空间中直线和平面的解析表达&#xff0c;然后推导线-线、线-面交点。 平面 空间平面的表达式为&#xff1a; A x B y…

React状态管理库快速上手-Redux(一)

基本使用 安装 pnpm install reduxjs/toolkit react-redux创建一个仓库 定义state createSlice相当于创建了一个模块仓库&#xff0c;initialState存放状态&#xff0c;reducers存放改变状态的方法。 import { createSlice } from reduxjs/toolkitexport const counterSli…

MATLAB环境下基于改进最大相关峭度解卷积的滚动轴承故障诊断

相关峭度解卷积MCKD是一种新的解卷积方法&#xff0c;其设计了一个新的目标函数—相关峭度&#xff0c;并以此为优化目标设计一系列的FIR滤波器&#xff0c;为实现最好的效果&#xff0c;需要从中找到最优滤波器并最终实现对信号中噪声的抑制和对信号中冲击成分的突出的目的。M…

产品推荐 | 基于XC7K325T的FMC接口万兆光纤网络验证平台

01、产品概述 TES307是一款基于XC7K325T FPGA的万兆光纤网络验证平台&#xff0c;板卡具有1个FMC&#xff08;HPC&#xff09;接口&#xff0c;4路SFP万兆光纤接口、4路SATA接口、1路USB3.0接口。 板载高性能的FPGA处理器可以实现光纤协议、SATA总线控制器、以及USB3.0高速串…

长安链Docker Java智能合约引擎的架构、应用与规划

#功能发布 长安链3.0正式版发布了多个重点功能&#xff0c;包括共识算法切换、支持java智能合约引擎、支持后量子密码、web3生态兼容等。我们接下来为大家详细介绍新功能的设计、应用与规划。 在《2022年度长安链开源社区开发者调研报告》中&#xff0c;对Java合约语言支持是开…

华为配置HTTPS服务器实验

配置HTTPS服务器示例 组网图形 图1 配置HTTPS服务器组网图 组网需求配置思路配置注意事项操作步骤配置文件 组网需求 如图1所示&#xff0c;用户通过Web方式访问网关设备AP。 为了防止传输的数据不被窃听和篡改&#xff0c;实现对设备的安全管理&#xff0c;网络管理员要…

Makefile编译make complie时报错的心路历程

本次报错是在Makefile文件里面找错 Makefile文件找错的方法很复杂&#xff0c;必须要有一双慧眼&#xff0c;一层一层剥离分析 第一个错误&#xff08;还有一个错在全志 76节 23&#xff1a;35的时候连接wiringPi库&#xff09; 第二个错误&#xff1a; undefined reference…

Java:类和对象

目录 1.面对对象的初步认识1.1 什么是面向对象&#xff1f;&#xff08;Java当中一切皆为对象&#xff09;1.2 面对对象与面对过程 2.类的定义和使用2.1简单认识类2.2 类的定义格式 3.类的实例化3.1 什么是实例化3.2类和对象的说明 4.this引用4.1为什么要使用this引用4.2 什么是…