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