Java 音频加水印

摘要:
先解释一下什么是音频加水印:音频加水印就是在一段音频中通过混音加入另一段音频,目的是让音频可以公开分享并有效保护原创。本文主要纪录自己关于给音频加水印的技术调研。接下来我们看看在Java中如何编码操作首先maven引入第三方工具包:˂!ERROR:{}",ExceptionUtils.getStackTrace);returnnull;}}再创建一个工具类:CmdExecutor,用来执行FFmpeg指令packagecom.whz.uni.audio.util;importlombok.extern.slf4j.Slf4j;importjava.io.BufferedReader;importjava.io.InputStreamReader;/***脚本命令执行器*@authorWangHangzhou*@since2021/4/28*/@Slf4jpublicclassCmdExecutor{/***CMD操作*@paramgetter获取控制台打印信息*@paramcmd命令*/publicstaticvoidexec{try{//创建新线程ProcessBuilderbuilder=newProcessBuilder();//执行命令builder.command;builder.redirectErrorStream;Processproc=builder.start();BufferedReaderstdout=newBufferedReader;Stringline;while((line=stdout.readLine())!

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

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

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

开发语言: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,并添加以下方法

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

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

packagecom.whz.uni.audio.util;

importlombok.extern.slf4j.Slf4j;

importjava.io.BufferedReader;
importjava.io.InputStreamReader;

/*** 脚本命令执行器
 * @authorWang Hangzhou
 * @since2021/4/28
 */@Slf4j
public classCmdExecutor {

   /*** CMD操作
    * @paramgetter 获取控制台打印信息
    * @paramcmd 命令
    */
   public static voidexec(CmdOutputGetter getter, String... cmd) {
      try{
         //创建新线程
         ProcessBuilder builder = newProcessBuilder();
         //执行命令
builder.command(cmd);
         builder.redirectErrorStream(true);
         Process proc =builder.start();
         BufferedReader stdout = new BufferedReader(newInputStreamReader(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);
      }
   }
}

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

packagecom.whz.audio;

importcn.hutool.core.io.FileUtil;
importcom.whz.uni.audio.util.AudioUtils;
importcom.whz.uni.audio.util.CmdExecutor;
importcom.whz.uni.audio.util.CmdOutputGetter;
importlombok.extern.slf4j.Slf4j;
importorg.junit.Test;

importjava.io.File;
importjava.util.Objects;

/*** 为音频添加水印
 * @authorWang Hangzhou
 * @since2021/4/28
 */
@Slf4j
public classFFmpegTest {

   /*** 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";

   /*** 生成音频水印文件
    * @paramseconds 水印循环间隔时间
    * @return
    */
   public static String buildWaterMarkFile(intseconds) {
      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("水印文件已存在");
            returnloop;
         }
         String[] command4empty = {FFMPEG_FILE, "-ar", "48000", "-t", seconds + "", "-f", "s16le", "-acodec",
               "pcm_s16le", "-i", "/dev/zero", "-f", "mp3", "-y", emptyAudioPath};
         //调用cmd操作类
         CmdExecutor.exec(newCmdOutputGetter() {
            @Override
            public voiddealLine(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(newCmdOutputGetter() {
            @Override
            public voiddealLine(String line) {
               //把cmd输出的信息每行syso,这个是实时输出的,可以换其他的处理方式
System.out.println(line);
            }
         }, command4concat);

         log.info("水印连接「{}秒」间隔音频完成!", seconds);

      } catch(Exception e) {
         e.printStackTrace();
      }

      returnloop;
   }

   /*** 为音频文件添加水印
    * @paramwatermarkFilePath 水印文件路径
    * @paramaudioPath 源音频文件路径
    */
   public voidaddWatermark4Audio(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(newCmdOutputGetter() {
         @Override
         public voiddealLine(String line) {
            //把cmd输出的信息每行syso,这个是实时输出的,可以换其他的处理方式
System.out.println(line);
         }
      }, command4addWatermark);

      log.info("添加音频水印完成!路径:{}", newAudioPath);
   }

   @Test
   public voidaddWaterMark() {
      //生成指定间隔水印文件
      String waterMarkFile = buildWaterMarkFile(4);
      //需要添加水印的音频文件
      String audioPath = "/Users/wanghz/Documents/音频水印/1.mp3";
      //添加水印
addWatermark4Audio(waterMarkFile, audioPath);
   }

}

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

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

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

免责声明:文章转载自《Java 音频加水印》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Protocol buffers--python 实践 简介以及安装与使用大文件传输解决方案:分片上传 / 下载限速下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

C# Pkcs8 1024位 加密 解密 签名 解签

部分代码来至https://www.cnblogs.com/dj258/p/6049786.html usingSystem; usingSystem.Collections.Generic; usingSystem.Linq; usingSystem.Text; usingOrg.BouncyCastle.Asn1.Pkcs; usingOrg...

C# 获取程序运行时路径

Ø  前言 开发中,很多时候都需要获取程序运行时路径,比如:反射、文件操作等。.NET Framework 已经封装了这些功能,可以很方便的使用。 C# 中有很多类都可以获取程序运行时路径,我们没必要记住所有的,只需要记住常用的(其他了解即可),比如: 1.   System.AppDomain.CurrentDomain.BaseDirectory,获取...

在springmvc中配置jedis(转)

主要学习https://github.com/thinkgem/jeesite。一下代码均参考于此并稍作修改。 1.jedis 首先,需要添加jedis: <!--jedis--> <dependency> <groupId>redis.clients</groupId> &l...

【Android】让你的安卓app国际化、支持语言自动切换

碎碎念 我在写app的时候,无论是布局上的字符串还是代码中的,我都喜欢直接敲上去,无视那个善意提醒的波浪线。 对于小项目来说,我觉得无可厚非,虽然不规范但方便直观,不需要苦于给字符串起名字。 如果你在项目初期就想着要将应用推向国际市场,就要注意字符串一定要养成习惯全部写在string.xml里,不然后期再加真的很崩溃。 创建多语言的string.xml 我...

FileUpload控件使用初步

FileUpload控件使用初步: 1.实现文件上传 protected void btnSubmit_click(object sender, EventArgs e) { if (FileUpload1.HasFile == true) { string strErr = ""; //获得上传文件的大小 int filesize = FileUploa...

Unity多语言本地化改进版

简介 之前捣鼓过一个通过csv配置游戏多语言支持的小工具,但是发现使用过程中,通过notepad++去进行转码很不方便,并且直接将配置的csv不加密的放在游戏中心里感觉不是很踏实 于是乎~~ 新的方案 1.在PC/MAC平台上解析多语言配置,也就是editor运行环境中解析csv或者excel 2.通过在Editor运行过程中生成多个语言对象,然后序列化并...