chore: import upstream snapshot with attribution
@@ -0,0 +1,71 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'com.yeyupiaoling.androidclient'
|
||||
compileSdk 33
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.yeyupiaoling.androidclient"
|
||||
minSdk 24
|
||||
targetSdk 33
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
useSupportLibrary true
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
buildFeatures {
|
||||
compose true
|
||||
}
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion '1.4.3'
|
||||
}
|
||||
packaging {
|
||||
resources {
|
||||
excludes += '/META-INF/{AL2.0,LGPL2.1}'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation 'androidx.core:core-ktx:1.9.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1'
|
||||
implementation 'androidx.activity:activity-compose:1.7.0'
|
||||
implementation platform('androidx.compose:compose-bom:2023.03.00')
|
||||
implementation 'androidx.compose.ui:ui'
|
||||
implementation 'androidx.compose.ui:ui-graphics'
|
||||
implementation 'androidx.compose.ui:ui-tooling-preview'
|
||||
implementation 'androidx.compose.material3:material3'
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'com.google.android.material:material:1.8.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
||||
androidTestImplementation platform('androidx.compose:compose-bom:2023.03.00')
|
||||
androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
|
||||
debugImplementation 'androidx.compose.ui:ui-tooling'
|
||||
debugImplementation 'androidx.compose.ui:ui-test-manifest'
|
||||
|
||||
implementation 'com.squareup.okhttp3:okhttp:4.9.1'
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.yeyupiaoling.androidclient
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("com.yeyupiaoling.androidclient", appContext.packageName)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@drawable/logo"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@drawable/logo"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.AndroidClient"
|
||||
tools:targetApi="31">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,216 @@
|
||||
package com.yeyupiaoling.androidclient;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Point;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class AudioView extends View {
|
||||
|
||||
// 频谱数量
|
||||
private static final int LUMP_COUNT = 128;
|
||||
private static final int LUMP_WIDTH = 6;
|
||||
private static final int LUMP_SPACE = 2;
|
||||
private static final int LUMP_MIN_HEIGHT = LUMP_WIDTH;
|
||||
private static final int LUMP_MAX_HEIGHT = 200;//TODO: HEIGHT
|
||||
private static final int LUMP_SIZE = LUMP_WIDTH + LUMP_SPACE;
|
||||
private static final int LUMP_COLOR = Color.parseColor("#6de8fd");
|
||||
|
||||
private static final int WAVE_SAMPLING_INTERVAL = 3;
|
||||
|
||||
private static final float SCALE = LUMP_MAX_HEIGHT / LUMP_COUNT;
|
||||
|
||||
private ShowStyle upShowStyle = ShowStyle.STYLE_HOLLOW_LUMP;
|
||||
private ShowStyle downShowStyle = ShowStyle.STYLE_WAVE;
|
||||
|
||||
private byte[] waveData;
|
||||
List<Point> pointList;
|
||||
|
||||
private Paint lumpPaint;
|
||||
Path wavePath = new Path();
|
||||
|
||||
|
||||
public AudioView(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public AudioView(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public AudioView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
lumpPaint = new Paint();
|
||||
lumpPaint.setAntiAlias(true);
|
||||
lumpPaint.setColor(LUMP_COLOR);
|
||||
|
||||
lumpPaint.setStrokeWidth(2);
|
||||
lumpPaint.setStyle(Paint.Style.STROKE);
|
||||
}
|
||||
|
||||
public void setWaveData(byte[] data) {
|
||||
this.waveData = readyData(data);
|
||||
genSamplingPoint(data);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
|
||||
public void setStyle(ShowStyle upShowStyle, ShowStyle downShowStyle) {
|
||||
this.upShowStyle = upShowStyle;
|
||||
this.downShowStyle = downShowStyle;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
wavePath.reset();
|
||||
|
||||
for (int i = 0; i < LUMP_COUNT; i++) {
|
||||
if (waveData == null) {
|
||||
canvas.drawRect((LUMP_WIDTH + LUMP_SPACE) * i,
|
||||
LUMP_MAX_HEIGHT - LUMP_MIN_HEIGHT,
|
||||
(LUMP_WIDTH + LUMP_SPACE) * i + LUMP_WIDTH,
|
||||
LUMP_MAX_HEIGHT,
|
||||
lumpPaint);
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (upShowStyle) {
|
||||
case STYLE_HOLLOW_LUMP:
|
||||
drawLump(canvas, i, false);
|
||||
break;
|
||||
case STYLE_WAVE:
|
||||
drawWave(canvas, i, false);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (downShowStyle) {
|
||||
case STYLE_HOLLOW_LUMP:
|
||||
drawLump(canvas, i, true);
|
||||
break;
|
||||
case STYLE_WAVE:
|
||||
drawWave(canvas, i, true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预处理数据
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private static byte[] readyData(byte[] fft) {
|
||||
byte[] newData = new byte[LUMP_COUNT];
|
||||
byte abs;
|
||||
for (int i = 0; i < LUMP_COUNT; i++) {
|
||||
abs = (byte) Math.abs(fft[i]);
|
||||
//描述:Math.abs -128时越界
|
||||
newData[i] = abs < 0 ? 127 : abs;
|
||||
}
|
||||
return newData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制曲线
|
||||
*
|
||||
* @param canvas
|
||||
* @param i
|
||||
* @param reversal
|
||||
*/
|
||||
private void drawWave(Canvas canvas, int i, boolean reversal) {
|
||||
if (pointList == null || pointList.size() < 2) {
|
||||
return;
|
||||
}
|
||||
float ratio = SCALE * (reversal ? -1 : 1);
|
||||
if (i < pointList.size() - 2) {
|
||||
Point point = pointList.get(i);
|
||||
Point nextPoint = pointList.get(i + 1);
|
||||
int midX = (point.x + nextPoint.x) >> 1;
|
||||
if (i == 0) {
|
||||
wavePath.moveTo(point.x, LUMP_MAX_HEIGHT - point.y * ratio);
|
||||
}
|
||||
wavePath.cubicTo(midX, LUMP_MAX_HEIGHT - point.y * ratio,
|
||||
midX, LUMP_MAX_HEIGHT - nextPoint.y * ratio,
|
||||
nextPoint.x, LUMP_MAX_HEIGHT - nextPoint.y * ratio);
|
||||
|
||||
canvas.drawPath(wavePath, lumpPaint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制矩形条
|
||||
*/
|
||||
private void drawLump(Canvas canvas, int i, boolean reversal) {
|
||||
int minus = reversal ? -1 : 1;
|
||||
float top = (LUMP_MAX_HEIGHT - (LUMP_MIN_HEIGHT + waveData[i] * SCALE) * minus);
|
||||
|
||||
canvas.drawRect(LUMP_SIZE * i,
|
||||
top,
|
||||
LUMP_SIZE * i + LUMP_WIDTH,
|
||||
LUMP_MAX_HEIGHT,
|
||||
lumpPaint);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成波形图的采样数据,减少计算量
|
||||
*
|
||||
* @param data
|
||||
*/
|
||||
private void genSamplingPoint(byte[] data) {
|
||||
if (upShowStyle != ShowStyle.STYLE_WAVE && downShowStyle != ShowStyle.STYLE_WAVE) {
|
||||
return;
|
||||
}
|
||||
if (pointList == null) {
|
||||
pointList = new ArrayList<>();
|
||||
} else {
|
||||
pointList.clear();
|
||||
}
|
||||
pointList.add(new Point(0, 0));
|
||||
for (int i = WAVE_SAMPLING_INTERVAL; i < LUMP_COUNT; i += WAVE_SAMPLING_INTERVAL) {
|
||||
pointList.add(new Point(LUMP_SIZE * i, waveData[i]));
|
||||
}
|
||||
pointList.add(new Point(LUMP_SIZE * LUMP_COUNT, 0));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 可视化样式
|
||||
*/
|
||||
public enum ShowStyle {
|
||||
/**
|
||||
* 空心的矩形小块
|
||||
*/
|
||||
STYLE_HOLLOW_LUMP,
|
||||
|
||||
/**
|
||||
* 曲线
|
||||
*/
|
||||
STYLE_WAVE,
|
||||
|
||||
/**
|
||||
* 不显示
|
||||
*/
|
||||
STYLE_NOTHING
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,361 @@
|
||||
package com.yeyupiaoling.androidclient;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.media.AudioFormat;
|
||||
import android.media.AudioRecord;
|
||||
import android.media.MediaRecorder;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.WebSocket;
|
||||
import okhttp3.WebSocketListener;
|
||||
import okio.ByteString;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
public static final String TAG = MainActivity.class.getSimpleName();
|
||||
// WebSocket地址
|
||||
public String ASR_HOST = "";
|
||||
// 官方WebSocket地址
|
||||
public static final String DEFAULT_HOST = "wss://101.37.77.25:10088";
|
||||
// 发送的JSON数据
|
||||
public static final String MODE = "2pass";
|
||||
public static final String CHUNK_SIZE = "5, 10, 5";
|
||||
public static final int CHUNK_INTERVAL = 10;
|
||||
public static final int SEND_SIZE = 1920;
|
||||
// 热词
|
||||
private String hotWords = "阿里巴巴 20\n达摩院 20\n夜雨飘零 20\n";
|
||||
// 采样率
|
||||
public static final int SAMPLE_RATE = 16000;
|
||||
// 声道数
|
||||
public static final int CHANNEL = AudioFormat.CHANNEL_IN_MONO;
|
||||
// 返回的音频数据的格式
|
||||
public static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
|
||||
private AudioRecord audioRecord;
|
||||
private boolean isRecording = false;
|
||||
private AudioView audioView;
|
||||
private String allAsrText = "";
|
||||
private String asrText = "";
|
||||
private SharedPreferences sharedPreferences;
|
||||
// 控件
|
||||
private Button recordBtn;
|
||||
private TextView resultText;
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
// 请求权限
|
||||
if (!hasPermission()) {
|
||||
requestPermission();
|
||||
}
|
||||
// 显示识别结果控件
|
||||
resultText = findViewById(R.id.result_text);
|
||||
// 显示录音状态控件
|
||||
audioView = findViewById(R.id.audioView);
|
||||
audioView.setStyle(AudioView.ShowStyle.STYLE_HOLLOW_LUMP, AudioView.ShowStyle.STYLE_NOTHING);
|
||||
// 按下识别按钮
|
||||
recordBtn = findViewById(R.id.record_button);
|
||||
recordBtn.setOnTouchListener((v, event) -> {
|
||||
if (event.getAction() == MotionEvent.ACTION_UP) {
|
||||
if (!ASR_HOST.equals("")) {
|
||||
isRecording = false;
|
||||
stopRecording();
|
||||
recordBtn.setText("按下录音");
|
||||
}
|
||||
} else if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
if (!ASR_HOST.equals("")) {
|
||||
allAsrText = "";
|
||||
asrText = "";
|
||||
isRecording = true;
|
||||
startRecording();
|
||||
recordBtn.setText("录音中...");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
// 读取WebSocket地址
|
||||
sharedPreferences = getSharedPreferences("FunASR", MODE_PRIVATE);
|
||||
String uri = sharedPreferences.getString("uri", "");
|
||||
if (uri.equals("")) {
|
||||
showUriInput();
|
||||
} else {
|
||||
ASR_HOST = uri;
|
||||
}
|
||||
// 读取热词
|
||||
String hotWords = sharedPreferences.getString("hotwords", null);
|
||||
if (hotWords != null) {
|
||||
this.hotWords = hotWords;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.menu, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
if (id == R.id.change_uri) {
|
||||
showUriInput();
|
||||
return true;
|
||||
} else if (id == R.id.change_hotwords) {
|
||||
showHotWordsInput();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
// 显示WebSocket地址输入框
|
||||
private void showUriInput() {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
|
||||
builder.setTitle("请输入WebSocket地址:");
|
||||
View view = LayoutInflater.from(MainActivity.this).inflate(R.layout.dialog_input_uri, null);
|
||||
final EditText input = view.findViewById(R.id.uri_edit_text);
|
||||
if (!ASR_HOST.equals("")) {
|
||||
input.setText(ASR_HOST);
|
||||
}
|
||||
builder.setView(view);
|
||||
builder.setPositiveButton("确定", (dialog, id) -> {
|
||||
ASR_HOST = input.getText().toString();
|
||||
if (!ASR_HOST.equals("")) {
|
||||
Toast.makeText(MainActivity.this, "WebSocket地址:" + ASR_HOST, Toast.LENGTH_SHORT).show();
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.putString("uri", ASR_HOST);
|
||||
editor.apply();
|
||||
}
|
||||
});
|
||||
builder.setNeutralButton("使用官方服务", (dialog, id) -> {
|
||||
ASR_HOST = DEFAULT_HOST;
|
||||
input.setText(DEFAULT_HOST);
|
||||
Toast.makeText(MainActivity.this, "WebSocket地址:" + ASR_HOST, Toast.LENGTH_SHORT).show();
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.putString("uri", ASR_HOST);
|
||||
editor.apply();
|
||||
});
|
||||
AlertDialog dialog = builder.create();
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
// 显示热词输入框
|
||||
private void showHotWordsInput() {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
|
||||
builder.setTitle("请输入热词:");
|
||||
View view = LayoutInflater.from(MainActivity.this).inflate(R.layout.dialog_input_hotwords, null);
|
||||
final EditText input = view.findViewById(R.id.hotwords_edit_text);
|
||||
if (!this.hotWords.equals("")) {
|
||||
input.setText(this.hotWords);
|
||||
}
|
||||
builder.setView(view);
|
||||
builder.setPositiveButton("确定", (dialog, id) -> {
|
||||
String hotwords = input.getText().toString();
|
||||
this.hotWords = hotwords;
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.putString("hotwords", hotwords);
|
||||
editor.apply();
|
||||
});
|
||||
AlertDialog dialog = builder.create();
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
// 开始录音
|
||||
private void startRecording() {
|
||||
// 准备录音器
|
||||
try {
|
||||
// 确保有权限
|
||||
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
|
||||
requestPermission();
|
||||
return;
|
||||
}
|
||||
// 创建录音器
|
||||
audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, SAMPLE_RATE, CHANNEL, AUDIO_FORMAT, SEND_SIZE);
|
||||
} catch (IllegalStateException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
// 开启一个线程将录音数据写入文件
|
||||
Thread recordingAudioThread = new Thread(() -> {
|
||||
try {
|
||||
setAudioData();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
recordingAudioThread.start();
|
||||
// 启动录音器
|
||||
audioRecord.startRecording();
|
||||
audioView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
// 停止录音器
|
||||
private void stopRecording() {
|
||||
audioRecord.stop();
|
||||
audioRecord.release();
|
||||
audioRecord = null;
|
||||
audioView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// 读取录音数据
|
||||
private void setAudioData() throws Exception {
|
||||
// 建立WebSocket连接
|
||||
OkHttpClient client = new OkHttpClient.Builder()
|
||||
// 忽略验证证书
|
||||
.sslSocketFactory(SSLSocketClient.getSSLSocketFactory(), SSLSocketClient.getX509TrustManager())
|
||||
// 不验证域名
|
||||
.hostnameVerifier(SSLSocketClient.getHostnameVerifier())
|
||||
.build();
|
||||
Request request = new Request.Builder()
|
||||
.url(ASR_HOST)
|
||||
.build();
|
||||
WebSocket webSocket = client.newWebSocket(request, new WebSocketListener() {
|
||||
|
||||
@Override
|
||||
public void onOpen(@NonNull WebSocket webSocket, @NonNull Response response) {
|
||||
// 连接成功时的处理
|
||||
Log.d(TAG, "WebSocket连接成功");
|
||||
runOnUiThread(() -> Toast.makeText(MainActivity.this, "WebSocket连接成功", Toast.LENGTH_SHORT).show());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(@NonNull WebSocket webSocket, @NonNull String text) {
|
||||
// 接收到消息时的处理
|
||||
Log.d(TAG, "WebSocket接收到消息: " + text);
|
||||
try {
|
||||
JSONObject jsonObject = new JSONObject(text);
|
||||
String t = jsonObject.getString("text");
|
||||
boolean isFinal = jsonObject.getBoolean("is_final");
|
||||
if (!t.equals("")) {
|
||||
// 拼接识别结果
|
||||
String mode = jsonObject.getString("mode");
|
||||
if (mode.equals("2pass-offline")) {
|
||||
asrText = "";
|
||||
allAsrText = allAsrText + t;
|
||||
// 这里可以做一些自动停止录音识别的程序
|
||||
} else {
|
||||
asrText = asrText + t;
|
||||
}
|
||||
}
|
||||
// 显示语音识别结果消息
|
||||
if (!(allAsrText + asrText).equals("")) {
|
||||
runOnUiThread(() -> resultText.setText(allAsrText + asrText));
|
||||
}
|
||||
// 如果检测的录音停止就关闭WebSocket连接
|
||||
if (isFinal) {
|
||||
webSocket.close(1000, "关闭WebSocket连接");
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClosing(@NonNull WebSocket webSocket, int code, @NonNull String reason) {
|
||||
// 关闭连接时的处理
|
||||
Log.d(TAG, "WebSocket关闭连接: " + reason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull WebSocket webSocket, @NonNull Throwable t, Response response) {
|
||||
// 连接失败时的处理
|
||||
Log.d(TAG, "WebSocket连接失败: " + t + ": " + response);
|
||||
runOnUiThread(() -> Toast.makeText(MainActivity.this, "WebSocket连接失败:" + t, Toast.LENGTH_SHORT).show());
|
||||
}
|
||||
});
|
||||
String message = getMessage(true);
|
||||
Log.d(TAG, "WebSocket发送消息: " + message);
|
||||
webSocket.send(message);
|
||||
|
||||
audioRecord.startRecording();
|
||||
byte[] bytes = new byte[SEND_SIZE];
|
||||
while (isRecording) {
|
||||
int readSize = audioRecord.read(bytes, 0, SEND_SIZE);
|
||||
if (readSize > 0) {
|
||||
ByteString byteString = ByteString.of(bytes);
|
||||
webSocket.send(byteString);
|
||||
audioView.post(() -> audioView.setWaveData(bytes));
|
||||
}
|
||||
}
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("is_speaking", false);
|
||||
webSocket.send(obj.toString());
|
||||
// webSocket.close(1000, "关闭WebSocket连接");
|
||||
}
|
||||
|
||||
// 发送第一步的JSON数据
|
||||
public String getMessage(boolean isSpeaking) {
|
||||
try {
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("mode", MODE);
|
||||
JSONArray array = new JSONArray();
|
||||
String[] chunkList = CHUNK_SIZE.split(",");
|
||||
for (String s : chunkList) {
|
||||
array.put(Integer.valueOf(s.trim()));
|
||||
}
|
||||
obj.put("chunk_size", array);
|
||||
obj.put("chunk_interval", CHUNK_INTERVAL);
|
||||
obj.put("wav_name", "default");
|
||||
if (!hotWords.equals("")) {
|
||||
JSONObject hotwordsJSON = new JSONObject();
|
||||
// 分割每一行字符串
|
||||
String[] hotWordsList = hotWords.split("\n");
|
||||
for (String s : hotWordsList) {
|
||||
if (s.equals("")) {
|
||||
Log.w(TAG, "hotWords为空");
|
||||
continue;
|
||||
}
|
||||
// 按照空格分割字符串
|
||||
String[] hotWordsArray = s.split(" ");
|
||||
if (hotWordsArray.length != 2) {
|
||||
Log.w(TAG, "hotWords格式不正确");
|
||||
continue;
|
||||
}
|
||||
hotwordsJSON.put(hotWordsArray[0], Integer.valueOf(hotWordsArray[1]));
|
||||
}
|
||||
obj.put("hotwords", hotwordsJSON.toString());
|
||||
}
|
||||
obj.put("wav_format", "pcm");
|
||||
obj.put("is_speaking", isSpeaking);
|
||||
return obj.toString();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
// 检查权限
|
||||
private boolean hasPermission() {
|
||||
return checkSelfPermission(Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED &&
|
||||
checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
// 请求权限
|
||||
private void requestPermission() {
|
||||
requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.yeyupiaoling.androidclient;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
public class SSLSocketClient {
|
||||
//获取SSLSocketFactory
|
||||
public static SSLSocketFactory getSSLSocketFactory() {
|
||||
try {
|
||||
SSLContext sslContext = SSLContext.getInstance("SSL");
|
||||
sslContext.init(null, getTrustManager(), new SecureRandom());
|
||||
return sslContext.getSocketFactory();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
//获取X509TrustManager
|
||||
public static X509TrustManager getX509TrustManager() {
|
||||
X509TrustManager x509TrustManager = new X509TrustManager() {
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return new X509Certificate[0];
|
||||
}
|
||||
};
|
||||
return x509TrustManager;
|
||||
}
|
||||
|
||||
//获取TrustManager
|
||||
private static TrustManager[] getTrustManager() {
|
||||
TrustManager[] trustAllCerts = new TrustManager[]{
|
||||
new X509TrustManager() {
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] chain, String authType) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] chain, String authType) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return new X509Certificate[]{};
|
||||
}
|
||||
}
|
||||
};
|
||||
return trustAllCerts;
|
||||
}
|
||||
|
||||
//获取HostnameVerifier
|
||||
public static HostnameVerifier getHostnameVerifier() {
|
||||
HostnameVerifier hostnameVerifier = (s, sslSession) -> true;
|
||||
return hostnameVerifier;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<stroke android:width="1dp" android:color="#000000" />
|
||||
<padding android:left="5dp" android:top="5dp" android:right="5dp" android:bottom="5dp" />
|
||||
</shape>
|
||||
@@ -0,0 +1,170 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
</vector>
|
||||
@@ -0,0 +1,30 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="85.84757"
|
||||
android:endY="92.4963"
|
||||
android:startX="42.9492"
|
||||
android:startY="49.59793"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout 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"
|
||||
android:orientation="vertical"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<Button
|
||||
android:id="@+id/record_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:text="按下录音" />
|
||||
|
||||
<com.yeyupiaoling.androidclient.AudioView
|
||||
android:id="@+id/audioView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="100dp"
|
||||
android:layout_above="@id/record_button"
|
||||
android:layout_marginStart="10dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/result_text"
|
||||
android:layout_above="@id/record_button"
|
||||
android:layout_width="match_parent"
|
||||
android:hint="显示识别结果"
|
||||
android:textSize="22sp"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
</RelativeLayout>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/hotwords_edit_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="200dp"
|
||||
android:layout_margin="10dp"
|
||||
android:inputType="textMultiLine"
|
||||
android:background="@drawable/edittext_border"
|
||||
android:minLines="3"
|
||||
android:gravity="top"
|
||||
android:hint="每一行为:热词 权重" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/uri_edit_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10dp"
|
||||
android:background="@drawable/edittext_border"
|
||||
android:hint="wss://" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/change_uri"
|
||||
android:title="服务地址" />
|
||||
<item
|
||||
android:id="@+id/change_hotwords"
|
||||
android:title="热词" />
|
||||
</menu>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 982 B |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="purple_200">#FFBB86FC</color>
|
||||
<color name="purple_500">#FF6200EE</color>
|
||||
<color name="purple_700">#FF3700B3</color>
|
||||
<color name="teal_200">#FF03DAC5</color>
|
||||
<color name="teal_700">#FF018786</color>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">FunASR</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,16 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.AndroidClient" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/purple_500</item>
|
||||
<item name="colorPrimaryVariant">@color/purple_700</item>
|
||||
<item name="colorOnPrimary">@color/white</item>
|
||||
<!-- Secondary brand color. -->
|
||||
<item name="colorSecondary">@color/teal_200</item>
|
||||
<item name="colorSecondaryVariant">@color/teal_700</item>
|
||||
<item name="colorOnSecondary">@color/black</item>
|
||||
<!-- Status bar color. -->
|
||||
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Sample backup rules file; uncomment and customize as necessary.
|
||||
See https://developer.android.com/guide/topics/data/autobackup
|
||||
for details.
|
||||
Note: This file is ignored for devices older that API 31
|
||||
See https://developer.android.com/about/versions/12/backup-restore
|
||||
-->
|
||||
<full-backup-content>
|
||||
<!--
|
||||
<include domain="sharedpref" path="."/>
|
||||
<exclude domain="sharedpref" path="device.xml"/>
|
||||
-->
|
||||
</full-backup-content>
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Sample data extraction rules file; uncomment and customize as necessary.
|
||||
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
|
||||
for details.
|
||||
-->
|
||||
<data-extraction-rules>
|
||||
<cloud-backup>
|
||||
<!--
|
||||
<include .../>
|
||||
<exclude .../>
|
||||
-->
|
||||
</cloud-backup>
|
||||
<!--
|
||||
<device-transfer>
|
||||
<include .../>
|
||||
<exclude .../>
|
||||
</device-transfer>
|
||||
-->
|
||||
</data-extraction-rules>
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.yeyupiaoling.androidclient
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
id 'com.android.application' version '8.1.2' apply false
|
||||
id 'org.jetbrains.kotlin.android' version '1.8.10' apply false
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
# Project-wide Gradle settings.
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app's APK
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
# Kotlin code style for this project: "official" or "obsolete":
|
||||
kotlin.code.style=official
|
||||
# Enables namespacing of each library's R class so that its R class includes only the
|
||||
# resources declared in the library itself and none from the library's dependencies,
|
||||
# thereby reducing the size of the R class for that library
|
||||
android.nonTransitiveRClass=true
|
||||
@@ -0,0 +1,6 @@
|
||||
#Fri Oct 13 14:55:29 CST 2023
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
@@ -0,0 +1,185 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
@@ -0,0 +1,89 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@@ -0,0 +1,17 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "AndroidClient"
|
||||
include ':app'
|
||||
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 253 KiB |
@@ -0,0 +1,20 @@
|
||||
# AndroidClient
|
||||
|
||||
先说明,本项目是使用WebSocket连接服务器的语音识别服务,并不是将FunASR部署到Android里,服务启动方式请查看文档[SDK_advanced_guide_online_zh.md](https://github.com/modelscope/FunASR/blob/main/funasr/runtime/docs/SDK_advanced_guide_online_zh.md)。
|
||||
|
||||
使用最新的 Android Studio 打开`AndroidClient`项目,运行即可。也可以直接下载[APK安装包](https://yeyupiaoling.cn/AndroidClient.apk)安装使用,或者使用手机扫码下载。
|
||||
|
||||
<div align="center">
|
||||
<img src="./images/QRcode.png" alt="APK安装包" width="300">
|
||||
</div>
|
||||
|
||||
|
||||
应用只有一个功能,按钮下开始识别,松开按钮结束识别。第一次打开应用需要设置WebSocket的地址,也可以在菜单栏修改,同时也可以在菜单栏修改热词。
|
||||
|
||||
应用效果图:
|
||||
|
||||
<div align="center">
|
||||
<img src="./images/image1.png" alt="应用效果图" width="300">
|
||||
<img src="./images/image2.png" alt="应用效果图" width="300">
|
||||
<img src="./images/image3.png" alt="应用效果图" width="300">
|
||||
</div>
|
||||
@@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommandLineParser" Version="2.9.1" />
|
||||
<PackageReference Include="NAudio" Version="2.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\AliFsmnVadSharp\AliFsmnVadSharp.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,107 @@
|
||||
using AliFsmnVadSharp;
|
||||
using AliFsmnVadSharp.Model;
|
||||
using CommandLine;
|
||||
using NAudio.Wave;
|
||||
|
||||
internal static class Program
|
||||
{
|
||||
public class ProgramParams
|
||||
{
|
||||
[Option('i', "input", Required = true, HelpText = "Input wav file/folder path.")]
|
||||
public string WavFilePath { get; set; }
|
||||
|
||||
[Option('m', "model", Default = "speech_fsmn_vad_zh-cn-16k-common-onnx", HelpText = "Model path.")]
|
||||
public string Model { get; set; }
|
||||
}
|
||||
|
||||
[STAThread]
|
||||
private static void Main(string[] args)
|
||||
{
|
||||
var argParams = Parser.Default.ParseArguments<ProgramParams>(args).Value;
|
||||
|
||||
string modelPath = argParams.Model;
|
||||
if (!Directory.Exists(argParams.Model))
|
||||
{
|
||||
modelPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, modelPath);
|
||||
if (!Directory.Exists(modelPath))
|
||||
{
|
||||
throw new DirectoryNotFoundException($"Model not found: {argParams.Model}");
|
||||
}
|
||||
}
|
||||
|
||||
string modelFilePath = Path.Combine(modelPath, "model_quant.onnx");
|
||||
string configFilePath = Path.Combine(modelPath, "config.yaml");
|
||||
string mvnFilePath = Path.Combine(modelPath, "am.mvn");
|
||||
|
||||
int batchSize = 1;
|
||||
AliFsmnVad aliFsmnVad = new AliFsmnVad(modelFilePath, configFilePath, mvnFilePath, batchSize);
|
||||
|
||||
List<string> wavFiles = new List<string>();
|
||||
|
||||
if (File.Exists(argParams.WavFilePath))
|
||||
{
|
||||
wavFiles.Add(argParams.WavFilePath);
|
||||
}
|
||||
else if (Directory.Exists(argParams.WavFilePath))
|
||||
{
|
||||
foreach (var wavFilePath in Directory.GetFiles(argParams.WavFilePath, "*.wav"))
|
||||
{
|
||||
wavFiles.Add(wavFilePath);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Invalid wav input path. {argParams.WavFilePath}");
|
||||
}
|
||||
|
||||
var start_time = DateTime.Now;
|
||||
|
||||
TimeSpan total_duration = new TimeSpan(0L);
|
||||
for (int i = 0; i < wavFiles.Count; i += batchSize)
|
||||
{
|
||||
List<float[]> samples = new List<float[]>();
|
||||
|
||||
foreach(var wavFile in wavFiles.Skip(i).Take(batchSize))
|
||||
{
|
||||
(var sample, var duration) = LoadWavFile(wavFile);
|
||||
samples.Add(sample);
|
||||
total_duration += duration;
|
||||
}
|
||||
|
||||
SegmentEntity[] segments_duration = aliFsmnVad.GetSegments(samples);
|
||||
Console.WriteLine("vad infer result:");
|
||||
foreach (SegmentEntity segment in segments_duration)
|
||||
{
|
||||
Console.Write("[");
|
||||
foreach (var x in segment.Segment)
|
||||
{
|
||||
Console.Write("[" + string.Join(",", x.ToArray()) + "]");
|
||||
}
|
||||
Console.Write("]\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
var end_time = DateTime.Now;
|
||||
|
||||
double elapsed_milliseconds = (end_time - start_time).TotalMilliseconds;
|
||||
|
||||
double rtf = elapsed_milliseconds / total_duration.TotalMilliseconds;
|
||||
Console.WriteLine("elapsed_milliseconds:{0}", elapsed_milliseconds.ToString());
|
||||
Console.WriteLine("total_duration:{0}", total_duration.TotalMilliseconds.ToString());
|
||||
Console.WriteLine("rtf:{1}", "0".ToString(), rtf.ToString());
|
||||
Console.WriteLine("------------------------");
|
||||
}
|
||||
|
||||
private static (float[] sample, TimeSpan duration) LoadWavFile(string wavFilePath)
|
||||
{
|
||||
AudioFileReader _audioFileReader = new AudioFileReader(wavFilePath);
|
||||
byte[] datas = new byte[_audioFileReader.Length];
|
||||
_audioFileReader.Read(datas, 0, datas.Length);
|
||||
var duration = _audioFileReader.TotalTime;
|
||||
float[] wavdata = new float[datas.Length / 4];
|
||||
Buffer.BlockCopy(datas, 0, wavdata, 0, datas.Length);
|
||||
var sample = wavdata.Select((float x) => x * 32768f).ToArray();
|
||||
|
||||
return (sample, duration);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.1.32210.238
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AliFsmnVadSharp", "AliFsmnVadSharp\AliFsmnVadSharp.csproj", "{BFB82F2E-AD5B-405C-AAFF-3CE33C548748}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AliFsmnVadSharp.Examples", "AliFsmnVadSharp.Examples\AliFsmnVadSharp.Examples.csproj", "{2FFA4D03-A62B-435B-B57B-7E49209810E1}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{212561CC-9836-4F45-A31B-298EF576F519}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
license = license
|
||||
README.md = README.md
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{BFB82F2E-AD5B-405C-AAFF-3CE33C548748}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BFB82F2E-AD5B-405C-AAFF-3CE33C548748}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BFB82F2E-AD5B-405C-AAFF-3CE33C548748}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BFB82F2E-AD5B-405C-AAFF-3CE33C548748}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{2FFA4D03-A62B-435B-B57B-7E49209810E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2FFA4D03-A62B-435B-B57B-7E49209810E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2FFA4D03-A62B-435B-B57B-7E49209810E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2FFA4D03-A62B-435B-B57B-7E49209810E1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {FCC1BBCC-91A3-4223-B368-D272FB5108B6}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,410 @@
|
||||
using AliFsmnVadSharp.Model;
|
||||
using AliFsmnVadSharp.Utils;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.ML.OnnxRuntime;
|
||||
using Microsoft.ML.OnnxRuntime.Tensors;
|
||||
|
||||
// 模型文件下载地址:https://modelscope.cn/models/iic/speech_fsmn_vad_zh-cn-16k-common-onnx/
|
||||
|
||||
namespace AliFsmnVadSharp
|
||||
{
|
||||
public class AliFsmnVad : IDisposable
|
||||
{
|
||||
private bool _disposed;
|
||||
private InferenceSession _onnxSession;
|
||||
private readonly ILogger _logger;
|
||||
private string _frontend;
|
||||
private WavFrontend _wavFrontend;
|
||||
private int _batchSize = 1;
|
||||
private int _max_end_sil = int.MinValue;
|
||||
private EncoderConfEntity _encoderConfEntity;
|
||||
private VadPostConfEntity _vad_post_conf;
|
||||
|
||||
public AliFsmnVad(string modelFilePath, string configFilePath, string mvnFilePath, int batchSize = 1)
|
||||
{
|
||||
SessionOptions options = new SessionOptions();
|
||||
options.AppendExecutionProvider_CPU(0);
|
||||
options.InterOpNumThreads = 1;
|
||||
_onnxSession = new InferenceSession(modelFilePath, options);
|
||||
|
||||
VadYamlEntity vadYamlEntity = YamlHelper.ReadYaml<VadYamlEntity>(configFilePath);
|
||||
_wavFrontend = new WavFrontend(mvnFilePath, vadYamlEntity.frontend_conf);
|
||||
_frontend = vadYamlEntity.frontend;
|
||||
_vad_post_conf = vadYamlEntity.model_conf;
|
||||
_batchSize = batchSize;
|
||||
_max_end_sil = _max_end_sil != int.MinValue ? _max_end_sil : vadYamlEntity.model_conf.max_end_silence_time;
|
||||
_encoderConfEntity = vadYamlEntity.encoder_conf;
|
||||
|
||||
ILoggerFactory loggerFactory = new LoggerFactory();
|
||||
_logger = new Logger<AliFsmnVad>(loggerFactory);
|
||||
}
|
||||
|
||||
public SegmentEntity[] GetSegments(List<float[]> samples)
|
||||
{
|
||||
int waveform_nums = samples.Count;
|
||||
_batchSize = Math.Min(waveform_nums, _batchSize);
|
||||
SegmentEntity[] segments = new SegmentEntity[waveform_nums];
|
||||
for (int beg_idx = 0; beg_idx < waveform_nums; beg_idx += _batchSize)
|
||||
{
|
||||
int end_idx = Math.Min(waveform_nums, beg_idx + _batchSize);
|
||||
List<float[]> waveform_list = new List<float[]>();
|
||||
for (int i = beg_idx; i < end_idx; i++)
|
||||
{
|
||||
waveform_list.Add(samples[i]);
|
||||
}
|
||||
List<VadInputEntity> vadInputEntitys = ExtractFeats(waveform_list);
|
||||
try
|
||||
{
|
||||
int t_offset = 0;
|
||||
int step = Math.Min(waveform_list.Max(x => x.Length), 6000);
|
||||
bool is_final = true;
|
||||
List<VadOutputEntity> vadOutputEntitys = Infer(vadInputEntitys);
|
||||
for (int batch_num = beg_idx; batch_num < end_idx; batch_num++)
|
||||
{
|
||||
var scores = vadOutputEntitys[batch_num - beg_idx].Scores;
|
||||
SegmentEntity[] segments_part = vadInputEntitys[batch_num].VadScorer.DefaultCall(scores, waveform_list[batch_num - beg_idx], is_final: is_final, max_end_sil: _max_end_sil, online: false);
|
||||
if (segments_part.Length > 0)
|
||||
{
|
||||
#pragma warning disable CS8602 // 解引用可能出现空引用。
|
||||
if (segments[batch_num] == null)
|
||||
{
|
||||
segments[batch_num] = new SegmentEntity();
|
||||
}
|
||||
segments[batch_num].Segment.AddRange(segments_part[0].Segment); //
|
||||
#pragma warning restore CS8602 // 解引用可能出现空引用。
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OnnxRuntimeException ex)
|
||||
{
|
||||
_logger.LogWarning("input wav is silence or noise");
|
||||
segments = null;
|
||||
}
|
||||
// for (int batch_num = 0; batch_num < _batchSize; batch_num++)
|
||||
// {
|
||||
// List<float[]> segment_waveforms = new List<float[]>();
|
||||
// foreach (int[] segment in segments[beg_idx + batch_num].Segment)
|
||||
// {
|
||||
// // (int)(16000 * (segment[0] / 1000.0) * 2);
|
||||
// int frame_length = (((6000 * 400) / 400 - 1) * 160 + 400) / 60 / 1000;
|
||||
// int frame_start = segment[0] * frame_length;
|
||||
// int frame_end = segment[1] * frame_length;
|
||||
// float[] segment_waveform = new float[frame_end - frame_start];
|
||||
// Array.Copy(waveform_list[batch_num], frame_start, segment_waveform, 0, segment_waveform.Length);
|
||||
// segment_waveforms.Add(segment_waveform);
|
||||
// }
|
||||
// segments[beg_idx + batch_num].Waveform.AddRange(segment_waveforms);
|
||||
// }
|
||||
}
|
||||
|
||||
return segments;
|
||||
}
|
||||
|
||||
public SegmentEntity[] GetSegmentsByStep(List<float[]> samples)
|
||||
{
|
||||
int waveform_nums = samples.Count;
|
||||
_batchSize=Math.Min(waveform_nums, _batchSize);
|
||||
SegmentEntity[] segments = new SegmentEntity[waveform_nums];
|
||||
for (int beg_idx = 0; beg_idx < waveform_nums; beg_idx += _batchSize)
|
||||
{
|
||||
int end_idx = Math.Min(waveform_nums, beg_idx + _batchSize);
|
||||
List<float[]> waveform_list = new List<float[]>();
|
||||
for (int i = beg_idx; i < end_idx; i++)
|
||||
{
|
||||
waveform_list.Add(samples[i]);
|
||||
}
|
||||
List<VadInputEntity> vadInputEntitys = ExtractFeats(waveform_list);
|
||||
int feats_len = vadInputEntitys.Max(x => x.SpeechLength);
|
||||
List<float[]> in_cache = new List<float[]>();
|
||||
in_cache = PrepareCache(in_cache);
|
||||
try
|
||||
{
|
||||
int step = Math.Min(vadInputEntitys.Max(x => x.SpeechLength), 6000 * 400);
|
||||
bool is_final = true;
|
||||
for (int t_offset = 0; t_offset < (int)(feats_len); t_offset += Math.Min(step, feats_len - t_offset))
|
||||
{
|
||||
|
||||
if (t_offset + step >= feats_len - 1)
|
||||
{
|
||||
step = feats_len - t_offset;
|
||||
is_final = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
is_final = false;
|
||||
}
|
||||
List<VadInputEntity> vadInputEntitys_step = new List<VadInputEntity>();
|
||||
foreach (VadInputEntity vadInputEntity in vadInputEntitys)
|
||||
{
|
||||
VadInputEntity vadInputEntity_step = new VadInputEntity();
|
||||
float[]? feats = vadInputEntity.Speech;
|
||||
int curr_step = Math.Min(feats.Length - t_offset, step);
|
||||
if (curr_step <= 0)
|
||||
{
|
||||
vadInputEntity_step.Speech = new float[32000];
|
||||
vadInputEntity_step.SpeechLength = 0;
|
||||
vadInputEntity_step.InCaches = in_cache;
|
||||
vadInputEntity_step.Waveform = new float[(((int)(32000) / 400 - 1) * 160 + 400)];
|
||||
vadInputEntitys_step.Add(vadInputEntity_step);
|
||||
continue;
|
||||
}
|
||||
float[]? feats_step = new float[curr_step];
|
||||
Array.Copy(feats, t_offset, feats_step, 0, feats_step.Length);
|
||||
float[]? waveform = vadInputEntity.Waveform;
|
||||
float[]? waveform_step = new float[Math.Min(waveform.Length, ((int)(t_offset + step) / 400 - 1) * 160 + 400) - t_offset / 400 * 160];
|
||||
Array.Copy(waveform, t_offset / 400 * 160, waveform_step, 0, waveform_step.Length);
|
||||
vadInputEntity_step.Speech = feats_step;
|
||||
vadInputEntity_step.SpeechLength = feats_step.Length;
|
||||
vadInputEntity_step.InCaches = vadInputEntity.InCaches;
|
||||
vadInputEntity_step.Waveform = waveform_step;
|
||||
vadInputEntitys_step.Add(vadInputEntity_step);
|
||||
}
|
||||
List<VadOutputEntity> vadOutputEntitys = Infer(vadInputEntitys_step);
|
||||
for (int batch_num = 0; batch_num < _batchSize; batch_num++)
|
||||
{
|
||||
vadInputEntitys[batch_num].InCaches = vadOutputEntitys[batch_num].OutCaches;
|
||||
var scores = vadOutputEntitys[batch_num].Scores;
|
||||
SegmentEntity[] segments_part = vadInputEntitys[batch_num].VadScorer.DefaultCall(scores, vadInputEntitys_step[batch_num].Waveform, is_final: is_final, max_end_sil: _max_end_sil, online: false);
|
||||
if (segments_part.Length > 0)
|
||||
{
|
||||
|
||||
#pragma warning disable CS8602 // 解引用可能出现空引用。
|
||||
if (segments[beg_idx + batch_num] == null)
|
||||
{
|
||||
segments[beg_idx + batch_num] = new SegmentEntity();
|
||||
}
|
||||
if (segments_part[0] != null)
|
||||
{
|
||||
segments[beg_idx + batch_num].Segment.AddRange(segments_part[0].Segment);
|
||||
}
|
||||
#pragma warning restore CS8602 // 解引用可能出现空引用。
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OnnxRuntimeException ex)
|
||||
{
|
||||
_logger.LogWarning("input wav is silence or noise");
|
||||
segments = null;
|
||||
}
|
||||
// for (int batch_num = 0; batch_num < _batchSize; batch_num++)
|
||||
// {
|
||||
// List<float[]> segment_waveforms=new List<float[]>();
|
||||
// foreach (int[] segment in segments[beg_idx + batch_num].Segment)
|
||||
// {
|
||||
// // (int)(16000 * (segment[0] / 1000.0) * 2);
|
||||
// int frame_length = (((6000 * 400) / 400 - 1) * 160 + 400) / 60 / 1000;
|
||||
// int frame_start = segment[0] * frame_length;
|
||||
// int frame_end = segment[1] * frame_length;
|
||||
// if(frame_end > waveform_list[batch_num].Length)
|
||||
// {
|
||||
// break;
|
||||
// }
|
||||
// float[] segment_waveform = new float[frame_end - frame_start];
|
||||
// Array.Copy(waveform_list[batch_num], frame_start, segment_waveform, 0, segment_waveform.Length);
|
||||
// segment_waveforms.Add(segment_waveform);
|
||||
// }
|
||||
// segments[beg_idx + batch_num].Waveform.AddRange(segment_waveforms);
|
||||
// }
|
||||
|
||||
}
|
||||
return segments;
|
||||
}
|
||||
|
||||
private List<float[]> PrepareCache(List<float[]> in_cache)
|
||||
{
|
||||
if (in_cache.Count > 0)
|
||||
{
|
||||
return in_cache;
|
||||
}
|
||||
|
||||
int fsmn_layers = _encoderConfEntity.fsmn_layers;
|
||||
|
||||
int proj_dim = _encoderConfEntity.proj_dim;
|
||||
int lorder = _encoderConfEntity.lorder;
|
||||
|
||||
for (int i = 0; i < fsmn_layers; i++)
|
||||
{
|
||||
float[] cache = new float[1 * proj_dim * (lorder - 1) * 1];
|
||||
in_cache.Add(cache);
|
||||
}
|
||||
return in_cache;
|
||||
}
|
||||
|
||||
private List<VadInputEntity> ExtractFeats(List<float[]> waveform_list)
|
||||
{
|
||||
List<float[]> in_cache = new List<float[]>();
|
||||
in_cache = PrepareCache(in_cache);
|
||||
List<VadInputEntity> vadInputEntitys = new List<VadInputEntity>();
|
||||
foreach (var waveform in waveform_list)
|
||||
{
|
||||
float[] fbanks = _wavFrontend.GetFbank(waveform);
|
||||
float[] features = _wavFrontend.LfrCmvn(fbanks);
|
||||
VadInputEntity vadInputEntity = new VadInputEntity();
|
||||
vadInputEntity.Waveform = waveform;
|
||||
vadInputEntity.Speech = features;
|
||||
vadInputEntity.SpeechLength = features.Length;
|
||||
vadInputEntity.InCaches = in_cache;
|
||||
vadInputEntity.VadScorer = new E2EVadModel(_vad_post_conf);
|
||||
vadInputEntitys.Add(vadInputEntity);
|
||||
}
|
||||
return vadInputEntitys;
|
||||
}
|
||||
/// <summary>
|
||||
/// 一维数组转3维数组
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <param name="len">一维长</param>
|
||||
/// <param name="wid">二维长</param>
|
||||
/// <returns></returns>
|
||||
public static T[,,] DimOneToThree<T>(T[] oneDimObj, int len, int wid)
|
||||
{
|
||||
if (oneDimObj.Length % (len * wid) != 0)
|
||||
return null;
|
||||
int height = oneDimObj.Length / (len * wid);
|
||||
T[,,] threeDimObj = new T[len, wid, height];
|
||||
|
||||
for (int i = 0; i < oneDimObj.Length; i++)
|
||||
{
|
||||
threeDimObj[i / (wid * height), (i / height) % wid, i % height] = oneDimObj[i];
|
||||
}
|
||||
return threeDimObj;
|
||||
}
|
||||
|
||||
private List<VadOutputEntity> Infer(List<VadInputEntity> vadInputEntitys)
|
||||
{
|
||||
List<VadOutputEntity> vadOutputEntities = new List<VadOutputEntity>();
|
||||
foreach (VadInputEntity vadInputEntity in vadInputEntitys)
|
||||
{
|
||||
int batchSize = 1;//_batchSize
|
||||
var inputMeta = _onnxSession.InputMetadata;
|
||||
var container = new List<NamedOnnxValue>();
|
||||
int[] dim = new int[] { batchSize, vadInputEntity.Speech.Length / 400 / batchSize, 400 };
|
||||
var tensor = new DenseTensor<float>(vadInputEntity.Speech, dim, false);
|
||||
container.Add(NamedOnnxValue.CreateFromTensor<float>("speech", tensor));
|
||||
|
||||
int i = 0;
|
||||
foreach (var cache in vadInputEntity.InCaches)
|
||||
{
|
||||
int[] cache_dim = new int[] { 1, 128, cache.Length / 128 / 1, 1 };
|
||||
var cache_tensor = new DenseTensor<float>(cache, cache_dim, false);
|
||||
container.Add(NamedOnnxValue.CreateFromTensor<float>("in_cache" + i.ToString(), cache_tensor));
|
||||
i++;
|
||||
}
|
||||
|
||||
IDisposableReadOnlyCollection<DisposableNamedOnnxValue> results = _onnxSession.Run(container);
|
||||
var resultsArray = results.ToArray();
|
||||
VadOutputEntity vadOutputEntity = new VadOutputEntity();
|
||||
for (int j = 0; j < resultsArray.Length; j++)
|
||||
{
|
||||
if (resultsArray[j].Name.Equals("logits"))
|
||||
{
|
||||
Tensor<float> tensors = resultsArray[0].AsTensor<float>();
|
||||
var _scores = DimOneToThree<float>(tensors.ToArray(), 1, tensors.Dimensions[1]);
|
||||
vadOutputEntity.Scores = _scores;
|
||||
}
|
||||
if (resultsArray[j].Name.StartsWith("out_cache"))
|
||||
{
|
||||
vadOutputEntity.OutCaches.Add(resultsArray[j].AsEnumerable<float>().ToArray());
|
||||
}
|
||||
|
||||
}
|
||||
vadOutputEntities.Add(vadOutputEntity);
|
||||
}
|
||||
|
||||
return vadOutputEntities;
|
||||
}
|
||||
|
||||
private float[] PadSequence(List<VadInputEntity> modelInputs)
|
||||
{
|
||||
int max_speech_length = modelInputs.Max(x => x.SpeechLength);
|
||||
int speech_length = max_speech_length * modelInputs.Count;
|
||||
float[] speech = new float[speech_length];
|
||||
float[,] xxx = new float[modelInputs.Count, max_speech_length];
|
||||
for (int i = 0; i < modelInputs.Count; i++)
|
||||
{
|
||||
if (max_speech_length == modelInputs[i].SpeechLength)
|
||||
{
|
||||
for (int j = 0; j < xxx.GetLength(1); j++)
|
||||
{
|
||||
#pragma warning disable CS8602 // 解引用可能出现空引用。
|
||||
xxx[i, j] = modelInputs[i].Speech[j];
|
||||
#pragma warning restore CS8602 // 解引用可能出现空引用。
|
||||
}
|
||||
continue;
|
||||
}
|
||||
float[] nullspeech = new float[max_speech_length - modelInputs[i].SpeechLength];
|
||||
float[]? curr_speech = modelInputs[i].Speech;
|
||||
float[] padspeech = new float[max_speech_length];
|
||||
// ///////////////////////////////////////////////////
|
||||
var arr_neg_mean = _onnxSession.ModelMetadata.CustomMetadataMap["neg_mean"].ToString().Split(',').ToArray();
|
||||
double[] neg_mean = arr_neg_mean.Select(x => (double)Convert.ToDouble(x)).ToArray();
|
||||
var arr_inv_stddev = _onnxSession.ModelMetadata.CustomMetadataMap["inv_stddev"].ToString().Split(',').ToArray();
|
||||
double[] inv_stddev = arr_inv_stddev.Select(x => (double)Convert.ToDouble(x)).ToArray();
|
||||
|
||||
int dim = neg_mean.Length;
|
||||
for (int j = 0; j < max_speech_length; j++)
|
||||
{
|
||||
int k = new Random().Next(0, dim);
|
||||
padspeech[j] = (float)((float)(0 + neg_mean[k]) * inv_stddev[k]);
|
||||
}
|
||||
Array.Copy(curr_speech, 0, padspeech, 0, curr_speech.Length);
|
||||
for (int j = 0; j < padspeech.Length; j++)
|
||||
{
|
||||
#pragma warning disable CS8602 // 解引用可能出现空引用。
|
||||
xxx[i, j] = padspeech[j];
|
||||
#pragma warning restore CS8602 // 解引用可能出现空引用。
|
||||
}
|
||||
|
||||
}
|
||||
int s = 0;
|
||||
for (int i = 0; i < xxx.GetLength(0); i++)
|
||||
{
|
||||
for (int j = 0; j < xxx.GetLength(1); j++)
|
||||
{
|
||||
speech[s] = xxx[i, j];
|
||||
s++;
|
||||
}
|
||||
}
|
||||
return speech;
|
||||
}
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (_onnxSession != null)
|
||||
{
|
||||
_onnxSession.Dispose();
|
||||
}
|
||||
if (_wavFrontend != null)
|
||||
{
|
||||
_wavFrontend.Dispose();
|
||||
}
|
||||
if (_encoderConfEntity != null)
|
||||
{
|
||||
_encoderConfEntity = null;
|
||||
}
|
||||
if (_vad_post_conf != null)
|
||||
{
|
||||
_vad_post_conf = null;
|
||||
}
|
||||
}
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
~AliFsmnVad()
|
||||
{
|
||||
Dispose(_disposed);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="KaldiNativeFbankSharp" Version="1.1.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.ML.OnnxRuntime" Version="1.15.0" />
|
||||
<PackageReference Include="YamlDotNet" Version="13.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="speech_fsmn_vad_zh-cn-16k-common-pytorch\example\0.wav">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="speech_fsmn_vad_zh-cn-16k-common-pytorch\example\1.wav">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="speech_fsmn_vad_zh-cn-16k-common-pytorch\model.onnx">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="speech_fsmn_vad_zh-cn-16k-common-pytorch\vad.mvn">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="speech_fsmn_vad_zh-cn-16k-common-pytorch\vad.yaml">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,724 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using AliFsmnVadSharp.Model;
|
||||
|
||||
namespace AliFsmnVadSharp
|
||||
{
|
||||
enum VadStateMachine
|
||||
{
|
||||
kVadInStateStartPointNotDetected = 1,
|
||||
kVadInStateInSpeechSegment = 2,
|
||||
kVadInStateEndPointDetected = 3,
|
||||
}
|
||||
enum VadDetectMode
|
||||
{
|
||||
kVadSingleUtteranceDetectMode = 0,
|
||||
kVadMutipleUtteranceDetectMode = 1,
|
||||
}
|
||||
|
||||
|
||||
internal class E2EVadModel
|
||||
{
|
||||
private VadPostConfEntity _vad_opts = new VadPostConfEntity();
|
||||
private WindowDetector _windows_detector = new WindowDetector();
|
||||
private bool _is_final = false;
|
||||
private int _data_buf_start_frame = 0;
|
||||
private int _frm_cnt = 0;
|
||||
private int _latest_confirmed_speech_frame = 0;
|
||||
private int _lastest_confirmed_silence_frame = -1;
|
||||
private int _continous_silence_frame_count = 0;
|
||||
private int _vad_state_machine = (int)VadStateMachine.kVadInStateStartPointNotDetected;
|
||||
private int _confirmed_start_frame = -1;
|
||||
private int _confirmed_end_frame = -1;
|
||||
private int _number_end_time_detected = 0;
|
||||
private int _sil_frame = 0;
|
||||
private int[] _sil_pdf_ids = new int[0];
|
||||
private double _noise_average_decibel = -100.0D;
|
||||
private bool _pre_end_silence_detected = false;
|
||||
private bool _next_seg = true;
|
||||
|
||||
private List<E2EVadSpeechBufWithDoaEntity> _output_data_buf;
|
||||
private int _output_data_buf_offset = 0;
|
||||
private List<E2EVadFrameProbEntity> _frame_probs = new List<E2EVadFrameProbEntity>();
|
||||
private int _max_end_sil_frame_cnt_thresh = 800 - 150;
|
||||
private float _speech_noise_thres = 0.6F;
|
||||
private float[,,] _scores = null;
|
||||
private int _idx_pre_chunk = 0;
|
||||
private bool _max_time_out = false;
|
||||
private List<double> _decibel = new List<double>();
|
||||
private int _data_buf_size = 0;
|
||||
private int _data_buf_all_size = 0;
|
||||
|
||||
public E2EVadModel(VadPostConfEntity vadPostConfEntity)
|
||||
{
|
||||
_vad_opts = vadPostConfEntity;
|
||||
_windows_detector = new WindowDetector(_vad_opts.window_size_ms,
|
||||
_vad_opts.sil_to_speech_time_thres,
|
||||
_vad_opts.speech_to_sil_time_thres,
|
||||
_vad_opts.frame_in_ms);
|
||||
AllResetDetection();
|
||||
}
|
||||
|
||||
private void AllResetDetection()
|
||||
{
|
||||
_is_final = false;
|
||||
_data_buf_start_frame = 0;
|
||||
_frm_cnt = 0;
|
||||
_latest_confirmed_speech_frame = 0;
|
||||
_lastest_confirmed_silence_frame = -1;
|
||||
_continous_silence_frame_count = 0;
|
||||
_vad_state_machine = (int)VadStateMachine.kVadInStateStartPointNotDetected;
|
||||
_confirmed_start_frame = -1;
|
||||
_confirmed_end_frame = -1;
|
||||
_number_end_time_detected = 0;
|
||||
_sil_frame = 0;
|
||||
_sil_pdf_ids = _vad_opts.sil_pdf_ids;
|
||||
_noise_average_decibel = -100.0F;
|
||||
_pre_end_silence_detected = false;
|
||||
_next_seg = true;
|
||||
|
||||
_output_data_buf = new List<E2EVadSpeechBufWithDoaEntity>();
|
||||
_output_data_buf_offset = 0;
|
||||
_frame_probs = new List<E2EVadFrameProbEntity>();
|
||||
_max_end_sil_frame_cnt_thresh = _vad_opts.max_end_silence_time - _vad_opts.speech_to_sil_time_thres;
|
||||
_speech_noise_thres = _vad_opts.speech_noise_thres;
|
||||
_scores = null;
|
||||
_idx_pre_chunk = 0;
|
||||
_max_time_out = false;
|
||||
_decibel = new List<double>();
|
||||
_data_buf_size = 0;
|
||||
_data_buf_all_size = 0;
|
||||
ResetDetection();
|
||||
}
|
||||
|
||||
private void ResetDetection()
|
||||
{
|
||||
_continous_silence_frame_count = 0;
|
||||
_latest_confirmed_speech_frame = 0;
|
||||
_lastest_confirmed_silence_frame = -1;
|
||||
_confirmed_start_frame = -1;
|
||||
_confirmed_end_frame = -1;
|
||||
_vad_state_machine = (int)VadStateMachine.kVadInStateStartPointNotDetected;
|
||||
_windows_detector.Reset();
|
||||
_sil_frame = 0;
|
||||
_frame_probs = new List<E2EVadFrameProbEntity>();
|
||||
}
|
||||
|
||||
private void ComputeDecibel(float[] waveform)
|
||||
{
|
||||
int frame_sample_length = (int)(_vad_opts.frame_length_ms * _vad_opts.sample_rate / 1000);
|
||||
int frame_shift_length = (int)(_vad_opts.frame_in_ms * _vad_opts.sample_rate / 1000);
|
||||
if (_data_buf_all_size == 0)
|
||||
{
|
||||
_data_buf_all_size = waveform.Length;
|
||||
_data_buf_size = _data_buf_all_size;
|
||||
}
|
||||
else
|
||||
{
|
||||
_data_buf_all_size += waveform.Length;
|
||||
}
|
||||
|
||||
for (int offset = 0; offset < waveform.Length - frame_sample_length + 1; offset += frame_shift_length)
|
||||
{
|
||||
float[] _waveform_chunk = new float[frame_sample_length];
|
||||
Array.Copy(waveform, offset, _waveform_chunk, 0, _waveform_chunk.Length);
|
||||
float[] _waveform_chunk_pow = _waveform_chunk.Select(x => (float)Math.Pow((double)x, 2)).ToArray();
|
||||
_decibel.Add(
|
||||
10 * Math.Log10(
|
||||
_waveform_chunk_pow.Sum() + 0.000001
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void ComputeScores(float[,,] scores)
|
||||
{
|
||||
_vad_opts.nn_eval_block_size = scores.GetLength(1);
|
||||
_frm_cnt += scores.GetLength(1);
|
||||
_scores = scores;
|
||||
}
|
||||
|
||||
private void PopDataBufTillFrame(int frame_idx)// need check again
|
||||
{
|
||||
while (_data_buf_start_frame < frame_idx)
|
||||
{
|
||||
if (_data_buf_size >= (int)(_vad_opts.frame_in_ms * _vad_opts.sample_rate / 1000))
|
||||
{
|
||||
_data_buf_start_frame += 1;
|
||||
_data_buf_size = _data_buf_all_size - _data_buf_start_frame * (int)(_vad_opts.frame_in_ms * _vad_opts.sample_rate / 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PopDataToOutputBuf(int start_frm, int frm_cnt, bool first_frm_is_start_point,
|
||||
bool last_frm_is_end_point, bool end_point_is_sent_end)
|
||||
{
|
||||
PopDataBufTillFrame(start_frm);
|
||||
int expected_sample_number = (int)(frm_cnt * _vad_opts.sample_rate * _vad_opts.frame_in_ms / 1000);
|
||||
if (last_frm_is_end_point)
|
||||
{
|
||||
int extra_sample = Math.Max(0, (int)(_vad_opts.frame_length_ms * _vad_opts.sample_rate / 1000 - _vad_opts.sample_rate * _vad_opts.frame_in_ms / 1000));
|
||||
expected_sample_number += (int)(extra_sample);
|
||||
}
|
||||
|
||||
if (end_point_is_sent_end)
|
||||
{
|
||||
expected_sample_number = Math.Max(expected_sample_number, _data_buf_size);
|
||||
}
|
||||
if (_data_buf_size < expected_sample_number)
|
||||
{
|
||||
Console.WriteLine("error in calling pop data_buf\n");
|
||||
}
|
||||
|
||||
if (_output_data_buf.Count == 0 || first_frm_is_start_point)
|
||||
{
|
||||
_output_data_buf.Add(new E2EVadSpeechBufWithDoaEntity());
|
||||
_output_data_buf.Last().Reset();
|
||||
_output_data_buf.Last().start_ms = start_frm * _vad_opts.frame_in_ms;
|
||||
_output_data_buf.Last().end_ms = _output_data_buf.Last().start_ms;
|
||||
_output_data_buf.Last().doa = 0;
|
||||
}
|
||||
|
||||
E2EVadSpeechBufWithDoaEntity cur_seg = _output_data_buf.Last();
|
||||
if (cur_seg.end_ms != start_frm * _vad_opts.frame_in_ms)
|
||||
{
|
||||
Console.WriteLine("warning\n");
|
||||
}
|
||||
|
||||
int out_pos = cur_seg.buffer.Length; // cur_seg.buff现在没做任何操作
|
||||
int data_to_pop = 0;
|
||||
if (end_point_is_sent_end)
|
||||
{
|
||||
data_to_pop = expected_sample_number;
|
||||
}
|
||||
else
|
||||
{
|
||||
data_to_pop = (int)(frm_cnt * _vad_opts.frame_in_ms * _vad_opts.sample_rate / 1000);
|
||||
}
|
||||
if (data_to_pop > _data_buf_size)
|
||||
{
|
||||
Console.WriteLine("VAD data_to_pop is bigger than _data_buf_size!!!\n");
|
||||
data_to_pop = _data_buf_size;
|
||||
expected_sample_number = _data_buf_size;
|
||||
}
|
||||
|
||||
|
||||
cur_seg.doa = 0;
|
||||
for (int sample_cpy_out = 0; sample_cpy_out < data_to_pop; sample_cpy_out++)
|
||||
{
|
||||
out_pos += 1;
|
||||
}
|
||||
for (int sample_cpy_out = data_to_pop; sample_cpy_out < expected_sample_number; sample_cpy_out++)
|
||||
{
|
||||
out_pos += 1;
|
||||
}
|
||||
|
||||
if (cur_seg.end_ms != start_frm * _vad_opts.frame_in_ms)
|
||||
{
|
||||
Console.WriteLine("Something wrong with the VAD algorithm\n");
|
||||
}
|
||||
|
||||
_data_buf_start_frame += frm_cnt;
|
||||
cur_seg.end_ms = (start_frm + frm_cnt) * _vad_opts.frame_in_ms;
|
||||
if (first_frm_is_start_point)
|
||||
{
|
||||
cur_seg.contain_seg_start_point = true;
|
||||
}
|
||||
|
||||
if (last_frm_is_end_point)
|
||||
{
|
||||
cur_seg.contain_seg_end_point = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSilenceDetected(int valid_frame)
|
||||
{
|
||||
_lastest_confirmed_silence_frame = valid_frame;
|
||||
if (_vad_state_machine == (int)VadStateMachine.kVadInStateStartPointNotDetected)
|
||||
{
|
||||
PopDataBufTillFrame(valid_frame);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void OnVoiceDetected(int valid_frame)
|
||||
{
|
||||
_latest_confirmed_speech_frame = valid_frame;
|
||||
PopDataToOutputBuf(valid_frame, 1, false, false, false);
|
||||
}
|
||||
|
||||
private void OnVoiceStart(int start_frame, bool fake_result = false)
|
||||
{
|
||||
if (_vad_opts.do_start_point_detection)
|
||||
{
|
||||
//do nothing
|
||||
}
|
||||
if (_confirmed_start_frame != -1)
|
||||
{
|
||||
|
||||
Console.WriteLine("not reset vad properly\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
_confirmed_start_frame = start_frame;
|
||||
}
|
||||
if (!fake_result || _vad_state_machine == (int)VadStateMachine.kVadInStateStartPointNotDetected)
|
||||
{
|
||||
|
||||
PopDataToOutputBuf(_confirmed_start_frame, 1, true, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnVoiceEnd(int end_frame, bool fake_result, bool is_last_frame)
|
||||
{
|
||||
for (int t = _latest_confirmed_speech_frame + 1; t < end_frame; t++)
|
||||
{
|
||||
OnVoiceDetected(t);
|
||||
}
|
||||
if (_vad_opts.do_end_point_detection)
|
||||
{
|
||||
//do nothing
|
||||
}
|
||||
if (_confirmed_end_frame != -1)
|
||||
{
|
||||
Console.WriteLine("not reset vad properly\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
_confirmed_end_frame = end_frame;
|
||||
}
|
||||
if (!fake_result)
|
||||
{
|
||||
_sil_frame = 0;
|
||||
PopDataToOutputBuf(_confirmed_end_frame, 1, false, true, is_last_frame);
|
||||
}
|
||||
_number_end_time_detected += 1;
|
||||
}
|
||||
|
||||
private void MaybeOnVoiceEndIfLastFrame(bool is_final_frame, int cur_frm_idx)
|
||||
{
|
||||
if (is_final_frame)
|
||||
{
|
||||
OnVoiceEnd(cur_frm_idx, false, true);
|
||||
_vad_state_machine = (int)VadStateMachine.kVadInStateEndPointDetected;
|
||||
}
|
||||
}
|
||||
|
||||
private int GetLatency()
|
||||
{
|
||||
return (int)(LatencyFrmNumAtStartPoint() * _vad_opts.frame_in_ms);
|
||||
}
|
||||
|
||||
private int LatencyFrmNumAtStartPoint()
|
||||
{
|
||||
int vad_latency = _windows_detector.GetWinSize();
|
||||
if (_vad_opts.do_extend != 0)
|
||||
{
|
||||
vad_latency += (int)(_vad_opts.lookback_time_start_point / _vad_opts.frame_in_ms);
|
||||
}
|
||||
return vad_latency;
|
||||
}
|
||||
|
||||
private FrameState GetFrameState(int t)
|
||||
{
|
||||
|
||||
FrameState frame_state = FrameState.kFrameStateInvalid;
|
||||
double cur_decibel = _decibel[t];
|
||||
double cur_snr = cur_decibel - _noise_average_decibel;
|
||||
if (cur_decibel < _vad_opts.decibel_thres)
|
||||
{
|
||||
frame_state = FrameState.kFrameStateSil;
|
||||
DetectOneFrame(frame_state, t, false);
|
||||
return frame_state;
|
||||
}
|
||||
|
||||
|
||||
double sum_score = 0.0D;
|
||||
double noise_prob = 0.0D;
|
||||
Trace.Assert(_sil_pdf_ids.Length == _vad_opts.silence_pdf_num, "");
|
||||
if (_sil_pdf_ids.Length > 0)
|
||||
{
|
||||
Trace.Assert(_scores.GetLength(0) == 1, "只支持batch_size = 1的测试"); // 只支持batch_size = 1的测试
|
||||
float[] sil_pdf_scores = new float[_sil_pdf_ids.Length];
|
||||
int j = 0;
|
||||
foreach (int sil_pdf_id in _sil_pdf_ids)
|
||||
{
|
||||
sil_pdf_scores[j] = _scores[0,t - _idx_pre_chunk,sil_pdf_id];
|
||||
j++;
|
||||
}
|
||||
sum_score = sil_pdf_scores.Length == 0 ? 0 : sil_pdf_scores.Sum();
|
||||
noise_prob = Math.Log(sum_score) * _vad_opts.speech_2_noise_ratio;
|
||||
double total_score = 1.0D;
|
||||
sum_score = total_score - sum_score;
|
||||
}
|
||||
double speech_prob = Math.Log(sum_score);
|
||||
if (_vad_opts.output_frame_probs)
|
||||
{
|
||||
E2EVadFrameProbEntity frame_prob = new E2EVadFrameProbEntity();
|
||||
frame_prob.noise_prob = noise_prob;
|
||||
frame_prob.speech_prob = speech_prob;
|
||||
frame_prob.score = sum_score;
|
||||
frame_prob.frame_id = t;
|
||||
_frame_probs.Add(frame_prob);
|
||||
}
|
||||
|
||||
if (Math.Exp(speech_prob) >= Math.Exp(noise_prob) + _speech_noise_thres)
|
||||
{
|
||||
if (cur_snr >= _vad_opts.snr_thres && cur_decibel >= _vad_opts.decibel_thres)
|
||||
{
|
||||
frame_state = FrameState.kFrameStateSpeech;
|
||||
}
|
||||
else
|
||||
{
|
||||
frame_state = FrameState.kFrameStateSil;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
frame_state = FrameState.kFrameStateSil;
|
||||
if (_noise_average_decibel < -99.9)
|
||||
{
|
||||
_noise_average_decibel = cur_decibel;
|
||||
}
|
||||
else
|
||||
{
|
||||
_noise_average_decibel = (cur_decibel + _noise_average_decibel * (_vad_opts.noise_frame_num_used_for_snr - 1)) / _vad_opts.noise_frame_num_used_for_snr;
|
||||
}
|
||||
}
|
||||
return frame_state;
|
||||
}
|
||||
|
||||
public SegmentEntity[] DefaultCall(float[,,] score, float[] waveform,
|
||||
bool is_final = false, int max_end_sil = 800, bool online = false
|
||||
)
|
||||
{
|
||||
_max_end_sil_frame_cnt_thresh = max_end_sil - _vad_opts.speech_to_sil_time_thres;
|
||||
// compute decibel for each frame
|
||||
ComputeDecibel(waveform);
|
||||
ComputeScores(score);
|
||||
if (!is_final)
|
||||
{
|
||||
DetectCommonFrames();
|
||||
}
|
||||
else
|
||||
{
|
||||
DetectLastFrames();
|
||||
}
|
||||
int batchSize = score.GetLength(0);
|
||||
SegmentEntity[] segments = new SegmentEntity[batchSize];
|
||||
for (int batch_num = 0; batch_num < batchSize; batch_num++) // only support batch_size = 1 now
|
||||
{
|
||||
List<int[]> segment_batch = new List<int[]>();
|
||||
if (_output_data_buf.Count > 0)
|
||||
{
|
||||
for (int i = _output_data_buf_offset; i < _output_data_buf.Count; i++)
|
||||
{
|
||||
int start_ms;
|
||||
int end_ms;
|
||||
if (online)
|
||||
{
|
||||
if (!_output_data_buf[i].contain_seg_start_point)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!_next_seg && !_output_data_buf[i].contain_seg_end_point)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
start_ms = _next_seg ? _output_data_buf[i].start_ms : -1;
|
||||
if (_output_data_buf[i].contain_seg_end_point)
|
||||
{
|
||||
end_ms = _output_data_buf[i].end_ms;
|
||||
_next_seg = true;
|
||||
_output_data_buf_offset += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
end_ms = -1;
|
||||
_next_seg = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!is_final && (!_output_data_buf[i].contain_seg_start_point || !_output_data_buf[i].contain_seg_end_point))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
start_ms = _output_data_buf[i].start_ms;
|
||||
end_ms = _output_data_buf[i].end_ms;
|
||||
_output_data_buf_offset += 1;
|
||||
|
||||
}
|
||||
int[] segment_ms = new int[] { start_ms, end_ms };
|
||||
segment_batch.Add(segment_ms);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (segment_batch.Count > 0)
|
||||
{
|
||||
if (segments[batch_num] == null)
|
||||
{
|
||||
segments[batch_num] = new SegmentEntity();
|
||||
}
|
||||
segments[batch_num].Segment.AddRange(segment_batch);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_final)
|
||||
{
|
||||
// reset class variables and clear the dict for the next query
|
||||
AllResetDetection();
|
||||
}
|
||||
|
||||
return segments;
|
||||
}
|
||||
|
||||
private int DetectCommonFrames()
|
||||
{
|
||||
if (_vad_state_machine == (int)VadStateMachine.kVadInStateEndPointDetected)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
for (int i = _vad_opts.nn_eval_block_size - 1; i > -1; i += -1)
|
||||
{
|
||||
FrameState frame_state = FrameState.kFrameStateInvalid;
|
||||
frame_state = GetFrameState(_frm_cnt - 1 - i);
|
||||
DetectOneFrame(frame_state, _frm_cnt - 1 - i, false);
|
||||
}
|
||||
|
||||
_idx_pre_chunk += _scores.GetLength(1)* _scores.GetLength(0); //_scores.shape[1];
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int DetectLastFrames()
|
||||
{
|
||||
if (_vad_state_machine == (int)VadStateMachine.kVadInStateEndPointDetected)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
try
|
||||
{
|
||||
for (int i = _vad_opts.nn_eval_block_size - 1; i > -1; i += -1)
|
||||
{
|
||||
FrameState frame_state = FrameState.kFrameStateInvalid;
|
||||
frame_state = GetFrameState(_frm_cnt - 1 - i);
|
||||
if (i != 0)
|
||||
{
|
||||
DetectOneFrame(frame_state, _frm_cnt - 1 - i, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
DetectOneFrame(frame_state, _frm_cnt - 1, true);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void DetectOneFrame(FrameState cur_frm_state, int cur_frm_idx, bool is_final_frame)
|
||||
{
|
||||
FrameState tmp_cur_frm_state = FrameState.kFrameStateInvalid;
|
||||
if (cur_frm_state == FrameState.kFrameStateSpeech)
|
||||
{
|
||||
if (Math.Abs(1.0) > _vad_opts.fe_prior_thres)//Fabs
|
||||
{
|
||||
tmp_cur_frm_state = FrameState.kFrameStateSpeech;
|
||||
}
|
||||
else
|
||||
{
|
||||
tmp_cur_frm_state = FrameState.kFrameStateSil;
|
||||
}
|
||||
}
|
||||
else if (cur_frm_state == FrameState.kFrameStateSil)
|
||||
{
|
||||
tmp_cur_frm_state = FrameState.kFrameStateSil;
|
||||
}
|
||||
|
||||
AudioChangeState state_change = _windows_detector.DetectOneFrame(tmp_cur_frm_state, cur_frm_idx);
|
||||
int frm_shift_in_ms = _vad_opts.frame_in_ms;
|
||||
if (AudioChangeState.kChangeStateSil2Speech == state_change)
|
||||
{
|
||||
int silence_frame_count = _continous_silence_frame_count; // no used
|
||||
_continous_silence_frame_count = 0;
|
||||
_pre_end_silence_detected = false;
|
||||
int start_frame = 0;
|
||||
if (_vad_state_machine == (int)VadStateMachine.kVadInStateStartPointNotDetected)
|
||||
{
|
||||
start_frame = Math.Max(_data_buf_start_frame, cur_frm_idx - LatencyFrmNumAtStartPoint());
|
||||
OnVoiceStart(start_frame);
|
||||
_vad_state_machine = (int)VadStateMachine.kVadInStateInSpeechSegment;
|
||||
for (int t = start_frame + 1; t < cur_frm_idx + 1; t++)
|
||||
{
|
||||
OnVoiceDetected(t);
|
||||
}
|
||||
|
||||
}
|
||||
else if (_vad_state_machine == (int)VadStateMachine.kVadInStateInSpeechSegment)
|
||||
{
|
||||
for (int t = _latest_confirmed_speech_frame + 1; t < cur_frm_idx; t++)
|
||||
{
|
||||
OnVoiceDetected(t);
|
||||
}
|
||||
if (cur_frm_idx - _confirmed_start_frame + 1 > _vad_opts.max_single_segment_time / frm_shift_in_ms)
|
||||
{
|
||||
OnVoiceEnd(cur_frm_idx, false, false);
|
||||
_vad_state_machine = (int)VadStateMachine.kVadInStateEndPointDetected;
|
||||
}
|
||||
|
||||
else if (!is_final_frame)
|
||||
{
|
||||
OnVoiceDetected(cur_frm_idx);
|
||||
}
|
||||
else
|
||||
{
|
||||
MaybeOnVoiceEndIfLastFrame(is_final_frame, cur_frm_idx);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (AudioChangeState.kChangeStateSpeech2Sil == state_change)
|
||||
{
|
||||
_continous_silence_frame_count = 0;
|
||||
if (_vad_state_machine == (int)VadStateMachine.kVadInStateStartPointNotDetected)
|
||||
{ return; }
|
||||
else if (_vad_state_machine == (int)VadStateMachine.kVadInStateInSpeechSegment)
|
||||
{
|
||||
if (cur_frm_idx - _confirmed_start_frame + 1 > _vad_opts.max_single_segment_time / frm_shift_in_ms)
|
||||
{
|
||||
OnVoiceEnd(cur_frm_idx, false, false);
|
||||
_vad_state_machine = (int)VadStateMachine.kVadInStateEndPointDetected;
|
||||
}
|
||||
else if (!is_final_frame)
|
||||
{
|
||||
OnVoiceDetected(cur_frm_idx);
|
||||
}
|
||||
else
|
||||
{
|
||||
MaybeOnVoiceEndIfLastFrame(is_final_frame, cur_frm_idx);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (AudioChangeState.kChangeStateSpeech2Speech == state_change)
|
||||
{
|
||||
_continous_silence_frame_count = 0;
|
||||
if (_vad_state_machine == (int)VadStateMachine.kVadInStateInSpeechSegment)
|
||||
{
|
||||
if (cur_frm_idx - _confirmed_start_frame + 1 > _vad_opts.max_single_segment_time / frm_shift_in_ms)
|
||||
{
|
||||
_max_time_out = true;
|
||||
OnVoiceEnd(cur_frm_idx, false, false);
|
||||
_vad_state_machine = (int)VadStateMachine.kVadInStateEndPointDetected;
|
||||
}
|
||||
else if (!is_final_frame)
|
||||
{
|
||||
OnVoiceDetected(cur_frm_idx);
|
||||
}
|
||||
else
|
||||
{
|
||||
MaybeOnVoiceEndIfLastFrame(is_final_frame, cur_frm_idx);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
else if (AudioChangeState.kChangeStateSil2Sil == state_change)
|
||||
{
|
||||
_continous_silence_frame_count += 1;
|
||||
if (_vad_state_machine == (int)VadStateMachine.kVadInStateStartPointNotDetected)
|
||||
{
|
||||
// silence timeout, return zero length decision
|
||||
if (((_vad_opts.detect_mode == (int)VadDetectMode.kVadSingleUtteranceDetectMode) && (
|
||||
_continous_silence_frame_count * frm_shift_in_ms > _vad_opts.max_start_silence_time)) || (is_final_frame && _number_end_time_detected == 0))
|
||||
{
|
||||
for (int t = _lastest_confirmed_silence_frame + 1; t < cur_frm_idx; t++)
|
||||
{
|
||||
OnSilenceDetected(t);
|
||||
}
|
||||
OnVoiceStart(0, true);
|
||||
OnVoiceEnd(0, true, false);
|
||||
_vad_state_machine = (int)VadStateMachine.kVadInStateEndPointDetected;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cur_frm_idx >= LatencyFrmNumAtStartPoint())
|
||||
{
|
||||
OnSilenceDetected(cur_frm_idx - LatencyFrmNumAtStartPoint());
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (_vad_state_machine == (int)VadStateMachine.kVadInStateInSpeechSegment)
|
||||
{
|
||||
if (_continous_silence_frame_count * frm_shift_in_ms >= _max_end_sil_frame_cnt_thresh)
|
||||
{
|
||||
int lookback_frame = (int)(_max_end_sil_frame_cnt_thresh / frm_shift_in_ms);
|
||||
if (_vad_opts.do_extend != 0)
|
||||
{
|
||||
lookback_frame -= (int)(_vad_opts.lookahead_time_end_point / frm_shift_in_ms);
|
||||
lookback_frame -= 1;
|
||||
lookback_frame = Math.Max(0, lookback_frame);
|
||||
}
|
||||
|
||||
OnVoiceEnd(cur_frm_idx - lookback_frame, false, false);
|
||||
_vad_state_machine = (int)VadStateMachine.kVadInStateEndPointDetected;
|
||||
}
|
||||
else if (cur_frm_idx - _confirmed_start_frame + 1 > _vad_opts.max_single_segment_time / frm_shift_in_ms)
|
||||
{
|
||||
OnVoiceEnd(cur_frm_idx, false, false);
|
||||
_vad_state_machine = (int)VadStateMachine.kVadInStateEndPointDetected;
|
||||
}
|
||||
|
||||
else if (_vad_opts.do_extend != 0 && !is_final_frame)
|
||||
{
|
||||
if (_continous_silence_frame_count <= (int)(_vad_opts.lookahead_time_end_point / frm_shift_in_ms))
|
||||
{
|
||||
OnVoiceDetected(cur_frm_idx);
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
MaybeOnVoiceEndIfLastFrame(is_final_frame, cur_frm_idx);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (_vad_state_machine == (int)VadStateMachine.kVadInStateEndPointDetected && _vad_opts.detect_mode == (int)VadDetectMode.kVadMutipleUtteranceDetectMode)
|
||||
{
|
||||
ResetDetection();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AliFsmnVadSharp.Model
|
||||
{
|
||||
internal class CmvnEntity
|
||||
{
|
||||
private List<float> _means = new List<float>();
|
||||
private List<float> _vars = new List<float>();
|
||||
|
||||
public List<float> Means { get => _means; set => _means = value; }
|
||||
public List<float> Vars { get => _vars; set => _vars = value; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AliFsmnVadSharp.Model
|
||||
{
|
||||
internal class E2EVadFrameProbEntity
|
||||
{
|
||||
private double _noise_prob = 0.0F;
|
||||
private double _speech_prob = 0.0F;
|
||||
private double _score = 0.0F;
|
||||
private int _frame_id = 0;
|
||||
private int _frm_state = 0;
|
||||
|
||||
public double noise_prob { get => _noise_prob; set => _noise_prob = value; }
|
||||
public double speech_prob { get => _speech_prob; set => _speech_prob = value; }
|
||||
public double score { get => _score; set => _score = value; }
|
||||
public int frame_id { get => _frame_id; set => _frame_id = value; }
|
||||
public int frm_state { get => _frm_state; set => _frm_state = value; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
// AliFsmnVadSharp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
|
||||
// AliFsmnVadSharp.Model.E2EVadSpeechBufWithDoaEntity
|
||||
internal class E2EVadSpeechBufWithDoaEntity
|
||||
{
|
||||
private int _start_ms = 0;
|
||||
|
||||
private int _end_ms = 0;
|
||||
|
||||
private byte[]? _buffer;
|
||||
|
||||
private bool _contain_seg_start_point = false;
|
||||
|
||||
private bool _contain_seg_end_point = false;
|
||||
|
||||
private int _doa = 0;
|
||||
|
||||
public int start_ms
|
||||
{
|
||||
get
|
||||
{
|
||||
return _start_ms;
|
||||
}
|
||||
set
|
||||
{
|
||||
_start_ms = value;
|
||||
}
|
||||
}
|
||||
|
||||
public int end_ms
|
||||
{
|
||||
get
|
||||
{
|
||||
return _end_ms;
|
||||
}
|
||||
set
|
||||
{
|
||||
_end_ms = value;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[]? buffer
|
||||
{
|
||||
get
|
||||
{
|
||||
return _buffer;
|
||||
}
|
||||
set
|
||||
{
|
||||
_buffer = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool contain_seg_start_point
|
||||
{
|
||||
get
|
||||
{
|
||||
return _contain_seg_start_point;
|
||||
}
|
||||
set
|
||||
{
|
||||
_contain_seg_start_point = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool contain_seg_end_point
|
||||
{
|
||||
get
|
||||
{
|
||||
return _contain_seg_end_point;
|
||||
}
|
||||
set
|
||||
{
|
||||
_contain_seg_end_point = value;
|
||||
}
|
||||
}
|
||||
|
||||
public int doa
|
||||
{
|
||||
get
|
||||
{
|
||||
return _doa;
|
||||
}
|
||||
set
|
||||
{
|
||||
_doa = value;
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_start_ms = 0;
|
||||
_end_ms = 0;
|
||||
_buffer = new byte[0];
|
||||
_contain_seg_start_point = false;
|
||||
_contain_seg_end_point = false;
|
||||
_doa = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AliFsmnVadSharp.Model
|
||||
{
|
||||
public class EncoderConfEntity
|
||||
{
|
||||
private int _input_dim=400;
|
||||
private int _input_affineDim = 140;
|
||||
private int _fsmn_layers = 4;
|
||||
private int _linear_dim = 250;
|
||||
private int _proj_dim = 128;
|
||||
private int _lorder = 20;
|
||||
private int _rorder = 0;
|
||||
private int _lstride = 1;
|
||||
private int _rstride = 0;
|
||||
private int _output_dffine_dim = 140;
|
||||
private int _output_dim = 248;
|
||||
|
||||
public int input_dim { get => _input_dim; set => _input_dim = value; }
|
||||
public int input_affine_dim { get => _input_affineDim; set => _input_affineDim = value; }
|
||||
public int fsmn_layers { get => _fsmn_layers; set => _fsmn_layers = value; }
|
||||
public int linear_dim { get => _linear_dim; set => _linear_dim = value; }
|
||||
public int proj_dim { get => _proj_dim; set => _proj_dim = value; }
|
||||
public int lorder { get => _lorder; set => _lorder = value; }
|
||||
public int rorder { get => _rorder; set => _rorder = value; }
|
||||
public int lstride { get => _lstride; set => _lstride = value; }
|
||||
public int rstride { get => _rstride; set => _rstride = value; }
|
||||
public int output_affine_dim { get => _output_dffine_dim; set => _output_dffine_dim = value; }
|
||||
public int output_dim { get => _output_dim; set => _output_dim = value; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AliFsmnVadSharp.Model
|
||||
{
|
||||
public class FrontendConfEntity
|
||||
{
|
||||
private int _fs = 16000;
|
||||
private string _window = "hamming";
|
||||
private int _n_mels = 80;
|
||||
private int _frame_length = 25;
|
||||
private int _frame_shift = 10;
|
||||
private float _dither = 0.0F;
|
||||
private int _lfr_m = 5;
|
||||
private int _lfr_n = 1;
|
||||
|
||||
public int fs { get => _fs; set => _fs = value; }
|
||||
public string window { get => _window; set => _window = value; }
|
||||
public int n_mels { get => _n_mels; set => _n_mels = value; }
|
||||
public int frame_length { get => _frame_length; set => _frame_length = value; }
|
||||
public int frame_shift { get => _frame_shift; set => _frame_shift = value; }
|
||||
public float dither { get => _dither; set => _dither = value; }
|
||||
public int lfr_m { get => _lfr_m; set => _lfr_m = value; }
|
||||
public int lfr_n { get => _lfr_n; set => _lfr_n = value; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AliFsmnVadSharp.Model
|
||||
{
|
||||
public class SegmentEntity
|
||||
{
|
||||
private List<int[]> _segment=new List<int[]>();
|
||||
private List<float[]> _waveform=new List<float[]>();
|
||||
|
||||
public List<int[]> Segment { get => _segment; set => _segment = value; }
|
||||
public List<float[]> Waveform { get => _waveform; set => _waveform = value; }
|
||||
//public SegmentEntity()
|
||||
//{
|
||||
// int[] t=new int[0];
|
||||
// _segment.Add(t);
|
||||
//}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AliFsmnVadSharp.Model
|
||||
{
|
||||
internal class VadInputEntity
|
||||
{
|
||||
private float[]? _speech;
|
||||
private int _speechLength;
|
||||
private List<float[]> _inCaches = new List<float[]>();
|
||||
private float[]? _waveform;
|
||||
private E2EVadModel _vad_scorer;
|
||||
|
||||
public float[]? Speech { get => _speech; set => _speech = value; }
|
||||
public int SpeechLength { get => _speechLength; set => _speechLength = value; }
|
||||
public List<float[]> InCaches { get => _inCaches; set => _inCaches = value; }
|
||||
public float[] Waveform { get => _waveform; set => _waveform = value; }
|
||||
internal E2EVadModel VadScorer { get => _vad_scorer; set => _vad_scorer = value; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AliFsmnVadSharp.Model
|
||||
{
|
||||
internal class VadOutputEntity
|
||||
{
|
||||
private float[,,]? _scores;
|
||||
private List<float[]> _outCaches=new List<float[]>();
|
||||
private float[]? _waveform;
|
||||
|
||||
public float[,,]? Scores { get => _scores; set => _scores = value; }
|
||||
public List<float[]> OutCaches { get => _outCaches; set => _outCaches = value; }
|
||||
public float[] Waveform { get => _waveform; set => _waveform = value; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AliFsmnVadSharp.Model
|
||||
{
|
||||
public class VadPostConfEntity
|
||||
{
|
||||
private int _sample_rate= 16000;
|
||||
private int _detect_mode = 1 ;
|
||||
private int _snr_mode = 0;
|
||||
private int _max_end_silence_time = 800;
|
||||
private int _max_start_silence_time = 3000;
|
||||
private bool _do_start_point_detection = true;
|
||||
private bool _do_end_point_detection = true;
|
||||
private int _window_size_ms = 200;
|
||||
private int _sil_to_speech_time_thres = 150;
|
||||
private int _speech_to_sil_time_thres = 150;
|
||||
private float _speech_2_noise_ratio = 1.0F;
|
||||
private int _do_extend = 1;
|
||||
private int _lookback_time_start_point = 200;
|
||||
private int _lookahead_time_end_point = 100;
|
||||
private int _max_single_segment_time = 60000;
|
||||
private int _nn_eval_block_size = 8;
|
||||
private int _dcd_block_size = 4;
|
||||
private float _snr_thres = -100.0F;
|
||||
private int _noise_frame_num_used_for_snr = 100;
|
||||
private float _decibel_thres = -100.0F;
|
||||
private float _speech_noise_thres = 0.6F;
|
||||
private float _fe_prior_thres = 0.0001F;
|
||||
private int _silence_pdf_num = 1;
|
||||
private int[] _sil_pdf_ids = new int[] {0};
|
||||
private float _speech_noise_thresh_low = -0.1F;
|
||||
private float _speech_noise_thresh_high = 0.3F;
|
||||
private bool _output_frame_probs = false;
|
||||
private int _frame_in_ms = 10;
|
||||
private int _frame_length_ms = 25;
|
||||
|
||||
public int sample_rate { get => _sample_rate; set => _sample_rate = value; }
|
||||
public int detect_mode { get => _detect_mode; set => _detect_mode = value; }
|
||||
public int snr_mode { get => _snr_mode; set => _snr_mode = value; }
|
||||
public int max_end_silence_time { get => _max_end_silence_time; set => _max_end_silence_time = value; }
|
||||
public int max_start_silence_time { get => _max_start_silence_time; set => _max_start_silence_time = value; }
|
||||
public bool do_start_point_detection { get => _do_start_point_detection; set => _do_start_point_detection = value; }
|
||||
public bool do_end_point_detection { get => _do_end_point_detection; set => _do_end_point_detection = value; }
|
||||
public int window_size_ms { get => _window_size_ms; set => _window_size_ms = value; }
|
||||
public int sil_to_speech_time_thres { get => _sil_to_speech_time_thres; set => _sil_to_speech_time_thres = value; }
|
||||
public int speech_to_sil_time_thres { get => _speech_to_sil_time_thres; set => _speech_to_sil_time_thres = value; }
|
||||
public float speech_2_noise_ratio { get => _speech_2_noise_ratio; set => _speech_2_noise_ratio = value; }
|
||||
public int do_extend { get => _do_extend; set => _do_extend = value; }
|
||||
public int lookback_time_start_point { get => _lookback_time_start_point; set => _lookback_time_start_point = value; }
|
||||
public int lookahead_time_end_point { get => _lookahead_time_end_point; set => _lookahead_time_end_point = value; }
|
||||
public int max_single_segment_time { get => _max_single_segment_time; set => _max_single_segment_time = value; }
|
||||
public int nn_eval_block_size { get => _nn_eval_block_size; set => _nn_eval_block_size = value; }
|
||||
public int dcd_block_size { get => _dcd_block_size; set => _dcd_block_size = value; }
|
||||
public float snr_thres { get => _snr_thres; set => _snr_thres = value; }
|
||||
public int noise_frame_num_used_for_snr { get => _noise_frame_num_used_for_snr; set => _noise_frame_num_used_for_snr = value; }
|
||||
public float decibel_thres { get => _decibel_thres; set => _decibel_thres = value; }
|
||||
public float speech_noise_thres { get => _speech_noise_thres; set => _speech_noise_thres = value; }
|
||||
public float fe_prior_thres { get => _fe_prior_thres; set => _fe_prior_thres = value; }
|
||||
public int silence_pdf_num { get => _silence_pdf_num; set => _silence_pdf_num = value; }
|
||||
public int[] sil_pdf_ids { get => _sil_pdf_ids; set => _sil_pdf_ids = value; }
|
||||
public float speech_noise_thresh_low { get => _speech_noise_thresh_low; set => _speech_noise_thresh_low = value; }
|
||||
public float speech_noise_thresh_high { get => _speech_noise_thresh_high; set => _speech_noise_thresh_high = value; }
|
||||
public bool output_frame_probs { get => _output_frame_probs; set => _output_frame_probs = value; }
|
||||
public int frame_in_ms { get => _frame_in_ms; set => _frame_in_ms = value; }
|
||||
public int frame_length_ms { get => _frame_length_ms; set => _frame_length_ms = value; }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AliFsmnVadSharp.Model
|
||||
{
|
||||
internal class VadYamlEntity
|
||||
{
|
||||
private int _input_size;
|
||||
private string _frontend = "wav_frontend";
|
||||
private FrontendConfEntity _frontend_conf=new FrontendConfEntity();
|
||||
private string _model = "e2evad";
|
||||
private string _encoder = "fsmn";
|
||||
private EncoderConfEntity _encoder_conf=new EncoderConfEntity();
|
||||
private VadPostConfEntity _vad_post_conf=new VadPostConfEntity();
|
||||
|
||||
public int input_size { get => _input_size; set => _input_size = value; }
|
||||
public string frontend { get => _frontend; set => _frontend = value; }
|
||||
public string model { get => _model; set => _model = value; }
|
||||
public string encoder { get => _encoder; set => _encoder = value; }
|
||||
public FrontendConfEntity frontend_conf { get => _frontend_conf; set => _frontend_conf = value; }
|
||||
public EncoderConfEntity encoder_conf { get => _encoder_conf; set => _encoder_conf = value; }
|
||||
public VadPostConfEntity model_conf { get => _vad_post_conf; set => _vad_post_conf = value; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Text.Json;
|
||||
using YamlDotNet.Serialization;
|
||||
|
||||
namespace AliFsmnVadSharp.Utils
|
||||
{
|
||||
internal class YamlHelper
|
||||
{
|
||||
public static T ReadYaml<T>(string yamlFilePath)
|
||||
{
|
||||
if (!File.Exists(yamlFilePath))
|
||||
{
|
||||
#pragma warning disable CS8603 // 可能返回 null 引用。
|
||||
return default(T);
|
||||
#pragma warning restore CS8603 // 可能返回 null 引用。
|
||||
}
|
||||
StreamReader yamlReader = File.OpenText(yamlFilePath);
|
||||
Deserializer yamlDeserializer = new Deserializer();
|
||||
T info = yamlDeserializer.Deserialize<T>(yamlReader);
|
||||
yamlReader.Close();
|
||||
return info;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
using AliFsmnVadSharp.Model;
|
||||
using KaldiNativeFbankSharp;
|
||||
|
||||
namespace AliFsmnVadSharp
|
||||
{
|
||||
internal class WavFrontend : IDisposable
|
||||
{
|
||||
private bool _disposed;
|
||||
|
||||
private FrontendConfEntity _frontendConfEntity;
|
||||
OnlineFbank _onlineFbank;
|
||||
private CmvnEntity _cmvnEntity;
|
||||
|
||||
private static int _fbank_beg_idx = 0;
|
||||
|
||||
public WavFrontend(string mvnFilePath, FrontendConfEntity frontendConfEntity)
|
||||
{
|
||||
_frontendConfEntity = frontendConfEntity;
|
||||
_fbank_beg_idx = 0;
|
||||
_onlineFbank = new OnlineFbank(
|
||||
dither: _frontendConfEntity.dither,
|
||||
snip_edges: true,
|
||||
sample_rate: _frontendConfEntity.fs,
|
||||
num_bins: _frontendConfEntity.n_mels
|
||||
);
|
||||
_cmvnEntity = LoadCmvn(mvnFilePath);
|
||||
}
|
||||
|
||||
public float[] GetFbank(float[] samples)
|
||||
{
|
||||
float sample_rate = _frontendConfEntity.fs;
|
||||
samples = samples.Select((float x) => x * 32768f).ToArray();
|
||||
float[] fbanks = _onlineFbank.GetFbank(samples);
|
||||
return fbanks;
|
||||
}
|
||||
|
||||
|
||||
public float[] LfrCmvn(float[] fbanks)
|
||||
{
|
||||
float[] features = fbanks;
|
||||
if (_frontendConfEntity.lfr_m != 1 || _frontendConfEntity.lfr_n != 1)
|
||||
{
|
||||
features = ApplyLfr(fbanks, _frontendConfEntity.lfr_m, _frontendConfEntity.lfr_n);
|
||||
}
|
||||
if (_cmvnEntity != null)
|
||||
{
|
||||
features = ApplyCmvn(features);
|
||||
}
|
||||
return features;
|
||||
}
|
||||
|
||||
private float[] ApplyCmvn(float[] inputs)
|
||||
{
|
||||
var arr_neg_mean = _cmvnEntity.Means;
|
||||
float[] neg_mean = arr_neg_mean.Select(x => (float)Convert.ToDouble(x)).ToArray();
|
||||
var arr_inv_stddev = _cmvnEntity.Vars;
|
||||
float[] inv_stddev = arr_inv_stddev.Select(x => (float)Convert.ToDouble(x)).ToArray();
|
||||
|
||||
int dim = neg_mean.Length;
|
||||
int num_frames = inputs.Length / dim;
|
||||
|
||||
for (int i = 0; i < num_frames; i++)
|
||||
{
|
||||
for (int k = 0; k != dim; ++k)
|
||||
{
|
||||
inputs[dim * i + k] = (inputs[dim * i + k] + neg_mean[k]) * inv_stddev[k];
|
||||
}
|
||||
}
|
||||
return inputs;
|
||||
}
|
||||
|
||||
public float[] ApplyLfr(float[] inputs, int lfr_m, int lfr_n)
|
||||
{
|
||||
int t = inputs.Length / 80;
|
||||
int t_lfr = (int)Math.Floor((double)(t / lfr_n));
|
||||
float[] input_0 = new float[80];
|
||||
Array.Copy(inputs, 0, input_0, 0, 80);
|
||||
int tile_x = (lfr_m - 1) / 2;
|
||||
t = t + tile_x;
|
||||
float[] inputs_temp = new float[t * 80];
|
||||
for (int i = 0; i < tile_x; i++)
|
||||
{
|
||||
Array.Copy(input_0, 0, inputs_temp, tile_x * 80, 80);
|
||||
}
|
||||
Array.Copy(inputs, 0, inputs_temp, tile_x * 80, inputs.Length);
|
||||
inputs = inputs_temp;
|
||||
|
||||
float[] LFR_outputs = new float[t_lfr * lfr_m * 80];
|
||||
for (int i = 0; i < t_lfr; i++)
|
||||
{
|
||||
if (lfr_m <= t - i * lfr_n)
|
||||
{
|
||||
Array.Copy(inputs, i * lfr_n * 80, LFR_outputs, i* lfr_m * 80, lfr_m * 80);
|
||||
}
|
||||
else
|
||||
{
|
||||
// process last LFR frame
|
||||
int num_padding = lfr_m - (t - i * lfr_n);
|
||||
float[] frame = new float[lfr_m * 80];
|
||||
Array.Copy(inputs, i * lfr_n * 80, frame, 0, (t - i * lfr_n) * 80);
|
||||
|
||||
for (int j = 0; j < num_padding; j++)
|
||||
{
|
||||
Array.Copy(inputs, (t - 1) * 80, frame, (lfr_m - num_padding + j) * 80, 80);
|
||||
}
|
||||
Array.Copy(frame, 0, LFR_outputs, i * lfr_m * 80, frame.Length);
|
||||
}
|
||||
}
|
||||
return LFR_outputs;
|
||||
}
|
||||
|
||||
private CmvnEntity LoadCmvn(string mvnFilePath)
|
||||
{
|
||||
List<float> means_list = new List<float>();
|
||||
List<float> vars_list = new List<float>();
|
||||
FileStreamOptions options = new FileStreamOptions();
|
||||
options.Access = FileAccess.Read;
|
||||
options.Mode = FileMode.Open;
|
||||
StreamReader srtReader = new StreamReader(mvnFilePath, options);
|
||||
int i = 0;
|
||||
while (!srtReader.EndOfStream)
|
||||
{
|
||||
string? strLine = srtReader.ReadLine();
|
||||
if (!string.IsNullOrEmpty(strLine))
|
||||
{
|
||||
if (strLine.StartsWith("<AddShift>"))
|
||||
{
|
||||
i=1;
|
||||
continue;
|
||||
}
|
||||
if (strLine.StartsWith("<Rescale>"))
|
||||
{
|
||||
i = 2;
|
||||
continue;
|
||||
}
|
||||
if (strLine.StartsWith("<LearnRateCoef>") && i==1)
|
||||
{
|
||||
string[] add_shift_line = strLine.Substring(strLine.IndexOf("[") + 1, strLine.LastIndexOf("]") - strLine.IndexOf("[") - 1).Split(" ");
|
||||
means_list = add_shift_line.Where(x => !string.IsNullOrEmpty(x)).Select(x => float.Parse(x.Trim())).ToList();
|
||||
continue;
|
||||
}
|
||||
if (strLine.StartsWith("<LearnRateCoef>") && i==2)
|
||||
{
|
||||
string[] rescale_line = strLine.Substring(strLine.IndexOf("[") + 1, strLine.LastIndexOf("]") - strLine.IndexOf("[") - 1).Split(" ");
|
||||
vars_list = rescale_line.Where(x => !string.IsNullOrEmpty(x)).Select(x => float.Parse(x.Trim())).ToList();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
CmvnEntity cmvnEntity = new CmvnEntity();
|
||||
cmvnEntity.Means = means_list;
|
||||
cmvnEntity.Vars = vars_list;
|
||||
return cmvnEntity;
|
||||
}
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (_onlineFbank != null)
|
||||
{
|
||||
_onlineFbank.Dispose();
|
||||
}
|
||||
}
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
~WavFrontend()
|
||||
{
|
||||
Dispose(_disposed);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AliFsmnVadSharp
|
||||
{
|
||||
public enum FrameState
|
||||
{
|
||||
kFrameStateInvalid = -1,
|
||||
kFrameStateSpeech = 1,
|
||||
kFrameStateSil = 0
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// final voice/unvoice state per frame
|
||||
/// </summary>
|
||||
public enum AudioChangeState
|
||||
{
|
||||
kChangeStateSpeech2Speech = 0,
|
||||
kChangeStateSpeech2Sil = 1,
|
||||
kChangeStateSil2Sil = 2,
|
||||
kChangeStateSil2Speech = 3,
|
||||
kChangeStateNoBegin = 4,
|
||||
kChangeStateInvalid = 5
|
||||
}
|
||||
|
||||
|
||||
internal class WindowDetector
|
||||
{
|
||||
private int _window_size_ms = 0; //window_size_ms;
|
||||
private int _sil_to_speech_time = 0; //sil_to_speech_time;
|
||||
private int _speech_to_sil_time = 0; //speech_to_sil_time;
|
||||
private int _frame_size_ms = 0; //frame_size_ms;
|
||||
|
||||
private int _win_size_frame = 0;
|
||||
private int _win_sum = 0;
|
||||
private int[] _win_state = new int[0];// * _win_size_frame; // 初始化窗
|
||||
|
||||
private int _cur_win_pos = 0;
|
||||
private int _pre_frame_state = (int)FrameState.kFrameStateSil;
|
||||
private int _cur_frame_state = (int)FrameState.kFrameStateSil;
|
||||
private int _sil_to_speech_frmcnt_thres = 0; //int(sil_to_speech_time / frame_size_ms);
|
||||
private int _speech_to_sil_frmcnt_thres = 0; //int(speech_to_sil_time / frame_size_ms);
|
||||
|
||||
private int _voice_last_frame_count = 0;
|
||||
private int _noise_last_frame_count = 0;
|
||||
private int _hydre_frame_count = 0;
|
||||
|
||||
public WindowDetector()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public WindowDetector(int window_size_ms, int sil_to_speech_time, int speech_to_sil_time, int frame_size_ms)
|
||||
{
|
||||
_window_size_ms = window_size_ms;
|
||||
_sil_to_speech_time = sil_to_speech_time;
|
||||
_speech_to_sil_time = speech_to_sil_time;
|
||||
_frame_size_ms = frame_size_ms;
|
||||
|
||||
_win_size_frame = (int)(window_size_ms / frame_size_ms);
|
||||
_win_sum = 0;
|
||||
_win_state = new int[_win_size_frame];//[0] * _win_size_frame; // 初始化窗
|
||||
|
||||
_cur_win_pos = 0;
|
||||
_pre_frame_state = (int)FrameState.kFrameStateSil;
|
||||
_cur_frame_state = (int)FrameState.kFrameStateSil;
|
||||
_sil_to_speech_frmcnt_thres = (int)(sil_to_speech_time / frame_size_ms);
|
||||
_speech_to_sil_frmcnt_thres = (int)(speech_to_sil_time / frame_size_ms);
|
||||
|
||||
_voice_last_frame_count = 0;
|
||||
_noise_last_frame_count = 0;
|
||||
_hydre_frame_count = 0;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_cur_win_pos = 0;
|
||||
_win_sum = 0;
|
||||
_win_state = new int[_win_size_frame];
|
||||
_pre_frame_state = (int)FrameState.kFrameStateSil;
|
||||
_cur_frame_state = (int)FrameState.kFrameStateSil;
|
||||
_voice_last_frame_count = 0;
|
||||
_noise_last_frame_count = 0;
|
||||
_hydre_frame_count = 0;
|
||||
}
|
||||
|
||||
|
||||
public int GetWinSize()
|
||||
{
|
||||
return _win_size_frame;
|
||||
}
|
||||
|
||||
public AudioChangeState DetectOneFrame(FrameState frameState, int frame_count)
|
||||
{
|
||||
|
||||
|
||||
_cur_frame_state = (int)FrameState.kFrameStateSil;
|
||||
if (frameState == FrameState.kFrameStateSpeech)
|
||||
{
|
||||
_cur_frame_state = 1;
|
||||
}
|
||||
|
||||
else if (frameState == FrameState.kFrameStateSil)
|
||||
{
|
||||
_cur_frame_state = 0;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
return AudioChangeState.kChangeStateInvalid;
|
||||
}
|
||||
|
||||
_win_sum -= _win_state[_cur_win_pos];
|
||||
_win_sum += _cur_frame_state;
|
||||
_win_state[_cur_win_pos] = _cur_frame_state;
|
||||
_cur_win_pos = (_cur_win_pos + 1) % _win_size_frame;
|
||||
|
||||
if (_pre_frame_state == (int)FrameState.kFrameStateSil && _win_sum >= _sil_to_speech_frmcnt_thres)
|
||||
{
|
||||
_pre_frame_state = (int)FrameState.kFrameStateSpeech;
|
||||
return AudioChangeState.kChangeStateSil2Speech;
|
||||
}
|
||||
|
||||
|
||||
if (_pre_frame_state == (int)FrameState.kFrameStateSpeech && _win_sum <= _speech_to_sil_frmcnt_thres)
|
||||
{
|
||||
_pre_frame_state = (int)FrameState.kFrameStateSil;
|
||||
return AudioChangeState.kChangeStateSpeech2Sil;
|
||||
}
|
||||
|
||||
|
||||
if (_pre_frame_state == (int)FrameState.kFrameStateSil)
|
||||
{
|
||||
return AudioChangeState.kChangeStateSil2Sil;
|
||||
}
|
||||
|
||||
if (_pre_frame_state == (int)FrameState.kFrameStateSpeech)
|
||||
{
|
||||
return AudioChangeState.kChangeStateSpeech2Speech;
|
||||
}
|
||||
|
||||
return AudioChangeState.kChangeStateInvalid;
|
||||
}
|
||||
|
||||
private int FrameSizeMs()
|
||||
{
|
||||
return _frame_size_ms;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
# AliFsmnVadSharp
|
||||
##### 简介:
|
||||
项目中使用的VAD模型是阿里巴巴达摩院提供的FSMN-Monophone VAD模型。
|
||||
**项目基于Net 6.0,使用C#编写,调用Microsoft.ML.OnnxRuntime对onnx模型进行解码,支持跨平台编译。项目以库的形式进行调用,部署非常方便。**
|
||||
VAD整体流程的rtf在0.008左右。
|
||||
|
||||
##### 用途:
|
||||
16k中文通用VAD模型:可用于检测长语音片段中有效语音的起止时间点.
|
||||
FSMN-Monophone VAD是达摩院语音团队提出的高效语音端点检测模型,用于检测输入音频中有效语音的起止时间点信息,并将检测出来的有效音频片段输入识别引擎进行识别,减少无效语音带来的识别错误。
|
||||
|
||||
##### VAD常用参数调整说明(参考:vad.yaml文件):
|
||||
max_end_silence_time:尾部连续检测到多长时间静音进行尾点判停,参数范围500ms~6000ms,默认值800ms(该值过低容易出现语音提前截断的情况)。
|
||||
speech_noise_thres:speech的得分减去noise的得分大于此值则判断为speech,参数范围:(-1,1)
|
||||
取值越趋于-1,噪音被误判定为语音的概率越大,FA越高
|
||||
取值越趋于+1,语音被误判定为噪音的概率越大,Pmiss越高
|
||||
通常情况下,该值会根据当前模型在长语音测试集上的效果取balance
|
||||
|
||||
##### 模型获取
|
||||
|
||||
##### 调用方式:
|
||||
###### 1.添加项目引用
|
||||
using AliFsmnVadSharp;
|
||||
|
||||
###### 2.初始化模型和配置
|
||||
```csharp
|
||||
string applicationBase = AppDomain.CurrentDomain.BaseDirectory;
|
||||
string modelFilePath = applicationBase + "./speech_fsmn_vad_zh-cn-16k-common-pytorch/model.onnx";
|
||||
string configFilePath = applicationBase + "./speech_fsmn_vad_zh-cn-16k-common-pytorch/vad.yaml";
|
||||
string mvnFilePath = applicationBase + "./speech_fsmn_vad_zh-cn-16k-common-pytorch/vad.mvn";
|
||||
int batchSize = 2;//批量解码
|
||||
AliFsmnVad aliFsmnVad = new AliFsmnVad(modelFilePath, configFilePath, mvnFilePath, batchSize);
|
||||
```
|
||||
###### 3.调用
|
||||
方法一(适用于小文件):
|
||||
```csharp
|
||||
SegmentEntity[] segments_duration = aliFsmnVad.GetSegments(samples);
|
||||
```
|
||||
方法二(适用于大文件):
|
||||
```csharp
|
||||
SegmentEntity[] segments_duration = aliFsmnVad.GetSegmentsByStep(samples);
|
||||
```
|
||||
###### 4.输出结果:
|
||||
```
|
||||
load model and init config elapsed_milliseconds:463.5390625
|
||||
vad infer result:
|
||||
[[70,2340][2620,6200][6480,23670][23950,26250][26780,28990][29950,31430][31750,37600][38210,46900][47310,49630][49910,56460][56740,59540][59820,70450]]
|
||||
elapsed_milliseconds:662.796875
|
||||
total_duration:70470.625
|
||||
rtf:0.009405292985552491
|
||||
```
|
||||
输出的数据,例如:[70,2340],是以毫秒为单位的segement的起止时间,可以以此为依据对音频进行分片。其中静音噪音部分已被去除。
|
||||
|
||||
其他说明:
|
||||
测试用例:AliFsmnVadSharp.Examples。
|
||||
测试环境:windows11。
|
||||
测试用例中samples的计算,使用的是NAudio库。
|
||||
|
||||
通过以下链接了解更多:
|
||||
https://www.modelscope.cn/models/damo/speech_fsmn_vad_zh-cn-16k-common-pytorch/summary
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version = "1.0" encoding = "UTF-8" ?>
|
||||
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:local="clr-namespace:MauiApp1"
|
||||
x:Class="MauiApp1.App">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="Resources/Styles/Colors.xaml" />
|
||||
<ResourceDictionary Source="Resources/Styles/Styles.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace MauiApp1;
|
||||
|
||||
public partial class App : Application
|
||||
{
|
||||
public App()
|
||||
{
|
||||
InitializeComponent();
|
||||
MainPage = new MySplashPage();
|
||||
_ = EndSplash();
|
||||
}
|
||||
async Task EndSplash()
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
MainThread.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
MainPage = new AppShell();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<Shell
|
||||
x:Class="MauiApp1.AppShell"
|
||||
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:local="clr-namespace:MauiApp1"
|
||||
Shell.FlyoutBehavior="Disabled">
|
||||
<FlyoutItem FlyoutDisplayOptions="AsMultipleItems">
|
||||
<ShellContent
|
||||
Title="Offline"
|
||||
ContentTemplate="{DataTemplate local:RecognitionForFiles}"
|
||||
Route="MainPage" />
|
||||
<ShellContent
|
||||
Title="Home"
|
||||
ContentTemplate="{DataTemplate local:MainPage}"
|
||||
Route="MainPage" />
|
||||
</FlyoutItem>
|
||||
|
||||
</Shell>
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace MauiApp1;
|
||||
|
||||
public partial class AppShell : Shell
|
||||
{
|
||||
public AppShell()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="MauiApp1.MainPage">
|
||||
|
||||
<ScrollView>
|
||||
<VerticalStackLayout
|
||||
Spacing="25"
|
||||
Padding="30,0"
|
||||
VerticalOptions="Center">
|
||||
|
||||
<Image
|
||||
Source="dotnet_bot.png"
|
||||
SemanticProperties.Description="Cute dot net bot waving hi to you!"
|
||||
HeightRequest="66"
|
||||
HorizontalOptions="Center" />
|
||||
</VerticalStackLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
</ContentPage>
|
||||
@@ -0,0 +1,25 @@
|
||||
using Microsoft.Maui.Storage;
|
||||
using NAudio.Wave;
|
||||
using System.ComponentModel;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Windows.Input;
|
||||
using static System.Net.Mime.MediaTypeNames;
|
||||
using MauiApp1.Utils;
|
||||
using System;
|
||||
using Microsoft.Maui.Graphics;
|
||||
|
||||
namespace MauiApp1;
|
||||
|
||||
public partial class MainPage : ContentPage
|
||||
{
|
||||
|
||||
public MainPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0-android;net6.0-ios;net6.0-maccatalyst</TargetFrameworks>
|
||||
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net6.0-windows10.0.19041.0</TargetFrameworks>
|
||||
<!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET -->
|
||||
<!-- <TargetFrameworks>$(TargetFrameworks);net6.0-tizen</TargetFrameworks> -->
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>MauiApp1</RootNamespace>
|
||||
<UseMaui>true</UseMaui>
|
||||
<SingleProject>true</SingleProject>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
|
||||
<!-- Display name -->
|
||||
<ApplicationTitle>ASR demo</ApplicationTitle>
|
||||
|
||||
<!-- App Identifier -->
|
||||
<ApplicationId>com.manyeyes.MauiApp1</ApplicationId>
|
||||
<ApplicationIdGuid>be632abf-f31d-4458-8964-b4e8787dee11</ApplicationIdGuid>
|
||||
|
||||
<!-- Versions -->
|
||||
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
|
||||
<ApplicationVersion>1</ApplicationVersion>
|
||||
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">14.2</SupportedOSPlatformVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">14.0</SupportedOSPlatformVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">21.0</SupportedOSPlatformVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion>
|
||||
<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'tizen'">6.5</SupportedOSPlatformVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net6.0-android|AnyCPU'">
|
||||
<EmbedAssembliesIntoApk>True</EmbedAssembliesIntoApk>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- App Icon -->
|
||||
<MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#F6F8F6" />
|
||||
|
||||
<!-- Splash Screen -->
|
||||
<MauiSplashScreen Include="Resources\Splash\splash.svg" Color="#F6F8F6" BaseSize="128,128" />
|
||||
|
||||
<!-- Images -->
|
||||
<MauiImage Include="Resources\Images\*" />
|
||||
<MauiImage Update="Resources\Images\dotnet_bot.svg" BaseSize="168,208" />
|
||||
|
||||
<!-- Custom Fonts -->
|
||||
<MauiFont Include="Resources\Fonts\*" />
|
||||
|
||||
<!-- Raw Assets (also remove the "Resources\Raw" prefix) -->
|
||||
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NAudio" Version="2.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\AliParaformerAsr\AliParaformerAsr.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="RecognitionForFiles.xaml.cs">
|
||||
<DependentUpon>RecognitionForFiles.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="MainPage.xaml.cs">
|
||||
<DependentUpon>MainPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<MauiXaml Update="MainPage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</MauiXaml>
|
||||
<MauiXaml Update="MySplashPage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</MauiXaml>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-onnx\model_quant.onnx">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="AllModels\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<IsFirstTimeProjectOpen>False</IsFirstTimeProjectOpen>
|
||||
<ActiveDebugFramework>net6.0-android</ActiveDebugFramework>
|
||||
<ActiveDebugProfile>Xiaomi Redmi Note 7 Pro (Android 10.0 - API 29)</ActiveDebugProfile>
|
||||
<SelectedPlatformGroup>PhysicalDevice</SelectedPlatformGroup>
|
||||
<DefaultDevice>pixel_5_-_api_33</DefaultDevice>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net6.0-android|AnyCPU'">
|
||||
<DebuggerFlavor>ProjectDebugger</DebuggerFlavor>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(TargetPlatformIdentifier)'=='iOS'">
|
||||
<RuntimeIdentifier>iossimulator-x64</RuntimeIdentifier>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Update="Platforms\Windows\Package.appxmanifest">
|
||||
<SubType>Designer</SubType>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace MauiApp1;
|
||||
|
||||
public static class MauiProgram
|
||||
{
|
||||
public static MauiApp CreateMauiApp()
|
||||
{
|
||||
var builder = MauiApp.CreateBuilder();
|
||||
builder
|
||||
.UseMauiApp<App>()
|
||||
.ConfigureFonts(fonts =>
|
||||
{
|
||||
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
|
||||
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
|
||||
});
|
||||
|
||||
return builder.Build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="MauiApp1.MySplashPage">
|
||||
|
||||
<ScrollView>
|
||||
<VerticalStackLayout
|
||||
Spacing="25"
|
||||
Padding="30,0"
|
||||
VerticalOptions="Center">
|
||||
|
||||
<Image
|
||||
Source="dotnet_bot.png"
|
||||
SemanticProperties.Description="Cute dot net bot waving hi to you!"
|
||||
HeightRequest="66"
|
||||
HorizontalOptions="Center" />
|
||||
</VerticalStackLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
</ContentPage>
|
||||
@@ -0,0 +1,25 @@
|
||||
using Microsoft.Maui.Storage;
|
||||
using NAudio.Wave;
|
||||
using System.ComponentModel;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Windows.Input;
|
||||
using static System.Net.Mime.MediaTypeNames;
|
||||
using MauiApp1.Utils;
|
||||
using System;
|
||||
using Microsoft.Maui.Graphics;
|
||||
|
||||
namespace MauiApp1;
|
||||
|
||||
public partial class MySplashPage : ContentPage
|
||||
{
|
||||
|
||||
public MySplashPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<application android:allowBackup="true" android:icon="@mipmap/appicon" android:supportsRtl="true"></application>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
|
||||
<!-- Required only if your app needs to access images or photos that other apps created -->
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
<!-- Required only if your app needs to access videos that other apps created -->
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||
<!-- Required only if your app needs to access audio files that other apps created -->
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.RecordAudio" />
|
||||
<uses-sdk />
|
||||
</manifest>
|
||||
@@ -0,0 +1,10 @@
|
||||
using Android.App;
|
||||
using Android.Content.PM;
|
||||
using Android.OS;
|
||||
|
||||
namespace MauiApp1;
|
||||
|
||||
[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
|
||||
public class MainActivity : MauiAppCompatActivity
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using Android.App;
|
||||
using Android.Runtime;
|
||||
|
||||
namespace MauiApp1;
|
||||
|
||||
[Application]
|
||||
|
||||
//[assembly: UsesPermission(Android.Manifest.Permission.ReadExternalStorage, MaxSDKVersion = 32)]
|
||||
[assembly: UsesPermission(Android.Manifest.Permission.ReadMediaAudio)]
|
||||
[assembly: UsesPermission(Android.Manifest.Permission.ReadMediaImages)]
|
||||
[assembly: UsesPermission(Android.Manifest.Permission.ReadMediaVideo)]
|
||||
[assembly: UsesPermission(Android.Manifest.Permission.ManageExternalStorage)]
|
||||
// Needed for Picking photo/video
|
||||
[assembly: UsesPermission(Android.Manifest.Permission.ReadExternalStorage)]
|
||||
|
||||
// Needed for Taking photo/video
|
||||
[assembly: UsesPermission(Android.Manifest.Permission.WriteExternalStorage)]
|
||||
[assembly: UsesPermission(Android.Manifest.Permission.Camera)]
|
||||
[assembly: UsesPermission(Android.Manifest.Permission.RecordAudio)]
|
||||
[assembly: UsesPermission(Android.Manifest.Permission.CaptureVideoOutput)]
|
||||
|
||||
// Add these properties if you would like to filter out devices that do not have cameras, or set to false to make them optional
|
||||
[assembly: UsesFeature("android.hardware.camera", Required = true)]
|
||||
[assembly: UsesFeature("android.hardware.camera.autofocus", Required = true)]
|
||||
[assembly: UsesFeature("android.hardware.recordaudio", Required = true)]
|
||||
[assembly: UsesFeature("android.hardware.recordaudio.autofocus", Required = true)]
|
||||
[assembly: UsesFeature("android.hardware.capturevideooutput", Required = true)]
|
||||
[assembly: UsesFeature("android.hardware.capturevideooutput.autofocus", Required = true)]
|
||||
|
||||
|
||||
public class MainApplication : MauiApplication
|
||||
{
|
||||
public MainApplication(IntPtr handle, JniHandleOwnership ownership)
|
||||
: base(handle, ownership)
|
||||
{
|
||||
}
|
||||
|
||||
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="colorPrimary">#512BD4</color>
|
||||
<color name="colorPrimaryDark">#2B0B98</color>
|
||||
<color name="colorAccent">#2B0B98</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,9 @@
|
||||
using Foundation;
|
||||
|
||||
namespace MauiApp1;
|
||||
|
||||
[Register("AppDelegate")]
|
||||
public class AppDelegate : MauiUIApplicationDelegate
|
||||
{
|
||||
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>UIDeviceFamily</key>
|
||||
<array>
|
||||
<integer>1</integer>
|
||||
<integer>2</integer>
|
||||
</array>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>XSAppIconAssets</key>
|
||||
<string>Assets.xcassets/appicon.appiconset</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,15 @@
|
||||
using ObjCRuntime;
|
||||
using UIKit;
|
||||
|
||||
namespace MauiApp1;
|
||||
|
||||
public class Program
|
||||
{
|
||||
// This is the main entry point of the application.
|
||||
static void Main(string[] args)
|
||||
{
|
||||
// if you want to use a different Application Delegate class from "AppDelegate"
|
||||
// you can specify it here.
|
||||
UIApplication.Main(args, null, typeof(AppDelegate));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using Microsoft.Maui;
|
||||
using Microsoft.Maui.Hosting;
|
||||
|
||||
namespace MauiApp1;
|
||||
|
||||
class Program : MauiApplication
|
||||
{
|
||||
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||
|
||||
static void Main(string[] args)
|
||||
{
|
||||
var app = new Program();
|
||||
app.Run(args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="maui-application-id-placeholder" version="0.0.0" api-version="7" xmlns="http://tizen.org/ns/packages">
|
||||
<profile name="common" />
|
||||
<ui-application appid="maui-application-id-placeholder" exec="MauiApp1.dll" multiple="false" nodisplay="false" taskmanage="true" type="dotnet" launch_mode="single">
|
||||
<label>maui-application-title-placeholder</label>
|
||||
<icon>maui-appicon-placeholder</icon>
|
||||
<metadata key="http://tizen.org/metadata/prefer_dotnet_aot" value="true" />
|
||||
</ui-application>
|
||||
<shortcut-list />
|
||||
<privileges>
|
||||
<privilege>http://tizen.org/privilege/internet</privilege>
|
||||
</privileges>
|
||||
<dependencies />
|
||||
<provides-appdefined-privileges />
|
||||
</manifest>
|
||||
@@ -0,0 +1,8 @@
|
||||
<maui:MauiWinUIApplication
|
||||
x:Class="MauiApp1.WinUI.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:maui="using:Microsoft.Maui"
|
||||
xmlns:local="using:MauiApp1.WinUI">
|
||||
|
||||
</maui:MauiWinUIApplication>
|
||||
@@ -0,0 +1,24 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
// To learn more about WinUI, the WinUI project structure,
|
||||
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||
|
||||
namespace MauiApp1.WinUI;
|
||||
|
||||
/// <summary>
|
||||
/// Provides application-specific behavior to supplement the default Application class.
|
||||
/// </summary>
|
||||
public partial class App : MauiWinUIApplication
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the singleton application object. This is the first line of authored code
|
||||
/// executed, and as such is the logical equivalent of main() or WinMain().
|
||||
/// </summary>
|
||||
public App()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
|
||||
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Package
|
||||
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
IgnorableNamespaces="uap rescap">
|
||||
|
||||
<Identity Name="maui-package-name-placeholder" Publisher="CN=User Name" Version="0.0.0.0" />
|
||||
|
||||
<mp:PhoneIdentity PhoneProductId="B8687A78-A016-4011-9FEF-910387A888E5" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||
|
||||
<Properties>
|
||||
<DisplayName>$placeholder$</DisplayName>
|
||||
<PublisherDisplayName>User Name</PublisherDisplayName>
|
||||
<Logo>$placeholder$.png</Logo>
|
||||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Resources>
|
||||
<Resource Language="x-generate" />
|
||||
</Resources>
|
||||
|
||||
<Applications>
|
||||
<Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="$targetentrypoint$">
|
||||
<uap:VisualElements
|
||||
DisplayName="$placeholder$"
|
||||
Description="$placeholder$"
|
||||
Square150x150Logo="$placeholder$.png"
|
||||
Square44x44Logo="$placeholder$.png"
|
||||
BackgroundColor="transparent">
|
||||
<uap:DefaultTile Square71x71Logo="$placeholder$.png" Wide310x150Logo="$placeholder$.png" Square310x310Logo="$placeholder$.png" />
|
||||
<uap:SplashScreen Image="$placeholder$.png" />
|
||||
</uap:VisualElements>
|
||||
</Application>
|
||||
</Applications>
|
||||
|
||||
<Capabilities>
|
||||
<rescap:Capability Name="runFullTrust" />
|
||||
</Capabilities>
|
||||
|
||||
</Package>
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="MauiApp1.WinUI.app"/>
|
||||
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<!-- The combination of below two tags have the following effect:
|
||||
1) Per-Monitor for >= Windows 10 Anniversary Update
|
||||
2) System < Windows 10 Anniversary Update
|
||||
-->
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</assembly>
|
||||
@@ -0,0 +1,9 @@
|
||||
using Foundation;
|
||||
|
||||
namespace MauiApp1;
|
||||
|
||||
[Register("AppDelegate")]
|
||||
public class AppDelegate : MauiUIApplicationDelegate
|
||||
{
|
||||
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UIDeviceFamily</key>
|
||||
<array>
|
||||
<integer>1</integer>
|
||||
<integer>2</integer>
|
||||
</array>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>XSAppIconAssets</key>
|
||||
<string>Assets.xcassets/appicon.appiconset</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,15 @@
|
||||
using ObjCRuntime;
|
||||
using UIKit;
|
||||
|
||||
namespace MauiApp1;
|
||||
|
||||
public class Program
|
||||
{
|
||||
// This is the main entry point of the application.
|
||||
static void Main(string[] args)
|
||||
{
|
||||
// if you want to use a different Application Delegate class from "AppDelegate"
|
||||
// you can specify it here.
|
||||
UIApplication.Main(args, null, typeof(AppDelegate));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"profiles": {
|
||||
"Windows Machine": {
|
||||
"commandName": "MsixPackage",
|
||||
"nativeDebugging": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="MauiApp1.RecognitionForFiles">
|
||||
<ScrollView>
|
||||
<VerticalStackLayout
|
||||
Spacing="25"
|
||||
Padding="15,15"
|
||||
VerticalOptions="Start">
|
||||
<StackLayout>
|
||||
<Grid ColumnSpacing="5" RowSpacing="5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="80" />
|
||||
<ColumnDefinition Width="120" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="40" />
|
||||
<RowDefinition Height="15" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Label Text="models not checked" Grid.Row="0" Grid.Column="0"
|
||||
x:Name="ModelStatusLabel"
|
||||
VerticalTextAlignment="Center" HorizontalTextAlignment="Start"/>
|
||||
<Button Grid.Row="0" Grid.Column="1"
|
||||
x:Name="BtnDownLoadCheck"
|
||||
Text="Check"
|
||||
SemanticProperties.Hint="Counts the number of times you click"
|
||||
Clicked="OnDownLoadCheckClicked"
|
||||
HorizontalOptions="Center" />
|
||||
<Button Grid.Row="0" Grid.Column="2"
|
||||
x:Name="BtnDownLoadModels"
|
||||
Text="Download"
|
||||
SemanticProperties.Hint="Counts the number of times you click"
|
||||
Clicked="OnDownLoadModelsClicked"
|
||||
HorizontalOptions="EndAndExpand" />
|
||||
<Label Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3"
|
||||
x:Name="DownloadProgressLabel"
|
||||
Text="" IsVisible="False"
|
||||
BackgroundColor="White"
|
||||
HorizontalOptions="FillAndExpand"
|
||||
HorizontalTextAlignment="Start"
|
||||
TextColor="Red"/>
|
||||
<ProgressBar IsVisible="False" Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="3"
|
||||
x:Name="DownloadProgressBar" HorizontalOptions="FillAndExpand" />
|
||||
</Grid>
|
||||
<Label Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3"
|
||||
x:Name="DownloadResultsLabel"
|
||||
Text="" IsVisible="False"
|
||||
BackgroundColor="White"
|
||||
TextColor="Red"/>
|
||||
<Grid ColumnSpacing="5" RowSpacing="5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="4*" />
|
||||
<ColumnDefinition Width="8*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="40" />
|
||||
</Grid.RowDefinitions>
|
||||
<Button Grid.Row="0" Grid.Column="0"
|
||||
x:Name="BtnRecognitionExample"
|
||||
Text="Example"
|
||||
SemanticProperties.Hint="Counts the number of times you click"
|
||||
Clicked="OnBtnRecognitionExampleClicked"
|
||||
HorizontalOptions="FillAndExpand" />
|
||||
<Button Grid.Row="0" Grid.Column="1"
|
||||
x:Name="BtnRecognitionFiles"
|
||||
Text="Recognition Files"
|
||||
SemanticProperties.Hint="Counts the number of times you click"
|
||||
Clicked="OnBtnRecognitionFilesClicked"
|
||||
HorizontalOptions="FillAndExpand" />
|
||||
</Grid>
|
||||
</StackLayout>
|
||||
<Label FlowDirection="MatchParent"
|
||||
Text="Display recognition results"
|
||||
SemanticProperties.HeadingLevel="Level2"
|
||||
SemanticProperties.Description="Welcome to dot net Multi platform App U I"
|
||||
FontSize="15"
|
||||
Background="#F6F8F6"
|
||||
Padding="10,10,10,10"
|
||||
x:Name="AsrResults"
|
||||
HorizontalOptions="FillAndExpand"
|
||||
BackgroundColor="White"
|
||||
TextColor="Red" />
|
||||
</VerticalStackLayout>
|
||||
</ScrollView>
|
||||
</ContentPage>
|
||||
@@ -0,0 +1,496 @@
|
||||
using Microsoft.Maui.Storage;
|
||||
using NAudio.Wave;
|
||||
using System.ComponentModel;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Windows.Input;
|
||||
using static System.Net.Mime.MediaTypeNames;
|
||||
using MauiApp1.Utils;
|
||||
using System;
|
||||
using Microsoft.Maui.Graphics;
|
||||
|
||||
namespace MauiApp1;
|
||||
|
||||
public partial class RecognitionForFiles : ContentPage
|
||||
{
|
||||
private AliParaformerAsr.OfflineRecognizer _offlineRecognizer = null;
|
||||
|
||||
|
||||
public RecognitionForFiles()
|
||||
{
|
||||
InitializeComponent();
|
||||
//GetDownloadState();
|
||||
}
|
||||
|
||||
private async void OnDownLoadCheckClicked(object sender, EventArgs e)
|
||||
{
|
||||
BtnDownLoadCheck.IsEnabled = false;
|
||||
TaskFactory taskFactory = new TaskFactory();
|
||||
await taskFactory.StartNew(async () =>
|
||||
{
|
||||
DownloadCheck();
|
||||
});
|
||||
BtnDownLoadCheck.IsEnabled = true;
|
||||
}
|
||||
|
||||
private async void OnDownLoadModelsClicked(object sender, EventArgs e)
|
||||
{
|
||||
BtnDownLoadModels.IsEnabled = false;
|
||||
DownloadProgressBar.Progress = 0 / 100.0;
|
||||
DownloadProgressLabel.Text = "";
|
||||
TaskFactory taskFactory = new TaskFactory();
|
||||
await taskFactory.StartNew(async () =>
|
||||
{
|
||||
DownloadModels();
|
||||
});
|
||||
BtnDownLoadModels.IsEnabled = true;
|
||||
}
|
||||
|
||||
async void DownloadModels()
|
||||
{
|
||||
string paraformerFilejson = "[{},{}]";
|
||||
|
||||
string[] downloadUrls = {
|
||||
"http://huggingface.co/manyeyes/speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-onnx/resolve/main/model_quant.onnx",
|
||||
"http://huggingface.co/manyeyes/speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-onnx/resolve/main/am.mvn",
|
||||
"http://huggingface.co/manyeyes/speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-onnx/resolve/main/asr.yaml",
|
||||
"http://huggingface.co/manyeyes/speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-onnx/resolve/main/tokens.txt",
|
||||
"http://huggingface.co/manyeyes/speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-onnx/resolve/main/example/0.wav" };
|
||||
string[] fileNames = { "model.int8.onnx", "am.mvn", "asr.yaml", "tokens.txt", "0.wav" };
|
||||
var rootFolderName = "AllModels";
|
||||
var subFolderName = "speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-onnx";
|
||||
List<int> indexs = new List<int>();
|
||||
DownloadHelper downloadHelper = new DownloadHelper(this.DownloadDisplay);
|
||||
DownloadProgressLabel.Dispatcher.Dispatch(
|
||||
new Action(
|
||||
delegate
|
||||
{
|
||||
DownloadProgressLabel.IsVisible = true;
|
||||
DownloadProgressLabel.Text = "";
|
||||
}));
|
||||
|
||||
DownloadProgressBar.Dispatcher.Dispatch(
|
||||
new Action(
|
||||
delegate
|
||||
{
|
||||
DownloadProgressBar.IsVisible = true;
|
||||
DownloadProgressBar.Progress = 0 / 100.0;
|
||||
}));
|
||||
while (indexs.Count < downloadUrls.Length)
|
||||
{
|
||||
if (downloadHelper.IsDownloading)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < downloadUrls.Length; i++)
|
||||
{
|
||||
if (indexs.Contains(i))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (downloadHelper.IsDownloading)
|
||||
{
|
||||
break;
|
||||
}
|
||||
var downloadUrl = downloadUrls[i];
|
||||
var fileName = fileNames[i];
|
||||
downloadHelper.DownloadCreate(downloadUrl, fileName, rootFolderName, subFolderName);
|
||||
downloadHelper.DownloadStart();
|
||||
indexs.Add(i);
|
||||
}
|
||||
}
|
||||
DownloadProgressLabel.Dispatcher.Dispatch(
|
||||
new Action(
|
||||
delegate
|
||||
{
|
||||
DownloadProgressLabel.IsVisible = false;
|
||||
DownloadProgressLabel.Text = "";
|
||||
}));
|
||||
|
||||
DownloadProgressBar.Dispatcher.Dispatch(
|
||||
new Action(
|
||||
delegate
|
||||
{
|
||||
DownloadProgressBar.IsVisible = false;
|
||||
DownloadProgressBar.Progress = 0 / 100.0;
|
||||
}));
|
||||
}
|
||||
|
||||
async void GetDownloadState()
|
||||
{
|
||||
string[] fileNames = { "model.int8.onnx", "am.mvn", "asr.yaml", "tokens.txt", "0.wav" };
|
||||
string[] md5Strs = { "a171eff9959f2486d5c1bafce61d0b7a", "dc1dbdeeb8961f012161cfce31eaacaf", "71d32ccf12dbb85a5d8b249558f258fb", "5c8702dc2686b846fe268d9aa712be9b", "af1295e53df7298458f808bc0cd946e2" };
|
||||
var rootFolderName = "AllModels";
|
||||
var subFolderName = "speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-onnx";
|
||||
DownloadHelper downloadHelper = new DownloadHelper();
|
||||
bool state = downloadHelper.GetDownloadState(fileNames, rootFolderName, subFolderName, md5Strs);
|
||||
ModelStatusLabel.Dispatcher.Dispatch(
|
||||
new Action(
|
||||
delegate
|
||||
{
|
||||
if (state)
|
||||
{
|
||||
ModelStatusLabel.Text = "model is ready";
|
||||
}
|
||||
else
|
||||
{
|
||||
ModelStatusLabel.Text = "model not ready";
|
||||
}
|
||||
|
||||
}));
|
||||
}
|
||||
|
||||
async void DownloadCheck()
|
||||
{
|
||||
string[] fileNames = { "model.int8.onnx", "am.mvn", "asr.yaml", "tokens.txt", "0.wav" };
|
||||
string[] md5Strs = { "a171eff9959f2486d5c1bafce61d0b7a", "dc1dbdeeb8961f012161cfce31eaacaf", "71d32ccf12dbb85a5d8b249558f258fb", "5c8702dc2686b846fe268d9aa712be9b", "af1295e53df7298458f808bc0cd946e2" };
|
||||
var rootFolderName = "AllModels";
|
||||
var subFolderName = "speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-onnx";
|
||||
DownloadHelper downloadHelper = new DownloadHelper(this.DownloadDisplay);
|
||||
bool state = downloadHelper.GetDownloadState(fileNames, rootFolderName, subFolderName, md5Strs);
|
||||
ModelStatusLabel.Dispatcher.Dispatch(
|
||||
new Action(
|
||||
delegate
|
||||
{
|
||||
if (state)
|
||||
{
|
||||
ModelStatusLabel.Text = "model is ready";
|
||||
}
|
||||
else
|
||||
{
|
||||
ModelStatusLabel.Text = "model not ready";
|
||||
}
|
||||
|
||||
}));
|
||||
if (!state)
|
||||
{
|
||||
DownloadResultsLabel.Dispatcher.Dispatch(
|
||||
new Action(
|
||||
delegate
|
||||
{
|
||||
DownloadResultsLabel.IsVisible = true;
|
||||
DownloadResultsLabel.Text = "";
|
||||
}));
|
||||
for (int i = 0; i < fileNames.Length; i++)
|
||||
{
|
||||
downloadHelper.DownloadCheck(fileNames[i], rootFolderName, subFolderName, md5Strs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void DownloadDisplay(int progress, DownloadState downloadState, string filename, string msg = "")
|
||||
{
|
||||
|
||||
switch (downloadState)
|
||||
{
|
||||
case DownloadState.inprogres:
|
||||
DownloadProgressBar.Dispatcher.Dispatch(
|
||||
new Action(
|
||||
delegate
|
||||
{
|
||||
DownloadProgressBar.Progress = progress / 100.0;
|
||||
}));
|
||||
DownloadProgressLabel.Dispatcher.Dispatch(
|
||||
new Action(
|
||||
delegate
|
||||
{
|
||||
DownloadProgressLabel.Text = $"文件:{filename},正在下载,进度:{progress}%\n";
|
||||
}));
|
||||
|
||||
break;
|
||||
case DownloadState.cancelled:
|
||||
DownloadProgressBar.Dispatcher.Dispatch(
|
||||
new Action(
|
||||
delegate
|
||||
{
|
||||
DownloadProgressBar.Progress = progress / 100.0;
|
||||
}));
|
||||
DownloadProgressLabel.Dispatcher.Dispatch(
|
||||
new Action(
|
||||
delegate
|
||||
{
|
||||
DownloadProgressLabel.Text = $"文件:{filename},下载已取消\n";
|
||||
}));
|
||||
break;
|
||||
case DownloadState.error:
|
||||
DownloadProgressBar.Dispatcher.Dispatch(
|
||||
new Action(
|
||||
delegate
|
||||
{
|
||||
DownloadProgressBar.Progress = progress / 100.0;
|
||||
}));
|
||||
DownloadProgressLabel.Dispatcher.Dispatch(
|
||||
new Action(
|
||||
delegate
|
||||
{
|
||||
DownloadProgressLabel.Text = $"文件:{filename},下载失败:{msg}\n";
|
||||
}));
|
||||
DownloadResultsLabel.Dispatcher.Dispatch(
|
||||
new Action(
|
||||
delegate
|
||||
{
|
||||
DownloadResultsLabel.Text += $"文件:{filename},下载失败:{msg}\n";
|
||||
}));
|
||||
break;
|
||||
case DownloadState.completed:
|
||||
DownloadProgressBar.Dispatcher.Dispatch(
|
||||
new Action(
|
||||
delegate
|
||||
{
|
||||
DownloadProgressBar.Progress = progress / 100.0;
|
||||
}));
|
||||
DownloadProgressLabel.Dispatcher.Dispatch(
|
||||
new Action(
|
||||
delegate
|
||||
{
|
||||
DownloadProgressLabel.Text = $"文件:{filename},下载完成\n";
|
||||
}));
|
||||
DownloadResultsLabel.Dispatcher.Dispatch(
|
||||
new Action(
|
||||
delegate
|
||||
{
|
||||
DownloadResultsLabel.Text += $"文件:{filename},下载完成\n";
|
||||
}));
|
||||
break;
|
||||
case DownloadState.existed:
|
||||
DownloadProgressBar.Dispatcher.Dispatch(
|
||||
new Action(
|
||||
delegate
|
||||
{
|
||||
DownloadProgressBar.Progress = progress / 100.0;
|
||||
}));
|
||||
DownloadResultsLabel.Dispatcher.Dispatch(
|
||||
new Action(
|
||||
delegate
|
||||
{
|
||||
DownloadResultsLabel.Text += $"文件:{filename},已存在\n";
|
||||
}));
|
||||
break;
|
||||
case DownloadState.noexisted:
|
||||
DownloadResultsLabel.Dispatcher.Dispatch(
|
||||
new Action(
|
||||
delegate
|
||||
{
|
||||
DownloadResultsLabel.Text += $"文件:{filename},不存在\n";
|
||||
}));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnBtnRecognitionExampleClicked(object sender, EventArgs e)
|
||||
{
|
||||
BtnRecognitionExample.IsEnabled = false;
|
||||
TaskFactory taskFactory = new TaskFactory();
|
||||
await taskFactory.StartNew(async () =>
|
||||
{
|
||||
TestAliParaformerAsrOfflineRecognizer();
|
||||
});
|
||||
BtnRecognitionExample.IsEnabled = true;
|
||||
}
|
||||
|
||||
private async void OnBtnRecognitionFilesClicked(object sender, EventArgs e)
|
||||
{
|
||||
var customFileType = new FilePickerFileType(
|
||||
new Dictionary<DevicePlatform, IEnumerable<string>>
|
||||
{
|
||||
{ DevicePlatform.iOS, new[] { "public.my.comic.extension" } }, // UTType values
|
||||
{ DevicePlatform.Android, new[] { "audio/x-wav" } }, // MIME type
|
||||
{ DevicePlatform.WinUI, new[] { ".cbr", ".cbz" } }, // file extension
|
||||
{ DevicePlatform.Tizen, new[] { "*/*" } },
|
||||
{ DevicePlatform.macOS, new[] { "cbr", "cbz" } }, // UTType values
|
||||
});
|
||||
|
||||
PickOptions options = new()
|
||||
{
|
||||
PickerTitle = "Please select a comic file",
|
||||
FileTypes = customFileType,
|
||||
};
|
||||
|
||||
|
||||
TaskFactory taskFactory = new TaskFactory();
|
||||
await taskFactory.StartNew(async () =>
|
||||
{
|
||||
var fileResult = await PickAndShow(options);
|
||||
string fullpath = fileResult.FullPath;
|
||||
List<string> fullpaths = new List<string>();
|
||||
fullpaths.Add(fullpath);
|
||||
RecognizerFilesByAliParaformerAsrOffline(fullpaths);
|
||||
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
public async Task<FileResult> PickAndShow(PickOptions options)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await FilePicker.Default.PickAsync(options);
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// The user canceled or something went wrong
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void CreateDownloadFile(string fileName)
|
||||
{
|
||||
|
||||
var downloadFolder = FileSystem.AppDataDirectory + "/Download/";
|
||||
Directory.CreateDirectory(downloadFolder);
|
||||
var filePath = downloadFolder + fileName;
|
||||
File.Create(filePath);
|
||||
}
|
||||
|
||||
public AliParaformerAsr.OfflineRecognizer initAliParaformerAsrOfflineRecognizer(string modelName)
|
||||
{
|
||||
if (_offlineRecognizer == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
string allModels = "AllModels";
|
||||
string modelFilePath = SysConf.ApplicationBase + "/" + allModels + "/" + modelName + "/model.int8.onnx";
|
||||
string configFilePath = SysConf.ApplicationBase + "/" + allModels + "/" + modelName + "/asr.yaml";
|
||||
string mvnFilePath = SysConf.ApplicationBase + "/" + allModels + "/" + modelName + "/am.mvn";
|
||||
string tokensFilePath = SysConf.ApplicationBase + "/" + allModels + "/" + modelName + "/tokens.txt";
|
||||
_offlineRecognizer = new AliParaformerAsr.OfflineRecognizer(modelFilePath, configFilePath, mvnFilePath, tokensFilePath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
DisplayAlert("Tips", "Please check if the model is correct", "close");
|
||||
}
|
||||
}
|
||||
return _offlineRecognizer;
|
||||
}
|
||||
|
||||
public void RecognizerFilesByAliParaformerAsrOffline(List<string> fullpaths)
|
||||
{
|
||||
string modelName = "speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-onnx";
|
||||
AliParaformerAsr.OfflineRecognizer offlineRecognizer = initAliParaformerAsrOfflineRecognizer(modelName);
|
||||
if (offlineRecognizer == null) { return; }
|
||||
AsrResults.Dispatcher.Dispatch(
|
||||
new Action(
|
||||
delegate
|
||||
{
|
||||
AsrResults.Text = "正在识别,请稍后……";
|
||||
}
|
||||
));
|
||||
List<float[]>? samples = new List<float[]>();
|
||||
TimeSpan total_duration = new TimeSpan(0L);
|
||||
foreach (string fullpath in fullpaths)
|
||||
{
|
||||
string wavFilePath = string.Format(fullpath);
|
||||
if (!File.Exists(wavFilePath))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
AudioFileReader _audioFileReader = new AudioFileReader(wavFilePath);
|
||||
byte[] datas = new byte[_audioFileReader.Length];
|
||||
_audioFileReader.Read(datas, 0, datas.Length);
|
||||
TimeSpan duration = _audioFileReader.TotalTime;
|
||||
float[] wavdata = new float[datas.Length / 4];
|
||||
Buffer.BlockCopy(datas, 0, wavdata, 0, datas.Length);
|
||||
float[] sample = wavdata.Select((float x) => x * 32768f).ToArray();
|
||||
samples.Add(sample);
|
||||
total_duration += duration;
|
||||
}
|
||||
AsrResults.Dispatcher.Dispatch(
|
||||
new Action(
|
||||
delegate
|
||||
{
|
||||
TimeSpan start_time = new TimeSpan(DateTime.Now.Ticks);
|
||||
foreach (var sample in samples)
|
||||
{
|
||||
List<float[]> temp_samples = new List<float[]>();
|
||||
temp_samples.Add(sample);
|
||||
List<string> results = offlineRecognizer.GetResults(temp_samples);
|
||||
foreach (string result in results)
|
||||
{
|
||||
Console.WriteLine(result);
|
||||
Console.WriteLine("");
|
||||
AsrResults.Text = string.Format("result:{0}\n", result);
|
||||
}
|
||||
}
|
||||
TimeSpan end_time = new TimeSpan(DateTime.Now.Ticks);
|
||||
double elapsed_milliseconds = end_time.TotalMilliseconds - start_time.TotalMilliseconds;
|
||||
double rtf = elapsed_milliseconds / total_duration.TotalMilliseconds;
|
||||
AsrResults.Text += string.Format("elapsed_milliseconds:{0}\n", elapsed_milliseconds.ToString());
|
||||
AsrResults.Text += string.Format("total_duration:{0}\n", total_duration.TotalMilliseconds.ToString());
|
||||
AsrResults.Text += string.Format("rtf:{1}\n", "0".ToString(), rtf.ToString());
|
||||
AsrResults.Text += string.Format("End!");
|
||||
}));
|
||||
|
||||
}
|
||||
|
||||
public void TestAliParaformerAsrOfflineRecognizer(List<float[]>? samples = null)
|
||||
{
|
||||
string modelName = "speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-onnx";
|
||||
AliParaformerAsr.OfflineRecognizer offlineRecognizer = initAliParaformerAsrOfflineRecognizer(modelName);
|
||||
if (offlineRecognizer == null) { return; }
|
||||
AsrResults.Dispatcher.Dispatch(
|
||||
new Action(
|
||||
delegate
|
||||
{
|
||||
AsrResults.Text = "正在识别,请稍后……";
|
||||
}
|
||||
));
|
||||
TimeSpan total_duration = new TimeSpan(0L);
|
||||
if (samples == null)
|
||||
{
|
||||
samples = new List<float[]>();
|
||||
for (int i = 0; i < 1; i++)
|
||||
{
|
||||
string wavFilePath = string.Format(SysConf.ApplicationBase + "/AllModels/" + modelName + "/{0}.wav", i.ToString());
|
||||
if (!File.Exists(wavFilePath))
|
||||
{
|
||||
break;
|
||||
}
|
||||
AudioFileReader _audioFileReader = new AudioFileReader(wavFilePath);
|
||||
byte[] datas = new byte[_audioFileReader.Length];
|
||||
_audioFileReader.Read(datas, 0, datas.Length);
|
||||
TimeSpan duration = _audioFileReader.TotalTime;
|
||||
float[] wavdata = new float[datas.Length / 4];
|
||||
Buffer.BlockCopy(datas, 0, wavdata, 0, datas.Length);
|
||||
float[] sample = wavdata.Select((float x) => x * 32768f).ToArray();
|
||||
samples.Add(sample);
|
||||
total_duration += duration;
|
||||
}
|
||||
}
|
||||
AsrResults.Dispatcher.Dispatch(
|
||||
new Action(
|
||||
delegate
|
||||
{
|
||||
TimeSpan start_time = new TimeSpan(DateTime.Now.Ticks);
|
||||
foreach (var sample in samples)
|
||||
{
|
||||
List<float[]> temp_samples = new List<float[]>();
|
||||
temp_samples.Add(sample);
|
||||
List<string> results = offlineRecognizer.GetResults(temp_samples);
|
||||
foreach (string result in results)
|
||||
{
|
||||
AsrResults.Text = string.Format("result:{0}\n", result);
|
||||
}
|
||||
}
|
||||
TimeSpan end_time = new TimeSpan(DateTime.Now.Ticks);
|
||||
double elapsed_milliseconds = end_time.TotalMilliseconds - start_time.TotalMilliseconds;
|
||||
double rtf = elapsed_milliseconds / total_duration.TotalMilliseconds;
|
||||
Console.WriteLine("elapsed_milliseconds:{0}", elapsed_milliseconds.ToString());
|
||||
Console.WriteLine("total_duration:{0}", total_duration.TotalMilliseconds.ToString());
|
||||
Console.WriteLine("rtf:{1}", "0".ToString(), rtf.ToString());
|
||||
Console.WriteLine("Hello, World!");
|
||||
AsrResults.Text += string.Format("elapsed_milliseconds:{0}\n", elapsed_milliseconds.ToString());
|
||||
AsrResults.Text += string.Format("total_duration:{0}\n", total_duration.TotalMilliseconds.ToString());
|
||||
AsrResults.Text += string.Format("rtf:{1}\n", "0".ToString(), rtf.ToString());
|
||||
AsrResults.Text += string.Format("End!");
|
||||
}
|
||||
));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="0" y="0" width="456" height="456" fill="#F6F8F6" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 228 B |
@@ -0,0 +1,492 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve"> <image id="image0" width="512" height="512" x="0" y="0"
|
||||
href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAYAAAD0eNT6AAAABGdBTUEAALGPC/xhBQAAACBjSFJN
|
||||
AAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QA/wD/AP+gvaeTAAAA
|
||||
CXBIWXMAAAsTAAALEwEAmpwYAABrVElEQVR42u3deZxbdfU//td5J5l9JtMVSlsobactlLKKyiKb
|
||||
RVFZBH+4ooJipaUsRXH7qLUqKh8tZS9U8IMK+vlYv6wiqCCLLCq7QLcpbSmlhS50ktlnknt+f7RT
|
||||
pmkyuXeSzD3vd8778fAh3LzneXPe97zPuWQyCc2ceUIFAAbQ+9BDjyLomDnzBACIAaB+h9VTTz31
|
||||
1FNPPcGecSkY9dRTTz311FPPn2dcCkY99dRTTz311PM3yPdMC4JRTz311FNPPfX8jcA3AJKDUU89
|
||||
9dRTTz31/I1ANwDSg1FPPfXUU0899fx5vm8AbAhGPfXUU0899dTz5/m6AbAlGPXUU0899dRTz5+X
|
||||
9wbApmDUU0899dRTTz1/HgWZLD0Y9dRTTz311FPPn0dBJksPRj311FNPPfXU8+dRkMnSg1FPPfXU
|
||||
U0899fx5FGSy9GDUU0899dRTTz1/HgWZLD0Y9dRTTz311FPPn0dBJksPRj311FNPPfXU8+eRS8Go
|
||||
p5566qmnnnr+PHIpGPXUU0899dRTz59nXApGPfXUU0899dTz5xmXglFPPfXUU0899fx5xqVg1FNP
|
||||
PfXUU089f54JMll6MOqpp5566qmnnj/PuBSMeuqpp5566qnnzzMuBaOeeuqpp5566vkb5HumBcGo
|
||||
p5566qmnnnr+RuAbAMnBqKeeeuqpp556/kagGwDpwainnnrqqaeeev483zcANgSjnnrqqaeeeur5
|
||||
83zdANgSjHrqqaeeeuqp58/LewNgUzDqqaeeeuqpp54/j4JMlh6Meuqpp5566qnnz6Mgk6UHo556
|
||||
6qmnnnrq+fMoyGTpwainnnrqqaeeev48CjJZejDqqaeeeuqpp54/j4JMlh6Meuqpp5566qnnz6Mg
|
||||
k6UHo5566qmnnnrq+fPIpWDUU0899dRTTz1/HrkUjHrqqaeeeuqp588zLgWjnnrqqaeeeur584xL
|
||||
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
|
||||
NCswMDowMCj2a7wAAAAASUVORK5CYII=" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 37 KiB |