为音频添加一段口播音频水印,水印音频循环播放至原音频结束

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

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

 

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

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

使用了开源软件:FFmpeg 4.2.4

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

 

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

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

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

 

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

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

这里要用到FFmpeg的命令:

  1. # -t后面的数字8是生成音频的时长,单位秒
  2. 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

 

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

  1. # 第一个 -i 后面是纯水印文件
  2. # 第二个 -i 后面跟的是8s的无声音频
  3. 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

 

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

  1. # 第一个 -i 后面是原音频文件
  2. # -stream_loop -1 指定后面一个音频将无限循环
  3. # 第二个 -i 后面是生成好的水印文件
  4. # -t 指定混音后文件的时长单位秒
  5. # -f 后面跟生成格式
  6. # -y 如果已存在文件,将其覆盖
  7. 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引入第三方工具包:

  1. <!-- 引入三方工具包 -->
  2. <dependency>
  3. <groupId>cn.hutool</groupId>
  4. <artifactId>hutool-all</artifactId>
  5. <version>5.4.6</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>org</groupId>
  9. <artifactId>jaudiotagger</artifactId>
  10. <version>2.0.1</version>
  11. </dependency>

 

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

  1. /**
  2. * 获取音频播放时长,返回值单位秒
  3. * @param path 音频路径
  4. * @return
  5. */
  6. public static Integer getAudioDuration(String path) {
  7. try {
  8. MP3File file = new MP3File(path);
  9. MP3AudioHeader audioHeader = (MP3AudioHeader) file.getAudioHeader();
  10. return audioHeader.getTrackLength();
  11. } catch (Exception e) {
  12. log.error("获取音频播放时长失败!ERROR:{}", ExceptionUtils.getStackTrace(e));
  13. return null;
  14. }
  15. }

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

  1. package com.whz.uni.audio.util;
  2. import lombok.extern.slf4j.Slf4j;
  3. import java.io.BufferedReader;
  4. import java.io.InputStreamReader;
  5. /**
  6. * 脚本命令执行器
  7. * @author Wang Hangzhou
  8. * @since 2021/4/28
  9. */
  10. @Slf4j
  11. public class CmdExecutor {
  12. /**
  13. * CMD操作
  14. * @param getter 获取控制台打印信息
  15. * @param cmd 命令
  16. */
  17. public static void exec(CmdOutputGetter getter, String... cmd) {
  18. try {
  19. // 创建新线程
  20. ProcessBuilder builder = new ProcessBuilder();
  21. // 执行命令
  22. builder.command(cmd);
  23. builder.redirectErrorStream(true);
  24. Process proc = builder.start();
  25. BufferedReader stdout = new BufferedReader(new InputStreamReader(proc.getInputStream()));
  26. String line;
  27. while ((line = stdout.readLine()) != null) {
  28. if (getter != null)
  29. getter.dealLine(line);
  30. }
  31. proc.waitFor();
  32. stdout.close();
  33. } catch (Exception e) {
  34. log.error(e.getMessage(), e);
  35. }
  36. }
  37. }

 

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

  1. package com.whz.audio;
  2. import cn.hutool.core.io.FileUtil;
  3. import com.whz.uni.audio.util.AudioUtils;
  4. import com.whz.uni.audio.util.CmdExecutor;
  5. import com.whz.uni.audio.util.CmdOutputGetter;
  6. import lombok.extern.slf4j.Slf4j;
  7. import org.junit.Test;
  8. import java.io.File;
  9. import java.util.Objects;
  10. /**
  11. * 为音频添加水印
  12. * @author Wang Hangzhou
  13. * @since 2021/4/28
  14. */
  15. @Slf4j
  16. public class FFmpegTest {
  17. /**
  18. * ffmpeg程序位置
  19. */
  20. private final static String FFMPEG_FILE = "/Users/wanghz/Documents/音频水印/ffmpeg-x86_64-osx";
  21. /**
  22. * 水印文件位置
  23. */
  24. private final static String WATERMARK_FILE = "/Users/wanghz/Documents/音频水印/watermark.mp3";
  25. /**
  26. * 生成音频水印文件
  27. * @param seconds 水印循环间隔时间
  28. * @return
  29. */
  30. public static String buildWaterMarkFile(int seconds) {
  31. String loop = "/Users/wanghz/Documents/音频水印/loop-" + seconds + ".mp3";
  32. try {
  33. String emptyAudioPath = "/Users/wanghz/Documents/音频水印/empty-" + seconds + ".mp3";
  34. File file = FileUtil.file(loop);
  35. if (file.exists()) {
  36. log.info("水印文件已存在");
  37. return loop;
  38. }
  39. String[] command4empty = {FFMPEG_FILE, "-ar", "48000", "-t", seconds + "", "-f", "s16le", "-acodec",
  40. "pcm_s16le", "-i", "/dev/zero", "-f", "mp3", "-y", emptyAudioPath};
  41. //调用cmd操作类
  42. CmdExecutor.exec(new CmdOutputGetter() {
  43. @Override
  44. public void dealLine(String line) {
  45. //把cmd输出的信息每行syso,这个是实时输出的,可以换其他的处理方式
  46. System.out.println(line);
  47. }
  48. }, command4empty);
  49. log.info("「{}秒」静音音频生成完成!", seconds);
  50. String[] command4concat = {FFMPEG_FILE, "-i", WATERMARK_FILE, "-i", emptyAudioPath, "-filter_complex",
  51. "[0:0] [1:0] concat=n=2:v=0:a=1 [a]", "-map", "[a]", "-ar", "48000", loop, "-y"};
  52. CmdExecutor.exec(new CmdOutputGetter() {
  53. @Override
  54. public void dealLine(String line) {
  55. //把cmd输出的信息每行syso,这个是实时输出的,可以换其他的处理方式
  56. System.out.println(line);
  57. }
  58. }, command4concat);
  59. log.info("水印连接「{}秒」间隔音频完成!", seconds);
  60. } catch (Exception e) {
  61. e.printStackTrace();
  62. }
  63. return loop;
  64. }
  65. /**
  66. * 为音频文件添加水印
  67. * @param watermarkFilePath 水印文件路径
  68. * @param audioPath 源音频文件路径
  69. */
  70. public void addWatermark4Audio(String watermarkFilePath, String audioPath) {
  71. // 获取源音频文件播放时长
  72. Integer duration = AudioUtils.getAudioDuration(audioPath);
  73. if (Objects.isNull(duration)) {
  74. log.error("获取音频文件时长失败");
  75. return;
  76. }
  77. log.info("源音频时长:「{}秒」", duration);
  78. String newAudioPath = "/Users/wanghz/Documents/音频水印/newAudio.mp3";
  79. String[] command4addWatermark = {FFMPEG_FILE, "-i", audioPath, "-stream_loop", "-1", "-i", watermarkFilePath,
  80. "-filter_complex", "[1:a][0:a]amix", "-t", duration + "", "-ar", "48000", "-f", "mp3", newAudioPath,
  81. "-y"};
  82. CmdExecutor.exec(new CmdOutputGetter() {
  83. @Override
  84. public void dealLine(String line) {
  85. //把cmd输出的信息每行syso,这个是实时输出的,可以换其他的处理方式
  86. System.out.println(line);
  87. }
  88. }, command4addWatermark);
  89. log.info("添加音频水印完成!路径:{}", newAudioPath);
  90. }
  91. @Test
  92. public void addWaterMark() {
  93. // 生成指定间隔水印文件
  94. String waterMarkFile = buildWaterMarkFile(4);
  95. // 需要添加水印的音频文件
  96. String audioPath = "/Users/wanghz/Documents/音频水印/1.mp3";
  97. // 添加水印
  98. addWatermark4Audio(waterMarkFile, audioPath);
  99. }
  100. }

 

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

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

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

  

版权声明:本文为whzbz894原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/whzbz894/p/15091120.html