zoukankan      html  css  js  c++  java
  • Java 音频加水印

    先解释一下什么是音频加水印:

    音频加水印就是在一段音频中通过混音加入另一段音频,目的是让音频可以公开分享并有效保护原创。

    本文主要纪录自己关于给音频加水印的技术调研。

    开发语言:Java,开发所处系统环境Mac

    使用了开源软件:FFmpeg 4.2.4

    FFmpeg官网下载链接:https://ffmpeg.org/download.html#build-mac

    第一步:准备一个水印音频

    一般水印音频都是一段口播文字,可以到这个网站(https://www.zaixianai.cn/voiceCompose)去免费文字转语音一下

    转完之后下载下来,命名为:watermark.mp3

    第二步:生成一段指定时长的无声音频

    水印音频混音在原音频中,肯定是需要一段间隔时间的,这里生成无声音频目的就是跟第一步的水印音频拼接,从而形成一个有间隔可以循环去播放的水印音频

    这里要用到FFmpeg的命令:

    # -t后面的数字8是生成音频的时长,单位秒
    ffmpeg-x86_64-osx -ar 48000 -t 8 -f s16le -acodec pcm_s16le -i /dev/zero -f mp3 -y /Users/wanghz/Documents/音频水印/empty-8.mp3

    第三步:拼接无声音频和水印音频(最终水印)

    # 第一个 -i 后面是纯水印文件
    # 第二个 -i 后面跟的是8s的无声音频
    ffmpeg-x86_64-osx -i /Users/wanghz/Documents/音频水印/watermark.mp3 -i /Users/wanghz/Documents/音频水印/empty-8.mp3 -filter_complex '[0:0] [1:0] concat=n=2:v=0:a=1 [a]' -map [a] -ar 48000 -y /Users/wanghz/Documents/音频水印/loop-8.mp3

    第四步:混合原音频和最终水印音频,水印音频循环播放至原音频播放结束

    # 第一个 -i 后面是原音频文件
    # -stream_loop -1 指定后面一个音频将无限循环
    # 第二个 -i 后面是生成好的水印文件
    # -t 指定混音后文件的时长单位秒
    # -f 后面跟生成格式
    # -y 如果已存在文件,将其覆盖
    ffmpeg-x86_64-osx -i /Users/wanghz/Documents/音频水印/原音频.mp3 -stream_loop -1 -i /Users/wanghz/Documents/音频水印/loop-8.mp3 -filter_complex "[1:a][0:a]amix" -t 163 -ar 48000 -f mp3 -y /Users/wanghz/Documents/音频水印/添加水印后.mp3

    以上4步就是在终端中,使用FFmpeg命令完成一次给音频加水印的操作了!


    接下来我们看看在Java中如何编码操作

    首先maven引入第三方工具包:

    <!-- 引入三方工具包 -->
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>5.4.6</version>
    </dependency>
    <dependency> 
        <groupId>org</groupId>
        <artifactId>jaudiotagger</artifactId>
        <version>2.0.1</version>
    </dependency>

    创建一个工具类:AudioUtils,并添加以下方法

    /**
     * 获取音频播放时长,返回值单位秒
     * @param path 音频路径
     * @return
     */
    public static Integer getAudioDuration(String path) {
       try {
          MP3File file = new MP3File(path);
          MP3AudioHeader audioHeader = (MP3AudioHeader) file.getAudioHeader();
          return audioHeader.getTrackLength();
       } catch (Exception e) {
          log.error("获取音频播放时长失败!ERROR:{}", ExceptionUtils.getStackTrace(e));
          return null;
       }
    }

    再创建一个工具类:CmdExecutor,用来执行FFmpeg指令

    package com.whz.uni.audio.util;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.io.BufferedReader;
    import java.io.InputStreamReader;
    
    /**
     * 脚本命令执行器
     * @author Wang Hangzhou
     * @since 2021/4/28
     */
    @Slf4j
    public class CmdExecutor {
    
       /**
        * CMD操作
        * @param getter 获取控制台打印信息
        * @param cmd 命令
        */
       public static void exec(CmdOutputGetter getter, String... cmd) {
          try {
             // 创建新线程
             ProcessBuilder builder = new ProcessBuilder();
             // 执行命令
             builder.command(cmd);
             builder.redirectErrorStream(true);
             Process proc = builder.start();
             BufferedReader stdout = new BufferedReader(new InputStreamReader(proc.getInputStream()));
             String line;
             while ((line = stdout.readLine()) != null) {
                if (getter != null)
                   getter.dealLine(line);
             }
             proc.waitFor();
             stdout.close();
          } catch (Exception e) {
             log.error(e.getMessage(), e);
          }
       }
    }

    完成以上操作,就可以使用了,贴一个测试类,供大家参考

    package com.whz.audio;
    
    import cn.hutool.core.io.FileUtil;
    import com.whz.uni.audio.util.AudioUtils;
    import com.whz.uni.audio.util.CmdExecutor;
    import com.whz.uni.audio.util.CmdOutputGetter;
    import lombok.extern.slf4j.Slf4j;
    import org.junit.Test;
    
    import java.io.File;
    import java.util.Objects;
    
    /**
     * 为音频添加水印
     * @author Wang Hangzhou
     * @since 2021/4/28
     */
    
    @Slf4j
    public class FFmpegTest {
    
       /**
        * ffmpeg程序位置
        */
       private final static String FFMPEG_FILE = "/Users/wanghz/Documents/音频水印/ffmpeg-x86_64-osx";
       /**
        * 水印文件位置
        */
       private final static String WATERMARK_FILE = "/Users/wanghz/Documents/音频水印/watermark.mp3";
    
       /**
        * 生成音频水印文件
        * @param seconds 水印循环间隔时间
        * @return
        */
       public static String buildWaterMarkFile(int seconds) {
          String loop = "/Users/wanghz/Documents/音频水印/loop-" + seconds + ".mp3";
          try {
             String emptyAudioPath = "/Users/wanghz/Documents/音频水印/empty-" + seconds + ".mp3";
             File file = FileUtil.file(loop);
             if (file.exists()) {
                log.info("水印文件已存在");
                return loop;
             }
             String[] command4empty = {FFMPEG_FILE, "-ar", "48000", "-t", seconds + "", "-f", "s16le", "-acodec",
                   "pcm_s16le", "-i", "/dev/zero", "-f", "mp3", "-y", emptyAudioPath};
             //调用cmd操作类
             CmdExecutor.exec(new CmdOutputGetter() {
                @Override
                public void dealLine(String line) {
                   //把cmd输出的信息每行syso,这个是实时输出的,可以换其他的处理方式
                   System.out.println(line);
                }
             }, command4empty);
             log.info("「{}秒」静音音频生成完成!", seconds);
    
             String[] command4concat = {FFMPEG_FILE, "-i", WATERMARK_FILE, "-i", emptyAudioPath, "-filter_complex",
                   "[0:0] [1:0] concat=n=2:v=0:a=1 [a]", "-map", "[a]", "-ar", "48000", loop, "-y"};
    
             CmdExecutor.exec(new CmdOutputGetter() {
                @Override
                public void dealLine(String line) {
                   //把cmd输出的信息每行syso,这个是实时输出的,可以换其他的处理方式
                   System.out.println(line);
                }
             }, command4concat);
    
             log.info("水印连接「{}秒」间隔音频完成!", seconds);
    
          } catch (Exception e) {
             e.printStackTrace();
          }
    
          return loop;
       }
    
       /**
        * 为音频文件添加水印
        * @param watermarkFilePath 水印文件路径
        * @param audioPath 源音频文件路径
        */
       public void addWatermark4Audio(String watermarkFilePath, String audioPath) {
          // 获取源音频文件播放时长
          Integer duration = AudioUtils.getAudioDuration(audioPath);
          if (Objects.isNull(duration)) {
             log.error("获取音频文件时长失败");
             return;
          }
          log.info("源音频时长:「{}秒」", duration);
          String newAudioPath = "/Users/wanghz/Documents/音频水印/newAudio.mp3";
    
          String[] command4addWatermark = {FFMPEG_FILE, "-i", audioPath, "-stream_loop", "-1", "-i", watermarkFilePath,
                "-filter_complex", "[1:a][0:a]amix", "-t", duration + "", "-ar", "48000", "-f", "mp3", newAudioPath,
                "-y"};
    
          CmdExecutor.exec(new CmdOutputGetter() {
             @Override
             public void dealLine(String line) {
                //把cmd输出的信息每行syso,这个是实时输出的,可以换其他的处理方式
                System.out.println(line);
             }
          }, command4addWatermark);
    
          log.info("添加音频水印完成!路径:{}", newAudioPath);
       }
    
       @Test
       public void addWaterMark() {
          // 生成指定间隔水印文件
          String waterMarkFile = buildWaterMarkFile(4);
          // 需要添加水印的音频文件
          String audioPath = "/Users/wanghz/Documents/音频水印/1.mp3";
          // 添加水印
          addWatermark4Audio(waterMarkFile, audioPath);
       }
    
    }

    当时也是参考了很多博客,但是没有找到一篇满足我需要的,

    本篇文章所记录的都是经过我验证的,遗憾本方法尚未在业务代码中使用。

    希望可以帮到有需要的同学!

      

  • 相关阅读:
    hibernate笔记--实体类映射文件"*.hbm.xml"详解
    struts2学习笔记--使用struts2插件实现ajax处理(返回json数据)
    struts2学习笔记--使用servletAPI实现ajax的一个小Demo
    Struts2学习笔记--使用Response下载文件和Struts2的StreamResult文件下载
    struts2学习笔记--上传单个和批量文件示例
    struts2学习笔记--拦截器(Interceptor)和登录权限验证Demo
    struts2学习笔记--使用Validator校验数据
    struts2学习笔记--OGNL表达式1
    easyui dialog 扩展load
    easyui filter 过滤时间段
  • 原文地址:https://www.cnblogs.com/whzbz894/p/15091120.html
Copyright © 2011-2022 走看看