Compare commits
57 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
7ba14570c0 | 2 days ago |
|
|
8d6e1fad53 | 3 days ago |
|
|
3cd384ba64 | 6 days ago |
|
|
09651bc87e | 1 week ago |
|
|
92aadd8f4e | 1 week ago |
|
|
f602c39e36 | 1 week ago |
|
|
149931988f | 2 weeks ago |
|
|
dd27cb26b9 | 2 months ago |
|
|
ee7f1893ba | 2 months ago |
|
|
b130a44d97 | 2 months ago |
|
|
e1a865ded9 | 2 months ago |
|
|
aac2db792b | 2 months ago |
|
|
bb0f5785af | 2 months ago |
|
|
17cb8d7093 | 2 months ago |
|
|
fa1874a0e1 | 2 months ago |
|
|
84965bc937 | 2 months ago |
|
|
108235443e | 2 months ago |
|
|
da06587620 | 3 months ago |
|
|
e3be3811da | 3 months ago |
|
|
7be4b31379 | 3 months ago |
|
|
016c8c3560 | 3 months ago |
|
|
2e889612dd | 3 months ago |
|
|
4af826eaf0 | 4 months ago |
|
|
f407f29a4a | 4 months ago |
|
|
8168e95450 | 4 months ago |
|
|
dcf5b77bc5 | 4 months ago |
|
|
df906835c6 | 4 months ago |
|
|
126b722af4 | 4 months ago |
|
|
d12e0ed9d9 | 4 months ago |
|
|
59e3ac1ca5 | 4 months ago |
|
|
d7efd91739 | 6 months ago |
|
|
e3a8cfcf6c | 6 months ago |
|
|
c89945e121 | 7 months ago |
|
|
438d7c214a | 7 months ago |
|
|
3fd11aea11 | 7 months ago |
|
|
7a7dc7373d | 7 months ago |
|
|
da6ad42ab7 | 7 months ago |
|
|
34b8b7cb6c | 7 months ago |
|
|
e4a6a98c71 | 8 months ago |
|
|
56e1fb8799 | 8 months ago |
|
|
1494c62ab4 | 8 months ago |
|
|
39d4b27007 | 8 months ago |
|
|
f3ca4d6300 | 8 months ago |
|
|
42b9523806 | 8 months ago |
|
|
f40d416311 | 8 months ago |
|
|
b6d1f99b88 | 8 months ago |
|
|
c2c9e4ea01 | 8 months ago |
|
|
f3470778d1 | 8 months ago |
|
|
eea35a6dbc | 8 months ago |
|
|
6c76042f53 | 8 months ago |
|
|
54616ffc75 | 8 months ago |
|
|
da756bd6db | 8 months ago |
|
|
3c66674762 | 8 months ago |
|
|
c914eea9ec | 9 months ago |
|
|
51870a2a7f | 9 months ago |
|
|
e8586712e2 | 9 months ago |
|
|
a4026ec295 | 9 months ago |
50 changed files with 1830 additions and 660 deletions
@ -0,0 +1,10 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<project version="4"> |
|||
<component name="deploymentTargetSelector"> |
|||
<selectionStates> |
|||
<SelectionState runConfigName="app"> |
|||
<option name="selectionMode" value="DROPDOWN" /> |
|||
</SelectionState> |
|||
</selectionStates> |
|||
</component> |
|||
</project> |
|||
@ -0,0 +1,10 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<project version="4"> |
|||
<component name="ProjectMigrations"> |
|||
<option name="MigrateToGradleLocalJavaHome"> |
|||
<set> |
|||
<option value="$PROJECT_DIR$" /> |
|||
</set> |
|||
</option> |
|||
</component> |
|||
</project> |
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
@ -0,0 +1,79 @@ |
|||
package qianmu.container.entity; |
|||
|
|||
public class TTSMessage { |
|||
String jsonrpc; |
|||
String method; |
|||
Params params; |
|||
|
|||
public String getJsonrpc() { |
|||
return jsonrpc; |
|||
} |
|||
|
|||
public void setJsonrpc(String jsonrpc) { |
|||
this.jsonrpc = jsonrpc; |
|||
} |
|||
|
|||
public String getMethod() { |
|||
return method; |
|||
} |
|||
|
|||
public void setMethod(String method) { |
|||
this.method = method; |
|||
} |
|||
|
|||
public Params getParams() { |
|||
return params; |
|||
} |
|||
|
|||
public void setParams(Params params) { |
|||
this.params = params; |
|||
} |
|||
|
|||
public static class Params { |
|||
String text; |
|||
String status; |
|||
String ttsId; |
|||
String sessionId; |
|||
String network; |
|||
|
|||
public String getNetwork() { |
|||
return network; |
|||
} |
|||
|
|||
public void setNetwork(String network) { |
|||
this.network = network; |
|||
} |
|||
public String getSessionId() { |
|||
return sessionId; |
|||
} |
|||
|
|||
public void setSessionId(String sessionId) { |
|||
this.sessionId = sessionId; |
|||
} |
|||
|
|||
public String getTtsId() { |
|||
return ttsId; |
|||
} |
|||
|
|||
public void setTtsId(String ttsId) { |
|||
this.ttsId = ttsId; |
|||
} |
|||
|
|||
public String getStatus() { |
|||
return status; |
|||
} |
|||
|
|||
public void setStatus(String status) { |
|||
this.status = status; |
|||
} |
|||
public String getText() { |
|||
return text; |
|||
} |
|||
|
|||
public void setText(String text) { |
|||
this.text = text; |
|||
} |
|||
|
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,60 @@ |
|||
package qianmu.container.util; |
|||
|
|||
import android.media.AudioAttributes; |
|||
import android.media.MediaPlayer; |
|||
|
|||
import java.io.IOException; |
|||
|
|||
public class AudioPlay { |
|||
|
|||
private static volatile MediaPlayer mediaPlayer; |
|||
|
|||
public static void startPlay(String url){ |
|||
if (mediaPlayer != null) { |
|||
mediaPlayer.release(); |
|||
mediaPlayer = null; |
|||
} |
|||
try { |
|||
mediaPlayer = new MediaPlayer(); |
|||
// 设置音频属性(需在 setDataSource 前调用)
|
|||
AudioAttributes attributes = new AudioAttributes.Builder() |
|||
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) |
|||
.setUsage(AudioAttributes.USAGE_MEDIA) |
|||
.build(); |
|||
mediaPlayer.setAudioAttributes(attributes); |
|||
// 设置数据源(确保路径正确,且有读取权限)
|
|||
mediaPlayer.setDataSource(url); |
|||
// 异步准备(避免阻塞主线程)
|
|||
mediaPlayer.prepareAsync(); |
|||
mediaPlayer.setVolume(1.0f, 1.0f); |
|||
mediaPlayer.setOnPreparedListener(mp -> { |
|||
// 准备完成后才能播放
|
|||
mp.start(); |
|||
}); |
|||
|
|||
// 监听错误状态
|
|||
mediaPlayer.setOnErrorListener((mp, what, extra) -> { |
|||
LoggerUtil.e("MediaPlayer", "Error: " + what + ", " + extra); |
|||
// 出错后释放资源
|
|||
mp.release(); |
|||
mediaPlayer = null; |
|||
return true; |
|||
}); |
|||
|
|||
} catch (IOException e) { |
|||
e.printStackTrace(); |
|||
// 处理文件读取错误(如路径无效、权限不足)
|
|||
} |
|||
} |
|||
|
|||
public static void stopPlay(){ |
|||
if(mediaPlayer != null){ |
|||
mediaPlayer.stop(); |
|||
mediaPlayer.release(); |
|||
mediaPlayer = null; |
|||
} |
|||
|
|||
} |
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,235 @@ |
|||
package qianmu.container.util; |
|||
|
|||
/** |
|||
* Created by Android Studio. |
|||
* User: linzhibin |
|||
* Date: 2025/11/26 |
|||
* Time: 19:22 |
|||
*/ |
|||
class Base64Util { |
|||
static private final int BASELENGTH = 128; |
|||
static private final int LOOKUPLENGTH = 64; |
|||
static private final int TWENTYFOURBITGROUP = 24; |
|||
static private final int EIGHTBIT = 8; |
|||
static private final int SIXTEENBIT = 16; |
|||
static private final int FOURBYTE = 4; |
|||
static private final int SIGN = -128; |
|||
static private final char PAD = '='; |
|||
static private final boolean fDebug = false; |
|||
static final private byte[] base64Alphabet = new byte[BASELENGTH]; |
|||
static final private char[] lookUpBase64Alphabet = new char[LOOKUPLENGTH]; |
|||
|
|||
static { |
|||
for (int i = 0; i < BASELENGTH; ++i) { |
|||
base64Alphabet[i] = -1; |
|||
} |
|||
for (int i = 'Z'; i >= 'A'; i--) { |
|||
base64Alphabet[i] = (byte) (i - 'A'); |
|||
} |
|||
for (int i = 'z'; i >= 'a'; i--) { |
|||
base64Alphabet[i] = (byte) (i - 'a' + 26); |
|||
} |
|||
for (int i = '9'; i >= '0'; i--) { |
|||
base64Alphabet[i] = (byte) (i - '0' + 52); |
|||
} |
|||
base64Alphabet['+'] = 62; |
|||
base64Alphabet['/'] = 63; |
|||
for (int i = 0; i <= 25; i++) { |
|||
lookUpBase64Alphabet[i] = (char) ('A' + i); |
|||
} |
|||
for (int i = 26, j = 0; i <= 51; i++, j++) { |
|||
lookUpBase64Alphabet[i] = (char) ('a' + j); |
|||
} |
|||
for (int i = 52, j = 0; i <= 61; i++, j++) { |
|||
lookUpBase64Alphabet[i] = (char) ('0' + j); |
|||
} |
|||
lookUpBase64Alphabet[62] = '+'; |
|||
lookUpBase64Alphabet[63] = '/'; |
|||
} |
|||
|
|||
private static boolean isWhiteSpace(char octect) { |
|||
return (octect == 0x20 || octect == 0xd || octect == 0xa || octect == 0x9); |
|||
} |
|||
|
|||
private static boolean isPad(char octect) { |
|||
return (octect == PAD); |
|||
} |
|||
|
|||
private static boolean isData(char octect) { |
|||
return (octect < BASELENGTH && base64Alphabet[octect] != -1); |
|||
} |
|||
|
|||
/** |
|||
* 将十六进制八位字节编码为Base64 |
|||
* |
|||
* @param binaryData 包含二进制数据的数组 |
|||
* @return 编码Base64字符串 |
|||
*/ |
|||
public static String encode(byte[] binaryData) { |
|||
if (binaryData == null) { |
|||
return null; |
|||
} |
|||
int lengthDataBits = binaryData.length * EIGHTBIT; |
|||
if (lengthDataBits == 0) { |
|||
return ""; |
|||
} |
|||
int fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP; |
|||
int numberTriplets = lengthDataBits / TWENTYFOURBITGROUP; |
|||
int numberQuartet = fewerThan24bits != 0 ? numberTriplets + 1 : numberTriplets; |
|||
char[] encodedData = new char[numberQuartet * 4]; |
|||
byte k = 0, l = 0, b1 = 0, b2 = 0, b3 = 0; |
|||
int encodedIndex = 0; |
|||
int dataIndex = 0; |
|||
if (fDebug) { |
|||
System.out.println("number of triplets = " + numberTriplets); |
|||
} |
|||
for (int i = 0; i < numberTriplets; i++) { |
|||
b1 = binaryData[dataIndex++]; |
|||
b2 = binaryData[dataIndex++]; |
|||
b3 = binaryData[dataIndex++]; |
|||
if (fDebug) { |
|||
System.out.println("b1= " + b1 + ", b2= " + b2 + ", b3= " + b3); |
|||
} |
|||
l = (byte) (b2 & 0x0f); |
|||
k = (byte) (b1 & 0x03); |
|||
byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); |
|||
byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0); |
|||
byte val3 = ((b3 & SIGN) == 0) ? (byte) (b3 >> 6) : (byte) ((b3) >> 6 ^ 0xfc); |
|||
if (fDebug) { |
|||
System.out.println("val2 = " + val2); |
|||
System.out.println("k4 = " + (k << 4)); |
|||
System.out.println("vak = " + (val2 | (k << 4))); |
|||
} |
|||
encodedData[encodedIndex++] = lookUpBase64Alphabet[val1]; |
|||
encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)]; |
|||
encodedData[encodedIndex++] = lookUpBase64Alphabet[(l << 2) | val3]; |
|||
encodedData[encodedIndex++] = lookUpBase64Alphabet[b3 & 0x3f]; |
|||
} |
|||
if (fewerThan24bits == EIGHTBIT) { |
|||
b1 = binaryData[dataIndex]; |
|||
k = (byte) (b1 & 0x03); |
|||
if (fDebug) { |
|||
System.out.println("b1=" + b1); |
|||
System.out.println("b1<<2 = " + (b1 >> 2)); |
|||
} |
|||
byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); |
|||
encodedData[encodedIndex++] = lookUpBase64Alphabet[val1]; |
|||
encodedData[encodedIndex++] = lookUpBase64Alphabet[k << 4]; |
|||
encodedData[encodedIndex++] = PAD; |
|||
encodedData[encodedIndex++] = PAD; |
|||
} else if (fewerThan24bits == SIXTEENBIT) { |
|||
b1 = binaryData[dataIndex]; |
|||
b2 = binaryData[dataIndex + 1]; |
|||
l = (byte) (b2 & 0x0f); |
|||
k = (byte) (b1 & 0x03); |
|||
byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); |
|||
byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0); |
|||
encodedData[encodedIndex++] = lookUpBase64Alphabet[val1]; |
|||
encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)]; |
|||
encodedData[encodedIndex++] = lookUpBase64Alphabet[l << 2]; |
|||
encodedData[encodedIndex++] = PAD; |
|||
} |
|||
return new String(encodedData); |
|||
} |
|||
|
|||
/** |
|||
* 将Base64数据解码为八位字节 |
|||
* |
|||
* @param encoded 包含Base64数据的字符串 |
|||
* @return 包含解码数据的数组. |
|||
*/ |
|||
public static byte[] decode(String encoded) { |
|||
if (encoded == null) { |
|||
return null; |
|||
} |
|||
char[] base64Data = encoded.toCharArray(); |
|||
//删除空白
|
|||
int len = removeWhiteSpace(base64Data); |
|||
//应该可以被四整除
|
|||
if (len % FOURBYTE != 0) { |
|||
return null; |
|||
} |
|||
int numberQuadruple = (len / FOURBYTE); |
|||
if (numberQuadruple == 0) { |
|||
return new byte[0]; |
|||
} |
|||
byte b1 = 0, b2 = 0, b3 = 0, b4 = 0; |
|||
char d1 = 0, d2 = 0, d3 = 0, d4 = 0; |
|||
int i = 0; |
|||
int encodedIndex = 0; |
|||
int dataIndex = 0; |
|||
byte[] decodedData = new byte[(numberQuadruple) * 3]; |
|||
for (; i < numberQuadruple - 1; i++) { |
|||
if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++])) |
|||
|| !isData((d3 = base64Data[dataIndex++])) |
|||
|| !isData((d4 = base64Data[dataIndex++]))) { |
|||
return null; |
|||
}//没有数据直接返回null
|
|||
b1 = base64Alphabet[d1]; |
|||
b2 = base64Alphabet[d2]; |
|||
b3 = base64Alphabet[d3]; |
|||
b4 = base64Alphabet[d4]; |
|||
decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); |
|||
decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); |
|||
decodedData[encodedIndex++] = (byte) (b3 << 6 | b4); |
|||
} |
|||
if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++]))) { |
|||
return null;////没有数据直接返回null
|
|||
} |
|||
b1 = base64Alphabet[d1]; |
|||
b2 = base64Alphabet[d2]; |
|||
d3 = base64Data[dataIndex++]; |
|||
d4 = base64Data[dataIndex++]; |
|||
if (!isData((d3)) || !isData((d4))) { |
|||
//检查是否为填充字符
|
|||
if (isPad(d3) && isPad(d4)) { |
|||
if ((b2 & 0xf) != 0) {//最后四位应为0
|
|||
return null; |
|||
} |
|||
byte[] tmp = new byte[i * 3 + 1]; |
|||
System.arraycopy(decodedData, 0, tmp, 0, i * 3); |
|||
tmp[encodedIndex] = (byte) (b1 << 2 | b2 >> 4); |
|||
return tmp; |
|||
} else if (!isPad(d3) && isPad(d4)) { |
|||
b3 = base64Alphabet[d3]; |
|||
if ((b3 & 0x3) != 0){//最后2位应为零
|
|||
return null; |
|||
} |
|||
byte[] tmp = new byte[i * 3 + 2]; |
|||
System.arraycopy(decodedData, 0, tmp, 0, i * 3); |
|||
tmp[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); |
|||
tmp[encodedIndex] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); |
|||
return tmp; |
|||
} else { |
|||
return null; |
|||
} |
|||
} else { //No PAD e.g 3cQl
|
|||
b3 = base64Alphabet[d3]; |
|||
b4 = base64Alphabet[d4]; |
|||
decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); |
|||
decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); |
|||
decodedData[encodedIndex++] = (byte) (b3 << 6 | b4); |
|||
} |
|||
return decodedData; |
|||
} |
|||
|
|||
/** |
|||
* 从包含编码Base64数据的MIME中删除空白 |
|||
* |
|||
* @param data base64数据的字节数组(带空白) |
|||
* @return 新的长度 |
|||
*/ |
|||
private static int removeWhiteSpace(char[] data) { |
|||
if (data == null) { |
|||
return 0; |
|||
} |
|||
int newSize = 0; |
|||
int len = data.length; |
|||
for (int i = 0; i < len; i++) { |
|||
if (!isWhiteSpace(data[i])) { |
|||
data[newSize++] = data[i]; |
|||
} |
|||
} |
|||
return newSize; |
|||
} |
|||
} |
|||
@ -0,0 +1,103 @@ |
|||
package qianmu.container.util; |
|||
|
|||
import java.nio.charset.StandardCharsets; |
|||
import java.security.InvalidKeyException; |
|||
import java.security.MessageDigest; |
|||
import java.security.NoSuchAlgorithmException; |
|||
import java.util.Base64; |
|||
import java.util.Locale; |
|||
|
|||
import javax.crypto.Mac; |
|||
import javax.crypto.spec.SecretKeySpec; |
|||
|
|||
import cn.hutool.core.util.ObjectUtil; |
|||
import qianmu.container.data.DeviceData; |
|||
import qianmu.container.data.FloorData; |
|||
|
|||
/** |
|||
* Created by Android Studio. |
|||
* User: linzhibin |
|||
* Date: 2025/11/26 |
|||
* Time: 19:16 |
|||
*/ |
|||
public class SignUtil { |
|||
|
|||
private static final String SignAlgorithm = "HmacSHA256"; |
|||
|
|||
public static Boolean checkSign(String activationCode, String projectCode, Long timeStrap, String sk) { |
|||
if (ObjectUtil.isEmpty(sk)) { |
|||
return false; |
|||
} |
|||
long thisTime = System.currentTimeMillis(); |
|||
// 大于/小于5min则过期
|
|||
long diff = thisTime - timeStrap; |
|||
if (diff < -1000 * 60 * 5 || diff > 1000 * 60 * 5) { |
|||
return false; |
|||
} |
|||
String anObject = buildSk(activationCode, projectCode, timeStrap); |
|||
boolean equals = sk.equals(anObject); |
|||
return equals; |
|||
} |
|||
|
|||
/** |
|||
* activationCode+timeStrap作为ak,projectCode为盐值 |
|||
*/ |
|||
public static String buildSk(String activationCode, String projectCode, Long timeStrap) { |
|||
String ak = activationCode + timeStrap; |
|||
return sign(projectCode, ak); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 生成签名 哈希后转16进制再sha256加密,最后转base64 |
|||
* @param secret 秘钥 |
|||
* @param content 待签名内容 |
|||
*/ |
|||
public static String sign(String secret, String content) { |
|||
try { |
|||
Mac mac = Mac.getInstance(SignAlgorithm); |
|||
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), SignAlgorithm)); |
|||
String data = toHex(hash(content)); |
|||
byte[] signByte = mac.doFinal(data.getBytes(StandardCharsets.UTF_8)); |
|||
|
|||
return Base64Util.encode(toHex(signByte).getBytes()); |
|||
} catch (NoSuchAlgorithmException | InvalidKeyException ex) { |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
public static byte[] hash(String text) { |
|||
try { |
|||
MessageDigest md = MessageDigest.getInstance("SHA-256"); |
|||
md.update(text.getBytes(StandardCharsets.UTF_8)); |
|||
|
|||
return md.digest(); |
|||
} catch (NoSuchAlgorithmException ex) { |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
public static String toHex(byte[] data) { |
|||
StringBuilder sb = new StringBuilder(data.length * 2); |
|||
for (byte b : data) { |
|||
String hex = Integer.toHexString(b); |
|||
if (hex.length() == 1) { |
|||
sb.append("0"); |
|||
} else if (hex.length() == 8) { |
|||
hex = hex.substring(6); |
|||
} |
|||
sb.append(hex); |
|||
} |
|||
|
|||
return sb.toString().toLowerCase(Locale.getDefault()); |
|||
} |
|||
|
|||
|
|||
public static void main(String[] args) { |
|||
long timeStrap = System.currentTimeMillis(); |
|||
String regKey = DeviceData.getDeviceInfo(DeviceData.HINT_REG_KEY); |
|||
String mallCode = FloorData.getMallCode(); |
|||
System.out.println(SignUtil.buildSk(regKey, mallCode, timeStrap)); |
|||
System.out.println(timeStrap); |
|||
} |
|||
} |
|||
@ -0,0 +1,179 @@ |
|||
package qianmu.container.util; |
|||
|
|||
|
|||
import android.media.AudioFormat; |
|||
import android.media.AudioManager; |
|||
import android.media.AudioTrack; |
|||
import android.os.Bundle; |
|||
import android.os.Handler; |
|||
import android.os.Looper; |
|||
import android.os.Message; |
|||
import android.util.Log; |
|||
import androidx.annotation.NonNull; |
|||
import com.iflytek.sparkchain.core.tts.OnlineTTS; |
|||
import com.iflytek.sparkchain.core.tts.TTS; |
|||
import com.iflytek.sparkchain.core.tts.TTSCallbacks; |
|||
|
|||
import org.greenrobot.eventbus.EventBus; |
|||
|
|||
import qianmu.container.app.Constant; |
|||
import qianmu.container.entity.MessageEvent; |
|||
|
|||
public class TTSUtil { |
|||
private static String TAG = "TTS: "; |
|||
// 语音合成对象
|
|||
private OnlineTTS mOnlineTTS; |
|||
private int sampleRate = 16000; |
|||
|
|||
TTSCallbacks mTTSCallback = new TTSCallbacks() { |
|||
|
|||
@Override |
|||
public void onResult(TTS.TTSResult result, Object o) { |
|||
//解析获取的交互结果,示例展示所有结果获取,开发者可根据自身需要,选择获取。
|
|||
byte[] audio = result.getData();//音频数据
|
|||
int status = result.getStatus();//数据状态
|
|||
|
|||
Bundle bundle = new Bundle(); |
|||
bundle.putByteArray("audio", audio); |
|||
Message msg = mAudioPlayHandler.obtainMessage(); |
|||
msg.what = AUDIOPLAYER_WRITE; |
|||
msg.obj = bundle; |
|||
mAudioPlayHandler.sendMessage(msg); |
|||
if(status == 2){ |
|||
//音频合成回调结束状态,注意,此状态不是播报完成状态
|
|||
mAudioPlayHandler.sendEmptyMessage(AUDIOPLAYER_END); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onError(TTS.TTSError ttsError, Object o) { |
|||
int errCode = ttsError.getCode();//错误码
|
|||
String errMsg = ttsError.getErrMsg();//错误信息
|
|||
LoggerUtil.d(TAG, "onError:errCode:" + errCode+ ",errMsg:" + errMsg); |
|||
//如果此时已经播报,则停止播报
|
|||
EventBus.getDefault().post(new MessageEvent(Constant.VOID_STOP)); |
|||
stopTTs(); |
|||
} |
|||
}; |
|||
|
|||
public void initTts(){ |
|||
mAudioPlayThread.start(); |
|||
// 初始化合成对象 发音人
|
|||
mOnlineTTS = new OnlineTTS("xiaoyan"); //xiaoyan
|
|||
} |
|||
|
|||
//开始听写
|
|||
public void startTTs(String texts){ |
|||
if (null == mOnlineTTS) { |
|||
LoggerUtil.d(TAG, "未初始化"); |
|||
initTts(); |
|||
} |
|||
if(audioTrack == null){ |
|||
mAudioPlayHandler.sendEmptyMessage(AUDIOPLAYER_INIT); |
|||
}else{ |
|||
mAudioPlayHandler.sendEmptyMessage(AUDIOPLAYER_START); |
|||
} |
|||
setParam(); |
|||
// 合成并播放
|
|||
int ret = mOnlineTTS.aRun(texts); |
|||
if(ret!=0){ |
|||
LoggerUtil.d(TAG, "语音合成失败" ); |
|||
} |
|||
} |
|||
|
|||
//停止听写
|
|||
public void stopTTs(){ |
|||
LoggerUtil.d(TAG, "手动暂停播放"); |
|||
if (null == mOnlineTTS) { |
|||
LoggerUtil.d(TAG, "未初始化"); |
|||
return; |
|||
} |
|||
mAudioPlayHandler.removeCallbacksAndMessages(null); |
|||
mAudioPlayHandler.sendEmptyMessage(AUDIOPLAYER_END); |
|||
mOnlineTTS.stop(); |
|||
} |
|||
|
|||
/** |
|||
* 参数设置 |
|||
* |
|||
* @return |
|||
*/ |
|||
private void setParam() { |
|||
mOnlineTTS.aue("raw"); |
|||
mOnlineTTS.auf("audio/L16;rate="+sampleRate); // 8K 或 16K
|
|||
mOnlineTTS.speed(60);//语速:0对应默认语速的1/2,100对应默认语速的2倍。最⼩值:0, 最⼤值:100
|
|||
mOnlineTTS.pitch(50);//语调:0对应默认语速的1/2,100对应默认语速的2倍。最⼩值:0, 最⼤值:100
|
|||
mOnlineTTS.volume(80);//音量:0是静音,1对应默认音量1/2,100对应默认音量的2倍。最⼩值:0, 最⼤值:100
|
|||
mOnlineTTS.bgs(0); //合成音频的背景音 0:无背景音(默认值) 1:有背景音
|
|||
mOnlineTTS.tte("UTF8"); |
|||
mOnlineTTS.registerCallbacks(mTTSCallback); |
|||
} |
|||
|
|||
/** |
|||
* 播放器,用于播报合成的音频。 |
|||
* 注意:当前Demo中的播放器仅实现了播放PCM格式的音频,如果客户合成的是其他格式的音频,需自行实现播放功能。 |
|||
*/ |
|||
private static final int AUDIOPLAYER_INIT = 0x0000; |
|||
private static final int AUDIOPLAYER_START = 0x0001; |
|||
private static final int AUDIOPLAYER_WRITE = 0x0002; |
|||
private static final int AUDIOPLAYER_END = 0x0003; |
|||
private static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_OUT_MONO; // 单声道输出
|
|||
private static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT; // PCM 16位编码
|
|||
private AudioTrack audioTrack; |
|||
private Handler mAudioPlayHandler; |
|||
private boolean isPlaying = false; |
|||
int count = 0; |
|||
private Thread mAudioPlayThread = new Thread(new Runnable() { |
|||
@Override |
|||
public void run() { |
|||
Looper.prepare(); |
|||
mAudioPlayHandler = new Handler(Looper.myLooper()){ |
|||
@Override |
|||
public void handleMessage(@NonNull Message msg) { |
|||
super.handleMessage(msg); |
|||
switch(msg.what){ |
|||
case AUDIOPLAYER_INIT: |
|||
Log.d(TAG,"audioInit"); |
|||
int minBufferSize = AudioTrack.getMinBufferSize(sampleRate, CHANNEL_CONFIG, AUDIO_FORMAT); |
|||
audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, CHANNEL_CONFIG, AUDIO_FORMAT, minBufferSize, AudioTrack.MODE_STREAM); |
|||
mAudioPlayHandler.sendEmptyMessage(AUDIOPLAYER_START); |
|||
break; |
|||
case AUDIOPLAYER_START: |
|||
Log.d(TAG,"audioStart"); |
|||
if(audioTrack!=null) { |
|||
isPlaying = true; |
|||
audioTrack.play(); |
|||
} |
|||
break; |
|||
case AUDIOPLAYER_WRITE: |
|||
count ++; |
|||
if(count%5 == 0){ |
|||
Log.d(TAG,"audioWrite"); |
|||
count = 0; |
|||
} |
|||
Bundle bundle = (Bundle) msg.obj; |
|||
byte[] audioData = bundle.getByteArray("audio"); |
|||
if(audioTrack!=null&&audioData.length>0){ |
|||
audioTrack.write(audioData,0,audioData.length); |
|||
} |
|||
break; |
|||
case AUDIOPLAYER_END: |
|||
Log.d(TAG,"audioEnd"); |
|||
EventBus.getDefault().post(new MessageEvent(Constant.VOID_STOP)); |
|||
if(audioTrack!=null) { |
|||
audioTrack.stop(); |
|||
audioTrack.release(); |
|||
audioTrack = null; |
|||
isPlaying = false; |
|||
} |
|||
break; |
|||
|
|||
} |
|||
} |
|||
}; |
|||
Looper.loop(); |
|||
} |
|||
}); |
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
|||
xmlns:tools="http://schemas.android.com/tools" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent" |
|||
tools:context=".activity.TestActivity" |
|||
android:orientation="vertical"> |
|||
|
|||
|
|||
<Button |
|||
android:id="@+id/button" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="@dimen/aspect_btn_height" |
|||
android:layout_weight="1" |
|||
android:text="开始语音识别" /> |
|||
|
|||
<Button |
|||
android:id="@+id/button2" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:layout_weight="1" |
|||
android:text="停止语音识别" /> |
|||
|
|||
<Button |
|||
android:id="@+id/button3" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:layout_weight="1" |
|||
android:text="播放语音合成" /> |
|||
|
|||
<Button |
|||
android:id="@+id/button4" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:layout_weight="1" |
|||
android:text="停止语音合成" /> |
|||
</LinearLayout> |
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in new issue