chore: import upstream snapshot with attribution
This commit is contained in:
@@ -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>
|
||||
+22
@@ -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>
|
||||
+25
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
+17
@@ -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>
|
||||
+10
@@ -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
|
||||
{
|
||||
}
|
||||
+39
@@ -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();
|
||||
}
|
||||
+6
@@ -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>
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
using Foundation;
|
||||
|
||||
namespace MauiApp1;
|
||||
|
||||
[Register("AppDelegate")]
|
||||
public class AppDelegate : MauiUIApplicationDelegate
|
||||
{
|
||||
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||
}
|
||||
+30
@@ -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>
|
||||
+15
@@ -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));
|
||||
}
|
||||
}
|
||||
+16
@@ -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);
|
||||
}
|
||||
}
|
||||
+15
@@ -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>
|
||||
+8
@@ -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>
|
||||
+24
@@ -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();
|
||||
}
|
||||
|
||||
+46
@@ -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>
|
||||
+15
@@ -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>
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
using Foundation;
|
||||
|
||||
namespace MauiApp1;
|
||||
|
||||
[Register("AppDelegate")]
|
||||
public class AppDelegate : MauiUIApplicationDelegate
|
||||
{
|
||||
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||
}
|
||||
+32
@@ -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>
|
||||
+15
@@ -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));
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"profiles": {
|
||||
"Windows Machine": {
|
||||
"commandName": "MsixPackage",
|
||||
"nativeDebugging": false
|
||||
}
|
||||
}
|
||||
}
|
||||
+88
@@ -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>
|
||||
+496
@@ -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!");
|
||||
}
|
||||
));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+4
@@ -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 |
+492
@@ -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 |
BIN
Binary file not shown.
BIN
Binary file not shown.
+93
@@ -0,0 +1,93 @@
|
||||
<svg width="419" height="519" viewBox="0 0 419 519" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M284.432 247.568L284.004 221.881C316.359 221.335 340.356 211.735 355.308 193.336C382.408 159.996 372.893 108.183 372.786 107.659L398.013 102.831C398.505 105.432 409.797 167.017 375.237 209.53C355.276 234.093 324.719 246.894 284.432 247.568Z" fill="#8A6FE8"/>
|
||||
<path d="M331.954 109.36L361.826 134.245C367.145 138.676 375.055 137.959 379.497 132.639C383.928 127.32 383.211 119.41 377.891 114.969L348.019 90.0842C342.7 85.6531 334.79 86.3702 330.348 91.6896C325.917 97.0197 326.634 104.929 331.954 109.36Z" fill="#8A6FE8"/>
|
||||
<path d="M407.175 118.062L417.92 94.2263C420.735 87.858 417.856 80.4087 411.488 77.5831C405.12 74.7682 397.67 77.6473 394.845 84.0156L383.831 108.461L407.175 118.062Z" fill="#8A6FE8"/>
|
||||
<path d="M401.363 105.175L401.234 69.117C401.181 62.1493 395.498 56.541 388.53 56.5945C381.562 56.648 375.954 62.3313 376.007 69.2989L376.018 96.11L401.363 105.175Z" fill="#8A6FE8"/>
|
||||
<path d="M386.453 109.071L378.137 73.9548C376.543 67.169 369.757 62.9628 362.971 64.5575C356.185 66.1523 351.979 72.938 353.574 79.7237L362.04 115.482L386.453 109.071Z" fill="#8A6FE8"/>
|
||||
<path d="M381.776 142.261C396.359 142.261 408.181 130.44 408.181 115.857C408.181 101.274 396.359 89.4527 381.776 89.4527C367.194 89.4527 355.372 101.274 355.372 115.857C355.372 130.44 367.194 142.261 381.776 142.261Z" fill="url(#paint0_radial)"/>
|
||||
<path d="M248.267 406.979C248.513 384.727 245.345 339.561 222.376 301.736L199.922 315.372C220.76 349.675 222.323 389.715 221.841 407.182C221.798 408.627 235.263 409.933 248.267 406.979Z" fill="url(#paint1_linear)"/>
|
||||
<path d="M221.841 406.936L242.637 406.84L262.052 518.065L220.311 518.258C217.132 518.269 214.724 515.711 214.938 512.532L221.841 406.936Z" fill="#522CD5"/>
|
||||
<path d="M306.566 488.814C310.173 491.661 310.109 495.782 309.831 500.127L308.964 513.452C308.803 515.839 306.727 517.798 304.34 517.809L260.832 518.012C258.125 518.023 256.08 515.839 256.262 513.142L256.551 499.335C256.883 494.315 255.192 492.474 251.307 487.744C244.649 479.663 224.967 435.62 226.84 406.925L248.256 406.829C249.691 423.858 272.167 461.682 306.566 488.814Z" fill="url(#paint2_linear)"/>
|
||||
<path d="M309.82 500.127C310.023 497.088 310.077 494.176 308.889 491.715L254.635 491.961C256.134 494.166 256.765 496.092 256.562 499.314L256.273 513.121C256.091 515.828 258.146 518.012 260.843 517.99L304.34 517.798C306.727 517.787 308.803 515.828 308.964 513.442L309.82 500.127Z" fill="url(#paint3_radial)"/>
|
||||
<path d="M133.552 407.471C133.103 385.22 135.864 340.021 158.49 301.993L181.073 315.425C160.545 349.921 159.346 389.972 159.989 407.428C160.042 408.884 146.578 410.318 133.552 407.471Z" fill="url(#paint4_linear)"/>
|
||||
<path d="M110.798 497.152C110.765 494.187 111.204 491.575 112.457 487.23C131.882 434.132 133.52 407.364 133.52 407.364L159.999 407.246C159.999 407.246 161.819 433.512 181.716 486.427C183.289 490.195 183.471 493.641 183.674 496.831L183.792 513.816C183.803 516.374 181.716 518.483 179.158 518.494L177.873 518.504L116.781 518.782L115.496 518.793C112.927 518.804 110.83 516.728 110.819 514.159L110.798 497.152Z" fill="url(#paint5_linear)"/>
|
||||
<path d="M110.798 497.152C110.798 496.67 110.808 496.199 110.83 495.739C110.969 494.262 111.643 492.603 114.875 492.582L180.207 492.282C182.561 492.367 183.343 494.176 183.589 495.311C183.621 495.814 183.664 496.328 183.696 496.82L183.813 513.806C183.824 515.411 183.011 516.824 181.769 517.669C181.031 518.172 180.132 518.472 179.179 518.483L177.895 518.494L116.802 518.772L115.528 518.782C114.244 518.793 113.077 518.269 112.232 517.434C111.386 516.599 110.862 515.432 110.851 514.148L110.798 497.152Z" fill="url(#paint6_radial)"/>
|
||||
<path d="M314.979 246.348C324.162 210.407 318.008 181.777 318.008 181.777L326.452 181.734L326.656 181.574C314.262 115.75 256.326 66.0987 186.949 66.4198C108.796 66.773 45.7233 130.424 46.0765 208.577C46.4297 286.731 110.08 349.803 188.234 349.45C249.905 349.172 302.178 309.474 321.304 254.343C321.872 251.999 321.797 247.804 314.979 246.348Z" fill="url(#paint7_radial)"/>
|
||||
<path d="M310.237 279.035L65.877 280.148C71.3998 289.428 77.95 298.012 85.3672 305.761L290.972 304.829C298.336 297.005 304.8 288.368 310.237 279.035Z" fill="#D8CFF7"/>
|
||||
<path d="M235.062 312.794L280.924 312.585L280.74 272.021L234.877 272.23L235.062 312.794Z" fill="#512BD4"/>
|
||||
<path d="M243.001 297.626C242.691 297.626 242.434 297.53 242.22 297.327C242.006 297.123 241.899 296.866 241.899 296.588C241.899 296.299 242.006 296.042 242.22 295.839C242.434 295.625 242.691 295.528 243.001 295.528C243.312 295.528 243.568 295.635 243.782 295.839C243.996 296.042 244.114 296.299 244.114 296.588C244.114 296.877 244.007 297.123 243.793 297.327C243.568 297.519 243.312 297.626 243.001 297.626Z" fill="white"/>
|
||||
<path d="M255.192 297.434H253.212L247.967 289.203C247.839 289 247.721 288.775 247.636 288.55H247.593C247.636 288.786 247.657 289.299 247.657 290.091L247.668 297.444H245.912L245.891 286.228H247.999L253.062 294.265C253.276 294.597 253.415 294.833 253.479 294.95H253.511C253.458 294.651 253.437 294.148 253.437 293.441L253.426 286.217H255.17L255.192 297.434Z" fill="white"/>
|
||||
<path d="M263.733 297.412L257.589 297.423L257.568 286.206L263.465 286.195V287.779L259.387 287.79L259.398 290.969L263.155 290.958V292.532L259.398 292.542L259.409 295.86L263.733 295.85V297.412Z" fill="white"/>
|
||||
<path d="M272.445 287.758L269.298 287.769L269.32 297.401H267.5L267.479 287.769L264.343 287.779V286.195L272.434 286.174L272.445 287.758Z" fill="white"/>
|
||||
<path d="M315.279 246.337C324.355 210.836 318.457 182.483 318.308 181.798L171.484 182.462C171.484 182.462 162.226 181.563 162.268 190.018C162.311 198.463 162.761 222.341 162.878 248.746C162.9 254.172 167.363 256.773 170.863 256.751C170.874 256.751 311.618 252.213 315.279 246.337Z" fill="url(#paint8_radial)"/>
|
||||
<path d="M227.685 246.798C227.685 246.798 250.183 228.827 254.571 225.499C258.959 222.17 262.812 221.977 266.869 225.445C270.925 228.913 293.616 246.498 293.616 246.498L227.685 246.798Z" fill="#A08BE8"/>
|
||||
<path d="M320.748 256.141C320.748 256.141 324.943 248.414 315.279 246.348C315.289 246.305 170.927 246.894 170.927 246.894C167.566 246.905 163.232 244.925 162.846 241.671C162.857 244.004 162.878 246.369 162.889 248.756C162.91 253.68 166.582 256.27 169.878 256.698C170.21 256.73 170.542 256.773 170.874 256.773L180.742 256.73L320.748 256.141Z" fill="#512BD4"/>
|
||||
<path d="M206.4 233.214C212.511 233.095 217.302 224.667 217.102 214.39C216.901 204.112 211.785 195.878 205.674 195.997C199.563 196.116 194.772 204.544 194.973 214.821C195.173 225.099 200.289 233.333 206.4 233.214Z" fill="#512BD4"/>
|
||||
<path d="M306.249 214.267C306.356 203.989 301.488 195.605 295.377 195.541C289.266 195.478 284.225 203.758 284.118 214.037C284.011 224.315 288.878 232.699 294.99 232.763C301.101 232.826 306.142 224.545 306.249 214.267Z" fill="#512BD4"/>
|
||||
<path d="M205.905 205.291C208.152 203.022 211.192 202.016 214.157 202.262C215.912 205.495 217.014 209.733 217.111 214.389C217.164 217.3 216.811 220.04 216.158 222.513C212.669 223.519 208.752 222.662 205.979 219.922C201.912 215.909 201.88 209.348 205.905 205.291Z" fill="#8065E0"/>
|
||||
<path d="M294.996 204.285C297.255 202.016 300.294 200.999 303.259 201.256C305.164 204.628 306.309 209.209 306.256 214.239C306.224 216.808 305.892 219.259 305.303 221.485C301.793 222.523 297.843 221.678 295.061 218.916C291.004 214.892 290.972 208.342 294.996 204.285Z" fill="#8065E0"/>
|
||||
<path d="M11.6342 357.017C10.9171 354.716 -5.72611 300.141 21.3204 258.903C36.9468 235.078 63.3083 221.035 99.6664 217.15L102.449 243.276C74.3431 246.273 54.4676 256.345 43.3579 273.202C23.0971 303.941 36.5722 348.733 36.7113 349.183L11.6342 357.017Z" fill="url(#paint9_linear)"/>
|
||||
<path d="M95.1498 252.802C109.502 252.802 121.137 241.167 121.137 226.815C121.137 212.463 109.502 200.828 95.1498 200.828C80.7976 200.828 69.1628 212.463 69.1628 226.815C69.1628 241.167 80.7976 252.802 95.1498 252.802Z" fill="url(#paint10_radial)"/>
|
||||
<path d="M72.0098 334.434L33.4683 329.307C26.597 328.397 20.2929 333.214 19.3725 340.085C18.4627 346.956 23.279 353.26 30.1504 354.181L68.6919 359.308C75.5632 360.217 81.8673 355.401 82.7878 348.53C83.6975 341.658 78.8705 335.344 72.0098 334.434Z" fill="#8A6FE8"/>
|
||||
<path d="M3.73535 367.185L7.35297 393.076C8.36975 399.968 14.7702 404.731 21.6629 403.725C28.5556 402.708 33.3185 396.308 32.3124 389.415L28.5984 362.861L3.73535 367.185Z" fill="#8A6FE8"/>
|
||||
<path d="M15.5194 374.988L34.849 405.427C38.6058 411.292 46.4082 413.005 52.2735 409.248C58.1387 405.491 59.8512 397.689 56.0945 391.823L41.7953 369.144L15.5194 374.988Z" fill="#8A6FE8"/>
|
||||
<path d="M26.0511 363.739L51.8026 389.019C56.7688 393.911 64.7532 393.846 69.6445 388.88C74.5358 383.914 74.4715 375.929 69.516 371.038L43.2937 345.297L26.0511 363.739Z" fill="#8A6FE8"/>
|
||||
<path d="M26.4043 381.912C40.987 381.912 52.8086 370.091 52.8086 355.508C52.8086 340.925 40.987 329.104 26.4043 329.104C11.8216 329.104 0 340.925 0 355.508C0 370.091 11.8216 381.912 26.4043 381.912Z" fill="url(#paint11_radial)"/>
|
||||
<path d="M184.73 63.6308L157.819 66.5892L158.561 38.5412L177.888 36.4178L184.73 63.6308Z" fill="#8A6FE8"/>
|
||||
<path d="M170.018 41.647C180.455 39.521 187.193 29.3363 185.067 18.8988C182.941 8.46126 172.757 1.72345 162.319 3.84944C151.882 5.97543 145.144 16.1601 147.27 26.5976C149.396 37.0351 159.58 43.773 170.018 41.647Z" fill="#D8CFF7"/>
|
||||
<path d="M196.885 79.385C198.102 79.2464 198.948 78.091 198.684 76.8997C195.851 64.2818 183.923 55.5375 170.773 56.9926C157.622 58.4371 147.886 69.5735 147.865 82.4995C147.863 83.7232 148.949 84.6597 150.168 84.5316L196.885 79.385Z" fill="url(#paint12_radial)"/>
|
||||
<defs>
|
||||
<radialGradient id="paint0_radial" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(382.004 103.457) scale(26.4058)">
|
||||
<stop stop-color="#8065E0"/>
|
||||
<stop offset="1" stop-color="#512BD4"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="paint1_linear" x1="214.439" y1="303.482" x2="236.702" y2="409.505" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#522CD5"/>
|
||||
<stop offset="0.4397" stop-color="#8A6FE8"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear" x1="231.673" y1="404.144" x2="297.805" y2="522.048" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#522CD5"/>
|
||||
<stop offset="0.4397" stop-color="#8A6FE8"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="paint3_radial" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(280.957 469.555) rotate(-0.260742) scale(45.8326)">
|
||||
<stop offset="0.034" stop-color="#522CD5"/>
|
||||
<stop offset="0.9955" stop-color="#8A6FE8"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="paint4_linear" x1="166.061" y1="303.491" x2="144.763" y2="409.709" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#522CD5"/>
|
||||
<stop offset="0.4397" stop-color="#8A6FE8"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint5_linear" x1="146.739" y1="407.302" x2="147.246" y2="518.627" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#522CD5"/>
|
||||
<stop offset="0.4397" stop-color="#8A6FE8"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="paint6_radial" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(148.63 470.023) rotate(179.739) scale(50.2476)">
|
||||
<stop offset="0.034" stop-color="#522CD5"/>
|
||||
<stop offset="0.9955" stop-color="#8A6FE8"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint7_radial" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(219.219 153.929) rotate(179.739) scale(140.935)">
|
||||
<stop offset="0.4744" stop-color="#A08BE8"/>
|
||||
<stop offset="0.8618" stop-color="#8065E0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint8_radial" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(314.861 158.738) rotate(179.739) scale(146.053)">
|
||||
<stop offset="0.0933" stop-color="#E1DFDD"/>
|
||||
<stop offset="0.6573" stop-color="white"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="paint9_linear" x1="54.1846" y1="217.159" x2="54.1846" y2="357.022" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.3344" stop-color="#9780E6"/>
|
||||
<stop offset="0.8488" stop-color="#8A6FE8"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="paint10_radial" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(90.3494 218.071) rotate(-0.260742) scale(25.9924)">
|
||||
<stop stop-color="#8065E0"/>
|
||||
<stop offset="1" stop-color="#512BD4"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint11_radial" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(25.805 345.043) scale(26.4106)">
|
||||
<stop stop-color="#8065E0"/>
|
||||
<stop offset="1" stop-color="#512BD4"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint12_radial" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(169.113 67.3662) rotate(-32.2025) scale(21.0773)">
|
||||
<stop stop-color="#8065E0"/>
|
||||
<stop offset="1" stop-color="#512BD4"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 12 KiB |
+15
@@ -0,0 +1,15 @@
|
||||
Any raw assets you want to be deployed with your application can be placed in
|
||||
this directory (and child directories). Deployment of the asset to your application
|
||||
is automatically handled by the following `MauiAsset` Build Action within your `.csproj`.
|
||||
|
||||
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
|
||||
|
||||
These files will be deployed with you package and will be accessible using Essentials:
|
||||
|
||||
async Task LoadMauiAsset()
|
||||
{
|
||||
using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt");
|
||||
using var reader = new StreamReader(stream);
|
||||
|
||||
var contents = reader.ReadToEnd();
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
<?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 width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<path d="m 105.50037,281.60863 c -2.70293,0 -5.00091,-0.90042 -6.893127,-2.70209 -1.892214,-1.84778 -2.837901,-4.04181 -2.837901,-6.58209 0,-2.58722 0.945687,-4.80389 2.837901,-6.65167 1.892217,-1.84778 4.190197,-2.77167 6.893127,-2.77167 2.74819,0 5.06798,0.92389 6.96019,2.77167 1.93749,1.84778 2.90581,4.06445 2.90581,6.65167 0,2.54028 -0.96832,4.73431 -2.90581,6.58209 -1.89221,1.80167 -4.212,2.70209 -6.96019,2.70209 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||
<path d="M 213.56111,280.08446 H 195.99044 L 149.69953,207.0544 c -1.17121,-1.84778 -2.14037,-3.76515 -2.90581,-5.75126 h -0.40578 c 0.36051,2.12528 0.54076,6.67515 0.54076,13.6496 v 65.13172 h -15.54349 v -99.36009 h 18.71925 l 44.7374,71.29798 c 1.89222,2.95695 3.1087,4.98917 3.64945,6.09751 h 0.26996 c -0.45021,-2.6325 -0.67573,-7.09015 -0.67573,-13.37293 v -64.02256 h 15.47557 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||
<path d="m 289.25134,280.08446 h -54.40052 v -99.36009 h 52.23835 v 13.99669 h -36.15411 v 28.13085 h 33.31621 v 13.9271 h -33.31621 v 29.37835 h 38.31628 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||
<path d="M 366.56466,194.72106 H 338.7222 v 85.3634 h -16.08423 v -85.3634 h -27.77455 v -13.99669 h 71.70124 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
+44
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<?xaml-comp compile="true" ?>
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
|
||||
|
||||
<Color x:Key="Primary">#512BD4</Color>
|
||||
<Color x:Key="Secondary">#DFD8F7</Color>
|
||||
<Color x:Key="Tertiary">#2B0B98</Color>
|
||||
<Color x:Key="White">White</Color>
|
||||
<Color x:Key="Black">Black</Color>
|
||||
<Color x:Key="Gray100">#E1E1E1</Color>
|
||||
<Color x:Key="Gray200">#C8C8C8</Color>
|
||||
<Color x:Key="Gray300">#ACACAC</Color>
|
||||
<Color x:Key="Gray400">#919191</Color>
|
||||
<Color x:Key="Gray500">#6E6E6E</Color>
|
||||
<Color x:Key="Gray600">#404040</Color>
|
||||
<Color x:Key="Gray900">#212121</Color>
|
||||
<Color x:Key="Gray950">#141414</Color>
|
||||
<SolidColorBrush x:Key="PrimaryBrush" Color="{StaticResource Primary}"/>
|
||||
<SolidColorBrush x:Key="SecondaryBrush" Color="{StaticResource Secondary}"/>
|
||||
<SolidColorBrush x:Key="TertiaryBrush" Color="{StaticResource Tertiary}"/>
|
||||
<SolidColorBrush x:Key="WhiteBrush" Color="{StaticResource White}"/>
|
||||
<SolidColorBrush x:Key="BlackBrush" Color="{StaticResource Black}"/>
|
||||
<SolidColorBrush x:Key="Gray100Brush" Color="{StaticResource Gray100}"/>
|
||||
<SolidColorBrush x:Key="Gray200Brush" Color="{StaticResource Gray200}"/>
|
||||
<SolidColorBrush x:Key="Gray300Brush" Color="{StaticResource Gray300}"/>
|
||||
<SolidColorBrush x:Key="Gray400Brush" Color="{StaticResource Gray400}"/>
|
||||
<SolidColorBrush x:Key="Gray500Brush" Color="{StaticResource Gray500}"/>
|
||||
<SolidColorBrush x:Key="Gray600Brush" Color="{StaticResource Gray600}"/>
|
||||
<SolidColorBrush x:Key="Gray900Brush" Color="{StaticResource Gray900}"/>
|
||||
<SolidColorBrush x:Key="Gray950Brush" Color="{StaticResource Gray950}"/>
|
||||
|
||||
<Color x:Key="Yellow100Accent">#F7B548</Color>
|
||||
<Color x:Key="Yellow200Accent">#FFD590</Color>
|
||||
<Color x:Key="Yellow300Accent">#FFE5B9</Color>
|
||||
<Color x:Key="Cyan100Accent">#28C2D1</Color>
|
||||
<Color x:Key="Cyan200Accent">#7BDDEF</Color>
|
||||
<Color x:Key="Cyan300Accent">#C3F2F4</Color>
|
||||
<Color x:Key="Blue100Accent">#3E8EED</Color>
|
||||
<Color x:Key="Blue200Accent">#72ACF1</Color>
|
||||
<Color x:Key="Blue300Accent">#A7CBF6</Color>
|
||||
|
||||
</ResourceDictionary>
|
||||
+384
@@ -0,0 +1,384 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<?xaml-comp compile="true" ?>
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
|
||||
|
||||
<Style TargetType="ActivityIndicator">
|
||||
<Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="IndicatorView">
|
||||
<Setter Property="IndicatorColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}"/>
|
||||
<Setter Property="SelectedIndicatorColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray100}}"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Border">
|
||||
<Setter Property="Stroke" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
|
||||
<Setter Property="StrokeShape" Value="Rectangle"/>
|
||||
<Setter Property="StrokeThickness" Value="1"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="BoxView">
|
||||
<Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray200}}" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Primary}}" />
|
||||
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
||||
<Setter Property="FontSize" Value="14"/>
|
||||
<Setter Property="CornerRadius" Value="8"/>
|
||||
<Setter Property="Padding" Value="14,10"/>
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray200}}" />
|
||||
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray600}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="CheckBox">
|
||||
<Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="DatePicker">
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
|
||||
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
||||
<Setter Property="FontSize" Value="14"/>
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Editor">
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
|
||||
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Entry">
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
|
||||
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Frame">
|
||||
<Setter Property="HasShadow" Value="False" />
|
||||
<Setter Property="BorderColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray950}}" />
|
||||
<Setter Property="CornerRadius" Value="8" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="ImageButton">
|
||||
<Setter Property="Opacity" Value="1" />
|
||||
<Setter Property="BorderColor" Value="Transparent"/>
|
||||
<Setter Property="BorderWidth" Value="0"/>
|
||||
<Setter Property="CornerRadius" Value="0"/>
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="Opacity" Value="0.5" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Label">
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
|
||||
<Setter Property="FontFamily" Value="OpenSansRegular" />
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="ListView">
|
||||
<Setter Property="SeparatorColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
|
||||
<Setter Property="RefreshControlColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Picker">
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
|
||||
<Setter Property="TitleColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
|
||||
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
||||
<Setter Property="FontSize" Value="14"/>
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
<Setter Property="TitleColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="ProgressBar">
|
||||
<Setter Property="ProgressColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="ProgressColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="RadioButton">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
|
||||
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
||||
<Setter Property="FontSize" Value="14"/>
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="RefreshView">
|
||||
<Setter Property="RefreshColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="SearchBar">
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
|
||||
<Setter Property="PlaceholderColor" Value="{StaticResource Gray500}" />
|
||||
<Setter Property="CancelButtonColor" Value="{StaticResource Gray500}" />
|
||||
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||
<Setter Property="FontFamily" Value="OpenSansRegular" />
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="SearchHandler">
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
|
||||
<Setter Property="PlaceholderColor" Value="{StaticResource Gray500}" />
|
||||
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||
<Setter Property="FontFamily" Value="OpenSansRegular" />
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Shadow">
|
||||
<Setter Property="Radius" Value="15" />
|
||||
<Setter Property="Opacity" Value="0.5" />
|
||||
<Setter Property="Brush" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource White}}" />
|
||||
<Setter Property="Offset" Value="10,10" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Slider">
|
||||
<Setter Property="MinimumTrackColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||
<Setter Property="MaximumTrackColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray600}}" />
|
||||
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="MinimumTrackColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}"/>
|
||||
<Setter Property="MaximumTrackColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}"/>
|
||||
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}"/>
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="SwipeItem">
|
||||
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Switch">
|
||||
<Setter Property="OnColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||
<Setter Property="ThumbColor" Value="{StaticResource White}" />
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="OnColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="On">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="OnColor" Value="{AppThemeBinding Light={StaticResource Secondary}, Dark={StaticResource Gray200}}" />
|
||||
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Off">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Gray400}, Dark={StaticResource Gray500}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="TimePicker">
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
||||
<Setter Property="FontSize" Value="14"/>
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Page" ApplyToDerivedTypes="True">
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Shell" ApplyToDerivedTypes="True">
|
||||
<Setter Property="Shell.BackgroundColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource Gray950}}" />
|
||||
<Setter Property="Shell.ForegroundColor" Value="{OnPlatform WinUI={StaticResource Primary}, Default={StaticResource White}}" />
|
||||
<Setter Property="Shell.TitleColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource White}}" />
|
||||
<Setter Property="Shell.DisabledColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray950}}" />
|
||||
<Setter Property="Shell.UnselectedColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray200}}" />
|
||||
<Setter Property="Shell.NavBarHasShadow" Value="False" />
|
||||
<Setter Property="Shell.TabBarBackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
|
||||
<Setter Property="Shell.TabBarForegroundColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||
<Setter Property="Shell.TabBarTitleColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||
<Setter Property="Shell.TabBarUnselectedColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="NavigationPage">
|
||||
<Setter Property="BarBackgroundColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource Gray950}}" />
|
||||
<Setter Property="BarTextColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource White}}" />
|
||||
<Setter Property="IconColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource White}}" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="TabbedPage">
|
||||
<Setter Property="BarBackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Gray950}}" />
|
||||
<Setter Property="BarTextColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||
<Setter Property="UnselectedTabColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray950}}" />
|
||||
<Setter Property="SelectedTabColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray200}}" />
|
||||
</Style>
|
||||
|
||||
</ResourceDictionary>
|
||||
+247
@@ -0,0 +1,247 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace MauiApp1.Utils
|
||||
{
|
||||
public enum DownloadState
|
||||
{
|
||||
cancelled = 0, // 取消
|
||||
inprogres = 1, //进行中
|
||||
completed = 2, //完成
|
||||
error = 3, //错误
|
||||
existed = 4,//已存在
|
||||
noexisted = 5,//已存在
|
||||
|
||||
}
|
||||
public delegate void DelegateDone(int progress, DownloadState downloadState,string filename, string msg = "");
|
||||
internal class DownloadHelper : INotifyPropertyChanged
|
||||
{
|
||||
private DelegateDone _callback;
|
||||
private bool _isDownloading = false;
|
||||
private int _progress;
|
||||
private string _fileName;
|
||||
private object _lockobj = new object();
|
||||
private WebClient _webClient;
|
||||
private ICommand DownloadCommand { get; set; }
|
||||
public bool IsDownloading
|
||||
{
|
||||
get => _isDownloading;
|
||||
set
|
||||
{
|
||||
if (value != _isDownloading)
|
||||
{
|
||||
_isDownloading = value;
|
||||
NotifyPropertyChanged("IsDownloading");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string FileName
|
||||
{
|
||||
get => _fileName;
|
||||
set
|
||||
{
|
||||
if (value != _fileName)
|
||||
{
|
||||
_fileName = value;
|
||||
NotifyPropertyChanged("FileName");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
protected void NotifyPropertyChanged(string property)
|
||||
{
|
||||
if (PropertyChanged != null)
|
||||
{
|
||||
PropertyChanged(this, new PropertyChangedEventArgs(property));
|
||||
}
|
||||
}
|
||||
|
||||
public DownloadHelper()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public DownloadHelper(DelegateDone callback)
|
||||
{
|
||||
_callback = callback;
|
||||
}
|
||||
|
||||
public void DownloadCreate(string downloadUrl, string fileName, string rootFolderName, string subFolderName)
|
||||
{
|
||||
lock (_lockobj)
|
||||
{
|
||||
//IsDownloading = true;
|
||||
FileName = fileName;
|
||||
var downloadFolder = Path.Combine(SysConf.ApplicationBase, rootFolderName);
|
||||
var modelFolder = Path.Combine(downloadFolder, subFolderName);
|
||||
Directory.CreateDirectory(downloadFolder);
|
||||
Directory.CreateDirectory(modelFolder);
|
||||
string fileFullname = Path.Combine(modelFolder, fileName);
|
||||
if (!File.Exists(fileFullname))
|
||||
{
|
||||
_webClient = new WebClient();
|
||||
_webClient.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36");
|
||||
_webClient.DownloadFileCompleted += OnDownloadCompleted;
|
||||
_webClient.DownloadProgressChanged += OnDownloadProgressChanged;
|
||||
DownloadCommand = new Command(() =>
|
||||
{
|
||||
if (!IsDownloading)
|
||||
{
|
||||
IsDownloading = true;
|
||||
_webClient.DownloadFileAsync(new Uri(downloadUrl), fileFullname);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
DownloadCommand = null;
|
||||
if (_callback != null)
|
||||
{
|
||||
_callback(100, DownloadState.existed, FileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void DownloadStart()
|
||||
{
|
||||
if (DownloadCommand != null && DownloadCommand.CanExecute(null))
|
||||
{
|
||||
DownloadCommand.Execute(null);
|
||||
}
|
||||
}
|
||||
|
||||
public void DownloadCancel()
|
||||
{
|
||||
if (_webClient != null)
|
||||
{
|
||||
_webClient.CancelAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public bool GetDownloadState(string[] fileNames, string rootFolderName, string subFolderName, string[] md5Strs)
|
||||
{
|
||||
bool state=true;
|
||||
var downloadFolder = Path.Combine(SysConf.ApplicationBase, rootFolderName);
|
||||
var modelFolder = Path.Combine(downloadFolder, subFolderName);
|
||||
Directory.CreateDirectory(downloadFolder);
|
||||
Directory.CreateDirectory(modelFolder);
|
||||
for (int i = 0; i < fileNames.Length; i++)
|
||||
{
|
||||
string fileFullname = Path.Combine(modelFolder, fileNames[i]);
|
||||
if (File.Exists(fileFullname))
|
||||
{
|
||||
FileInfo fileInfo = new FileInfo(fileFullname);
|
||||
if (fileInfo.Length == 0)
|
||||
{
|
||||
state = state && false;
|
||||
}
|
||||
else
|
||||
{
|
||||
string md5str = GetMD5Hash(fileFullname);
|
||||
if (!md5str.Equals(md5Strs[i]))
|
||||
{
|
||||
state = state && false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
state = state && false;
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
public void DownloadCheck(string fileName, string rootFolderName, string subFolderName,string md5Str)
|
||||
{
|
||||
var downloadFolder = Path.Combine(SysConf.ApplicationBase, rootFolderName);
|
||||
var modelFolder = Path.Combine(downloadFolder, subFolderName);
|
||||
Directory.CreateDirectory(downloadFolder);
|
||||
Directory.CreateDirectory(modelFolder);
|
||||
string fileFullname = Path.Combine(modelFolder, fileName);
|
||||
if (File.Exists(fileFullname))
|
||||
{
|
||||
FileInfo fileInfo = new FileInfo(fileFullname);
|
||||
if (fileInfo.Length == 0)
|
||||
{
|
||||
File.Delete(fileFullname);
|
||||
string filename = fileInfo.Name;
|
||||
_callback(0, DownloadState.noexisted, filename);
|
||||
}
|
||||
else
|
||||
{
|
||||
string md5str = GetMD5Hash(fileFullname);
|
||||
if (!md5str.Equals(md5Str))
|
||||
{
|
||||
File.Delete(fileFullname);
|
||||
string filename = fileInfo.Name;
|
||||
_callback(0, DownloadState.noexisted, filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string filename = fileName;
|
||||
_callback(0, DownloadState.noexisted, filename);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void OnDownloadProgressChanged(object? sender, DownloadProgressChangedEventArgs e)
|
||||
{
|
||||
string filename = FileName;
|
||||
_progress = (int)(e.BytesReceived * 100 / e.TotalBytesToReceive);
|
||||
if (_callback != null)
|
||||
{
|
||||
_callback(_progress, DownloadState.inprogres, filename);
|
||||
}
|
||||
}
|
||||
private void OnDownloadCompleted(object? sender, AsyncCompletedEventArgs e)
|
||||
{
|
||||
string filename = FileName;
|
||||
IsDownloading = false;
|
||||
if (_callback != null)
|
||||
{
|
||||
if (e.Cancelled)
|
||||
{
|
||||
_callback(_progress, DownloadState.cancelled, filename);
|
||||
}
|
||||
else if (e.Error != null)
|
||||
{
|
||||
_callback(_progress, DownloadState.error,filename, msg: e.Error.Message);
|
||||
}
|
||||
else
|
||||
{
|
||||
_callback(_progress, DownloadState.completed, filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string GetMD5Hash(string path)
|
||||
{
|
||||
using (FileStream fs = new FileStream(path, FileMode.Open))
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
MD5 md5 = MD5.Create();
|
||||
byte[] array = md5.ComputeHash(fs);
|
||||
fs.Close();
|
||||
for (int i = 0; i < array.Length; i++)
|
||||
{
|
||||
sb.Append(array[i].ToString("x2"));
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MauiApp1.Utils
|
||||
{
|
||||
internal class SysConf
|
||||
{
|
||||
#if WINDOWS
|
||||
private static string _applicationBase = Microsoft.Maui.Storage.FileSystem.AppDataDirectory;// "/data/user/0/com.companyname.mauiapp1/files/AllModels";//"/data/data/com.companyname.mauiapp1/files/Assets";// AppDomain.CurrentDomain.BaseDirectory;//
|
||||
#else
|
||||
private static string _applicationBase = AppDomain.CurrentDomain.BaseDirectory;
|
||||
#endif
|
||||
public SysConf() { }
|
||||
|
||||
public static string ApplicationBase { get => _applicationBase; set => _applicationBase = value; }
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="te21wv1n.h0a~" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommandLineParser" Version="2.9.1" />
|
||||
<PackageReference Include="NAudio" Version="2.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\AliParaformerAsr\AliParaformerAsr.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-onnx\am.mvn">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-onnx\asr.yaml">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-onnx\example\0.wav">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-onnx\example\1.wav">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-onnx\example\2.wav">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-onnx\example\3.wav">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-onnx\example\4.wav">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-onnx\tokens.txt">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,116 @@
|
||||
using AliParaformerAsr;
|
||||
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_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-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, "asr.yaml");
|
||||
string mvnFilePath = Path.Combine(modelPath, "am.mvn");
|
||||
string tokensFilePath = Path.Combine(modelPath, "tokens.json");
|
||||
|
||||
|
||||
var offlineRecognizer = new OfflineRecognizer(modelFilePath, configFilePath, mvnFilePath, tokensFilePath, OnnxRumtimeTypes.CPU);
|
||||
|
||||
List<float[]> samples = new List<float[]>();
|
||||
TimeSpan total_duration = new TimeSpan(0L);
|
||||
|
||||
if (File.Exists(argParams.WavFilePath))
|
||||
{
|
||||
(var sample, var duration) = LoadWavFile(argParams.WavFilePath);
|
||||
|
||||
samples.Add(sample);
|
||||
total_duration += duration;
|
||||
}
|
||||
else if (Directory.Exists(argParams.WavFilePath))
|
||||
{
|
||||
var findWavCount = 0;
|
||||
|
||||
foreach (var wavFilePath in Directory.EnumerateFiles(argParams.WavFilePath, "*.wav"))
|
||||
{
|
||||
(var sample, var duration) = LoadWavFile(wavFilePath);
|
||||
|
||||
samples.Add(sample);
|
||||
total_duration += duration;
|
||||
findWavCount++;
|
||||
}
|
||||
|
||||
Console.WriteLine($"Total WAV files found: {findWavCount} duration:{total_duration}");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Invalid wav input path. {argParams.WavFilePath}");
|
||||
}
|
||||
|
||||
var start_time = DateTime.Now;
|
||||
|
||||
int batchSize = 1; // 输入参数支持批处理,但是实际效果提升有限,感觉还是负优化,等GPU版本优化后再试
|
||||
for (int i = 0; i < samples.Count; i += batchSize)
|
||||
{
|
||||
List<float[]> temp_samples = samples.Skip(i).Take(batchSize).ToList();
|
||||
|
||||
List<string> results = offlineRecognizer.GetResults(temp_samples);
|
||||
|
||||
foreach (string result in results)
|
||||
{
|
||||
Console.WriteLine(result);
|
||||
Console.WriteLine("");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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());
|
||||
|
||||
// 实时因子是处理时间与音频时长的比值。
|
||||
// 例如,如果一个 10 秒的音频片段需要 5 秒来处理,那么实时因子就是 0.5。
|
||||
// 如果处理时间和音频时长相等,那么实时因子就是 1,这意味着系统以实时速度进行处理。
|
||||
// 数值越小,表示处理速度越快。
|
||||
// from chatgpt 解释
|
||||
Console.WriteLine("Real-Time Factor :{0}", rtf.ToString());
|
||||
Console.WriteLine("end!");
|
||||
}
|
||||
|
||||
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,45 @@
|
||||
|
||||
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}") = "AliParaformerAsr.Examples", "AliParaformerAsr.Examples\AliParaformerAsr.Examples.csproj", "{0CC20DAF-D6F4-481B-AE5F-09521DAC3CA2}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AliParaformerAsr", "AliParaformerAsr\AliParaformerAsr.csproj", "{763DE8F4-D05C-4317-B627-3CE1B09431A3}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{FC70D2F3-89D8-40D2-A59A-47D4960C508F}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
LICENSE = LICENSE
|
||||
README.md = README.md
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MauiApp1", "AliParaformerAsr.Examples.MauiApp\MauiApp1.csproj", "{3190BB8F-83E1-42D8-B3CF-6C43BB419768}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{0CC20DAF-D6F4-481B-AE5F-09521DAC3CA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0CC20DAF-D6F4-481B-AE5F-09521DAC3CA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0CC20DAF-D6F4-481B-AE5F-09521DAC3CA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0CC20DAF-D6F4-481B-AE5F-09521DAC3CA2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{763DE8F4-D05C-4317-B627-3CE1B09431A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{763DE8F4-D05C-4317-B627-3CE1B09431A3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{763DE8F4-D05C-4317-B627-3CE1B09431A3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{763DE8F4-D05C-4317-B627-3CE1B09431A3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{3190BB8F-83E1-42D8-B3CF-6C43BB419768}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3190BB8F-83E1-42D8-B3CF-6C43BB419768}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3190BB8F-83E1-42D8-B3CF-6C43BB419768}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
||||
{3190BB8F-83E1-42D8-B3CF-6C43BB419768}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{3190BB8F-83E1-42D8-B3CF-6C43BB419768}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{3190BB8F-83E1-42D8-B3CF-6C43BB419768}.Release|Any CPU.Deploy.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {FADC677C-FC04-47A3-B4DE-704D30A42AF8}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.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.18.1" />
|
||||
<PackageReference Include="Microsoft.ML.OnnxRuntime.Managed" Version="1.18.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="YamlDotNet" Version="13.1.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,43 @@
|
||||
// See https://github.com/manyeyes for more information
|
||||
// Copyright (c) 2024 by manyeyes
|
||||
using AliParaformerAsr.Model;
|
||||
using Microsoft.ML.OnnxRuntime;
|
||||
|
||||
namespace AliParaformerAsr
|
||||
{
|
||||
internal interface IOfflineProj
|
||||
{
|
||||
InferenceSession ModelSession
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
int Blank_id
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
int Sos_eos_id
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
int Unk_id
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
int SampleRate
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
int FeatureDim
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
internal ModelOutputEntity ModelProj(List<OfflineInputEntity> modelInputs);
|
||||
internal void Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// See https://github.com/manyeyes for more information
|
||||
// Copyright (c) 2023 by manyeyes
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AliParaformerAsr.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,36 @@
|
||||
// See https://github.com/manyeyes for more information
|
||||
// Copyright (c) 2023 by manyeyes
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AliParaformerAsr.Model
|
||||
{
|
||||
public class DecoderConfEntity
|
||||
{
|
||||
private int _attention_heads = 4;
|
||||
private int _linear_units = 2048;
|
||||
private int _num_blocks = 16;
|
||||
private float _dropout_rate = 0.1F;
|
||||
private float _positional_dropout_rate = 0.1F;
|
||||
private float _self_attention_dropout_rate= 0.1F;
|
||||
private float _src_attention_dropout_rate = 0.1F;
|
||||
private int _att_layer_num = 16;
|
||||
private int _kernel_size = 11;
|
||||
private int _sanm_shfit = 0;
|
||||
|
||||
public int attention_heads { get => _attention_heads; set => _attention_heads = value; }
|
||||
public int linear_units { get => _linear_units; set => _linear_units = value; }
|
||||
public int num_blocks { get => _num_blocks; set => _num_blocks = value; }
|
||||
public float dropout_rate { get => _dropout_rate; set => _dropout_rate = value; }
|
||||
public float positional_dropout_rate { get => _positional_dropout_rate; set => _positional_dropout_rate = value; }
|
||||
public float self_attention_dropout_rate { get => _self_attention_dropout_rate; set => _self_attention_dropout_rate = value; }
|
||||
public float src_attention_dropout_rate { get => _src_attention_dropout_rate; set => _src_attention_dropout_rate = value; }
|
||||
public int att_layer_num { get => _att_layer_num; set => _att_layer_num = value; }
|
||||
public int kernel_size { get => _kernel_size; set => _kernel_size = value; }
|
||||
public int sanm_shfit { get => _sanm_shfit; set => _sanm_shfit = value; }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// See https://github.com/manyeyes for more information
|
||||
// Copyright (c) 2023 by manyeyes
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AliParaformerAsr.Model
|
||||
{
|
||||
public class EncoderConfEntity
|
||||
{
|
||||
private int _output_size = 512;
|
||||
private int _attention_heads = 4;
|
||||
private int _linear_units = 2048;
|
||||
private int _num_blocks = 50;
|
||||
private float _dropout_rate = 0.1F;
|
||||
private float _positional_dropout_rate = 0.1F;
|
||||
private float _attention_dropout_rate= 0.1F;
|
||||
private string _input_layer = "pe";
|
||||
private string _pos_enc_class = "SinusoidalPositionEncoder";
|
||||
private bool _normalize_before = true;
|
||||
private int _kernel_size = 11;
|
||||
private int _sanm_shfit = 0;
|
||||
private string _selfattention_layer_type = "sanm";
|
||||
|
||||
public int output_size { get => _output_size; set => _output_size = value; }
|
||||
public int attention_heads { get => _attention_heads; set => _attention_heads = value; }
|
||||
public int linear_units { get => _linear_units; set => _linear_units = value; }
|
||||
public int num_blocks { get => _num_blocks; set => _num_blocks = value; }
|
||||
public float dropout_rate { get => _dropout_rate; set => _dropout_rate = value; }
|
||||
public float positional_dropout_rate { get => _positional_dropout_rate; set => _positional_dropout_rate = value; }
|
||||
public float attention_dropout_rate { get => _attention_dropout_rate; set => _attention_dropout_rate = value; }
|
||||
public string input_layer { get => _input_layer; set => _input_layer = value; }
|
||||
public string pos_enc_class { get => _pos_enc_class; set => _pos_enc_class = value; }
|
||||
public bool normalize_before { get => _normalize_before; set => _normalize_before = value; }
|
||||
public int kernel_size { get => _kernel_size; set => _kernel_size = value; }
|
||||
public int sanm_shfit { get => _sanm_shfit; set => _sanm_shfit = value; }
|
||||
public string selfattention_layer_type { get => _selfattention_layer_type; set => _selfattention_layer_type = value; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// See https://github.com/manyeyes for more information
|
||||
// Copyright (c) 2023 by manyeyes
|
||||
namespace AliParaformerAsr.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 = 1.0F;
|
||||
private int _lfr_m = 7;
|
||||
private int _lfr_n = 6;
|
||||
private bool _snip_edges = false;
|
||||
|
||||
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; }
|
||||
public bool snip_edges { get => _snip_edges; set => _snip_edges = value; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// See https://github.com/manyeyes for more information
|
||||
// Copyright (c) 2023 by manyeyes
|
||||
namespace AliParaformerAsr.Model
|
||||
{
|
||||
public class ModelConfEntity
|
||||
{
|
||||
private float _ctc_weight = 0.0F;
|
||||
private float _lsm_weight = 0.1F;
|
||||
private bool _length_normalized_loss = true;
|
||||
private float _predictor_weight = 1.0F;
|
||||
private int _predictor_bias = 1;
|
||||
private float _sampling_ratio = 0.75F;
|
||||
private int _sos = 1;
|
||||
private int _eos = 2;
|
||||
private int _ignore_id = -1;
|
||||
|
||||
public float ctc_weight { get => _ctc_weight; set => _ctc_weight = value; }
|
||||
public float lsm_weight { get => _lsm_weight; set => _lsm_weight = value; }
|
||||
public bool length_normalized_loss { get => _length_normalized_loss; set => _length_normalized_loss = value; }
|
||||
public float predictor_weight { get => _predictor_weight; set => _predictor_weight = value; }
|
||||
public int predictor_bias { get => _predictor_bias; set => _predictor_bias = value; }
|
||||
public float sampling_ratio { get => _sampling_ratio; set => _sampling_ratio = value; }
|
||||
public int sos { get => _sos; set => _sos = value; }
|
||||
public int eos { get => _eos; set => _eos = value; }
|
||||
public int ignore_id { get => _ignore_id; set => _ignore_id = value; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// See https://github.com/manyeyes for more information
|
||||
// Copyright (c) 2024 by manyeyes
|
||||
using Microsoft.ML.OnnxRuntime.Tensors;
|
||||
|
||||
namespace AliParaformerAsr.Model
|
||||
{
|
||||
internal class ModelOutputEntity
|
||||
{
|
||||
private Tensor<float>? _model_out;
|
||||
private int[]? _model_out_lens;
|
||||
private Tensor<float>? _cif_peak_tensor;
|
||||
|
||||
public Tensor<float>? model_out { get => _model_out; set => _model_out = value; }
|
||||
public int[]? model_out_lens { get => _model_out_lens; set => _model_out_lens = value; }
|
||||
public Tensor<float>? cif_peak_tensor { get => _cif_peak_tensor; set => _cif_peak_tensor = value; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// See https://github.com/manyeyes for more information
|
||||
// Copyright (c) 2023 by manyeyes
|
||||
namespace AliParaformerAsr.Model
|
||||
{
|
||||
public class OfflineInputEntity
|
||||
{
|
||||
private float[]? _speech;
|
||||
private int _speech_length;
|
||||
|
||||
//public List<float[]>? speech { get; set; }
|
||||
public float[]? Speech { get; set; }
|
||||
public int SpeechLength { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// See https://github.com/manyeyes for more information
|
||||
// Copyright (c) 2023 by manyeyes
|
||||
namespace AliParaformerAsr.Model
|
||||
{
|
||||
public class OfflineOutputEntity
|
||||
{
|
||||
|
||||
private float[]? logits;
|
||||
private long[]? _token_num;
|
||||
private List<int[]>? _token_nums=new List<int[]>() { new int[4]};
|
||||
private int[] _token_nums_length;
|
||||
|
||||
public float[]? Logits { get => logits; set => logits = value; }
|
||||
public long[]? Token_num { get => _token_num; set => _token_num = value; }
|
||||
public List<int[]>? Token_nums { get => _token_nums; set => _token_nums = value; }
|
||||
public int[] Token_nums_length { get => _token_nums_length; set => _token_nums_length = value; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// See https://github.com/manyeyes for more information
|
||||
// Copyright (c) 2023 by manyeyes
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AliParaformerAsr.Model
|
||||
{
|
||||
internal class OfflineYamlEntity
|
||||
{
|
||||
private int _input_size;
|
||||
private string _frontend = "wav_frontend";
|
||||
private FrontendConfEntity _frontend_conf = new FrontendConfEntity();
|
||||
private string _model = "paraformer";
|
||||
private ModelConfEntity _model_conf = new ModelConfEntity();
|
||||
private string _preencoder = string.Empty;
|
||||
private PostEncoderConfEntity _preencoder_conf = new PostEncoderConfEntity();
|
||||
private string _encoder = "sanm";
|
||||
private EncoderConfEntity _encoder_conf = new EncoderConfEntity();
|
||||
private string _postencoder = string.Empty;
|
||||
private PostEncoderConfEntity _postencoder_conf = new PostEncoderConfEntity();
|
||||
private string _decoder = "paraformer_decoder_sanm";
|
||||
private DecoderConfEntity _decoder_conf = new DecoderConfEntity();
|
||||
private string _predictor = "cif_predictor_v2";
|
||||
private PredictorConfEntity _predictor_conf = new PredictorConfEntity();
|
||||
private string _version = string.Empty;
|
||||
|
||||
|
||||
public int input_size { get => _input_size; set => _input_size = value; }
|
||||
public string frontend { get => _frontend; set => _frontend = value; }
|
||||
public FrontendConfEntity frontend_conf { get => _frontend_conf; set => _frontend_conf = value; }
|
||||
public string model { get => _model; set => _model = value; }
|
||||
public ModelConfEntity model_conf { get => _model_conf; set => _model_conf = value; }
|
||||
public string preencoder { get => _preencoder; set => _preencoder = value; }
|
||||
public PostEncoderConfEntity preencoder_conf { get => _preencoder_conf; set => _preencoder_conf = value; }
|
||||
public string encoder { get => _encoder; set => _encoder = value; }
|
||||
public EncoderConfEntity encoder_conf { get => _encoder_conf; set => _encoder_conf = value; }
|
||||
public string postencoder { get => _postencoder; set => _postencoder = value; }
|
||||
public PostEncoderConfEntity postencoder_conf { get => _postencoder_conf; set => _postencoder_conf = value; }
|
||||
public string decoder { get => _decoder; set => _decoder = value; }
|
||||
public DecoderConfEntity decoder_conf { get => _decoder_conf; set => _decoder_conf = value; }
|
||||
public string predictor { get => _predictor; set => _predictor = value; }
|
||||
public string version { get => _version; set => _version = value; }
|
||||
public PredictorConfEntity predictor_conf { get => _predictor_conf; set => _predictor_conf = value; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// See https://github.com/manyeyes for more information
|
||||
// Copyright (c) 2023 by manyeyes
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AliParaformerAsr.Model
|
||||
{
|
||||
public class PostEncoderConfEntity
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// See https://github.com/manyeyes for more information
|
||||
// Copyright (c) 2023 by manyeyes
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AliParaformerAsr.Model
|
||||
{
|
||||
public class PreEncoderConfEntity
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// See https://github.com/manyeyes for more information
|
||||
// Copyright (c) 2023 by manyeyes
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AliParaformerAsr.Model
|
||||
{
|
||||
public class PredictorConfEntity
|
||||
{
|
||||
private int _idim = 512;
|
||||
private float _threshold = 1.0F;
|
||||
private int _l_order = 1;
|
||||
private int _r_order = 1;
|
||||
private float _tail_threshold = 0.45F;
|
||||
|
||||
public int idim { get => _idim; set => _idim = value; }
|
||||
public float threshold { get => _threshold; set => _threshold = value; }
|
||||
public int l_order { get => _l_order; set => _l_order = value; }
|
||||
public int r_order { get => _r_order; set => _r_order = value; }
|
||||
public float tail_threshold { get => _tail_threshold; set => _tail_threshold = value; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// See https://github.com/manyeyes for more information
|
||||
// Copyright (c) 2024 by manyeyes
|
||||
using Microsoft.ML.OnnxRuntime;
|
||||
|
||||
namespace AliParaformerAsr
|
||||
{
|
||||
public enum OnnxRumtimeTypes
|
||||
{
|
||||
CPU = 0,
|
||||
|
||||
DML = 1,
|
||||
|
||||
CUDA = 2,
|
||||
}
|
||||
public class OfflineModel
|
||||
{
|
||||
private InferenceSession _modelSession;
|
||||
private int _blank_id = 0;
|
||||
private int sos_eos_id = 1;
|
||||
private int _unk_id = 2;
|
||||
private int _featureDim = 80;
|
||||
private int _sampleRate = 16000;
|
||||
|
||||
public OfflineModel(string modelFilePath, int threadsNum = 2, OnnxRumtimeTypes rumtimeType = OnnxRumtimeTypes.CPU, int deviceId = 0)
|
||||
{
|
||||
_modelSession = initModel(modelFilePath, threadsNum, rumtimeType, deviceId);
|
||||
}
|
||||
public int Blank_id { get => _blank_id; set => _blank_id = value; }
|
||||
public int Sos_eos_id { get => sos_eos_id; set => sos_eos_id = value; }
|
||||
public int Unk_id { get => _unk_id; set => _unk_id = value; }
|
||||
public int FeatureDim { get => _featureDim; set => _featureDim = value; }
|
||||
public InferenceSession ModelSession { get => _modelSession; set => _modelSession = value; }
|
||||
public int SampleRate { get => _sampleRate; set => _sampleRate = value; }
|
||||
|
||||
public InferenceSession initModel(string modelFilePath, int threadsNum = 2, OnnxRumtimeTypes rumtimeType = OnnxRumtimeTypes.CPU, int deviceId = 0)
|
||||
{
|
||||
var options = new SessionOptions();
|
||||
switch (rumtimeType)
|
||||
{
|
||||
case OnnxRumtimeTypes.DML:
|
||||
options.AppendExecutionProvider_DML(deviceId);
|
||||
break;
|
||||
case OnnxRumtimeTypes.CUDA:
|
||||
options.AppendExecutionProvider_CUDA(deviceId);
|
||||
break;
|
||||
default:
|
||||
options.AppendExecutionProvider_CPU(deviceId);
|
||||
break;
|
||||
}
|
||||
//options.LogSeverityLevel = OrtLoggingLevel.ORT_LOGGING_LEVEL_INFO;
|
||||
options.InterOpNumThreads = threadsNum;
|
||||
InferenceSession onnxSession = new InferenceSession(modelFilePath, options);
|
||||
return onnxSession;
|
||||
}
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (_modelSession != null)
|
||||
{
|
||||
_modelSession.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
// See https://github.com/manyeyes for more information
|
||||
// Copyright (c) 2024 by manyeyes
|
||||
using AliParaformerAsr.Model;
|
||||
using Microsoft.ML.OnnxRuntime;
|
||||
using Microsoft.ML.OnnxRuntime.Tensors;
|
||||
using AliParaformerAsr.Utils;
|
||||
|
||||
namespace AliParaformerAsr
|
||||
{
|
||||
internal class OfflineProjOfParaformer : IOfflineProj, IDisposable
|
||||
{
|
||||
// To detect redundant calls
|
||||
private bool _disposed;
|
||||
|
||||
private InferenceSession _modelSession;
|
||||
private int _blank_id = 0;
|
||||
private int _sos_eos_id = 1;
|
||||
private int _unk_id = 2;
|
||||
|
||||
private int _featureDim = 80;
|
||||
private int _sampleRate = 16000;
|
||||
|
||||
public OfflineProjOfParaformer(OfflineModel offlineModel)
|
||||
{
|
||||
_modelSession = offlineModel.ModelSession;
|
||||
_blank_id = offlineModel.Blank_id;
|
||||
_sos_eos_id = offlineModel.Sos_eos_id;
|
||||
_unk_id = offlineModel.Unk_id;
|
||||
_featureDim = offlineModel.FeatureDim;
|
||||
_sampleRate = offlineModel.SampleRate;
|
||||
}
|
||||
public InferenceSession ModelSession { get => _modelSession; set => _modelSession = value; }
|
||||
public int Blank_id { get => _blank_id; set => _blank_id = value; }
|
||||
public int Sos_eos_id { get => _sos_eos_id; set => _sos_eos_id = value; }
|
||||
public int Unk_id { get => _unk_id; set => _unk_id = value; }
|
||||
public int FeatureDim { get => _featureDim; set => _featureDim = value; }
|
||||
public int SampleRate { get => _sampleRate; set => _sampleRate = value; }
|
||||
|
||||
public ModelOutputEntity ModelProj(List<OfflineInputEntity> modelInputs)
|
||||
{
|
||||
int batchSize = modelInputs.Count;
|
||||
float[] padSequence = PadHelper.PadSequence(modelInputs);
|
||||
var inputMeta = _modelSession.InputMetadata;
|
||||
var container = new List<NamedOnnxValue>();
|
||||
foreach (var name in inputMeta.Keys)
|
||||
{
|
||||
if (name == "speech")
|
||||
{
|
||||
int[] dim = new int[] { batchSize, padSequence.Length / 560 / batchSize, 560 };
|
||||
var tensor = new DenseTensor<float>(padSequence, dim, false);
|
||||
container.Add(NamedOnnxValue.CreateFromTensor<float>(name, tensor));
|
||||
}
|
||||
if (name == "speech_lengths")
|
||||
{
|
||||
int[] dim = new int[] { batchSize };
|
||||
int[] speech_lengths = new int[batchSize];
|
||||
for (int i = 0; i < batchSize; i++)
|
||||
{
|
||||
speech_lengths[i] = padSequence.Length / 560 / batchSize;
|
||||
}
|
||||
var tensor = new DenseTensor<int>(speech_lengths, dim, false);
|
||||
container.Add(NamedOnnxValue.CreateFromTensor<int>(name, tensor));
|
||||
}
|
||||
}
|
||||
ModelOutputEntity modelOutputEntity = new ModelOutputEntity();
|
||||
try
|
||||
{
|
||||
IDisposableReadOnlyCollection<DisposableNamedOnnxValue> results = _modelSession.Run(container);
|
||||
|
||||
if (results != null)
|
||||
{
|
||||
var resultsArray = results.ToArray();
|
||||
modelOutputEntity.model_out = resultsArray[0].AsTensor<float>();
|
||||
modelOutputEntity.model_out_lens = resultsArray[1].AsEnumerable<int>().ToArray();
|
||||
if (resultsArray.Length >= 4)
|
||||
{
|
||||
Tensor<float> cif_peak_tensor = resultsArray[3].AsTensor<float>();
|
||||
modelOutputEntity.cif_peak_tensor = cif_peak_tensor;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
//
|
||||
}
|
||||
return modelOutputEntity;
|
||||
}
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (_modelSession != null)
|
||||
{
|
||||
_modelSession.Dispose();
|
||||
}
|
||||
}
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
~OfflineProjOfParaformer()
|
||||
{
|
||||
Dispose(_disposed);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
// See https://github.com/manyeyes for more information
|
||||
// Copyright (c) 2024 by manyeyes
|
||||
using AliParaformerAsr.Model;
|
||||
using Microsoft.ML.OnnxRuntime;
|
||||
using Microsoft.ML.OnnxRuntime.Tensors;
|
||||
using AliParaformerAsr.Utils;
|
||||
|
||||
namespace AliParaformerAsr
|
||||
{
|
||||
internal class OfflineProjOfSenseVoiceSmall : IOfflineProj, IDisposable
|
||||
{
|
||||
// To detect redundant calls
|
||||
private bool _disposed;
|
||||
|
||||
private InferenceSession _modelSession;
|
||||
private int _blank_id = 0;
|
||||
private int _sos_eos_id = 1;
|
||||
private int _unk_id = 2;
|
||||
|
||||
private int _featureDim = 80;
|
||||
private int _sampleRate = 16000;
|
||||
|
||||
private bool _use_itn = false;
|
||||
private string _textnorm = "woitn";
|
||||
private Dictionary<string, int> _lidDict = new Dictionary<string, int>() { { "auto", 0 }, { "zh", 3 }, { "en", 4 }, { "yue", 7 }, { "ja", 11 }, { "ko", 12 }, { "nospeech", 13 } };
|
||||
private Dictionary<int, int> _lidIntDict = new Dictionary<int, int>() { { 24884, 3 }, { 24885, 4 }, { 24888, 7 }, { 24892, 11 }, { 24896, 12 }, { 24992, 13 } };
|
||||
private Dictionary<string, int> _textnormDict = new Dictionary<string, int>() { { "withitn", 14 }, { "woitn", 15 } };
|
||||
private Dictionary<int, int> _textnormIntDict = new Dictionary<int, int>() { { 25016, 14 }, { 25017, 15 } };
|
||||
|
||||
public OfflineProjOfSenseVoiceSmall(OfflineModel offlineModel)
|
||||
{
|
||||
_modelSession = offlineModel.ModelSession;
|
||||
_blank_id = offlineModel.Blank_id;
|
||||
_sos_eos_id = offlineModel.Sos_eos_id;
|
||||
_unk_id = offlineModel.Unk_id;
|
||||
_featureDim = offlineModel.FeatureDim;
|
||||
_sampleRate = offlineModel.SampleRate;
|
||||
}
|
||||
public InferenceSession ModelSession { get => _modelSession; set => _modelSession = value; }
|
||||
public int Blank_id { get => _blank_id; set => _blank_id = value; }
|
||||
public int Sos_eos_id { get => _sos_eos_id; set => _sos_eos_id = value; }
|
||||
public int Unk_id { get => _unk_id; set => _unk_id = value; }
|
||||
public int FeatureDim { get => _featureDim; set => _featureDim = value; }
|
||||
public int SampleRate { get => _sampleRate; set => _sampleRate = value; }
|
||||
|
||||
public ModelOutputEntity ModelProj(List<OfflineInputEntity> modelInputs)
|
||||
{
|
||||
int batchSize = modelInputs.Count;
|
||||
float[] padSequence = PadHelper.PadSequence(modelInputs);
|
||||
//
|
||||
string languageValue = "ja";
|
||||
int languageId = 0;
|
||||
if (_lidDict.ContainsKey(languageValue))
|
||||
{
|
||||
languageId = _lidDict.GetValueOrDefault(languageValue);
|
||||
}
|
||||
string textnormValue = "withitn";
|
||||
int textnormId = 15;
|
||||
if (_textnormDict.ContainsKey(textnormValue))
|
||||
{
|
||||
textnormId = _textnormDict.GetValueOrDefault(textnormValue);
|
||||
}
|
||||
var inputMeta = _modelSession.InputMetadata;
|
||||
var container = new List<NamedOnnxValue>();
|
||||
foreach (var name in inputMeta.Keys)
|
||||
{
|
||||
if (name == "speech")
|
||||
{
|
||||
int[] dim = new int[] { batchSize, padSequence.Length / 560 / batchSize, 560 };
|
||||
var tensor = new DenseTensor<float>(padSequence, dim, false);
|
||||
container.Add(NamedOnnxValue.CreateFromTensor<float>(name, tensor));
|
||||
}
|
||||
if (name == "speech_lengths")
|
||||
{
|
||||
|
||||
int[] dim = new int[] { batchSize };
|
||||
int[] speech_lengths = new int[batchSize];
|
||||
for (int i = 0; i < batchSize; i++)
|
||||
{
|
||||
speech_lengths[i] = padSequence.Length / 560 / batchSize;
|
||||
}
|
||||
var tensor = new DenseTensor<int>(speech_lengths, dim, false);
|
||||
container.Add(NamedOnnxValue.CreateFromTensor<int>(name, tensor));
|
||||
}
|
||||
if (name == "language")
|
||||
{
|
||||
int[] language = new int[batchSize];
|
||||
for (int i = 0; i < batchSize; i++)
|
||||
{
|
||||
language[i] = languageId;
|
||||
}
|
||||
int[] dim = new int[] { batchSize };
|
||||
var tensor = new DenseTensor<int>(language, dim, false);
|
||||
container.Add(NamedOnnxValue.CreateFromTensor<int>(name, tensor));
|
||||
}
|
||||
if (name == "textnorm")
|
||||
{
|
||||
int[] textnorm = new int[batchSize];
|
||||
for (int i = 0; i < batchSize; i++)
|
||||
{
|
||||
textnorm[i] = textnormId;
|
||||
}
|
||||
int[] dim = new int[] { batchSize };
|
||||
var tensor = new DenseTensor<int>(textnorm, dim, false);
|
||||
container.Add(NamedOnnxValue.CreateFromTensor<int>(name, tensor));
|
||||
}
|
||||
}
|
||||
ModelOutputEntity modelOutputEntity = new ModelOutputEntity();
|
||||
try
|
||||
{
|
||||
IDisposableReadOnlyCollection<DisposableNamedOnnxValue> results = _modelSession.Run(container);
|
||||
|
||||
if (results != null)
|
||||
{
|
||||
var resultsArray = results.ToArray();
|
||||
modelOutputEntity.model_out = resultsArray[0].AsTensor<float>();
|
||||
modelOutputEntity.model_out_lens = resultsArray[1].AsEnumerable<int>().ToArray();
|
||||
if (resultsArray.Length >= 4)
|
||||
{
|
||||
Tensor<float> cif_peak_tensor = resultsArray[3].AsTensor<float>();
|
||||
modelOutputEntity.cif_peak_tensor = cif_peak_tensor;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
//
|
||||
}
|
||||
return modelOutputEntity;
|
||||
}
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (_modelSession != null)
|
||||
{
|
||||
_modelSession.Dispose();
|
||||
}
|
||||
}
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
~OfflineProjOfSenseVoiceSmall()
|
||||
{
|
||||
Dispose(_disposed);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
// See https://github.com/manyeyes for more information
|
||||
// Copyright (c) 2024 by manyeyes
|
||||
using AliParaformerAsr.Model;
|
||||
using AliParaformerAsr.Utils;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.ML.OnnxRuntime;
|
||||
using Microsoft.ML.OnnxRuntime.Tensors;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
// 模型文件地址: https://modelscope.cn/models/iic/speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-onnx
|
||||
// 模型文件地址: https://www.modelscope.cn/models/manyeyes/sensevoice-small-onnx
|
||||
namespace AliParaformerAsr
|
||||
{
|
||||
/// <summary>
|
||||
/// offline recognizer package
|
||||
/// Copyright (c) 2023 by manyeyes
|
||||
/// </summary>
|
||||
public class OfflineRecognizer
|
||||
{
|
||||
private InferenceSession _onnxSession;
|
||||
private readonly ILogger<OfflineRecognizer> _logger;
|
||||
private WavFrontend _wavFrontend;
|
||||
private string _frontend;
|
||||
private FrontendConfEntity _frontendConfEntity;
|
||||
private string[] _tokens;
|
||||
private IOfflineProj? _offlineProj;
|
||||
private OfflineModel _offlineModel;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="modelFilePath"></param>
|
||||
/// <param name="configFilePath"></param>
|
||||
/// <param name="mvnFilePath"></param>
|
||||
/// <param name="tokensFilePath"></param>
|
||||
/// <param name="rumtimeType">可以选择gpu,但是目前情况下,不建议使用,因为性能提升有限</param>
|
||||
/// <param name="deviceId">设备id,多显卡时用于指定执行的显卡</param>
|
||||
/// <param name="batchSize"></param>
|
||||
/// <param name="threadsNum"></param>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
public OfflineRecognizer(string modelFilePath, string configFilePath, string mvnFilePath, string tokensFilePath, int threadsNum = 1, OnnxRumtimeTypes rumtimeType = OnnxRumtimeTypes.CPU, int deviceId = 0)
|
||||
{
|
||||
_offlineModel = new OfflineModel(modelFilePath, threadsNum);
|
||||
|
||||
string[] tokenLines;
|
||||
if (tokensFilePath.EndsWith(".txt"))
|
||||
{
|
||||
tokenLines = File.ReadAllLines(tokensFilePath);
|
||||
}
|
||||
else if (tokensFilePath.EndsWith(".json"))
|
||||
{
|
||||
string jsonContent = File.ReadAllText(tokensFilePath);
|
||||
JArray tokenArray = JArray.Parse(jsonContent);
|
||||
tokenLines = tokenArray.Select(t => t.ToString()).ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Invalid tokens file format. Only .txt and .json are supported.");
|
||||
}
|
||||
|
||||
_tokens = tokenLines;
|
||||
|
||||
OfflineYamlEntity offlineYamlEntity = YamlHelper.ReadYaml<OfflineYamlEntity>(configFilePath);
|
||||
switch (offlineYamlEntity.model.ToLower())
|
||||
{
|
||||
case "paraformer":
|
||||
_offlineProj = new OfflineProjOfParaformer(_offlineModel);
|
||||
break;
|
||||
case "sensevoicesmall":
|
||||
_offlineProj = new OfflineProjOfSenseVoiceSmall(_offlineModel);
|
||||
break;
|
||||
default:
|
||||
_offlineProj = null;
|
||||
break;
|
||||
}
|
||||
_wavFrontend = new WavFrontend(mvnFilePath, offlineYamlEntity.frontend_conf);
|
||||
_frontend = offlineYamlEntity.frontend;
|
||||
_frontendConfEntity = offlineYamlEntity.frontend_conf;
|
||||
ILoggerFactory loggerFactory = new LoggerFactory();
|
||||
_logger = new Logger<OfflineRecognizer>(loggerFactory);
|
||||
}
|
||||
|
||||
public List<string> GetResults(List<float[]> samples)
|
||||
{
|
||||
_logger.LogInformation("get features begin");
|
||||
List<OfflineInputEntity> offlineInputEntities = ExtractFeats(samples);
|
||||
OfflineOutputEntity modelOutput = Forward(offlineInputEntities);
|
||||
List<string> text_results = DecodeMulti(modelOutput.Token_nums);
|
||||
return text_results;
|
||||
}
|
||||
|
||||
private List<OfflineInputEntity> ExtractFeats(List<float[]> waveform_list)
|
||||
{
|
||||
List<float[]> in_cache = new List<float[]>();
|
||||
List<OfflineInputEntity> offlineInputEntities = new List<OfflineInputEntity>();
|
||||
foreach (var waveform in waveform_list)
|
||||
{
|
||||
float[] fbanks = _wavFrontend.GetFbank(waveform);
|
||||
float[] features = _wavFrontend.LfrCmvn(fbanks);
|
||||
OfflineInputEntity offlineInputEntity = new OfflineInputEntity();
|
||||
offlineInputEntity.Speech = features;
|
||||
offlineInputEntity.SpeechLength = features.Length;
|
||||
offlineInputEntities.Add(offlineInputEntity);
|
||||
}
|
||||
return offlineInputEntities;
|
||||
}
|
||||
|
||||
private OfflineOutputEntity Forward(List<OfflineInputEntity> modelInputs)
|
||||
{
|
||||
OfflineOutputEntity offlineOutputEntity = new OfflineOutputEntity();
|
||||
try
|
||||
{
|
||||
ModelOutputEntity modelOutputEntity = _offlineProj.ModelProj(modelInputs);
|
||||
if (modelOutputEntity != null)
|
||||
{
|
||||
offlineOutputEntity.Token_nums_length = modelOutputEntity.model_out_lens.AsEnumerable<int>().ToArray();
|
||||
Tensor<float> logits_tensor = modelOutputEntity.model_out;
|
||||
List<int[]> token_nums = new List<int[]> { };
|
||||
|
||||
for (int i = 0; i < logits_tensor.Dimensions[0]; i++)
|
||||
{
|
||||
int[] item = new int[logits_tensor.Dimensions[1]];
|
||||
for (int j = 0; j < logits_tensor.Dimensions[1]; j++)
|
||||
{
|
||||
int token_num = 0;
|
||||
for (int k = 1; k < logits_tensor.Dimensions[2]; k++)
|
||||
{
|
||||
token_num = logits_tensor[i, j, token_num] > logits_tensor[i, j, k] ? token_num : k;
|
||||
}
|
||||
item[j] = (int)token_num;
|
||||
}
|
||||
token_nums.Add(item);
|
||||
}
|
||||
offlineOutputEntity.Token_nums = token_nums;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
|
||||
return offlineOutputEntity;
|
||||
}
|
||||
|
||||
private List<string> DecodeMulti(List<int[]> token_nums)
|
||||
{
|
||||
List<string> text_results = new List<string>();
|
||||
#pragma warning disable CS8602 // 解引用可能出现空引用。
|
||||
foreach (int[] token_num in token_nums)
|
||||
{
|
||||
string text_result = "";
|
||||
foreach (int token in token_num)
|
||||
{
|
||||
if (token == 2)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
string tokenChar = _tokens[token].Split("\t")[0];
|
||||
|
||||
if (tokenChar != "</s>" && tokenChar != "<s>" && tokenChar != "<blank>" && tokenChar != "<unk>")
|
||||
{
|
||||
if (IsChinese(tokenChar, true))
|
||||
{
|
||||
text_result += tokenChar;
|
||||
}
|
||||
else
|
||||
{
|
||||
text_result += "▁" + tokenChar + "▁";
|
||||
}
|
||||
}
|
||||
}
|
||||
text_results.Add(text_result.Replace("@@▁▁", "").Replace("▁▁", " ").Replace("▁", ""));
|
||||
}
|
||||
#pragma warning restore CS8602 // 解引用可能出现空引用。
|
||||
|
||||
return text_results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify if the string is in Chinese.
|
||||
/// </summary>
|
||||
/// <param name="checkedStr">The string to be verified.</param>
|
||||
/// <param name="allMatch">Is it an exact match. When the value is true,all are in Chinese;
|
||||
/// When the value is false, only Chinese is included.
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
private bool IsChinese(string checkedStr, bool allMatch)
|
||||
{
|
||||
string pattern;
|
||||
if (allMatch)
|
||||
pattern = @"^[\u4e00-\u9fa5]+$";
|
||||
else
|
||||
pattern = @"[\u4e00-\u9fa5]";
|
||||
if (Regex.IsMatch(checkedStr, pattern))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using AliParaformerAsr.Model;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AliParaformerAsr.Utils
|
||||
{
|
||||
internal static class PadHelper
|
||||
{
|
||||
public static float[] PadSequence(List<OfflineInputEntity> 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];
|
||||
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++;
|
||||
}
|
||||
}
|
||||
speech = speech.Select(x => x == 0 ? -23.025850929940457F * 32768 : x).ToArray();
|
||||
return speech;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// See https://github.com/manyeyes for more information
|
||||
// Copyright (c) 2023 by manyeyes
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Text.Json;
|
||||
using YamlDotNet.Serialization;
|
||||
|
||||
namespace AliParaformerAsr.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// YamlHelper
|
||||
/// Copyright (c) 2023 by manyeyes
|
||||
/// </summary>
|
||||
internal class YamlHelper
|
||||
{
|
||||
public static T ReadYaml<T>(string yamlFilePath) where T:new()
|
||||
{
|
||||
if (!File.Exists(yamlFilePath))
|
||||
{
|
||||
// 如果允许返回默认对象,则新建一个默认对象,否则应该是抛出异常
|
||||
// If allowing to return a default object, create a new default object; otherwise, throw an exception
|
||||
|
||||
return new T();
|
||||
// throw new Exception($"not find yaml config file: {yamlFilePath}");
|
||||
}
|
||||
|
||||
StreamReader yamlReader = File.OpenText(yamlFilePath);
|
||||
Deserializer yamlDeserializer = new Deserializer();
|
||||
T info = yamlDeserializer.Deserialize<T>(yamlReader);
|
||||
yamlReader.Close();
|
||||
return info;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
// See https://github.com/manyeyes for more information
|
||||
// Copyright (c) 2023 by manyeyes
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using AliParaformerAsr.Model;
|
||||
using KaldiNativeFbankSharp;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace AliParaformerAsr
|
||||
{
|
||||
/// <summary>
|
||||
/// WavFrontend
|
||||
/// Copyright (c) 2023 by manyeyes
|
||||
/// </summary>
|
||||
internal class WavFrontend
|
||||
{
|
||||
private string _mvnFilePath;
|
||||
private FrontendConfEntity _frontendConfEntity;
|
||||
OnlineFbank _onlineFbank;
|
||||
private CmvnEntity _cmvnEntity;
|
||||
private static int _fbank_beg_idx = 0;
|
||||
|
||||
public WavFrontend(string mvnFilePath, FrontendConfEntity frontendConfEntity)
|
||||
{
|
||||
_mvnFilePath = mvnFilePath;
|
||||
_frontendConfEntity = frontendConfEntity;
|
||||
_fbank_beg_idx = 0;
|
||||
_onlineFbank = new OnlineFbank(
|
||||
dither: _frontendConfEntity.dither,
|
||||
snip_edges: _frontendConfEntity.snip_edges,
|
||||
window_type: _frontendConfEntity.window,
|
||||
sample_rate: _frontendConfEntity.fs,
|
||||
num_bins: _frontendConfEntity.n_mels
|
||||
);
|
||||
_cmvnEntity = LoadCmvn(mvnFilePath);
|
||||
}
|
||||
|
||||
public float[] GetFbank(float[] samples)
|
||||
{
|
||||
float sample_rate = _frontendConfEntity.fs;
|
||||
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;
|
||||
}
|
||||
|
||||
public 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();
|
||||
//i++;
|
||||
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();
|
||||
//i++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
CmvnEntity cmvnEntity = new CmvnEntity();
|
||||
cmvnEntity.Means = means_list;
|
||||
cmvnEntity.Vars = vars_list;
|
||||
return cmvnEntity;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
# AliParaformerAsr
|
||||
##### 支持模型
|
||||
## paraformer-large offline onnx模型下载
|
||||
https://huggingface.co/manyeyes/speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-onnx
|
||||
## SenseVoiceSmall offline onnx模型下载
|
||||
https://www.modelscope.cn/models/manyeyes/sensevoice-small-onnx
|
||||
|
||||
##### 简介:
|
||||
项目中使用的Asr模型是阿里巴巴达摩院提供的Paraformer-large ASR模型。
|
||||
**项目基于Net 6.0,使用C#编写,调用Microsoft.ML.OnnxRuntime对onnx模型进行解码,支持跨平台编译。项目以库的形式进行调用,部署非常方便。**
|
||||
ASR整体流程的rtf在0.03左右。
|
||||
|
||||
##### 用途:
|
||||
Paraformer是达摩院语音团队提出的一种高效的非自回归端到端语音识别框架。本项目为Paraformer中文通用语音识别模型,采用工业级数万小时的标注音频进行模型训练,保证了模型的通用识别效果。模型可以被应用于语音输入法、语音导航、智能会议纪要等场景。
|
||||
|
||||
##### Paraformer模型结构:
|
||||

|
||||
|
||||
Paraformer模型结构如上图所示,由 Encoder、Predictor、Sampler、Decoder 与 Loss function 五部分组成。Encoder可以采用不同的网络结构,例如self-attention,conformer,SAN-M等。Predictor 为两层FFN,预测目标文字个数以及抽取目标文字对应的声学向量。Sampler 为无可学习参数模块,依据输入的声学向量和目标向量,生产含有语义的特征向量。Decoder 结构与自回归模型类似,为双向建模(自回归为单向建模)。Loss function 部分,除了交叉熵(CE)与 MWER 区分性优化目标,还包括了 Predictor 优化目标 MAE。
|
||||
|
||||
其核心点主要有:
|
||||
|
||||
Predictor 模块:基于 Continuous integrate-and-fire (CIF) 的 预测器 (Predictor) 来抽取目标文字对应的声学特征向量,可以更加准确的预测语音中目标文字个数。
|
||||
Sampler:通过采样,将声学特征向量与目标文字向量变换成含有语义信息的特征向量,配合双向的 Decoder 来增强模型对于上下文的建模能力。
|
||||
基于负样本采样的 MWER 训练准则。
|
||||
更详细的细节见:
|
||||
|
||||
论文: [Paraformer: Fast and Accurate Parallel Transformer for Non-autoregressive End-to-End Speech Recognition](https://arxiv.org/abs/2206.08317 "Paraformer: Fast and Accurate Parallel Transformer for Non-autoregressive End-to-End Speech Recognition")
|
||||
|
||||
论文解读:[Paraformer: 高识别率、高计算效率的单轮非自回归端到端语音识别模型](https://mp.weixin.qq.com/s/xQ87isj5_wxWiQs4qUXtVw "Paraformer: 高识别率、高计算效率的单轮非自回归端到端语音识别模型")
|
||||
|
||||
##### ASR常用参数(参考:asr.yaml文件):
|
||||
用于解码的asr.yaml配置参数,取自官方模型配置config.yaml原文件。便于跟进和升级。
|
||||
|
||||
## 离线(非流式)模型调用方法:
|
||||
|
||||
###### 1.添加项目引用
|
||||
using AliParaformerAsr;
|
||||
|
||||
###### 2.模型初始化和配置
|
||||
```csharp
|
||||
string applicationBase = AppDomain.CurrentDomain.BaseDirectory;
|
||||
string modelName = "speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-onnx";
|
||||
string modelFilePath = applicationBase + "./"+ modelName + "/model_quant.onnx";
|
||||
string configFilePath = applicationBase + "./" + modelName + "/asr.yaml";
|
||||
string mvnFilePath = applicationBase + "./" + modelName + "/am.mvn";
|
||||
string tokensFilePath = applicationBase + "./" + modelName + "/tokens.txt";
|
||||
AliParaformerAsr.OfflineRecognizer offlineRecognizer = new OfflineRecognizer(modelFilePath, configFilePath, mvnFilePath, tokensFilePath);
|
||||
```
|
||||
###### 3.调用
|
||||
```csharp
|
||||
List<float[]> samples = new List<float[]>();
|
||||
//这里省略wav文件转samples...
|
||||
//具体参考示例(AliParaformerAsr.Examples)代码
|
||||
List<string> results_batch = offlineRecognizer.GetResults(samples);
|
||||
```
|
||||
###### 4.输出结果:
|
||||
```
|
||||
欢迎大家来体验达摩院推出的语音识别模型
|
||||
|
||||
正是因为存在绝对正义所以我们接受现实的相对正义但是不要因为现实的相对正义我们就认为这个世界没有正义因为如果当你认为这个世界没有正义
|
||||
|
||||
非常的方便但是现在不同啊英国脱欧欧盟内部完善的产业链的红利人
|
||||
|
||||
he must be home now for the light is on他一定在家因为灯亮着就是有一种推理或者解释的那种感觉
|
||||
|
||||
after early nightfall the yellow lamps would light up here in there the squalid quarter of the broffles
|
||||
|
||||
elapsed_milliseconds:1502.8828125
|
||||
total_duration:40525.6875
|
||||
rtf:0.037084696280599808
|
||||
end!
|
||||
```
|
||||
*
|
||||
处理长音频,推荐结合AliFsmnVad一起使用:https://github.com/manyeyes/AliFsmnVad
|
||||
*
|
||||
|
||||
其他说明:
|
||||
测试用例:AliParaformerAsr.Examples。
|
||||
测试环境:windows11。
|
||||
测试用例中samples的计算,使用的是NAudio库。
|
||||
|
||||
通过以下链接了解更多:
|
||||
https://www.modelscope.cn/models/damo/speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-pytorch/summary
|
||||
@@ -0,0 +1,31 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.6.33829.357
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FunASRWSClient_Offline", "FunASRWSClient_Offline\FunASRWSClient_Offline.csproj", "{E0986CC4-D443-44E2-96E8-F6E4B691CA57}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FunASRWSClient_Online", "FunASRWSClient_Online\FunASRWSClient_Online.csproj", "{11E80B4F-A838-4DFB-A0C8-9BAE6726BAC0}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{E0986CC4-D443-44E2-96E8-F6E4B691CA57}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E0986CC4-D443-44E2-96E8-F6E4B691CA57}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E0986CC4-D443-44E2-96E8-F6E4B691CA57}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E0986CC4-D443-44E2-96E8-F6E4B691CA57}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{11E80B4F-A838-4DFB-A0C8-9BAE6726BAC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{11E80B4F-A838-4DFB-A0C8-9BAE6726BAC0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{11E80B4F-A838-4DFB-A0C8-9BAE6726BAC0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{11E80B4F-A838-4DFB-A0C8-9BAE6726BAC0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {E8483245-31D3-4C42-AAF9-B3195EAB97C2}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
Binary file not shown.
@@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Websocket.Client" Version="4.6.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,115 @@
|
||||
using System.Collections.Specialized;
|
||||
using WebSocketSpace;
|
||||
|
||||
namespace FunASRWSClient_Offline
|
||||
{
|
||||
/// <summary>
|
||||
/// /主程序入口
|
||||
/// </summary>
|
||||
public class Program
|
||||
{
|
||||
private static void Main()
|
||||
{
|
||||
WSClient_Offline m_funasrclient = new WSClient_Offline();
|
||||
m_funasrclient.FunASR_Main();
|
||||
}
|
||||
}
|
||||
|
||||
public class WSClient_Offline
|
||||
{
|
||||
public static string host = "0.0.0.0";
|
||||
public static string port = "10095";
|
||||
public static string hotword = null;
|
||||
private static CWebSocketClient m_websocketclient = new CWebSocketClient();
|
||||
[STAThread]
|
||||
public async void FunASR_Main()
|
||||
{
|
||||
loadconfig();
|
||||
loadhotword();
|
||||
//初始化通信连接
|
||||
string errorStatus = string.Empty;
|
||||
string commstatus = ClientConnTest();
|
||||
if (commstatus != "通信连接成功")
|
||||
errorStatus = commstatus;
|
||||
//程序初始监测异常--报错、退出
|
||||
if (errorStatus != string.Empty)
|
||||
{
|
||||
//报错方式待加
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
//循环输入推理文件
|
||||
while (true)
|
||||
{
|
||||
Console.WriteLine("请输入转录文件路径:");
|
||||
string filepath = Console.ReadLine();
|
||||
if (filepath != string.Empty && filepath != null)
|
||||
{
|
||||
await m_websocketclient.ClientSendFileFunc(filepath);
|
||||
}
|
||||
}
|
||||
}
|
||||
private void loadconfig()
|
||||
{
|
||||
string filePath = "config.ini";
|
||||
NameValueCollection settings = new NameValueCollection();
|
||||
using (StreamReader reader = new StreamReader(filePath))
|
||||
{
|
||||
string line;
|
||||
while ((line = reader.ReadLine()) != null)
|
||||
{
|
||||
// 忽略空行和注释
|
||||
if (string.IsNullOrEmpty(line) || line.StartsWith(";") || line.StartsWith("#"))
|
||||
continue;
|
||||
// 解析键值对
|
||||
int equalsIndex = line.IndexOf('=');
|
||||
if (equalsIndex > 0)
|
||||
{
|
||||
string key = line.Substring(0, equalsIndex).Trim();
|
||||
string value = line.Substring(equalsIndex + 1).Trim();
|
||||
if (key == "host")
|
||||
host = value;
|
||||
else if (key == "port")
|
||||
port = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
static void loadhotword()
|
||||
{
|
||||
string filePath = "hotword.txt";
|
||||
try
|
||||
{
|
||||
// 使用 StreamReader 打开文本文件
|
||||
using (StreamReader sr = new StreamReader(filePath))
|
||||
{
|
||||
string line;
|
||||
// 逐行读取文件内容
|
||||
while ((line = sr.ReadLine()) != null)
|
||||
{
|
||||
hotword += line;
|
||||
hotword += " ";
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("读取文件时发生错误:" + ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (hotword.Length > 0 && hotword[hotword.Length - 1] == ' ')
|
||||
hotword = hotword.Substring(0,hotword.Length - 1);
|
||||
}
|
||||
}
|
||||
private static string ClientConnTest()
|
||||
{
|
||||
//WebSocket连接状态监测
|
||||
Task<string> websocketstatus = m_websocketclient.ClientConnTest();
|
||||
if (websocketstatus != null && websocketstatus.Result.IndexOf("成功") == -1)
|
||||
return websocketstatus.Result;
|
||||
return "通信连接成功";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
# cshape-client-offline
|
||||
|
||||
这是一个基于FunASR-Websocket服务器的CShape客户端,用于转录本地音频文件。
|
||||
|
||||
将配置文件放在与程序相同目录下的config文件夹中,并在config.ini中配置服务器ip地址和端口号。
|
||||
|
||||
配置好服务端ip和端口号,在vs中打开需添加Websocket.Client的Nuget程序包后,可直接进行测试,按照控制台提示操作即可。
|
||||
|
||||
更新:支持热词和时间戳,热词需将config文件夹下的hotword.txt放置在执行路径下。
|
||||
|
||||
注:运行后台须注意热词和时间戳为不同模型,本客户端在win11下完成测试,编译环境VS2022。
|
||||
@@ -0,0 +1,177 @@
|
||||
using Websocket.Client;
|
||||
using System.Text.Json;
|
||||
using System.Reactive.Linq;
|
||||
using FunASRWSClient_Offline;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace WebSocketSpace
|
||||
{
|
||||
internal class CWebSocketClient
|
||||
{
|
||||
private static readonly Uri serverUri = new Uri($"ws://{WSClient_Offline.host}:{WSClient_Offline.port}"); // 你要连接的WebSocket服务器地址
|
||||
private static WebsocketClient client = new WebsocketClient(serverUri);
|
||||
public async Task<string> ClientConnTest()
|
||||
{
|
||||
string commstatus = "WebSocket通信连接失败";
|
||||
try
|
||||
{
|
||||
client.Name = "funasr";
|
||||
client.ReconnectTimeout = null;
|
||||
client.ReconnectionHappened.Subscribe(info =>
|
||||
Console.WriteLine($"Reconnection happened, type: {info.Type}, url: {client.Url}"));
|
||||
client.DisconnectionHappened.Subscribe(info =>
|
||||
Console.WriteLine($"Disconnection happened, type: {info.Type}"));
|
||||
|
||||
client
|
||||
.MessageReceived
|
||||
.Where(msg => msg.Text != null)
|
||||
.Subscribe(msg =>
|
||||
{
|
||||
recmessage(msg.Text);
|
||||
});
|
||||
|
||||
await client.Start();
|
||||
|
||||
if (client.IsRunning)
|
||||
commstatus = "WebSocket通信连接成功";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.ToString());
|
||||
client.Dispose();
|
||||
}
|
||||
|
||||
return commstatus;
|
||||
}
|
||||
|
||||
public async Task<Task> ClientSendFileFunc(string file_name)//文件转录
|
||||
{
|
||||
string fileExtension = Path.GetExtension(file_name);
|
||||
fileExtension = fileExtension.Replace(".", "");
|
||||
if (!(fileExtension == "mp3" || fileExtension == "mp4" || fileExtension == "wav" || fileExtension == "pcm"))
|
||||
return Task.CompletedTask;
|
||||
|
||||
try
|
||||
{
|
||||
if (client.IsRunning)
|
||||
{
|
||||
if (fileExtension == "wav")
|
||||
{
|
||||
var exitEvent = new ManualResetEvent(false);
|
||||
string path = Path.GetFileName(file_name);
|
||||
string firstbuff = string.Format("{{\"mode\": \"offline\", \"wav_name\": \"{0}\", \"is_speaking\": true,\"hotwords\":\"{1}\"}}", Path.GetFileName(file_name), WSClient_Offline.hotword);
|
||||
client.Send(firstbuff);
|
||||
showWAVForm(client, file_name);
|
||||
}
|
||||
else
|
||||
{
|
||||
var exitEvent = new ManualResetEvent(false);
|
||||
string path = Path.GetFileName(file_name);
|
||||
string firstbuff = string.Format("{{\"mode\": \"offline\", \"wav_name\": \"{0}\", \"is_speaking\": true,\"hotwords\":\"{1}\", \"wav_format\":\"{2}\"}}", Path.GetFileName(file_name), WSClient_Offline.hotword, fileExtension);
|
||||
client.Send(firstbuff);
|
||||
showWAVForm_All(client, file_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.ToString());
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void recmessage(string message)
|
||||
{
|
||||
if (message != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
string timestamp = string.Empty;
|
||||
JsonDocument jsonDoc = JsonDocument.Parse(message);
|
||||
JsonElement root = jsonDoc.RootElement;
|
||||
string mode = root.GetProperty("mode").GetString();
|
||||
string text = root.GetProperty("text").GetString();
|
||||
string name = root.GetProperty("wav_name").GetString();
|
||||
if (message.IndexOf("timestamp") != -1)
|
||||
{
|
||||
Console.WriteLine($"文件名称:{name}");
|
||||
//识别内容处理
|
||||
text = text.Replace(",", "。");
|
||||
text = text.Replace("?", "。");
|
||||
List<string> sens = text.Split("。").ToList();
|
||||
//时间戳处理
|
||||
timestamp = root.GetProperty("timestamp").GetString();
|
||||
List<List<int>> data = new List<List<int>>();
|
||||
string pattern = @"\[(\d+),(\d+)\]";
|
||||
foreach (Match match in Regex.Matches(timestamp, pattern))
|
||||
{
|
||||
int start = int.Parse(match.Groups[1].Value);
|
||||
int end = int.Parse(match.Groups[2].Value);
|
||||
data.Add(new List<int> { start, end });
|
||||
}
|
||||
int count = 0;
|
||||
for (int i = 0; i< sens.Count; i++)
|
||||
{
|
||||
if (sens[i].Length == 0)
|
||||
continue;
|
||||
Console.WriteLine(string.Format($"[{data[count][0]}-{data[count + sens[i].Length - 1][1]}]:{sens[i]}"));
|
||||
count += sens[i].Length;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"文件名称:{name} 文件转录内容: {text} 时间戳:{timestamp}");
|
||||
}
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
Console.WriteLine("JSON 解析错误: " + ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void showWAVForm(WebsocketClient client, string file_name)
|
||||
{
|
||||
byte[] getbyte = FileToByte(file_name).Skip(44).ToArray();
|
||||
|
||||
for (int i = 0; i < getbyte.Length; i += 1024000)
|
||||
{
|
||||
byte[] send = getbyte.Skip(i).Take(1024000).ToArray();
|
||||
client.Send(send);
|
||||
Thread.Sleep(5);
|
||||
}
|
||||
Thread.Sleep(10);
|
||||
client.Send("{\"is_speaking\": false}");
|
||||
}
|
||||
|
||||
private void showWAVForm_All(WebsocketClient client, string file_name)
|
||||
{
|
||||
byte[] getbyte = FileToByte(file_name).ToArray();
|
||||
for (int i = 0; i < getbyte.Length; i += 1024000)
|
||||
{
|
||||
byte[] send = getbyte.Skip(i).Take(1024000).ToArray();
|
||||
client.Send(send);
|
||||
Thread.Sleep(5);
|
||||
}
|
||||
Thread.Sleep(10);
|
||||
client.Send("{\"is_speaking\": false}");
|
||||
}
|
||||
|
||||
public byte[] FileToByte(string fileUrl)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (FileStream fs = new FileStream(fileUrl, FileMode.Open, FileAccess.Read))
|
||||
{
|
||||
byte[] byteArray = new byte[fs.Length];
|
||||
fs.Read(byteArray, 0, byteArray.Length);
|
||||
return byteArray;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NAudio" Version="2.1.0" />
|
||||
<PackageReference Include="Websocket.Client" Version="4.6.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,255 @@
|
||||
using AliFsmnVadSharp;
|
||||
using NAudio.Wave;
|
||||
using System.Collections.Concurrent;
|
||||
using WebSocketSpace;
|
||||
using NAudio.CoreAudioApi;
|
||||
using System.IO;
|
||||
using System.Collections.Specialized;
|
||||
|
||||
namespace FunASRWSClient_Online
|
||||
{
|
||||
/// <summary>
|
||||
/// /主程序入口
|
||||
/// </summary>
|
||||
public class Program
|
||||
{
|
||||
private static void Main()
|
||||
{
|
||||
WSClient_Online m_funasrclient = new WSClient_Online();
|
||||
m_funasrclient.FunASR_Main();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// /主线程入口,初始化后读取数据
|
||||
/// </summary>
|
||||
public class WSClient_Online
|
||||
{
|
||||
/// <summary>
|
||||
/// FunASR客户端软件运行状态
|
||||
/// </summary>
|
||||
///
|
||||
public static string host = "0.0.0.0";
|
||||
public static string port = "10095";
|
||||
public static string onlineasrmode = string.Empty;
|
||||
private static WaveCollect m_wavecollect = new WaveCollect();
|
||||
private static CWebSocketClient m_websocketclient = new CWebSocketClient();
|
||||
public static readonly ConcurrentQueue<byte[]> ActiveAudioSet = new ConcurrentQueue<byte[]>();
|
||||
public static readonly ConcurrentQueue<string> AudioFileQueue = new ConcurrentQueue<string>();
|
||||
[STAThread]
|
||||
public void FunASR_Main()
|
||||
{
|
||||
loadconfig();
|
||||
//麦克风状态监测
|
||||
string errorStatus = string.Empty;
|
||||
if (GetCurrentMicVolume() == -2)
|
||||
errorStatus = "注意:麦克风被设置为静音!";
|
||||
else if (GetCurrentMicVolume() == -1)
|
||||
errorStatus = "注意:麦克风未连接!";
|
||||
else if (GetCurrentMicVolume() == 0)
|
||||
errorStatus = "注意:麦克风声音设置为0!";
|
||||
|
||||
//初始化通信连接
|
||||
string commstatus = ClientConnTest();
|
||||
if (commstatus != "通信连接成功")
|
||||
errorStatus = commstatus;
|
||||
//程序初始监测异常--报错、退出
|
||||
if (errorStatus != string.Empty)
|
||||
{
|
||||
Environment.Exit(0);//报错方式待加
|
||||
}
|
||||
|
||||
//启动客户端向服务端发送音频数据线程
|
||||
Thread SendAudioThread = new Thread(SendAudioToSeverAsync);
|
||||
SendAudioThread.Start();
|
||||
|
||||
//启动音频文件转录线程
|
||||
Thread AudioFileThread = new Thread(SendAudioFileToSeverAsync);
|
||||
AudioFileThread.Start();
|
||||
|
||||
while (true)
|
||||
{
|
||||
Console.WriteLine("请选择语音识别方式:1.离线文件转写;2.实时语音识别");
|
||||
string str = Console.ReadLine();
|
||||
if (str != string.Empty)
|
||||
{
|
||||
if (str == "1")//离线文件转写
|
||||
{
|
||||
onlineasrmode = "offline";
|
||||
Console.WriteLine("请输入转录文件路径");
|
||||
str = Console.ReadLine();
|
||||
if (!string.IsNullOrEmpty(str))
|
||||
AudioFileQueue.Enqueue(str);
|
||||
}
|
||||
else if (str == "2")//实时语音识别
|
||||
{
|
||||
Console.WriteLine("请输入实时语音识别模式:1.online;2.2pass");
|
||||
str = Console.ReadLine();
|
||||
OnlineASR(str);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private void loadconfig()
|
||||
{
|
||||
string filePath = "config.ini";
|
||||
NameValueCollection settings = new NameValueCollection();
|
||||
using (StreamReader reader = new StreamReader(filePath))
|
||||
{
|
||||
string line;
|
||||
while ((line = reader.ReadLine()) != null)
|
||||
{
|
||||
// 忽略空行和注释
|
||||
if (string.IsNullOrEmpty(line) || line.StartsWith(";") || line.StartsWith("#"))
|
||||
continue;
|
||||
// 解析键值对
|
||||
int equalsIndex = line.IndexOf('=');
|
||||
if (equalsIndex > 0)
|
||||
{
|
||||
string key = line.Substring(0, equalsIndex).Trim();
|
||||
string value = line.Substring(equalsIndex + 1).Trim();
|
||||
if (key == "host")
|
||||
host = value;
|
||||
else if (key == "port")
|
||||
port = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private void OnlineASR(string str)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(str))
|
||||
{
|
||||
if (str == "1")//实时语音识别
|
||||
onlineasrmode = "online";
|
||||
else if (str == "2")//实时语音识别-动态修正
|
||||
onlineasrmode = "2pass";
|
||||
}
|
||||
//开始录制声音、发送识别
|
||||
if (onlineasrmode != string.Empty)
|
||||
{
|
||||
m_wavecollect.StartRec();
|
||||
m_websocketclient.ClientFirstConnOnline(onlineasrmode);
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (!WaveCollect.voicebuff.IsEmpty)
|
||||
{
|
||||
byte[] buff;
|
||||
int buffcnt = WaveCollect.voicebuff.Count;
|
||||
WaveCollect.voicebuff.TryDequeue(out buff);
|
||||
if (buff != null)
|
||||
ActiveAudioSet.Enqueue(buff);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Console.KeyAvailable)
|
||||
{
|
||||
var key = Console.ReadKey(true);
|
||||
|
||||
// 检测到按下Ctrl+C
|
||||
if ((key.Modifiers & ConsoleModifiers.Control) != 0 && key.Key == ConsoleKey.C)
|
||||
{
|
||||
// 执行相应的操作
|
||||
Console.WriteLine("Ctrl+C Pressed!");
|
||||
// 退出循环或执行其他操作
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine("实时识别出现异常!");
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_wavecollect.StopRec();
|
||||
m_websocketclient.ClientLastConnOnline();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string ClientConnTest()
|
||||
{
|
||||
//WebSocket连接状态监测
|
||||
Task<string> websocketstatus = m_websocketclient.ClientConnTest();
|
||||
if (websocketstatus != null && websocketstatus.Result.IndexOf("成功") == -1)
|
||||
return websocketstatus.Result;
|
||||
return "通信连接成功";
|
||||
}
|
||||
private void SendAudioFileToSeverAsync()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
Thread.Sleep(1000);
|
||||
if (AudioFileQueue.Count > 0)
|
||||
{
|
||||
string filepath = string.Empty;
|
||||
AudioFileQueue.TryDequeue(out filepath);
|
||||
if (filepath != string.Empty && filepath != null)
|
||||
{
|
||||
m_websocketclient.ClientSendFileFunc(filepath);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
}
|
||||
}
|
||||
private void SendAudioToSeverAsync()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (ActiveAudioSet.Count > 0)
|
||||
{
|
||||
byte[] audio;
|
||||
ActiveAudioSet.TryDequeue(out audio);
|
||||
if (audio == null)
|
||||
continue;
|
||||
|
||||
byte[] mArray = new byte[audio.Length];
|
||||
Array.Copy(audio, 0, mArray, 0, audio.Length);
|
||||
if (mArray != null)
|
||||
m_websocketclient.ClientSendAudioFunc(mArray);
|
||||
}
|
||||
else
|
||||
{
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveAsWav(byte[] pcmData, string fileName, int sampleRate, int bitsPerSample, int channels)
|
||||
{
|
||||
using (var writer = new WaveFileWriter(fileName, new WaveFormat(sampleRate, bitsPerSample, channels)))
|
||||
{
|
||||
writer.Write(pcmData, 0, pcmData.Length);
|
||||
}
|
||||
}
|
||||
|
||||
private int GetCurrentMicVolume() //获取麦克风设置
|
||||
{
|
||||
int volume = -1;
|
||||
var enumerator = new MMDeviceEnumerator();
|
||||
|
||||
//获取音频输入设备
|
||||
IEnumerable<MMDevice> captureDevices = enumerator.EnumerateAudioEndPoints(DataFlow.Capture, DeviceState.Active).ToArray();
|
||||
if (captureDevices.Count() > 0)
|
||||
{
|
||||
MMDevice mMDevice = captureDevices.ToList()[0];
|
||||
if (mMDevice.AudioEndpointVolume.Mute)
|
||||
return -2;
|
||||
volume = (int)(mMDevice.AudioEndpointVolume.MasterVolumeLevelScalar * 100);
|
||||
|
||||
}
|
||||
return volume;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
# cshape-client-online
|
||||
|
||||
这是一个基于FunASR-Websocket服务器的CShape客户端,用于实时语音识别和转录本地音频文件。
|
||||
|
||||
将配置文件放在与程序相同目录下的config文件夹中,并在config.ini中配置服务器ip地址和端口号。
|
||||
|
||||
配置好服务端ip和端口号,在vs中打开需添加NAudio和Websocket.Client的Nuget程序包后,可直接进行测试,按照控制台提示操作即可。
|
||||
|
||||
注:实时语音识别使用online或2pass,转录文件默认使用offline,在win11下完成测试,编译环境VS2022。
|
||||
@@ -0,0 +1,106 @@
|
||||
using System.Collections.Concurrent;
|
||||
using NAudio.Wave;
|
||||
using NAudio.CoreAudioApi;
|
||||
|
||||
namespace AliFsmnVadSharp
|
||||
{
|
||||
class WaveCollect
|
||||
{
|
||||
private string fileName = string.Empty;
|
||||
private WaveInEvent? waveSource = null;
|
||||
private WaveFileWriter? waveFile = null;
|
||||
public static int wave_buffer_milliseconds = 600;
|
||||
public static int wave_buffer_collectbits = 16;
|
||||
public static int wave_buffer_collectchannels = 1;
|
||||
public static int wave_buffer_collectfrequency = 16000;
|
||||
public static readonly ConcurrentQueue<byte[]> voicebuff = new ConcurrentQueue<byte[]>();
|
||||
|
||||
public void StartRec()
|
||||
{
|
||||
// 获取麦克风设备
|
||||
var captureDevices = new MMDeviceEnumerator().EnumerateAudioEndPoints(DataFlow.Capture, DeviceState.Active);
|
||||
foreach (var device in captureDevices)
|
||||
{
|
||||
Console.WriteLine("Device Name: " + device.FriendlyName);
|
||||
using (var capture = new WasapiLoopbackCapture(device))
|
||||
{
|
||||
// 获取支持的采样率列表
|
||||
Console.WriteLine("Device Channels:" + capture.WaveFormat.Channels);
|
||||
Console.WriteLine("Device SampleRate:" + capture.WaveFormat.SampleRate);
|
||||
Console.WriteLine("Device BitsPerSample:" + capture.WaveFormat.BitsPerSample);
|
||||
}
|
||||
}
|
||||
//清空缓存数据
|
||||
int buffnum = voicebuff.Count;
|
||||
for (int i = 0; i < buffnum; i++)
|
||||
voicebuff.TryDequeue(out byte[] buff);
|
||||
|
||||
waveSource = new WaveInEvent();
|
||||
waveSource.BufferMilliseconds = wave_buffer_milliseconds;
|
||||
waveSource.WaveFormat = new WaveFormat(wave_buffer_collectfrequency, wave_buffer_collectbits, wave_buffer_collectchannels); // 16bit,16KHz,Mono的录音格式
|
||||
waveSource.DataAvailable += new EventHandler<WaveInEventArgs>(waveSource_DataAvailable);
|
||||
SetFileName(AppDomain.CurrentDomain.BaseDirectory + "tmp.wav");
|
||||
waveFile = new WaveFileWriter(fileName, waveSource.WaveFormat);
|
||||
waveSource.StartRecording();
|
||||
}
|
||||
|
||||
public void StopRec()
|
||||
{
|
||||
if (waveSource != null)
|
||||
{
|
||||
waveSource.StopRecording();
|
||||
if (waveSource != null)
|
||||
{
|
||||
waveSource.Dispose();
|
||||
waveSource = null;
|
||||
}
|
||||
if (waveFile != null)
|
||||
{
|
||||
waveFile.Dispose();
|
||||
waveFile = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetFileName(string fileName)
|
||||
{
|
||||
this.fileName = fileName;
|
||||
}
|
||||
|
||||
private void waveSource_DataAvailable(object sender, WaveInEventArgs e)
|
||||
{
|
||||
if (waveFile != null)
|
||||
{
|
||||
if (e.Buffer != null && e.BytesRecorded > 0)
|
||||
{
|
||||
voicebuff.Enqueue(e.Buffer);
|
||||
//waveFile.Write(e.Buffer, 0, e.BytesRecorded);
|
||||
waveFile.Flush();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] Wavedata_Dequeue()
|
||||
{
|
||||
byte[] datas;
|
||||
voicebuff.TryDequeue(out datas);
|
||||
return datas;
|
||||
}
|
||||
|
||||
private void waveSource_RecordingStopped(object sender, StoppedEventArgs e)
|
||||
{
|
||||
if (waveSource != null)
|
||||
{
|
||||
waveSource.Dispose();
|
||||
waveSource = null;
|
||||
}
|
||||
|
||||
if (waveFile != null)
|
||||
{
|
||||
waveFile.Dispose();
|
||||
waveFile = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
using System.Net.WebSockets;
|
||||
using Websocket.Client;
|
||||
using System.Text.Json;
|
||||
using NAudio.Wave;
|
||||
using AliFsmnVadSharp;
|
||||
using System.Reactive.Linq;
|
||||
using FunASRWSClient_Online;
|
||||
|
||||
namespace WebSocketSpace
|
||||
{
|
||||
internal class CWebSocketClient
|
||||
{
|
||||
private static int chunk_interval = 10;
|
||||
private static int[] chunk_size = new int[] { 5, 10, 5 };
|
||||
private static readonly Uri serverUri = new Uri($"ws://{WSClient_Online.host}:{WSClient_Online.port}"); // 你要连接的WebSocket服务器地址
|
||||
private static WebsocketClient client = new WebsocketClient(serverUri);
|
||||
public async Task<string> ClientConnTest()
|
||||
{
|
||||
string commstatus = "WebSocket通信连接失败";
|
||||
try
|
||||
{
|
||||
client.Name = "funasr";
|
||||
client.ReconnectTimeout = null;
|
||||
client.ReconnectionHappened.Subscribe(info =>
|
||||
Console.WriteLine($"Reconnection happened, type: {info.Type}, url: {client.Url}"));
|
||||
client.DisconnectionHappened.Subscribe(info =>
|
||||
Console.WriteLine($"Disconnection happened, type: {info.Type}"));
|
||||
|
||||
client
|
||||
.MessageReceived
|
||||
.Where(msg => msg.Text != null)
|
||||
.Subscribe(msg =>
|
||||
{
|
||||
rec_message(msg.Text, client);
|
||||
});
|
||||
|
||||
await client.Start();
|
||||
|
||||
if (client.IsRunning)
|
||||
commstatus = "WebSocket通信连接成功";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.ToString());
|
||||
client.Dispose();
|
||||
}
|
||||
|
||||
return commstatus;
|
||||
}
|
||||
|
||||
public bool ClientFirstConnOnline(string asrmode)
|
||||
{
|
||||
if (client.IsRunning)
|
||||
{
|
||||
string firstbuff = string.Format("{{\"mode\": \"{0}\", \"chunk_size\": [{1},{2},{3}], \"chunk_interval\": {4}, \"wav_name\": \"microphone\", \"is_speaking\": true}}"
|
||||
, asrmode, chunk_size[0], chunk_size[1], chunk_size[2], chunk_interval);
|
||||
Task.Run(() => client.Send(firstbuff));
|
||||
}
|
||||
else
|
||||
{
|
||||
client.Reconnect();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
public bool ClientSendAudioFunc(byte[] buff) //实时识别
|
||||
{
|
||||
if (client.IsRunning)
|
||||
{
|
||||
////发送音频数据
|
||||
int CHUNK = WaveCollect.wave_buffer_collectfrequency / 1000 * 60 * chunk_size[1] / chunk_interval;
|
||||
for (int i = 0; i < buff.Length; i += CHUNK)
|
||||
{
|
||||
byte[] send = buff.Skip(i).Take(CHUNK).ToArray();
|
||||
Task.Run(() => client.Send(send));
|
||||
Thread.Sleep(1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
client.Reconnect();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
public void ClientLastConnOnline()
|
||||
{
|
||||
Task.Run(() => client.Send("{\"is_speaking\": false}"));
|
||||
}
|
||||
|
||||
public int ClientSendFileFunc(string file_name)//文件转录 0:发送成功 ret -1:文件类型不支持 -2:通信断开
|
||||
{
|
||||
string fileExtension = Path.GetExtension(file_name);
|
||||
fileExtension = fileExtension.Replace(".", "");
|
||||
if (!(fileExtension == "mp3" || fileExtension == "mp4" || fileExtension == "wav" || fileExtension == "pcm"))
|
||||
return -1;
|
||||
|
||||
if (client.IsRunning)
|
||||
{
|
||||
if (fileExtension == "wav" || fileExtension == "pcm")
|
||||
{
|
||||
string firstbuff = string.Format("{{\"mode\": \"office\", \"chunk_size\": [{0},{1},{2}], \"chunk_interval\": {3}, \"wav_name\": \"{4}\", \"is_speaking\": true, \"wav_format\":\"pcm\"}}"
|
||||
, chunk_size[0], chunk_size[1], chunk_size[2], chunk_interval, Path.GetFileName(file_name));
|
||||
Task.Run(() => client.Send(firstbuff));
|
||||
if (fileExtension == "wav")
|
||||
showWAVForm(file_name);
|
||||
else if (fileExtension == "pcm")
|
||||
showWAVForm_All(file_name);
|
||||
}
|
||||
else if (fileExtension == "mp3" || fileExtension == "mp4")
|
||||
{
|
||||
string firstbuff = string.Format("{{\"mode\": \"offline\", \"chunk_size\": \"{0},{1},{2}\", \"chunk_interval\": {3}, \"wav_name\": \"{4}\", \"is_speaking\": true, \"wav_format\":\"{5}\"}}"
|
||||
, chunk_size[0], chunk_size[1], chunk_size[2], chunk_interval, Path.GetFileName(file_name), fileExtension);
|
||||
Task.Run(() => client.Send(firstbuff));
|
||||
showWAVForm_All(file_name);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
client.Reconnect();
|
||||
return -2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
private string recbuff = string.Empty;//接收累计缓存内容
|
||||
private string onlinebuff = string.Empty;//接收累计在线缓存内容
|
||||
public void rec_message(string message, WebsocketClient client)
|
||||
{
|
||||
if (message != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
string name = string.Empty;
|
||||
JsonDocument jsonDoc = JsonDocument.Parse(message);
|
||||
JsonElement root = jsonDoc.RootElement;
|
||||
string mode = root.GetProperty("mode").GetString();
|
||||
string text = root.GetProperty("text").GetString();
|
||||
bool isfinal = root.GetProperty("is_final").GetBoolean();
|
||||
if (message.IndexOf("wav_name ") != -1)
|
||||
name = root.GetProperty("wav_name").GetString();
|
||||
|
||||
//if (name == "microphone")
|
||||
// Console.WriteLine($"实时识别内容: {text}");
|
||||
//else
|
||||
// Console.WriteLine($"文件名称:{name} 文件转录内容: {text}");
|
||||
|
||||
if (mode == "2pass-online" && WSClient_Online.onlineasrmode != "offline")
|
||||
{
|
||||
onlinebuff += text;
|
||||
Console.WriteLine(recbuff + onlinebuff);
|
||||
}
|
||||
else if (mode == "2pass-offline")
|
||||
{
|
||||
recbuff += text;
|
||||
onlinebuff = string.Empty;
|
||||
Console.WriteLine(recbuff);
|
||||
}
|
||||
|
||||
if (isfinal && WSClient_Online.onlineasrmode != "offline")//未结束当前识别
|
||||
{
|
||||
recbuff = string.Empty;
|
||||
}
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
Console.WriteLine("JSON 解析错误: " + ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void showWAVForm(string file_name)
|
||||
{
|
||||
byte[] getbyte = FileToByte(file_name).Skip(44).ToArray();
|
||||
|
||||
for (int i = 0; i < getbyte.Length; i += 102400)
|
||||
{
|
||||
byte[] send = getbyte.Skip(i).Take(102400).ToArray();
|
||||
Task.Run(() => client.Send(send));
|
||||
Thread.Sleep(5);
|
||||
}
|
||||
Thread.Sleep(100);
|
||||
Task.Run(() => client.Send("{\"is_speaking\": false}"));
|
||||
}
|
||||
|
||||
private void showWAVForm_All(string file_name)
|
||||
{
|
||||
byte[] getbyte = FileToByte(file_name).ToArray();
|
||||
|
||||
for (int i = 0; i < getbyte.Length; i += 1024000)
|
||||
{
|
||||
byte[] send = getbyte.Skip(i).Take(1024000).ToArray();
|
||||
Task.Run(() => client.Send(send));
|
||||
Thread.Sleep(5);
|
||||
}
|
||||
Thread.Sleep(10);
|
||||
Task.Run(() => client.Send("{\"is_speaking\": false}"));
|
||||
}
|
||||
|
||||
public byte[] FileToByte(string fileUrl)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (FileStream fs = new FileStream(fileUrl, FileMode.Open, FileAccess.Read))
|
||||
{
|
||||
byte[] byteArray = new byte[fs.Length];
|
||||
fs.Read(byteArray, 0, byteArray.Length);
|
||||
return byteArray;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
host=127.0.0.1
|
||||
port=10095
|
||||
@@ -0,0 +1,3 @@
|
||||
阿里巴巴
|
||||
达摩院
|
||||
FunASR
|
||||
Binary file not shown.
Reference in New Issue
Block a user