Răsfoiți Sursa

Add microphone example for .Net (#248)

Fangjun Kuang 2 ani în urmă
părinte
comite
77c34f3cff

+ 65 - 58
dotnet-examples/decode-file/DecodeFile.cs

@@ -1,9 +1,11 @@
 // Copyright (c)  2023  Xiaomi Corporation (authors: Fangjun Kuang)
 using System;
 
-class DecodeFile {
-  public static void Main(String[] args) {
-    String usage = @"
+class DecodeFile
+{
+    public static void Main(String[] args)
+    {
+        String usage = @"
       ./DecodeFile.exe \
          /path/to/tokens.txt \
          /path/to/encoder.ncnn.param \
@@ -14,66 +16,71 @@ class DecodeFile {
          /path/to/joiner.ncnn.bin \
          /path/to/foo.wav [<num_threads> [decode_method]]
 
-      num_threads: Default to 2
+      num_threads: Default to 1
       decoding_method: greedy_search (default), or modified_beam_search
 
       Please refer to
       https://k2-fsa.github.io/sherpa/ncnn/pretrained_models/index.html
       for a list of pre-trained models to download.
       ";
-    if (args.Length < 8 || args.Length > 10) {
-      Console.WriteLine(usage);
-      return;
+        if (args.Length < 8 || args.Length > 10)
+        {
+            Console.WriteLine(usage);
+            return;
+        }
+
+        String waveFilename = args[7];
+        SherpaNcnn.WaveReader waveReader = new SherpaNcnn.WaveReader(waveFilename);
+
+        SherpaNcnn.OnlineRecognizerConfig config = new SherpaNcnn.OnlineRecognizerConfig();
+        config.FeatConfig.SampleRate = 16000;
+        config.FeatConfig.FeatureDim = 80;
+        config.ModelConfig.Tokens = args[0];
+        config.ModelConfig.EncoderParam = args[1];
+        config.ModelConfig.EncoderBin = args[2];
+
+        config.ModelConfig.DecoderParam = args[3];
+        config.ModelConfig.DecoderBin = args[4];
+
+        config.ModelConfig.JoinerParam = args[5];
+        config.ModelConfig.JoinerBin = args[6];
+
+        config.ModelConfig.UseVulkanCompute = 0;
+        config.ModelConfig.NumThreads = 1;
+        if (args.Length >= 9)
+        {
+            config.ModelConfig.NumThreads = Int32.Parse(args[8]);
+            if (config.ModelConfig.NumThreads > 1)
+            {
+                Console.WriteLine($"Use num_threads: {config.ModelConfig.NumThreads}");
+            }
+        }
+
+        config.DecoderConfig.DecodingMethod = "greedy_search";
+        if (args.Length == 10 && args[9] != "greedy_search")
+        {
+            Console.WriteLine($"Use decoding_method {args[9]}");
+            config.DecoderConfig.DecodingMethod = args[9];
+        }
+
+        config.DecoderConfig.NumActivePaths = 4;
+
+        SherpaNcnn.OnlineRecognizer recognizer = new SherpaNcnn.OnlineRecognizer(config);
+
+        SherpaNcnn.OnlineStream stream = recognizer.CreateStream();
+        stream.AcceptWaveform(waveReader.SampleRate, waveReader.Samples);
+
+        float[] tailPadding = new float[(int)(waveReader.SampleRate * 0.3)];
+        stream.AcceptWaveform(waveReader.SampleRate, tailPadding);
+
+        stream.InputFinished();
+
+        while (recognizer.IsReady(stream))
+        {
+            recognizer.Decode(stream);
+        }
+
+        SherpaNcnn.OnlineRecognizerResult result = recognizer.GetResult(stream);
+        Console.WriteLine(result.Text);
     }
-
-    String waveFilename = args[7];
-    SherpaNcnn.WaveReader waveReader = new SherpaNcnn.WaveReader(waveFilename);
-
-    SherpaNcnn.OnlineRecognizerConfig config = new SherpaNcnn.OnlineRecognizerConfig();
-    config.FeatConfig.SampleRate =  16000;
-    config.FeatConfig.FeatureDim =  80;
-    config.ModelConfig.Tokens = args[0];
-    config.ModelConfig.EncoderParam = args[1];
-    config.ModelConfig.EncoderBin = args[2];
-
-    config.ModelConfig.DecoderParam = args[3];
-    config.ModelConfig.DecoderBin = args[4];
-
-    config.ModelConfig.JoinerParam = args[5];
-    config.ModelConfig.JoinerBin = args[6];
-
-    config.ModelConfig.UseVulkanCompute = 0;
-    config.ModelConfig.NumThreads = 1;
-    if (args.Length >= 9) {
-      config.ModelConfig.NumThreads = Int32.Parse(args[8]);
-      if (config.ModelConfig.NumThreads > 1) {
-        Console.WriteLine($"Use num_threads: {config.ModelConfig.NumThreads}");
-      }
-    }
-
-    config.DecoderConfig.DecodingMethod = "greedy_search";
-    if (args.Length == 10 && args[9] != "greedy_search") {
-      Console.WriteLine($"Use decoding_method {args[9]}");
-      config.DecoderConfig.DecodingMethod = args[9];
-    }
-
-    config.DecoderConfig.NumActivePaths = 4;
-
-    SherpaNcnn.OnlineRecognizer recognizer = new SherpaNcnn.OnlineRecognizer(config);
-
-    SherpaNcnn.OnlineStream stream =  recognizer.CreateStream();
-    stream.AcceptWaveform(waveReader.SampleRate, waveReader.Samples);
-
-    float[] tailPadding = new float[(int)(waveReader.SampleRate * 0.3)];
-    stream.AcceptWaveform(waveReader.SampleRate, tailPadding);
-
-    stream.InputFinished();
-
-    while(recognizer.IsReady(stream)) {
-      recognizer.Decode(stream);
-    }
-
-    SherpaNcnn.OnlineRecognizerResult result = recognizer.GetResult(stream);
-    Console.WriteLine(result.Text);
-  }
 }

+ 153 - 130
dotnet-examples/decode-file/WaveReader.cs

@@ -4,148 +4,171 @@ using System.IO;
 
 using System.Runtime.InteropServices;
 
-namespace SherpaNcnn {
-
-[StructLayout(LayoutKind.Sequential)]
-public struct WaveHeader {
-  public Int32 ChunkID;
-  public Int32 ChunkSize;
-  public Int32 Format;
-  public Int32 SubChunk1ID;
-  public Int32 SubChunk1Size;
-  public Int16 AudioFormat;
-  public Int16 NumChannels;
-  public Int32 SampleRate;
-  public Int32 ByteRate;
-  public Int16 BlockAlign;
-  public Int16 BitsPerSample;
-  public Int32 SubChunk2ID;
-  public Int32 SubChunk2Size;
-
-  public bool Validate() {
-    if (ChunkID != 0x46464952) {
-      Console.WriteLine($"Invalid chunk ID: 0x{ChunkID:X}. Expect 0x46464952");
-      return false;
-    }
-
-    //               E V A W
-    if (Format != 0x45564157) {
-      Console.WriteLine($"Invalid format: 0x{Format:X}. Expect 0x45564157");
-      return false;
-    }
-
-    //                      t m f
-    if (SubChunk1ID != 0x20746d66) {
-      Console.WriteLine($"Invalid SubChunk1ID: 0x{SubChunk1ID:X}. Expect 0x20746d66");
-      return false;
-    }
-
-    if (SubChunk1Size != 16) {
-      Console.WriteLine($"Invalid SubChunk1Size: {SubChunk1Size}. Expect 16");
-      return false;
-    }
-
-    if (AudioFormat != 1) {
-      Console.WriteLine($"Invalid AudioFormat: {AudioFormat}. Expect 1");
-      return false;
-    }
-
-    if (NumChannels != 1) {
-      Console.WriteLine($"Invalid NumChannels: {NumChannels}. Expect 1");
-      return false;
-    }
-
-    if (ByteRate != (SampleRate * NumChannels * BitsPerSample / 8)) {
-      Console.WriteLine($"Invalid byte rate: {ByteRate}.");
-      return false;
-    }
-
-    if (BlockAlign != (NumChannels * BitsPerSample / 8)) {
-      Console.WriteLine($"Invalid block align: {ByteRate}.");
-      return false;
-    }
-
-    if (BitsPerSample != 16) {  // we support only 16 bits per sample
-      Console.WriteLine($"Invalid bits per sample: {BitsPerSample}. Expect 16");
-      return false;
-    }
-
-    return true;
-  }
-}
-
-// It supports only 16-bit, single channel WAVE format.
-// The sample rate can be any value.
-public class WaveReader {
-  public WaveReader(String fileName) {
-    if (!File.Exists(fileName)) {
-      throw new ApplicationException($"{fileName} does not exist!");
+namespace SherpaNcnn
+{
+
+    [StructLayout(LayoutKind.Sequential)]
+    public struct WaveHeader
+    {
+        public Int32 ChunkID;
+        public Int32 ChunkSize;
+        public Int32 Format;
+        public Int32 SubChunk1ID;
+        public Int32 SubChunk1Size;
+        public Int16 AudioFormat;
+        public Int16 NumChannels;
+        public Int32 SampleRate;
+        public Int32 ByteRate;
+        public Int16 BlockAlign;
+        public Int16 BitsPerSample;
+        public Int32 SubChunk2ID;
+        public Int32 SubChunk2Size;
+
+        public bool Validate()
+        {
+            if (ChunkID != 0x46464952)
+            {
+                Console.WriteLine($"Invalid chunk ID: 0x{ChunkID:X}. Expect 0x46464952");
+                return false;
+            }
+
+            //               E V A W
+            if (Format != 0x45564157)
+            {
+                Console.WriteLine($"Invalid format: 0x{Format:X}. Expect 0x45564157");
+                return false;
+            }
+
+            //                      t m f
+            if (SubChunk1ID != 0x20746d66)
+            {
+                Console.WriteLine($"Invalid SubChunk1ID: 0x{SubChunk1ID:X}. Expect 0x20746d66");
+                return false;
+            }
+
+            if (SubChunk1Size != 16)
+            {
+                Console.WriteLine($"Invalid SubChunk1Size: {SubChunk1Size}. Expect 16");
+                return false;
+            }
+
+            if (AudioFormat != 1)
+            {
+                Console.WriteLine($"Invalid AudioFormat: {AudioFormat}. Expect 1");
+                return false;
+            }
+
+            if (NumChannels != 1)
+            {
+                Console.WriteLine($"Invalid NumChannels: {NumChannels}. Expect 1");
+                return false;
+            }
+
+            if (ByteRate != (SampleRate * NumChannels * BitsPerSample / 8))
+            {
+                Console.WriteLine($"Invalid byte rate: {ByteRate}.");
+                return false;
+            }
+
+            if (BlockAlign != (NumChannels * BitsPerSample / 8))
+            {
+                Console.WriteLine($"Invalid block align: {ByteRate}.");
+                return false;
+            }
+
+            if (BitsPerSample != 16)
+            {  // we support only 16 bits per sample
+                Console.WriteLine($"Invalid bits per sample: {BitsPerSample}. Expect 16");
+                return false;
+            }
+
+            return true;
+        }
     }
 
-    using (var stream = File.Open(fileName, FileMode.Open)) {
-      using (var reader = new BinaryReader(stream)) {
-        _header = ReadHeader(reader);
-
-        if(!_header.Validate()) {
-           throw new ApplicationException($"Invalid wave file ${fileName}");
+    // It supports only 16-bit, single channel WAVE format.
+    // The sample rate can be any value.
+    public class WaveReader
+    {
+        public WaveReader(String fileName)
+        {
+            if (!File.Exists(fileName))
+            {
+                throw new ApplicationException($"{fileName} does not exist!");
+            }
+
+            using (var stream = File.Open(fileName, FileMode.Open))
+            {
+                using (var reader = new BinaryReader(stream))
+                {
+                    _header = ReadHeader(reader);
+
+                    if (!_header.Validate())
+                    {
+                        throw new ApplicationException($"Invalid wave file ${fileName}");
+                    }
+
+                    SkipMetaData(reader);
+
+                    // now read samples
+                    // _header.SubChunk2Size contains number of bytes in total.
+                    // we assume each sample is of type int16
+                    byte[] buffer = reader.ReadBytes(_header.SubChunk2Size);
+                    short[] samples_int16 = new short[_header.SubChunk2Size / 2];
+                    Buffer.BlockCopy(buffer, 0, samples_int16, 0, buffer.Length);
+
+                    _samples = new float[samples_int16.Length];
+
+                    for (var i = 0; i < samples_int16.Length; ++i)
+                    {
+                        _samples[i] = samples_int16[i] / 32768.0F;
+                    }
+                }
+            }
         }
 
-        SkipMetaData(reader);
+        private static WaveHeader ReadHeader(BinaryReader reader)
+        {
+            byte[] bytes = reader.ReadBytes(Marshal.SizeOf(typeof(WaveHeader)));
 
-        // now read samples
-        // _header.SubChunk2Size contains number of bytes in total.
-        // we assume each sample is of type int16
-        byte[] buffer = reader.ReadBytes(_header.SubChunk2Size);
-        short[] samples_int16 = new short[_header.SubChunk2Size/2];
-        Buffer.BlockCopy(buffer, 0, samples_int16, 0, buffer.Length);
+            GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
+            WaveHeader header = (WaveHeader)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(WaveHeader));
+            handle.Free();
 
-        _samples = new float[samples_int16.Length];
-
-        for(var i = 0; i < samples_int16.Length; ++i) {
-          _samples[i] = samples_int16[i] / 32768.0F;
+            return header;
         }
-      }
-    }
-  }
 
-  private static WaveHeader ReadHeader(BinaryReader reader) {
-    byte[] bytes = reader.ReadBytes(Marshal.SizeOf(typeof(WaveHeader)));
-
-    GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
-    WaveHeader header = (WaveHeader)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(WaveHeader));
-    handle.Free();
+        private void SkipMetaData(BinaryReader reader)
+        {
+            var bs = reader.BaseStream;
+
+            Int32 subChunk2ID = _header.SubChunk2ID;
+            Int32 subChunk2Size = _header.SubChunk2Size;
+
+            while (bs.Position != bs.Length && subChunk2ID != 0x61746164)
+            {
+                bs.Seek(subChunk2Size, SeekOrigin.Current);
+                subChunk2ID = reader.ReadInt32();
+                subChunk2Size = reader.ReadInt32();
+            }
+            _header.SubChunk2ID = subChunk2ID;
+            _header.SubChunk2Size = subChunk2Size;
+        }
 
-    return header;
-  }
+        private WaveHeader _header;
 
-  private void SkipMetaData(BinaryReader reader) {
-    var bs = reader.BaseStream;
+        // Samples are normalized to the range [-1, 1]
+        private float[] _samples;
 
-    Int32 subChunk2ID = _header.SubChunk2ID;
-    Int32 subChunk2Size = _header.SubChunk2Size;
+        public int SampleRate => _header.SampleRate;
+        public float[] Samples => _samples;
 
-    while (bs.Position != bs.Length && subChunk2ID != 0x61746164) {
-      bs.Seek(subChunk2Size, SeekOrigin.Current);
-      subChunk2ID = reader.ReadInt32();
-      subChunk2Size = reader.ReadInt32();
+        public static void Test(String fileName)
+        {
+            WaveReader reader = new WaveReader(fileName);
+            Console.WriteLine($"samples length: {reader.Samples.Length}");
+            Console.WriteLine($"samples rate: {reader.SampleRate}");
+        }
     }
-    _header.SubChunk2ID = subChunk2ID;
-    _header.SubChunk2Size = subChunk2Size;
-  }
-
-  private WaveHeader _header;
-
-  // Samples are normalized to the range [-1, 1]
-  private float[] _samples;
-
-  public int SampleRate => _header.SampleRate;
-  public float [] Samples => _samples;
-
-  public static void Test(String fileName) {
-    WaveReader reader = new WaveReader(fileName);
-    Console.WriteLine($"samples length: {reader.Samples.Length}");
-    Console.WriteLine($"samples rate: {reader.SampleRate}");
-  }
-}
 
 }

+ 168 - 0
dotnet-examples/microphone/Program.cs

@@ -0,0 +1,168 @@
+// Copyright (c)  2023  Xiaomi Corporation (authors: Fangjun Kuang)
+using System;
+using System.Threading;
+using PortAudioSharp;
+using System.Runtime.InteropServices;
+
+class Microphone
+{
+    public static void Main(String[] args)
+    {
+        String usage = @"
+      ./microphone.exe \
+         /path/to/tokens.txt \
+         /path/to/encoder.ncnn.param \
+         /path/to/encoder.ncnn.bin \
+         /path/to/decoder.ncnn.param \
+         /path/to/decoder.ncnn.bin \
+         /path/to/joiner.ncnn.param \
+         /path/to/joiner.ncnn.bin \
+         [<num_threads> [decode_method]]
+
+      num_threads: Default to 1
+      decoding_method: greedy_search (default), or modified_beam_search
+
+      Please refer to
+      https://k2-fsa.github.io/sherpa/ncnn/pretrained_models/index.html
+      for a list of pre-trained models to download.
+      ";
+        if (args.Length < 7 || args.Length > 9)
+        {
+            Console.WriteLine(usage);
+            return;
+        }
+
+        SherpaNcnn.OnlineRecognizerConfig config = new SherpaNcnn.OnlineRecognizerConfig();
+        config.FeatConfig.SampleRate = 16000;
+        config.FeatConfig.FeatureDim = 80;
+        config.ModelConfig.Tokens = args[0];
+        config.ModelConfig.EncoderParam = args[1];
+        config.ModelConfig.EncoderBin = args[2];
+
+        config.ModelConfig.DecoderParam = args[3];
+        config.ModelConfig.DecoderBin = args[4];
+
+        config.ModelConfig.JoinerParam = args[5];
+        config.ModelConfig.JoinerBin = args[6];
+
+        config.ModelConfig.UseVulkanCompute = 0;
+        config.ModelConfig.NumThreads = 1;
+        if (args.Length >= 8)
+        {
+            config.ModelConfig.NumThreads = Int32.Parse(args[7]);
+            if (config.ModelConfig.NumThreads > 1)
+            {
+                Console.WriteLine($"Use num_threads: {config.ModelConfig.NumThreads}");
+            }
+        }
+
+        config.DecoderConfig.DecodingMethod = "greedy_search";
+        if (args.Length == 9 && args[8] != "greedy_search")
+        {
+            Console.WriteLine($"Use decoding_method {args[8]}");
+            config.DecoderConfig.DecodingMethod = args[8];
+        }
+
+        config.DecoderConfig.NumActivePaths = 4;
+        config.EnableEndpoint = 1;
+        config.Rule1MinTrailingSilence = 2.4F;
+        config.Rule2MinTrailingSilence = 1.2F;
+        config.Rule3MinUtteranceLength = 20.0F;
+
+
+        SherpaNcnn.OnlineRecognizer recognizer = new SherpaNcnn.OnlineRecognizer(config);
+
+        SherpaNcnn.OnlineStream s = recognizer.CreateStream();
+
+        Console.WriteLine(PortAudio.VersionInfo.versionText);
+        PortAudio.Initialize();
+
+        Console.WriteLine($"Number of devices: {PortAudio.DeviceCount}");
+        for (int i = 0; i != PortAudio.DeviceCount; ++i)
+        {
+            Console.WriteLine($" Device {i}");
+            DeviceInfo deviceInfo = PortAudio.GetDeviceInfo(i);
+            Console.WriteLine($"   Name: {deviceInfo.name}");
+            Console.WriteLine($"   Max input channels: {deviceInfo.maxInputChannels}");
+            Console.WriteLine($"   Default sample rate: {deviceInfo.defaultSampleRate}");
+        }
+        int deviceIndex = PortAudio.DefaultInputDevice;
+        if (deviceIndex == PortAudio.NoDevice)
+        {
+            Console.WriteLine("No default input device found");
+            Environment.Exit(1);
+        }
+
+        DeviceInfo info = PortAudio.GetDeviceInfo(deviceIndex);
+
+        Console.WriteLine();
+        Console.WriteLine($"Use default device {deviceIndex} ({info.name})");
+
+        StreamParameters param = new StreamParameters();
+        param.device = deviceIndex;
+        param.channelCount = 1;
+        param.sampleFormat = SampleFormat.Float32;
+        param.suggestedLatency = info.defaultLowInputLatency;
+        param.hostApiSpecificStreamInfo = IntPtr.Zero;
+
+        PortAudioSharp.Stream.Callback callback = (IntPtr input, IntPtr output,
+            UInt32 frameCount,
+            ref StreamCallbackTimeInfo timeInfo,
+            StreamCallbackFlags statusFlags,
+            IntPtr userData
+            ) =>
+        {
+            float[] samples = new float[frameCount];
+            Marshal.Copy(input, samples, 0, (Int32)frameCount);
+
+            s.AcceptWaveform(16000, samples);
+
+            return StreamCallbackResult.Continue;
+        };
+
+        PortAudioSharp.Stream stream = new PortAudioSharp.Stream(inParams: param, outParams: null, sampleRate: 16000,
+            framesPerBuffer: 0,
+            streamFlags: StreamFlags.ClipOff,
+            callback: callback,
+            userData: IntPtr.Zero
+            );
+
+        Console.WriteLine(param);
+        Console.WriteLine("Started! Please speak\n\n");
+
+        stream.Start();
+
+        String lastText = "";
+        int segmentIndex = 0;
+
+        while (true)
+        {
+            while (recognizer.IsReady(s))
+            {
+                recognizer.Decode(s);
+            }
+
+            var text = recognizer.GetResult(s).Text;
+            bool isEndpoint = recognizer.IsEndpoint(s);
+            if (!string.IsNullOrWhiteSpace(text) && lastText != text)
+            {
+                lastText = text;
+                Console.Write($"\r{segmentIndex}: {lastText}");
+            }
+
+            if (isEndpoint)
+            {
+                if (!string.IsNullOrWhiteSpace(text))
+                {
+                    ++segmentIndex;
+                    Console.WriteLine();
+                }
+                recognizer.Reset(s);
+            }
+
+            Thread.Sleep(200); // ms
+        }
+
+        PortAudio.Terminate();
+    }
+}

+ 17 - 0
dotnet-examples/microphone/microphone.csproj

@@ -0,0 +1,17 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net6.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <!-- Use * to denote the latest stable version. -->
+    <PackageReference Include="org.k2fsa.sherpa.ncnn" Version="*" />
+
+    <PackageReference Include="PortAudioSharp2" Version="*" />
+  </ItemGroup>
+
+</Project>

+ 26 - 0
dotnet-examples/microphone/run.sh

@@ -0,0 +1,26 @@
+#!/usr/bin/env bash
+
+dotnet build -c Release --verbosity normal
+
+# Please refer to
+# https://k2-fsa.github.io/sherpa/ncnn/pretrained_models/zipformer-transucer-models.html#csukuangfj-sherpa-ncnn-streaming-zipformer-bilingual-zh-en-2023-02-13-bilingual-chinese-english
+# to download the model files
+
+if [ ! -d ./sherpa-ncnn-streaming-zipformer-bilingual-zh-en-2023-02-13 ]; then
+  GIT_LFS_SKIP_SMUDGE=1 git clone https://huggingface.co/csukuangfj/sherpa-ncnn-streaming-zipformer-bilingual-zh-en-2023-02-13
+  cd sherpa-ncnn-streaming-zipformer-bilingual-zh-en-2023-02-13
+  git lfs pull --include "*.bin"
+
+  cd ..
+fi
+
+dotnet run -c Release \
+  ./sherpa-ncnn-streaming-zipformer-bilingual-zh-en-2023-02-13/tokens.txt \
+  ./sherpa-ncnn-streaming-zipformer-bilingual-zh-en-2023-02-13/encoder_jit_trace-pnnx.ncnn.param \
+  ./sherpa-ncnn-streaming-zipformer-bilingual-zh-en-2023-02-13/encoder_jit_trace-pnnx.ncnn.bin \
+  ./sherpa-ncnn-streaming-zipformer-bilingual-zh-en-2023-02-13/decoder_jit_trace-pnnx.ncnn.param \
+  ./sherpa-ncnn-streaming-zipformer-bilingual-zh-en-2023-02-13/decoder_jit_trace-pnnx.ncnn.bin \
+  ./sherpa-ncnn-streaming-zipformer-bilingual-zh-en-2023-02-13/joiner_jit_trace-pnnx.ncnn.param \
+  ./sherpa-ncnn-streaming-zipformer-bilingual-zh-en-2023-02-13/joiner_jit_trace-pnnx.ncnn.bin \
+  1 \
+  greedy_search

+ 6 - 0
dotnet-examples/sherpa-ncnn.sln

@@ -5,6 +5,8 @@ VisualStudioVersion = 17.0.31903.59
 MinimumVisualStudioVersion = 10.0.40219.1
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "decode-file", "decode-file\decode-file.csproj", "{5F363113-5F81-4C73-9D96-D26BCFC6F2A8}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "microphone", "microphone\microphone.csproj", "{E212B828-F63E-4B00-B52D-1420A2A41E12}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -18,5 +20,9 @@ Global
 		{5F363113-5F81-4C73-9D96-D26BCFC6F2A8}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{5F363113-5F81-4C73-9D96-D26BCFC6F2A8}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{5F363113-5F81-4C73-9D96-D26BCFC6F2A8}.Release|Any CPU.Build.0 = Release|Any CPU
+		{E212B828-F63E-4B00-B52D-1420A2A41E12}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{E212B828-F63E-4B00-B52D-1420A2A41E12}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{E212B828-F63E-4B00-B52D-1420A2A41E12}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{E212B828-F63E-4B00-B52D-1420A2A41E12}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 EndGlobal