zoukankan      html  css  js  c++  java
  • 音视频点播服务基础系列之:FFmpeg音视频剪辑实用命令以及踩坑经验分享

    前言

      公司业务中有一些场景需要用到服务端音视频剪辑技术,最开始为了快速上线使用的是某公有云的商用解决方案,但由于费用太高所以我们团队经过一个星期的冲刺,给出了一个FFmpeg+Serverless的解决方案,更好地满足了业务方的视频剪辑需求。经过统计,自研方案成功地将剪辑失败率降到了万分之一左右,并且将费用成本降到了商用解决方案的零头,是一个非常大的突破。现在我们计划把该平台做得更加通用化,让更多的业务可以无缝接入,通过任务编排来实现更多定制化需求。

      题外话,为什么我们会选择Serverless来实现该业务呢,因为我们的业务高峰期特别明显,时效性要求也高,在使用某公有云解决方案期间经常触发系统QPS限制,经过多次沟通也只能临时调整,而且对方技术半夜还打电话来问我是否可以限制QPS,作为使用方肯定是不愿意的,所以除了成本外性能也是我们下决心自研系统的原因之一。Serverless在使用过程中遇到的问题我也会在后续时间里面记录下来。

      本编博客主要是记录在整个过程中涉及到的FFmpeg常见命令,以及一些坑,分享给大家使用,若有谬误请批评指正。

    基本命令

    音视频剪切

    String.format("%s -i %s -vcodec libx264  -ss %s -to %s %s -y", ffmpegCommandPath, sourceFilePath, startTime, endTime, targetFilePath)

    ffmpegCommandPath    表示FFmpeg执行命令的路径

    sourceFilePath              表示源文件路径

    startTime                     表示剪切的起始点,格式为 "00:00:00", 例如 "00:00:15" 表示剪切从第15秒开始

    endTime                      表示剪切的终止点,格式为 "00:00:00", 例如 "00:00:20" 表示剪切截止到第20秒

    targetFilePath               表示剪切后的输出文件

    -y                               表示输出文件若存在则覆盖  

    音频/视频简单拼接

    String.format("%s -f concat -safe 0 -i %s -c copy %s", ffmpegCommandPath, concatListFilePath, destinationFilePath)

    ffmpegCommandPath    表示FFmpeg执行命令的路径

    concatListFilePath         表示拼接的配置文件,内容格式为

    file 'filePath1.audio'

    file 'filePath2.audio'

    destinationFilePath        表示拼接后的输出文件

    使用限制:该方式不涉及到文件的解码、编码,所以速度极快,但如果待处理文件的编码格式不同则请勿使用,否则输出的文件可能无法正常播放(或者只能播放一部分)。如果编码格式不同,请参考下文中的音频拼接/视频拼接方式,会更加可靠,当会更加消耗资源。

    音频拼接

    由于涉及到到参数拼接,所以直接上代码(Java方式)。

    /**
     * 音频文件拼接
     * @param files                     音频文件资源路径数组
     * @param destinationFilePath       处理后输出文件路径
     */
    public static void audioConcat(String[] files, String destinationFilePath) {
        // command list
        List<String> commandList = new ArrayList<>();
        commandList.add("ffmpeg");
    
        // input_options
        for (String file : files) {
            commandList.add("-i");
            commandList.add(file);
        }
    
        // filter_complex
        StringBuilder filterComplexOptions = new StringBuilder();
        for (int i = 0; i < files.length; i++) {
            filterComplexOptions.append(String.format("[%s:0]", i));
        }
        filterComplexOptions.append(String.format("concat=n=%s:v=0:a=1[out]", files.length));
        commandList.add("-filter_complex");
        commandList.add(filterComplexOptions.toString());
        commandList.add("-map");
        commandList.add("[out]");
        commandList.add(destinationFilePath);
        Runtime.getRuntime().exec(commandList.toArray(new String[0]));
    
        // next process
    }

    视频拼接

    由于涉及到到参数拼接,所以直接上代码(Java方式)。

    /**
     * 视频拼接
     * @param files                  音频文件资源路径数组
     * @param destinationFilePath    处理后输出文件路径
     * @param outputWidth            输出视频的宽度
     * @param outputHeight           输出视频的高度
     */
    public static void videoConcat(String[] files, String destinationFilePath, Integer outputWidth, Integer outputHeight) {
        // command list
        List<String> commandList = buildFfmpegCommand();
        commandList.add("ffmpeg");
    
        // input_options
        for (String file : files) {
            commandList.add("-i");
            commandList.add(file);
        }
    
        // filter_complex
        StringBuilder filterComplexOptions = new StringBuilder();
        StringBuilder streamsOptions = new StringBuilder();
        for (int i = 0; i < files.length; i++) {
            filterComplexOptions.append(String.format("[%s:v]scale=w=%s:h=%s,setsar=1/1[v%s];", i, outputWidth, outputHeight, i));
            streamsOptions.append(String.format("[v%s][%s:a]", i, i));
        }
        streamsOptions.append(String.format("concat=n=%s:v=1:a=1 [vv] [aa]", files.length));
        commandList.add("-filter_complex");
        commandList.add(String.format("%s%s", filterComplexOptions.toString(), streamsOptions.toString()));
        Collections.addAll(commandList, "-map", "[vv]", "-map", "[aa]", "-c:v", "libx264", "-x264-params",
            "profile=main:level=3.1", "-crf", "18", "-y", "-vsync", "vfr");
        commandList.add(destinationFilePath);
        Runtime.getRuntime().exec(commandList.toArray(new String[0]));
    
        // next process
    }

    踩坑经验: 我们在拼接过程中遇到了视频拼接出错的情况,但数量比较少,通过FFprobe命令分析,发现这种情况出现在其中某个视频无音轨的情况,找了很多解决方案,最后采用的方式是为这个视频配一个音轨,相当于先把素材标准化处理,为视频注入音轨的方式见下文 "无音轨视频配音"。

    音视频混合

    String.format("%s -i %s -i %s -filter_complex amix -map 0:v -map 0:a -map 1:a -shortest -y %s", ffmpegCommandPath, videoFilePath, audioFilePath, targetFilePath)

    ffmpegCommandPath    表示FFmpeg执行命令的路径

    videoFilePath                表示视频文件路径

    audioFilePath                表示音频文件路径

    targetFilePath               表示输出文件路径

    无音轨视频配音

    String.format("%s -i %s -f lavfi -i aevalsrc=0 -shortest -y %s", ffmpegCommandPath, videoFilePath, targetFilePath)

    ffmpegCommandPath    表示FFmpeg执行命令的路径

    videoFilePath                表示视频文件路径

    targetFilePath               表示输出文件路径

    踩坑经验

    Runtime.getRuntime().exec() 的问题

    上述涉及到Java的部分都是采用的 Runtime.getRuntime().exec(String[] cmdarray) 而不是 Runtime.getRuntime().exec(String command),因为后者一旦遇到双引号就会带来问题,当时就想使用字符串的方式来执行(这样的话一旦程序中遇到问题就可以很方便地复制到Shell中复现),但命令中一旦存在双引号就无法解决,困扰了很久,读到了 "getruntime() exec() with double quotes in command" 这篇文章后决定就用数组形式吧。

    执行结果 Process.waitFor() 的问题

    Runtime.getRuntime().exec() 的执行结果需要通过 waitFor() 方式来获取子进程退出码,以此来判断是否执行成功,但倘若 FFmpeg 没有关闭调试信息,则可能会导致该函数一直卡在这。当时程序中有少部分任务会卡死,我还以为是Serverless平台的问题,但后面通过 "Java process.waitFor() 卡死问题" 这篇文章找到了解决方案,因此可以通过关闭调试信息的方式来规避,即"ffmpeg -loglevel quiet"。

  • 相关阅读:
    fedora上部署ASP.NET——(卡带式电脑跑.NET WEB服务器)
    SQL Server 请求失败或服务未及时响应。有关详细信息,请参见事件日志或其它适合的错误日志
    8086CPU的出栈(pop)和入栈(push) 都是以字为单位进行的
    FTP 服务搭建后不能访问问题解决
    指定的 DSN 中,驱动程序和应用程序之间的体系结构不匹配
    Linux 安装MongoDB 并设置防火墙,使用远程客户端访问
    svn Please execute the 'Cleanup' command. 问题解决
    .net 操作MongoDB 基础
    oracle 使用绑定变量极大的提升性能
    尝试加载 Oracle 客户端库时引发 BadImageFormatException。如果在安装 32 位 Oracle 客户端组件的情况下以 64 位模式运行,将出现此问题。
  • 原文地址:https://www.cnblogs.com/odirus/p/14929982.html
Copyright © 2011-2022 走看看