springboot获取wav文件音频长度
1. 首次方案:首次方案使用Java Sound API获取wav录音音频长度,在本地测试是可以成功,获取长度时间较长,当发布到服务器上时,报错:
java.lang.IllegalArgumentException: No line matching interface Clip supporting format PCM_SIGNED unknown sample rate, 16 bit, stereo, 4 bytes/frame, big-endian is supported.
报错的意思是:系统找不到支持PCM_SIGNED、未知采样率、16位、立体声、大端序的音频格式的Clip。
2. 最终方案:
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.concurrent.TimeUnit;public class VoiceUtils {/*** 通过 URL 获取 WAV 音频时长(秒)** @param wavUrl WAV 文件的 URL* @return 音频时长(秒),四舍五入* @throws IOException 如果无法读取或解析文件*/public static long getDurationSecondsFromUrl(String wavUrl) throws IOException {URL url = new URL(wavUrl);URLConnection connection = url.openConnection();// 设置合理的超时时间connection.setConnectTimeout((int) TimeUnit.SECONDS.toMillis(10));connection.setReadTimeout((int) TimeUnit.SECONDS.toMillis(30));// 只请求文件头部(前512字节,确保包含完整的WAV头)connection.setRequestProperty("Range", "bytes=0-511");try (InputStream inputStream = new BufferedInputStream(connection.getInputStream())) {long l = parseWavHeaderToSeconds(inputStream);return l;}}/*** 解析 WAV 头部信息并计算时长(秒)*/private static long parseWavHeaderToSeconds(InputStream inputStream) throws IOException {byte[] header = new byte[512];int bytesRead = inputStream.read(header);if (bytesRead < 44) {throw new IOException("Invalid WAV file: header too short (only " + bytesRead + " bytes read)");}// 验证文件头标识if (!"RIFF".equals(new String(header, 0, 4)) ||!"WAVE".equals(new String(header, 8, 4))) {throw new IOException("Invalid WAV file format");}// 查找"fmt "块int fmtChunkPosition = findChunkPosition(header, "fmt ");if (fmtChunkPosition == -1) {throw new IOException("Invalid WAV format: 'fmt ' chunk not found");}// 解析音频格式 (第20-21字节)int audioFormat = readLittleEndianShort(header, fmtChunkPosition + 8);if (audioFormat != 1) {throw new IOException("Unsupported WAV format: only PCM (format 1) is supported");}// 解析声道数int numChannels = readLittleEndianShort(header, fmtChunkPosition + 10);// 解析采样率int sampleRate = readLittleEndianInt(header, fmtChunkPosition + 12);// 解析位深度int bitsPerSample = readLittleEndianShort(header, fmtChunkPosition + 22);// 查找"data"块int dataChunkPosition = findChunkPosition(header, "data");if (dataChunkPosition == -1) {throw new IOException("Could not find 'data' chunk in WAV header");}// 解析数据大小int dataSize = readLittleEndianInt(header, dataChunkPosition + 4);// 计算时长 (秒) = 数据大小 / (采样率 * 声道数 * (位深度/8))double durationSeconds = (double) dataSize / (sampleRate * numChannels * (bitsPerSample / 8.0));// 四舍五入到最接近的秒数long round = Math.round(durationSeconds);return round;}/*** 在 WAV 头部中查找指定的块*/private static int findChunkPosition(byte[] header, String chunkId) {int position = 12; // 跳过RIFF头while (position < header.length - 8) {String currentChunk = new String(header, position, 4);if (chunkId.equals(currentChunk)) {return position;}// 移动到下一个块int chunkSize = readLittleEndianInt(header, position + 4);position += 8 + chunkSize;// 确保对齐到偶数边界if (chunkSize % 2 != 0) position++;}return -1;}/*** 读取小端序的 short 值*/private static int readLittleEndianShort(byte[] data, int offset) {return (data[offset] & 0xFF) | ((data[offset + 1] & 0xFF) << 8);}/*** 读取小端序的 int 值*/private static int readLittleEndianInt(byte[] data, int offset) {return (data[offset] & 0xFF) |((data[offset + 1] & 0xFF) << 8) |((data[offset + 2] & 0xFF) << 16) |((data[offset + 3] & 0xFF) << 24);}
}