先解釋一下什么是音頻加水印:
音頻加水印就是在一段音頻中通過混音加入另一段音頻,目的是讓音頻可以公開分享並有效保護原創。
本文主要紀錄自己關於給音頻加水印的技術調研。
開發語言: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); } }
當時也是參考了很多博客,但是沒有找到一篇滿足我需要的,
本篇文章所記錄的都是經過我驗證的,遺憾本方法尚未在業務代碼中使用。
希望可以幫到有需要的同學!