0
0
Files
fys-Multi-tenant/fys-admin/src/main/java/com/fuyuanshen/app/service/AudioProcessService.java

308 lines
10 KiB
Java
Raw Normal View History

package com.fuyuanshen.app.service;
import com.fuyuanshen.equipment.utils.AlibabaTTSUtil;
import com.fuyuanshen.equipment.utils.AudioProcessUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
2025-10-31 08:50:08 +08:00
import java.io.FileOutputStream;
import java.io.IOException;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
/**
* 音频处理服务
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class AudioProcessService {
// 配置参数
private static final int MAX_AUDIO_SIZE = 5 * 1024 * 1024; // 5MB
private static final List<String> SUPPORTED_FORMATS = Arrays.asList(
".wav", ".mp3", ".aac", ".flac", ".m4a", ".ogg"
);
private final AudioProcessUtil audioProcessUtil;
private final AlibabaTTSUtil alibabaTTSUtil;
/**
* 处理上传的音频文件
*/
public List<String> processAudio(MultipartFile file) {
// 1. 参数校验
validateAudioFile(file);
File tempFile = null;
try {
// 2. 创建临时文件
tempFile = createTempAudioFile(file);
// 3. 转码为标准PCM-WAV格式
byte[] pcmData = audioProcessUtil.convertToStandardWav(tempFile);
log.info("音频处理成功,输出数据大小: {} bytes", pcmData.length);
// 获取音频信息
// String audioInfo = audioProcessUtil.getAudioInfo(pcmData);
// log.info("音频处理成功,音频信息: {}", audioInfo);
//
// // 保存测试文件(用于验证)
// String savedPath = audioProcessUtil.saveWavToFile(pcmData, "test_output.wav");
// if (savedPath != null) {
// log.info("测试文件已保存: {}", savedPath);
// }
// 将byte[]转换为16进制字符串列表
List<String> hexList = audioProcessUtil.bytesToHexList(pcmData);
log.info("音频处理完成,原始数据大小: {} bytes, 16进制数据长度: {}",
pcmData.length, hexList.size());
return hexList;
} catch (Exception e) {
log.error("音频处理失败", e);
throw new RuntimeException("音频处理失败", e);
} finally {
// 4. 清理临时文件
deleteTempFile(tempFile);
}
}
/**
* 生成标准PCM数据单声道16K采样率16bit深度包含44字节WAV头
* 数据总大小不超过2MB如果超过将抛出异常
* @param text 要转换的文本内容
* @return 标准PCM数据字节数组WAV格式
* @throws IOException 处理失败时抛出
* @throws IllegalArgumentException 如果生成的数据超过2MB
*/
public List<String> generateStandardPcmData(String text) throws IOException {
// 参数校验
if (text == null || text.trim().isEmpty()) {
throw new IllegalArgumentException("文本内容不能为空");
}
if (text.length() > 100) {
throw new IllegalArgumentException("文本长度超过限制最大100字符");
}
log.info("输入文本长度: {}", text.length());
try {
byte[] rawPcmData = alibabaTTSUtil.generateStandardPcmData(text);
// 使用AudioProcessUtil转换成带头44字节 PCM
byte[] pcmData = audioProcessUtil.rawPcmToStandardWav(rawPcmData);
// String savedPath = audioProcessUtil.saveWavToFile(pcmData, "test_output.wav");
// if (savedPath != null) {
// log.info("测试文件已保存: {}", savedPath);
// }
2025-10-31 08:50:08 +08:00
// 保存WAV文件到本地
String savedPath = saveByteArrayToFile(pcmData, "tts_output.wav");
if (savedPath != null) {
log.info("WAV文件已保存: {}", savedPath);
}
// 将byte[]转换为16进制字符串列表
List<String> hexList = audioProcessUtil.bytesToHexList(pcmData);
log.info("generateStandardPcmData音频处理完成原始数据大小: {} bytes, 16进制数据长度: {}",
pcmData.length, hexList.size());
return hexList;
} finally {
// 4. 清理临时文件
}
}
2025-10-31 08:50:08 +08:00
public String saveWavFileLocally(String text, String filename) throws IOException {
// 参数校验
if (text == null || text.trim().isEmpty()) {
throw new IllegalArgumentException("文本内容不能为空");
}
if (filename == null || filename.trim().isEmpty()) {
filename = "tts_output.wav"; // 默认文件名
}
try {
// 生成PCM数据
byte[] rawPcmData = alibabaTTSUtil.generateStandardPcmData(text);
// 转换为标准WAV格式添加44字节头部
byte[] wavData = audioProcessUtil.rawPcmToStandardWav(rawPcmData);
// 保存到本地文件
String filePath = saveByteArrayToFile(wavData, filename);
log.info("WAV文件已保存: {}", filePath);
return filePath;
} catch (Exception e) {
log.error("保存WAV文件失败: {}", e.getMessage(), e);
throw new IOException("保存WAV文件失败", e);
}
}
private String saveByteArrayToFile(byte[] data, String filename) throws IOException {
// 确定保存路径(可以是临时目录或指定目录)
String directory = System.getProperty("java.io.tmpdir"); // 使用系统临时目录
File dir = new File(directory);
if (!dir.exists()) {
dir.mkdirs();
}
// 创建完整文件路径
File file = new File(dir, filename);
// 写入文件
try (FileOutputStream fos = new FileOutputStream(file)) {
fos.write(data);
}
return file.getAbsolutePath();
}
/**
* 验证音频文件
*/
private void validateAudioFile(MultipartFile file) {
if (file == null || file.isEmpty()) {
throw new IllegalArgumentException("上传文件不能为空");
}
if (!isAudioFile(file.getOriginalFilename())) {
throw new IllegalArgumentException("只允许上传音频文件");
}
if (file.getSize() > MAX_AUDIO_SIZE) {
throw new IllegalArgumentException("音频大小不能超过5MB");
}
}
/**
* 判断是否是支持的音频格式
*/
private boolean isAudioFile(String filename) {
if (filename == null || filename.lastIndexOf('.') == -1) {
return false;
}
String ext = filename.substring(filename.lastIndexOf('.')).toLowerCase();
return SUPPORTED_FORMATS.contains(ext);
}
/**
* 创建临时音频文件
*/
private File createTempAudioFile(MultipartFile file) throws IOException {
String originalFilename = file.getOriginalFilename();
String extension = "";
if (originalFilename != null && originalFilename.contains(".")) {
extension = originalFilename.substring(originalFilename.lastIndexOf("."));
}
File tempFile = File.createTempFile("audio-", extension);
file.transferTo(tempFile);
log.debug("创建临时音频文件: {}", tempFile.getAbsolutePath());
return tempFile;
}
/**
* 删除临时文件
*/
private void deleteTempFile(File file) {
if (file != null && file.exists()) {
if (file.delete()) {
log.debug("删除临时文件成功: {}", file.getAbsolutePath());
} else {
log.warn("无法删除临时文件: {}", file.getAbsolutePath());
}
}
}
/**
* 提取文本
*/
public String extract(MultipartFile file) throws Exception {
String name = file.getOriginalFilename();
if (name == null ||
(!name.endsWith(".txt") && !name.endsWith(".docx"))) {
throw new IllegalArgumentException("仅支持 .txt 或 .docx");
}
if (file.getSize() > MAX_AUDIO_SIZE) {
throw new IllegalArgumentException("文件超过5MB");
}
String text;
/* 全程流式,不落地磁盘,不一次性读字节数组 */
try (InputStream in = file.getInputStream()) {
if (name.endsWith(".txt")) {
text = readTxt(in);
} else {
text = readDocx(in);
}
}
return text;
}
/* ---------- txt按行读StringBuilder 复用 ---------- */
private String readTxt(InputStream in) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder(4096);
String line;
while ((line = br.readLine()) != null) {
sb.append(line).append('\n');
}
return sb.toString();
}
/* ---------- docxZipInputStream 只扫 document.xml ---------- */
private String readDocx(InputStream in) throws IOException {
ZipInputStream zin = new ZipInputStream(in);
ZipEntry e;
while ((e = zin.getNextEntry()) != null) {
if ("word/document.xml".equals(e.getName())) {
return staxExtract(zin); // 流式读 XML
}
}
return "";
}
/* ---------- StAX 流式提取 <w:t> ---------- */
private String staxExtract(InputStream xml) throws IOException {
XMLStreamReader r = null;
StringBuilder sb = new StringBuilder(4096);
try {
//System.out.println(new String(xml.readAllBytes()));
r = XMLInputFactory.newInstance().createXMLStreamReader(xml);
while (r.hasNext()) {
if (r.next() == XMLStreamConstants.START_ELEMENT &&
"t".equals(r.getLocalName())) {
String elementText = r.getElementText();
sb.append(elementText);
}
}
} catch (XMLStreamException ex) {
throw new IOException(ex);
} finally {
if (r != null) try { r.close(); } catch (XMLStreamException ignore) {}
}
return sb.toString();
}
}