|
@@ -0,0 +1,160 @@
|
|
|
+package com.fs.app.util;
|
|
|
+
|
|
|
+import com.fs.common.utils.uuid.IdUtils;
|
|
|
+import com.fs.system.oss.CloudStorageService;
|
|
|
+import com.fs.system.oss.OSSFactory;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.apache.http.client.methods.CloseableHttpResponse;
|
|
|
+import org.apache.http.client.methods.HttpGet;
|
|
|
+import org.apache.http.impl.client.CloseableHttpClient;
|
|
|
+import org.apache.http.impl.client.HttpClients;
|
|
|
+import org.springframework.http.HttpStatus;
|
|
|
+
|
|
|
+import java.io.ByteArrayOutputStream;
|
|
|
+import java.io.FileOutputStream;
|
|
|
+import java.io.IOException;
|
|
|
+import java.io.InputStream;
|
|
|
+import java.nio.file.Files;
|
|
|
+import java.nio.file.Path;
|
|
|
+import java.nio.file.Paths;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.List;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
+
|
|
|
+@Slf4j
|
|
|
+public class AudioUtils {
|
|
|
+
|
|
|
+ /**
|
|
|
+ * silk转换为mp3
|
|
|
+ * @param silkUrl silk语音链接地址
|
|
|
+ * @return mp3链接地址
|
|
|
+ */
|
|
|
+ public static String convertSilk2Mp3(String silkUrl) {
|
|
|
+ String uniqueId = IdUtils.fastSimpleUUID();
|
|
|
+ Path uploadDirPath = Paths.get(System.getProperty("java.io.tmpdir"), "/");
|
|
|
+ Path downloadedSilkFilePath = uploadDirPath.resolve(uniqueId + ".silk");
|
|
|
+ Path pcmFilePath = uploadDirPath.resolve(uniqueId + ".pcm");
|
|
|
+ Path mp3FilePath = uploadDirPath.resolve(uniqueId + ".mp3");
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 1. 从网络下载 SILK 文件
|
|
|
+ downloadFile(silkUrl, downloadedSilkFilePath);
|
|
|
+ log.info("SILK file downloaded to: {}", downloadedSilkFilePath);
|
|
|
+
|
|
|
+ // 2. 使用 silk-v3-decoder 解码 SILK 到 PCM
|
|
|
+ List<String> silkDecodeCommand = new ArrayList<>();
|
|
|
+ silkDecodeCommand.add("silk_v3_decoder");
|
|
|
+ silkDecodeCommand.add(downloadedSilkFilePath.toString());
|
|
|
+ silkDecodeCommand.add(pcmFilePath.toString());
|
|
|
+
|
|
|
+ ProcessBuilder silkDecoderPb = new ProcessBuilder(silkDecodeCommand);
|
|
|
+ silkDecoderPb.redirectErrorStream(true); // 将错误流合并到标准输出
|
|
|
+ Process silkDecoderProcess = silkDecoderPb.start();
|
|
|
+
|
|
|
+ String silkDecoderOutput = readInputStreamToString(silkDecoderProcess.getInputStream());
|
|
|
+
|
|
|
+ boolean silkDecoderExited = silkDecoderProcess.waitFor(60, TimeUnit.SECONDS);
|
|
|
+ if (!silkDecoderExited || silkDecoderProcess.exitValue() != 0) {
|
|
|
+ log.error("silk conversion failed or timed out. error: {}", silkDecoderOutput);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ log.info("SILK decoder to PCM successfully.");
|
|
|
+
|
|
|
+ // 3. 使用 FFmpeg 将 PCM 转码为 MP3
|
|
|
+ Process ffmpegProcess = getFfmpegProcess(pcmFilePath, mp3FilePath);
|
|
|
+ String ffmpegOutput = readInputStreamToString(ffmpegProcess.getInputStream());
|
|
|
+
|
|
|
+ boolean ffmpegExited = ffmpegProcess.waitFor(120, TimeUnit.SECONDS);
|
|
|
+ if (!ffmpegExited || ffmpegProcess.exitValue() != 0) {
|
|
|
+ log.error("ffmpeg conversion failed or timed out. error: {}", ffmpegOutput);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ log.info("ffmpeg conversion to MP3 successfully.");
|
|
|
+
|
|
|
+ // 4. 上传oss
|
|
|
+ String fileName = mp3FilePath.getFileName().toString();
|
|
|
+ String suffix = fileName.substring(fileName.lastIndexOf("."));
|
|
|
+ CloudStorageService storage = OSSFactory.build();
|
|
|
+ return storage.uploadSuffix(Files.newInputStream(mp3FilePath), suffix);
|
|
|
+
|
|
|
+ } catch (IOException | InterruptedException | NullPointerException e) {
|
|
|
+ log.error("Conversion error: {}", e.getMessage());
|
|
|
+ return null;
|
|
|
+ } finally {
|
|
|
+ // 清理临时文件 (重要!)
|
|
|
+ try {
|
|
|
+ if (Files.exists(downloadedSilkFilePath)) Files.delete(downloadedSilkFilePath);
|
|
|
+ if (Files.exists(pcmFilePath)) Files.delete(pcmFilePath);
|
|
|
+ if (Files.exists(mp3FilePath)) Files.delete(mp3FilePath);
|
|
|
+ } catch (IOException e) {
|
|
|
+ log.error("Error cleaning up temporary files:: {}", e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 执行ffmpeg
|
|
|
+ * @param pcmFilePath pcm文件
|
|
|
+ * @param mp3FilePath mp3地址
|
|
|
+ * @return process
|
|
|
+ * @throws IOException exception
|
|
|
+ */
|
|
|
+ private static Process getFfmpegProcess(Path pcmFilePath, Path mp3FilePath) throws IOException {
|
|
|
+ List<String> ffmpegCommand = new ArrayList<>();
|
|
|
+ ffmpegCommand.add("ffmpeg");
|
|
|
+ ffmpegCommand.add("-y");
|
|
|
+ ffmpegCommand.add("-f");
|
|
|
+ ffmpegCommand.add("s16le");
|
|
|
+ ffmpegCommand.add("-ar");
|
|
|
+ ffmpegCommand.add("24000"); // 注意:这里假设是 24kHz,如果你的 SILK 文件是其他采样率,请调整
|
|
|
+ ffmpegCommand.add("-ac");
|
|
|
+ ffmpegCommand.add("1");
|
|
|
+ ffmpegCommand.add("-i");
|
|
|
+ ffmpegCommand.add(pcmFilePath.toString());
|
|
|
+ ffmpegCommand.add(mp3FilePath.toString());
|
|
|
+
|
|
|
+ ProcessBuilder ffmpegPb = new ProcessBuilder(ffmpegCommand);
|
|
|
+ ffmpegPb.redirectErrorStream(true);
|
|
|
+ return ffmpegPb.start();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理文件流
|
|
|
+ * @param is 输入流
|
|
|
+ * @return 输出
|
|
|
+ * @throws IOException exception
|
|
|
+ */
|
|
|
+ private static String readInputStreamToString(InputStream is) throws IOException {
|
|
|
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
|
+ byte[] buffer = new byte[1024];
|
|
|
+ int len;
|
|
|
+ while ((len = is.read(buffer)) != -1) {
|
|
|
+ bos.write(buffer, 0, len);
|
|
|
+ }
|
|
|
+ return bos.toString("UTF-8"); // 使用 UTF-8 编码
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 下载网络文件
|
|
|
+ * @param fileUrl 网络文件
|
|
|
+ * @param destination 临时文件
|
|
|
+ * @throws IOException exception
|
|
|
+ */
|
|
|
+ private static void downloadFile(String fileUrl, Path destination) throws IOException {
|
|
|
+ try (CloseableHttpClient httpClient = HttpClients.createDefault();
|
|
|
+ CloseableHttpResponse response = httpClient.execute(new HttpGet(fileUrl));
|
|
|
+ InputStream inputStream = response.getEntity().getContent();
|
|
|
+ FileOutputStream outputStream = new FileOutputStream(destination.toFile())) {
|
|
|
+
|
|
|
+ if (response.getStatusLine().getStatusCode() != HttpStatus.OK.value()) {
|
|
|
+ throw new IOException("Failed to download file from " + fileUrl + ", HTTP Status: " + response.getStatusLine().getStatusCode());
|
|
|
+ }
|
|
|
+
|
|
|
+ byte[] buffer = new byte[4096];
|
|
|
+ int bytesRead;
|
|
|
+ while ((bytesRead = inputStream.read(buffer)) != -1) {
|
|
|
+ outputStream.write(buffer, 0, bytesRead);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|