临时提交

This commit is contained in:
dengwenjie
2025-08-29 10:30:35 +08:00
parent 8bf620a330
commit 86ea7eb03e
364 changed files with 8572 additions and 540 deletions

188
speech/pom.xml Normal file
View File

@@ -0,0 +1,188 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.smartjavaai</groupId>
<artifactId>smartjavaai-parent</artifactId>
<version>1.0.24</version>
</parent>
<artifactId>speech</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>cn.smartjavaai</groupId>
<artifactId>common</artifactId>
<version>${project.version}</version>
</dependency>
<!-- JNA dependency -->
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.13.0</version>
</dependency>
<dependency>
<groupId>io.github.givimad</groupId>
<artifactId>whisper-jni</artifactId>
<version>1.7.1</version>
</dependency>
<dependency>
<groupId>com.alphacephei</groupId>
<artifactId>vosk</artifactId>
<version>0.3.45</version>
</dependency>
<dependency>
<groupId>ws.schild</groupId>
<artifactId>jave-core</artifactId>
<version>3.5.0</version>
</dependency>
</dependencies>
<version>1.0.24</version>
<name>speech</name>
<description>SmartJavaAI</description>
<url>https://github.com/geekwenjie/SmartJavaAI</url>
<licenses>
<license>
<name>MIT License</name>
<url>https://opensource.org/licenses/MIT</url>
</license>
</licenses>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.sonatype.central</groupId>
<artifactId>central-publishing-maven-plugin</artifactId>
<version>0.4.0</version>
<extensions>true</extensions>
<configuration>
<publishingServerId>dengwenjie</publishingServerId>
<tokenAuth>true</tokenAuth>
<deploymentName>${project.groupId}:${project.artifactId}:${project.version}</deploymentName>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<!-- <javadocExecutable>${java.home}/bin/javadoc</javadocExecutable>-->
<doclint>none</doclint>
<additionalJOptions>
<additionalJOption>-Xdoclint:none</additionalJOption>
</additionalJOptions>
</configuration>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-toolchains-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<goals>
<goal>toolchain</goal>
</goals>
<configuration>
<toolchains>
<jdk>
<version>11</version>
</jdk>
</toolchains>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<!-- 必须添加SCM信息 -->
<scm>
<connection>scm:git:git://github.com/geekwenjie/SmartJavaAI.git</connection>
<developerConnection>scm:git:ssh://github.com/geekwenjie/SmartJavaAI.git</developerConnection>
<url>http://github.com/geekwenjie/SmartJavaAI/tree/master</url>
</scm>
<distributionManagement>
<snapshotRepository>
<id>dengwenjie</id>
<url>https://s01.oss.sonatype.org/content/repositories/snapshots</url>
</snapshotRepository>
<repository>
<id>dengwenjie</id>
<url>https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/</url>
</repository>
</distributionManagement>
<developers>
<developer>
<name>dengwenjie</name>
<email>775747758@qq.com</email>
<roles>
<role>Project Manager</role>
<role>Architect</role>
</roles>
</developer>
</developers>
</project>

View File

@@ -0,0 +1,176 @@
package cn.smartjavaai.speech.asr.audio;
import ai.djl.modality.audio.Audio;
import ai.djl.modality.audio.AudioFactory;
import cn.smartjavaai.speech.asr.exception.AsrException;
import cn.smartjavaai.speech.utils.AudioUtils;
import lombok.extern.slf4j.Slf4j;
import ws.schild.jave.EncoderException;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.ShortBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.UnsupportedAudioFileException;
/**
* @author dwj
* @date 2025/8/1
*/
@Slf4j
public class SmartAudioFactory {
private static final SmartAudioFactory INSTANCE = new SmartAudioFactory();
private SmartAudioFactory() {
}
public static SmartAudioFactory getInstance() {
return INSTANCE;
}
public Audio fromFile(Path path, AudioFormat targetFormat) throws IOException {
try (AudioInputStream ais = AudioSystem.getAudioInputStream(path.toFile())) {
if(targetFormat != null){
try (AudioInputStream convertedAis = AudioSystem.getAudioInputStream(targetFormat, ais);) {
byte[] bytes = read(convertedAis);
float[] floats = bytesToFloats(bytes, targetFormat.isBigEndian());
return new Audio(floats, targetFormat.getSampleRate(), targetFormat.getChannels());
}
}else{
AudioFormat format = ais.getFormat();
byte[] bytes = read(ais);
float[] floats = bytesToFloats(bytes, format.isBigEndian());
return new Audio(floats, format.getSampleRate(), format.getChannels());
}
} catch (UnsupportedAudioFileException e) {
log.debug("Unsupported Audio file, Conversion to WAV is required");
byte[] allBytes = Files.readAllBytes(path);
try(AudioInputStream audioInputStream = convertWav(allBytes)){
return fromAudioInputStream(audioInputStream, targetFormat);
}
}
}
public Audio fromAudioInputStream(AudioInputStream ais, AudioFormat targetFormat) throws IOException {
try (AudioInputStream audioInputStream = ais) {
if(targetFormat != null){
try (AudioInputStream convertedAis = AudioSystem.getAudioInputStream(targetFormat, audioInputStream);) {
byte[] bytes = read(convertedAis);
float[] floats = bytesToFloats(bytes, targetFormat.isBigEndian());
return new Audio(floats, targetFormat.getSampleRate(), targetFormat.getChannels());
}
}else{
AudioFormat format = audioInputStream.getFormat();
byte[] bytes = read(audioInputStream);
float[] floats = bytesToFloats(bytes, format.isBigEndian());
return new Audio(floats, format.getSampleRate(), format.getChannels());
}
}
}
public AudioInputStream convertWav(byte[] allBytes) {
InputStream conversionStream = new BufferedInputStream(new ByteArrayInputStream(allBytes));
File tempFile = null;
try {
tempFile = AudioUtils.audioFormatConversion(conversionStream, "wav");
InputStream fis = new BufferedInputStream(new FileInputStream(tempFile));
AudioInputStream ais = AudioSystem.getAudioInputStream(fis);
return ais;
} catch (EncoderException | IOException | UnsupportedAudioFileException e) {
throw new AsrException(e);
} finally {
if(tempFile != null && tempFile.exists()){
tempFile.delete();
}
if(conversionStream != null){
try {
conversionStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
public Audio fromInputStream(InputStream is, AudioFormat targetFormat) throws IOException {
byte[] allBytes = is.readAllBytes();
try (BufferedInputStream tryStream = new BufferedInputStream(new ByteArrayInputStream(allBytes));
AudioInputStream ais = AudioSystem.getAudioInputStream(tryStream)) {
if(targetFormat != null){
try (AudioInputStream convertedAis = AudioSystem.getAudioInputStream(targetFormat, ais);) {
byte[] bytes = read(convertedAis);
float[] floats = bytesToFloats(bytes, targetFormat.isBigEndian());
return new Audio(floats, targetFormat.getSampleRate(), targetFormat.getChannels());
}
}else{
AudioFormat format = ais.getFormat();
byte[] bytes = read(ais);
float[] floats = bytesToFloats(bytes, format.isBigEndian());
return new Audio(floats, format.getSampleRate(), format.getChannels());
}
} catch (UnsupportedAudioFileException e) {
log.debug("Unsupported Audio file, Conversion to WAV is required");
try(AudioInputStream audioInputStream = convertWav(allBytes)){
return fromAudioInputStream(audioInputStream, targetFormat);
}
}
}
private byte[] read(AudioInputStream ais) throws IOException {
AudioFormat format = ais.getFormat();
int frameSize = format.getFrameSize();
// Some audio formats may have unspecified frame size
if (frameSize == AudioSystem.NOT_SPECIFIED) {
frameSize = 1;
}
int size = (int) ais.getFrameLength() * frameSize;
if (ais.getFrameLength() == AudioSystem.NOT_SPECIFIED || size <= 0){
// unknown length, use ByteArrayOutputStream to read all data
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buf = new byte[4096];
int read;
while ((read = ais.read(buf)) != -1) {
baos.write(buf, 0, read);
}
return baos.toByteArray();
}else{
byte[] ret = new byte[size];
byte[] buf = new byte[1024];
int offset = 0;
int read;
while ((read = ais.read(buf)) != -1) {
System.arraycopy(buf, 0, ret, offset, read);
offset += read;
}
return ret;
}
}
private float[] bytesToFloats(byte[] bytes, boolean isBigEndian) {
ByteOrder order = isBigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN;
ShortBuffer buffer = ByteBuffer.wrap(bytes).order(order).asShortBuffer();
short[] shorts = new short[buffer.capacity()];
buffer.get(shorts);
// Feed in float values between -1.0f and 1.0f.
float[] floats = new float[shorts.length];
for (int i = 0; i < shorts.length; i++) {
floats[i] = ((float) shorts[i]) / (float) Short.MAX_VALUE;
}
return floats;
}
}

View File

@@ -0,0 +1,25 @@
package cn.smartjavaai.speech.asr.config;
import cn.smartjavaai.common.config.ModelConfig;
import cn.smartjavaai.speech.asr.enums.AsrModelEnum;
import lombok.Data;
import java.nio.file.Path;
/**
* Asr模型配置
* @author dwj
* @date 2025/7/31
*/
@Data
public class AsrModelConfig extends ModelConfig {
private AsrModelEnum modelEnum;
private String modelPath;
/**
* 依赖库目录
*/
private Path libPath;
}

View File

@@ -0,0 +1,30 @@
package cn.smartjavaai.speech.asr.entity;
import lombok.Data;
import java.util.List;
/**
* 语音识别结果
* @author dwj
*/
@Data
public class AsrResult {
private String text;
private List<AsrSegment> segments;
public AsrResult() {
}
public AsrResult(String text) {
this.text = text;
}
public AsrResult(String text, List<AsrSegment> segments) {
this.text = text;
this.segments = segments;
}
}

View File

@@ -0,0 +1,27 @@
package cn.smartjavaai.speech.asr.entity;
import lombok.Data;
/**
* @author dwj
*/
@Data
public class AsrSegment {
private String text;
private long startTime;
private long endTime;
public AsrSegment(String text, long startTime, long endTime) {
this.text = text;
this.startTime = startTime;
this.endTime = endTime;
}
public AsrSegment(String text) {
this.text = text;
}
public AsrSegment() {
}
}

View File

@@ -0,0 +1,27 @@
package cn.smartjavaai.speech.asr.entity;
import cn.smartjavaai.common.entity.Language;
import lombok.Data;
/**
* 语音识别参数
* @author dwj
*/
@Data
public abstract class RecParams {
/**
* 语言
*/
private Language language = Language.ZH;
public Language getLanguage() {
return language;
}
public void setLanguage(Language language) {
this.language = language;
}
}

View File

@@ -0,0 +1,20 @@
package cn.smartjavaai.speech.asr.entity;
import cn.smartjavaai.common.entity.Language;
import io.github.givimad.whisperjni.WhisperFullParams;
import io.github.givimad.whisperjni.WhisperSamplingStrategy;
import lombok.Data;
/**
* @author dwj
*/
@Data
public class VoskParams extends RecParams{
/**
* 限定词汇表
*/
private String grammar;
}

View File

@@ -0,0 +1,32 @@
package cn.smartjavaai.speech.asr.entity;
import cn.smartjavaai.common.entity.Language;
import io.github.givimad.whisperjni.WhisperFullParams;
import io.github.givimad.whisperjni.WhisperSamplingStrategy;
import lombok.Data;
/**
* @author dwj
*/
@Data
public class WhisperParams extends RecParams{
private WhisperFullParams params;
public WhisperParams(WhisperFullParams params) {
this.params = params;
}
public WhisperParams() {
this.params = new WhisperFullParams(WhisperSamplingStrategy.BEAN_SEARCH);
this.params.language = Language.ZH.getCode();
}
@Override
public void setLanguage(Language language) {
super.setLanguage(language);
if (language != null) {
this.params.language = language.getCode(); // 关键:设置给底层 whisper 参数
}
}
}

View File

@@ -0,0 +1,26 @@
package cn.smartjavaai.speech.asr.enums;
/**
* 语音识别模型枚举
* @author dwj
*/
public enum AsrModelEnum {
WHISPER,
VOSK;
/**
* 根据名称获取枚举 (忽略大小写和下划线变体)
*/
public static AsrModelEnum fromName(String name) {
String formatted = name.trim().toUpperCase().replaceAll("[-_]", "");
for (AsrModelEnum model : values()) {
if (model.name().replaceAll("_", "").equals(formatted)) {
return model;
}
}
throw new IllegalArgumentException("未知模型名称: " + name);
}
}

View File

@@ -0,0 +1,29 @@
package cn.smartjavaai.speech.asr.exception;
/**
* 语音识别异常
* @author dwj
*/
public class AsrException extends RuntimeException{
public AsrException() {
super();
}
public AsrException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public AsrException(String message, Throwable cause) {
super(message, cause);
}
public AsrException(String message) {
super(message);
}
public AsrException(Throwable cause) {
super(cause);
}
}

View File

@@ -0,0 +1,103 @@
package cn.smartjavaai.speech.asr.factory;
import cn.smartjavaai.common.config.Config;
import cn.smartjavaai.speech.asr.config.AsrModelConfig;
import cn.smartjavaai.speech.asr.enums.AsrModelEnum;
import cn.smartjavaai.speech.asr.exception.AsrException;
import cn.smartjavaai.speech.asr.model.SpeechRecognizer;
import cn.smartjavaai.speech.asr.model.VoskRecognizer;
import cn.smartjavaai.speech.asr.model.WhisperRecognizer;
import lombok.extern.slf4j.Slf4j;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
/**
* 语音识别模型工厂
* @author dwj
*/
@Slf4j
public class SpeechRecognizerFactory {
// 使用 volatile 和双重检查锁定来确保线程安全的单例模式
private static volatile SpeechRecognizerFactory instance;
/**
* 模型缓存
*/
private static final ConcurrentHashMap<AsrModelEnum, SpeechRecognizer> modelMap = new ConcurrentHashMap<>();
/**
* 模型注册表
*/
private static final Map<AsrModelEnum, Class<? extends SpeechRecognizer>> registry =
new ConcurrentHashMap<>();
public static SpeechRecognizerFactory getInstance() {
if (instance == null) {
synchronized (SpeechRecognizerFactory.class) {
if (instance == null) {
instance = new SpeechRecognizerFactory();
}
}
}
return instance;
}
/**
* 注册模型
* @param expressionModelEnum
* @param clazz
*/
private static void registerModel(AsrModelEnum expressionModelEnum, Class<? extends SpeechRecognizer> clazz) {
registry.put(expressionModelEnum, clazz);
}
/**
* 获取模型(通过配置)
* @param config
* @return
*/
public SpeechRecognizer getModel(AsrModelConfig config) {
if(Objects.isNull(config) || Objects.isNull(config.getModelEnum())){
throw new AsrException("未配置语音识别模型枚举");
}
return modelMap.computeIfAbsent(config.getModelEnum(), k -> {
return createFaceModel(config);
});
}
/**
* 使用ModelConfig创建模型
* @param config
* @return
*/
private SpeechRecognizer createFaceModel(AsrModelConfig config) {
Class<?> clazz = registry.get(config.getModelEnum());
if(clazz == null){
throw new AsrException("Unsupported model");
}
SpeechRecognizer model = null;
try {
model = (SpeechRecognizer) clazz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new AsrException(e);
}
model.loadModel(config);
return model;
}
// 初始化默认算法
static {
registerModel(AsrModelEnum.WHISPER, WhisperRecognizer.class);
registerModel(AsrModelEnum.VOSK, VoskRecognizer.class);
log.debug("缓存目录:{}", Config.getCachePath());
}
}

View File

@@ -0,0 +1,48 @@
package cn.smartjavaai.speech.asr.model;
import cn.smartjavaai.common.entity.R;
import cn.smartjavaai.speech.asr.config.AsrModelConfig;
import cn.smartjavaai.speech.asr.entity.AsrResult;
import cn.smartjavaai.speech.asr.entity.RecParams;
import cn.smartjavaai.speech.asr.exception.AsrException;
import java.io.InputStream;
/**
* 语音识别
* @author dwj
*/
public interface SpeechRecognizer extends AutoCloseable{
/**
* 加载模型
* @param config
*/
void loadModel(AsrModelConfig config); // 加载模型
default R<AsrResult> recognize(String audioPath, RecParams params){
throw new UnsupportedOperationException("默认不支持该功能");
}
default R<AsrResult> recognize(byte[] audioData, RecParams params){
throw new UnsupportedOperationException("默认不支持该功能");
}
default R<AsrResult> recognize(InputStream audioStream, RecParams params){
throw new UnsupportedOperationException("默认不支持该功能");
}
default R<AsrResult> recognize(String audioPath){
throw new UnsupportedOperationException("默认不支持该功能");
}
default R<AsrResult> recognize(byte[] audioData){
throw new UnsupportedOperationException("默认不支持该功能");
}
default R<AsrResult> recognize(InputStream audioStream){
throw new UnsupportedOperationException("默认不支持该功能");
}
}

View File

@@ -0,0 +1,342 @@
package cn.smartjavaai.speech.asr.model;
import ai.djl.modality.audio.Audio;
import ai.djl.modality.audio.AudioFactory;
import ai.djl.util.JsonUtils;
import cn.smartjavaai.common.entity.Language;
import cn.smartjavaai.common.entity.R;
import cn.smartjavaai.speech.asr.audio.SmartAudioFactory;
import cn.smartjavaai.speech.asr.config.AsrModelConfig;
import cn.smartjavaai.speech.asr.entity.*;
import cn.smartjavaai.speech.asr.exception.AsrException;
import cn.smartjavaai.speech.asr.pool.WhisperStatePool;
import cn.smartjavaai.speech.utils.AudioUtils;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.vosk.LibVosk;
import org.vosk.LogLevel;
import org.vosk.Model;
import org.vosk.Recognizer;
import ws.schild.jave.Encoder;
import ws.schild.jave.EncoderException;
import ws.schild.jave.info.MultimediaInfo;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.UnsupportedAudioFileException;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.ShortBuffer;
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.Objects;
import java.util.stream.Collectors;
import static ai.djl.util.JsonUtils.GSON;
/**
* Vosk 识别器
* @author dwj
*/
@Slf4j
public class VoskRecognizer implements SpeechRecognizer{
private Model model;
@Override
public void loadModel(AsrModelConfig config) {
if(StringUtils.isBlank(config.getModelPath())){
throw new AsrException("modelPath is null");
}
Path testModelPath = Paths.get(config.getModelPath());
if(!testModelPath.toFile().exists()){
throw new AsrException("Missing model file: " + testModelPath.toAbsolutePath());
}
try {
//加载自定义依赖库
if(Objects.nonNull(config.getLibPath())){
System.load(config.getLibPath().toAbsolutePath().toString());
}
model = new Model(config.getModelPath());
LibVosk.setLogLevel(LogLevel.DEBUG);
log.debug("Vosk init success");
} catch (IOException e) {
throw new AsrException(e);
}
}
@Override
public R<AsrResult> recognize(String audioPath) {
return recognize(audioPath, new VoskParams());
}
@Override
public R<AsrResult> recognize(byte[] audioData) {
return recognize(audioData, new VoskParams());
}
@Override
public R<AsrResult> recognize(InputStream audioStream) {
return recognize(audioStream, new VoskParams());
}
private R<AsrResult> recognizeAudioStream(AudioInputStream ais, RecParams params) {
try (Recognizer recognizer = buildRecognizer(params, ais.getFormat().getSampleRate())){
AudioFormat audioFormat = ais.getFormat();
log.debug("sampleRate:{}", audioFormat.getSampleRate());
log.debug("channels:{}", audioFormat.getChannels());
int nbytes;
byte[] b = new byte[4096];
List<AsrSegment> segments = new ArrayList<AsrSegment>();
StringBuilder text = new StringBuilder();
String temp = "";
while ((nbytes = ais.read(b)) >= 0) {
if (recognizer.acceptWaveForm(b, nbytes)) {
String result = recognizer.getResult();
// log.info("result:{}", result);
AsrSegment segment = parseSegment(result, params);
if(segment != null){
segments.add(segment);
text.append(segment.getText());
}
}else{
temp = recognizer.getPartialResult();
// log.info("temp:{}", temp);
}
}
if(StringUtils.isNotBlank(temp)){
AsrSegment segment = parsePartialSegment(temp, params);
if(segment != null){
segments.add(segment);
text.append(segment.getText());
}
}
//补全结果
String finalText = recognizer.getFinalResult();
if(StringUtils.isNotBlank(text.toString()) && StringUtils.isNotBlank(finalText)){
AsrSegment finalSegment = parseSegment(finalText, params);
if(finalSegment != null){
//需要补全
if(!text.toString().endsWith(finalSegment.getText())){
AsrSegment alignSegment = VoskRecognizer.alignSegment(segments.get(segments.size() - 1), finalSegment);
//如果匹配失败,则直接使用最终片段
if(alignSegment != null){
segments.set(segments.size() - 1,alignSegment);
}else{
segments.set(segments.size() - 1,finalSegment);
}
}
}
}
String result = segments.stream()
.map(AsrSegment::getText)
.collect(Collectors.joining("\n"));
return R.ok(new AsrResult(result, segments));
} catch (IOException e) {
throw new AsrException(e);
}
}
/**
* 解析结果
* @param segment
* @return
*/
private AsrSegment parseSegment(String segment, RecParams params) {
JsonObject json = GSON.fromJson(segment, JsonObject.class);
JsonArray resultArray = json.getAsJsonArray("result");
if(Objects.isNull(resultArray) || resultArray.size() == 0){
return null;
}
double segmentStart = resultArray.get(0).getAsJsonObject().get("start").getAsDouble();
double segmentEnd = resultArray.get(resultArray.size() - 1).getAsJsonObject().get("end").getAsDouble();
long startMs = Math.round(segmentStart * 1000);
long endMs = Math.round(segmentEnd * 1000);
String text = json.get("text").getAsString();
if(Objects.nonNull(params.getLanguage()) && params.getLanguage() == Language.ZH){
text = text.replace(" ", "");
}
return new AsrSegment(text, startMs, endMs);
}
/**
* 解析结果
* @param segment
* @return
*/
private AsrSegment parsePartialSegment(String segment, RecParams params) {
JsonObject json = GSON.fromJson(segment, JsonObject.class);
JsonArray resultArray = json.getAsJsonArray("partial_result");
if(Objects.isNull(resultArray) || resultArray.size() == 0){
return null;
}
double segmentStart = resultArray.get(0).getAsJsonObject().get("start").getAsDouble();
double segmentEnd = resultArray.get(resultArray.size() - 1).getAsJsonObject().get("end").getAsDouble();
long startMs = Math.round(segmentStart * 1000);
long endMs = Math.round(segmentEnd * 1000);
String text = json.get("partial").getAsString();
if(Objects.nonNull(params.getLanguage()) && params.getLanguage() == Language.ZH){
text = text.replace(" ", "");
}
return new AsrSegment(text, startMs, endMs);
}
/**
* 补全
* @param shortSeg
* @param longSeg
* @return
*/
public static AsrSegment alignSegment(AsrSegment shortSeg, AsrSegment longSeg) {
String shortText = shortSeg.getText();
String longText = longSeg.getText();
int index = longText.indexOf(shortText);
if (index == -1) {
// log.debug("短文本不在长文本中");
return null;
}
String resultText = longText.substring(index);
return new AsrSegment(resultText, shortSeg.getStartTime(), longSeg.getEndTime());
}
/**
* 创建识别器
* @param params
* @param sampleRate
* @return
* @throws IOException
*/
private Recognizer buildRecognizer(RecParams params,float sampleRate) throws IOException {
if(!(params instanceof VoskParams)){
throw new AsrException("params is not VoskParams");
}
VoskParams voskParams = (VoskParams) params;
Recognizer recognizer = new Recognizer(model, sampleRate);
if(StringUtils.isNotBlank(voskParams.getGrammar())){
recognizer.setGrammar(voskParams.getGrammar());
}
// if(voskParams.getMaxAlternatives() > 0){
// recognizer.setMaxAlternatives(voskParams.getMaxAlternatives());
// }
//暂时只支持返回一个结果
// recognizer.setMaxAlternatives(1);
recognizer.setWords(true);
recognizer.setPartialWords(true);
return recognizer;
}
@Override
public R<AsrResult> recognize(String audioPath, RecParams params) {
Path audioFilePath = Paths.get(audioPath);
if(!audioFilePath.toFile().exists()){
return R.fail(R.Status.FILE_NOT_FOUND);
}
try (InputStream is = Files.newInputStream(audioFilePath)){
return recognize(is, params);
} catch (IOException e) {
throw new AsrException(e);
}
}
@Override
public R<AsrResult> recognize(byte[] audioData, RecParams params) {
try (InputStream is = new ByteArrayInputStream(audioData)) {
return recognize(is, params);
} catch (IOException e) {
throw new AsrException(e);
}
}
@Override
public R<AsrResult> recognize(InputStream audioStream, RecParams params) {
AudioInputStream ais = null;
InputStream tryStream = null;
InputStream conversionStream = null;
boolean needConversion = false;
try {
// 缓存全部字节数据
byte[] allBytes = audioStream.readAllBytes();
// 创建两个独立流
tryStream = new BufferedInputStream(new ByteArrayInputStream(allBytes));
conversionStream = new BufferedInputStream(new ByteArrayInputStream(allBytes));
ais = AudioSystem.getAudioInputStream(tryStream);
return recognizeAudioStream(ais, params);
} catch (UnsupportedAudioFileException e) {
log.debug("Unsupported Audio file, Conversion to WAV is required");
needConversion = true;
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if(tryStream != null){
try {
tryStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
//转换wav格式
File tempFile = null;
if(needConversion){
try {
tempFile = AudioUtils.audioFormatConversion(conversionStream, "wav");
// log.info("tempFile:{}", tempFile.getAbsolutePath());
} catch (EncoderException | IOException e) {
throw new AsrException(e);
}
try (InputStream fis = new BufferedInputStream(new FileInputStream(tempFile))){
ais = AudioSystem.getAudioInputStream(fis);
return recognizeAudioStream(ais, params);
} catch (IOException | UnsupportedAudioFileException e) {
throw new AsrException("音频转换异常", e);
} finally {
if(tempFile != null && tempFile.exists()){
tempFile.delete();
}
if(conversionStream != null){
try {
conversionStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
return R.fail(R.Status.Unknown);
}
@Override
public void close() throws Exception {
if(model != null){
model.close();
}
}
/**
* 创建高级识别器
* @param sampleRate 采样率
* @return
*/
public Recognizer createAdvancedRecognizer(float sampleRate){
try {
return new Recognizer(model, sampleRate);
} catch (IOException e) {
throw new AsrException(e);
}
}
}

View File

@@ -0,0 +1,219 @@
package cn.smartjavaai.speech.asr.model;
import ai.djl.modality.audio.Audio;
import ai.djl.modality.audio.AudioFactory;
import cn.smartjavaai.common.entity.Language;
import cn.smartjavaai.common.entity.R;
import cn.smartjavaai.common.enums.DeviceEnum;
import cn.smartjavaai.speech.asr.audio.SmartAudioFactory;
import cn.smartjavaai.speech.asr.config.AsrModelConfig;
import cn.smartjavaai.speech.asr.entity.AsrResult;
import cn.smartjavaai.speech.asr.entity.AsrSegment;
import cn.smartjavaai.speech.asr.entity.RecParams;
import cn.smartjavaai.speech.asr.entity.WhisperParams;
import cn.smartjavaai.speech.asr.exception.AsrException;
import cn.smartjavaai.speech.asr.pool.WhisperStatePool;
import io.github.givimad.whisperjni.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.vosk.Model;
import org.vosk.Recognizer;
import javax.sound.sampled.AudioFormat;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* @author dwj
*/
@Slf4j
public class WhisperRecognizer implements SpeechRecognizer{
private WhisperJNI whisper;
private WhisperContext ctx;
private WhisperStatePool statePool;
@Override
public void loadModel(AsrModelConfig config) {
if(StringUtils.isBlank(config.getModelPath())){
throw new AsrException("modelPath is null");
}
Path testModelPath = Paths.get(config.getModelPath());
if(!testModelPath.toFile().exists()){
throw new AsrException("Missing model file: " + testModelPath.toAbsolutePath());
}
try {
//加载自定义依赖库
if(Objects.nonNull(config.getLibPath())){
System.setProperty("io.github.givimad.whisperjni.libdir",config.getLibPath().toAbsolutePath().toString());
}
WhisperJNI.loadLibrary();
WhisperJNI.setLibraryLogger(null);
whisper = new WhisperJNI();
WhisperJNI.setLibraryLogger(new WhisperJNI.LibraryLogger() {
@Override
public void log(String s) {
log.debug("WhisperJNI {}", s);
}
});
ctx = whisper.initNoState(testModelPath);
Boolean initOpenVINO = config.getCustomParam("initOpenVINO", Boolean.class);
if(Objects.nonNull(initOpenVINO) && initOpenVINO){
String device = Objects.isNull(config.getDevice()) ? DeviceEnum.CPU.name() : config.getDevice().name();
whisper.initOpenVINO(ctx, device);
log.debug("WhisperJNI initOpenVINO success");
}
statePool = new WhisperStatePool(whisper, ctx);
log.debug("WhisperJNI init success");
} catch (IOException e) {
throw new AsrException(e);
}
}
@Override
public R<AsrResult> recognize(String audioPath, RecParams params) {
Path audioFilePath = Paths.get(audioPath);
if(!audioFilePath.toFile().exists()){
return R.fail(R.Status.FILE_NOT_FOUND);
}
Audio audio = null;
try {
audio = SmartAudioFactory.getInstance().fromFile(audioFilePath, getDefaultAudioFormat());
} catch (IOException e) {
throw new AsrException("读取音频异常",e);
}
return recognize(audio, params);
}
@Override
public R<AsrResult> recognize(byte[] audioData, RecParams params) {
try (InputStream is = new ByteArrayInputStream(audioData)) {
return recognize(is, params);
} catch (IOException e) {
throw new AsrException(e);
}
}
@Override
public R<AsrResult> recognize(InputStream audioStream, RecParams params) {
Audio audio = null;
try {
audio = SmartAudioFactory.getInstance().fromInputStream(audioStream, getDefaultAudioFormat());
} catch (IOException e) {
throw new AsrException("读取音频异常",e);
}
return recognize(audio, params);
}
public R<AsrResult> recognize(Audio audio, RecParams params) {
WhisperState state = null;
StringBuilder text = new StringBuilder();
try {
WhisperParams whisperParams = (WhisperParams) params;
if(Objects.isNull(whisperParams.getParams().language)){
return R.fail(1003, "请指定语言");
}
//不是英语,需要检查是否是多语言模型
if(!Language.EN.getCode().equals(whisperParams.getParams().language)){
if(!whisper.isMultilingual(ctx)){
return R.fail(1002, "当前为非多语种模型,仅支持英语识别,暂不支持其他语言,请更换多语种模型");
}
}
state = statePool.borrowObject();
int result = whisper.fullWithState(ctx, state, whisperParams.getParams(), audio.getData(), audio.getData().length);
if(result != 0) {
return R.fail(1000, "Transcription failed with code " + result);
}
int numSegments = whisper.fullNSegmentsFromState(state);
if(numSegments <= 0){
return R.fail(1001, "未识别出有效语音");
}
List<AsrSegment> segments = new ArrayList<AsrSegment>(numSegments);
for(int i = 0; i < numSegments; i++){
long startTime = whisper.fullGetSegmentTimestamp0FromState(state,i);
long endTime = whisper.fullGetSegmentTimestamp1FromState(state,i);
String content = whisper.fullGetSegmentTextFromState(state, i);
segments.add(new AsrSegment(content, startTime * 10, endTime * 10));
text.append(content).append("\n");
}
return R.ok(new AsrResult(text.toString(), segments));
} catch (Exception e) {
throw new AsrException(e);
} finally {
if(state != null){
try {
statePool.returnObject(state);
} catch (Exception e) {
log.warn("returnObject失败", e);
}
}
}
}
@Override
public R<AsrResult> recognize(String audioPath) {
return recognize(audioPath, new WhisperParams());
}
@Override
public R<AsrResult> recognize(byte[] audioData) {
return recognize(audioData, new WhisperParams());
}
@Override
public R<AsrResult> recognize(InputStream audioStream) {
return recognize(audioStream, new WhisperParams());
}
public WhisperGrammar parseGrammar(String grammarText) {
try {
return whisper.parseGrammar(grammarText);
} catch (IOException e) {
throw new AsrException(e);
}
}
@Override
public void close() throws Exception {
if(statePool != null){
statePool.close();
}
if(ctx != null){
ctx.close();
}
}
/**
* 获取一个WhisperState对象
* @return
*/
public WhisperState getWhisperState(){
try {
return statePool.borrowObject();
} catch (Exception e) {
throw new AsrException(e);
}
}
private AudioFormat getDefaultAudioFormat(){
return new AudioFormat(
AudioFormat.Encoding.PCM_SIGNED,
16000,
16,
1,
2,
16000,
false
);
}
}

View File

@@ -0,0 +1,47 @@
package cn.smartjavaai.speech.asr.pool;
import io.github.givimad.whisperjni.WhisperContext;
import io.github.givimad.whisperjni.WhisperJNI;
import io.github.givimad.whisperjni.WhisperState;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.PooledObjectFactory;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.apache.commons.pool2.impl.GenericObjectPool;
/**
* WhisperState 实例对象池
* @author dwj
*/
public class WhisperStatePool extends GenericObjectPool<WhisperState> {
public WhisperStatePool(WhisperJNI whisperInstance, WhisperContext context) {
super(new PooledObjectFactory<WhisperState>(){
@Override
public void activateObject(PooledObject<WhisperState> pooledObject) throws Exception {
}
@Override
public void destroyObject(PooledObject<WhisperState> pooledObject) throws Exception {
pooledObject.getObject().close();
}
@Override
public PooledObject<WhisperState> makeObject() throws Exception {
WhisperState state = whisperInstance.initState(context);
return new DefaultPooledObject(state);
}
@Override
public void passivateObject(PooledObject<WhisperState> pooledObject) throws Exception {
}
@Override
public boolean validateObject(PooledObject<WhisperState> pooledObject) {
return false;
}
});
}
}

View File

@@ -0,0 +1,212 @@
package cn.smartjavaai.speech.utils;
import cn.hutool.core.lang.UUID;
import cn.smartjavaai.speech.asr.exception.AsrException;
import ws.schild.jave.Encoder;
import ws.schild.jave.EncoderException;
import ws.schild.jave.MultimediaObject;
import ws.schild.jave.encode.AudioAttributes;
import ws.schild.jave.encode.EncodingAttributes;
import ws.schild.jave.info.MultimediaInfo;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import java.io.*;
import java.nio.file.Files;
/**
* 音频工具类
* @author dwj
*/
public class AudioUtils {
public static byte[] read(AudioInputStream ais) throws IOException {
AudioFormat format = ais.getFormat();
int frameSize = format.getFrameSize();
// Some audio formats may have unspecified frame size
if (frameSize == AudioSystem.NOT_SPECIFIED) {
frameSize = 1;
}
int size = (int) ais.getFrameLength() * frameSize;
if (ais.getFrameLength() == AudioSystem.NOT_SPECIFIED || size <= 0){
// unknown length, use ByteArrayOutputStream to read all data
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buf = new byte[4096];
int read;
while ((read = ais.read(buf)) != -1) {
baos.write(buf, 0, read);
}
return baos.toByteArray();
}else{
byte[] ret = new byte[size];
byte[] buf = new byte[1024];
int offset = 0;
int read;
while ((read = ais.read(buf)) != -1) {
System.arraycopy(buf, 0, ret, offset, read);
offset += read;
}
return ret;
}
}
/**
* 音频格式转换
*
* @param sourceFilePath
* @param targetFilePath
* @param format wav/mp3/amr
* @return
*/
public static byte[] getAudioFormatConversionBytes(String sourceFilePath,String targetFilePath,String format) {
InputStream fis = null;
ByteArrayOutputStream bos = null;
byte[] bytes = null;
try {
File sourceFile = new File(sourceFilePath);
if (sourceFile.isFile()) {
File targetFile = new File(targetFilePath);
// 音频格式转换
audioFormatConversion(sourceFile, targetFile, format);
fis = new FileInputStream(targetFile);
bos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
bos.write(buffer, 0, bytesRead);
}
bytes = bos.toByteArray();
}
} catch (Exception e) {
throw new AsrException("音频格式转换异常:" + e.getMessage(), e);
} finally {
try {
if (fis != null) {
fis.close();
}
if (bos != null) {
bos.close();
}
} catch (IOException e) {
throw new AsrException("音频格式转换资源关闭异常:" + e.getMessage(), e);
}
}
return bytes;
}
/**
* 音频格式转换
*
* @param sourceFilePath
* @param targetFilePath
* @param format wav/mp3/amr
* @return
*/
public static InputStream getAudioFormatConversionIns(String sourceFilePath, String targetFilePath, String format) throws EncoderException, FileNotFoundException {
File sourceFile = new File(sourceFilePath);
if (sourceFile.isFile()) {
File targetFile = new File(targetFilePath);
// 音频格式转换
audioFormatConversion(sourceFile, targetFile, format);
return new FileInputStream(targetFile);
}
return null;
}
/**
* 音频格式转换
* @param source 源音频文件
* @param target 输出的音频文件
* @param format wav/mp3/amr
*/
public static void audioFormatConversion(File source,File target,String format) throws EncoderException {
//Audio Attributes
AudioAttributes audio = new AudioAttributes();
switch (format) {
case "wav":
audio.setCodec("pcm_s16le");
break;
case "mp3":
audio.setCodec("libmp3lame");
break;
case "amr":
audio.setCodec("libvo_amrwbenc");
break;
default:
throw new IllegalArgumentException("不支持的音频格式:" + format);
}
audio.setBitRate(16000);
audio.setChannels(1);
audio.setSamplingRate(16000);
//Encoding attributes
EncodingAttributes attrs = new EncodingAttributes();
attrs.setOutputFormat(format);
attrs.setAudioAttributes(audio);
//Encode
Encoder encoder = new Encoder();
encoder.encode(new MultimediaObject(source), target, attrs);
}
/**
* 音频格式转换
* @param sourceStream 源音频流
* @param format wav/mp3/amr
* @return
*/
public static File audioFormatConversion(InputStream sourceStream, String format) throws EncoderException, IOException {
// 1. 写入临时源文件
File sourceFile = Files.createTempFile("source-", ".tmp").toFile();
try (OutputStream os = new FileOutputStream(sourceFile)) {
byte[] buffer = new byte[8192];
int len;
while ((len = sourceStream.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
}
// 2. 创建临时目标文件
File targetFile = Files.createTempFile("target-", "." + format).toFile();
// 3. 调用已有方法进行格式转换
audioFormatConversion(sourceFile, targetFile, format);
// 4. 删除源文件(可选)
sourceFile.delete();
return targetFile;
}
/**
* 获取音频信息
* @param source
* @return
*/
public static MultimediaInfo getAudioInfo(File source) throws EncoderException {
MultimediaObject mo = new MultimediaObject(source);
return mo.getInfo();
}
/**
* 获取音频信息
* @param ais
* @return
*/
public static MultimediaInfo getAudioInfo(AudioInputStream ais) throws EncoderException, IOException {
File tempFile = Files.createTempFile("audio_" + UUID.fastUUID().toString(), null).toFile();
tempFile.deleteOnExit(); // JVM退出时自动删除
try (OutputStream out = new BufferedOutputStream(new FileOutputStream(tempFile))) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = ais.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
}
return getAudioInfo(tempFile);
}
}

View File

@@ -0,0 +1,218 @@
import ai.djl.util.JsonUtils;
import cn.smartjavaai.common.entity.Language;
import cn.smartjavaai.common.entity.R;
import cn.smartjavaai.speech.asr.config.AsrModelConfig;
import cn.smartjavaai.speech.asr.entity.AsrResult;
import cn.smartjavaai.speech.asr.entity.AsrSegment;
import cn.smartjavaai.speech.asr.entity.RecParams;
import cn.smartjavaai.speech.asr.entity.WhisperParams;
import cn.smartjavaai.speech.asr.enums.AsrModelEnum;
import cn.smartjavaai.speech.asr.model.VoskRecognizer;
import cn.smartjavaai.speech.asr.model.WhisperRecognizer;
import io.github.givimad.whisperjni.WhisperFullParams;
import io.github.givimad.whisperjni.WhisperSamplingStrategy;
import lombok.extern.slf4j.Slf4j;
import ws.schild.jave.Encoder;
import ws.schild.jave.EncoderException;
import ws.schild.jave.InputFormatException;
import ws.schild.jave.MultimediaObject;
import ws.schild.jave.encode.AudioAttributes;
import ws.schild.jave.encode.EncodingAttributes;
import ws.schild.jave.info.AudioInfo;
import ws.schild.jave.info.MultimediaInfo;
import java.io.*;
/**
* @author dwj
* @date 2025/8/1
*/
@Slf4j
public class Test {
public static void main(String[] args) {
// System.out.println("TMPDIR = " + System.getProperty("java.io.tmpdir"));
//
//// System.setProperty("io.github.givimad.whisperjni.libdir","/Users/wenjie/smartjavaai_cache/whisper");
// WhisperRecognizer whisperRecognizer = new WhisperRecognizer();
// AsrModelConfig config = new AsrModelConfig();
// config.setModelEnum(AsrModelEnum.WHISPER);
//// config.setModelPath("/Users/wenjie/Downloads/ggml-medium.bin");
// config.setModelPath("/Users/wenjie/Documents/develop/model/speech/ggml-medium.bin");
// whisperRecognizer.loadModel(config);
// WhisperParams params = new WhisperParams();
// WhisperFullParams params1 = new WhisperFullParams(WhisperSamplingStrategy.BEAN_SEARCH);
//// params1.detectLanguage = true;
// params1.language = Language.ZH.getCode();
// //params1.translate = true;
// params1.initialPrompt = "语音模型";
// params.setParams(params1);
//// params1.printTimestamps = true;
// params1.printRealtime = true;
// //params.setLanguage(Language.ZH);
// R<AsrResult> result = whisperRecognizer.recognize("/Users/wenjie/Downloads/友谊大街.m4a",params);
// if (result.isSuccess()){
// System.out.println("结果:" + JsonUtils.toJson(result.getData()));
// }else{
// System.out.println(result.getMessage());
// }
//
// while (true){
// try {
// Thread.sleep(10);
// } catch (InterruptedException e) {
// throw new RuntimeException(e);
// }
// }
testVosk();
// testConvert();
}
public static void testVosk(){
System.load("/Users/wenjie/Downloads/vosk-arrch64-dylib-main/libvosk.dylib");
VoskRecognizer voskRecognizer = new VoskRecognizer();
AsrModelConfig config = new AsrModelConfig();
config.setModelEnum(AsrModelEnum.VOSK);
config.setModelPath("/Users/wenjie/Documents/develop/model/speech/vosk-model-cn-0.22");
voskRecognizer.loadModel(config);
R<AsrResult> result = voskRecognizer.recognize("/Users/wenjie/Documents/idea_workplace/SmartJavaAI/examples/speech-examples/src/main/resources/lff_zh.mp3");
if (result.isSuccess()){
System.out.println("结果:" + JsonUtils.toJson(result.getData()));
}else{
System.out.println(result.getMessage());
}
// while (true){
// try {
// Thread.sleep(10);
// } catch (InterruptedException e) {
// throw new RuntimeException(e);
// }
// }
}
public static void testConvert(){
getAudioFormatConversionIns("/Users/wenjie/Downloads/中国建设银行(包头当代支行).m4a","/Users/wenjie/Downloads/test1.wav","wav");
}
/**
* 音频格式转换
*
* @param sourceFilePath
* @param targetFilePath
* @param format wav/mp3/amr
* @return
*/
public static byte[] getAudioFormatConversionBytes(String sourceFilePath,String targetFilePath,String format) {
InputStream fis = null;
ByteArrayOutputStream bos = null;
byte[] bytes = null;
try {
File sourceFile = new File(sourceFilePath);
if (sourceFile.isFile()) {
File targetFile = new File(targetFilePath);
// 音频格式转换
audioFormatConversion(sourceFile, targetFile, format);
fis = new FileInputStream(targetFile);
bos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
bos.write(buffer, 0, bytesRead);
}
bytes = bos.toByteArray();
}
} catch (Exception e) {
log.error("音频格式转换异常:" + e.getMessage(), e);
return null;
} finally {
try {
if (fis != null) {
fis.close();
}
if (bos != null) {
bos.close();
}
} catch (IOException e) {
log.error("音频格式转换资源关闭异常:" + e.getMessage(), e);
}
}
return bytes;
}
/**
* 音频格式转换
*
* @param sourceFilePath
* @param targetFilePath
* @param format wav/mp3/amr
* @return
*/
public static InputStream getAudioFormatConversionIns(String sourceFilePath, String targetFilePath, String format) {
try {
File sourceFile = new File(sourceFilePath);
if (sourceFile.isFile()) {
File targetFile = new File(targetFilePath);
// 音频格式转换
audioFormatConversion(sourceFile, targetFile, format);
return new FileInputStream(targetFile);
}
} catch (Exception e) {
log.error("音频格式转换异常:" + e.getMessage(), e);
}
return null;
}
/**
* 音频格式转换
* @param source 源音频文件
* @param target 输出的音频文件
* @param format wav/mp3/amr
*/
public static void audioFormatConversion(File source,File target,String format) {
try {
//Audio Attributes
AudioAttributes audio = new AudioAttributes();
switch (format) {
case "wav":
audio.setCodec("pcm_s16le");
break;
case "mp3":
audio.setCodec("libmp3lame");
break;
case "amr":
audio.setCodec("libvo_amrwbenc");
break;
default:
log.error("音频格式不合法!");
return;
}
audio.setBitRate(16000);
audio.setChannels(1);
audio.setSamplingRate(16000);
//Encoding attributes
EncodingAttributes attrs = new EncodingAttributes();
attrs.setOutputFormat(format);
attrs.setAudioAttributes(audio);
//Encode
Encoder encoder = new Encoder();
encoder.encode(new MultimediaObject(source), target, attrs);
} catch (Exception e) {
log.error("音频格式转换异常:" + e.getMessage(), e);
}
}
}