Browse Source

feat(音频处理): 添加音频时长精确计算功能

引入AudioDurationUtil工具类,通过解析音频数据获取真实时长,替代原有的文本估算方法。当解析失败时仍保留文本估算作为备选方案,提高时长计算的准确性。
master
前端-胡立永 1 month ago
parent
commit
c03bea4c6b
2 changed files with 153 additions and 5 deletions
  1. +13
    -5
      jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/service/impl/AppletApiTTServiceImpl.java
  2. +140
    -0
      jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/util/AudioDurationUtil.java

+ 13
- 5
jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/service/impl/AppletApiTTServiceImpl.java View File

@ -18,6 +18,7 @@ import org.jeecg.modules.demo.appletTtsTimbre.entity.AppletTtsTimbre;
import org.jeecg.modules.demo.appletTtsTimbre.service.IAppletTtsTimbreService;
import org.jeecg.modules.demo.appletTtsCache.entity.AppletTtsCache;
import org.jeecg.modules.demo.appletTtsCache.service.IAppletTtsCacheService;
import org.jeecg.modules.applet.util.AudioDurationUtil;
import java.io.ByteArrayInputStream;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
@ -153,11 +154,18 @@ public class AppletApiTTServiceImpl implements AppletApiTTService {
cache.setSuccess("Y");
cache.setCreateTime(new java.util.Date());
// 计算音频时长简单估算实际可以通过音频文件解析获得
if (text != null) {
// 按照平均语速估算时长字符数 / 5
double estimatedDuration = text.length() / 5.0;
cache.setDuration(estimatedDuration);
// 计算音频时长通过音频文件解析获得真实时长
Double realDuration = AudioDurationUtil.calculateDuration(audioData);
if (realDuration != null) {
cache.setDuration(realDuration);
log.info("音频真实时长计算成功: {}秒", realDuration);
} else {
// 如果真实时长计算失败使用文本长度估算作为备选方案
if (text != null) {
double estimatedDuration = text.length() / 5.0;
cache.setDuration(estimatedDuration);
log.warn("音频真实时长计算失败,使用文本长度估算: {}秒", estimatedDuration);
}
}
appletTtsCacheService.save(cache);


+ 140
- 0
jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/util/AudioDurationUtil.java View File

@ -0,0 +1,140 @@
package org.jeecg.modules.applet.util;
import lombok.extern.log4j.Log4j2;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.UnsupportedAudioFileException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
/**
* 音频时长计算工具类
*
* @author jeecg-boot
* @date 2025-01-22
*/
@Log4j2
public class AudioDurationUtil {
/**
* 计算音频数据的时长
*
* @param audioData 音频字节数据
* @return 音频时长如果计算失败返回null
*/
public static Double calculateDuration(byte[] audioData) {
if (audioData == null || audioData.length == 0) {
log.warn("音频数据为空,无法计算时长");
return null;
}
try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(audioData);
AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(byteArrayInputStream)) {
AudioFormat format = audioInputStream.getFormat();
long frames = audioInputStream.getFrameLength();
if (frames == AudioSystem.NOT_SPECIFIED) {
log.warn("无法获取音频帧数,尝试通过数据大小估算");
return calculateDurationByDataSize(audioData, format);
}
double durationInSeconds = (frames + 0.0) / format.getFrameRate();
log.debug("音频时长计算成功: {}秒, 帧数: {}, 帧率: {}", durationInSeconds, frames, format.getFrameRate());
return durationInSeconds;
} catch (UnsupportedAudioFileException e) {
log.warn("不支持的音频格式,尝试通过采样率估算时长: {}", e.getMessage());
return estimateDurationBySampleRate(audioData);
} catch (IOException e) {
log.error("读取音频数据时发生IO异常: {}", e.getMessage(), e);
return null;
} catch (Exception e) {
log.error("计算音频时长时发生未知异常: {}", e.getMessage(), e);
return null;
}
}
/**
* 通过音频数据大小和格式估算时长
*
* @param audioData 音频数据
* @param format 音频格式
* @return 估算的时长
*/
private static Double calculateDurationByDataSize(byte[] audioData, AudioFormat format) {
try {
int frameSize = format.getFrameSize();
float frameRate = format.getFrameRate();
if (frameSize <= 0 || frameRate <= 0) {
log.warn("音频格式信息不完整,frameSize: {}, frameRate: {}", frameSize, frameRate);
return null;
}
long estimatedFrames = audioData.length / frameSize;
double duration = estimatedFrames / frameRate;
log.debug("通过数据大小估算音频时长: {}秒", duration);
return duration;
} catch (Exception e) {
log.error("通过数据大小估算时长失败: {}", e.getMessage(), e);
return null;
}
}
/**
* 通过标准采样率估算时长适用于WAV格式
*
* @param audioData 音频数据
* @return 估算的时长
*/
private static Double estimateDurationBySampleRate(byte[] audioData) {
try {
// WAV文件的标准参数16位深度单声道16000Hz采样率
int bitsPerSample = 16;
int channels = 1;
int sampleRate = 16000;
// 跳过WAV文件头通常44字节
int headerSize = 44;
int audioDataSize = Math.max(0, audioData.length - headerSize);
// 计算样本数
int bytesPerSample = bitsPerSample / 8;
long totalSamples = audioDataSize / (bytesPerSample * channels);
// 计算时长
double duration = (double) totalSamples / sampleRate;
log.debug("通过采样率估算音频时长: {}秒 (数据大小: {}字节)", duration, audioDataSize);
return duration;
} catch (Exception e) {
log.error("通过采样率估算时长失败: {}", e.getMessage(), e);
return null;
}
}
/**
* 格式化时长显示
*
* @param durationSeconds 时长
* @return 格式化的时长字符串 (mm:ss)
*/
public static String formatDuration(Double durationSeconds) {
if (durationSeconds == null || durationSeconds < 0) {
return "00:00";
}
int totalSeconds = (int) Math.round(durationSeconds);
int minutes = totalSeconds / 60;
int seconds = totalSeconds % 60;
return String.format("%02d:%02d", minutes, seconds);
}
}

Loading…
Cancel
Save