|
|
@ -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); |
|
|
|
|
|
} |
|
|
|
|
|
} |