Ver código fonte

Support .Net (#190)

Fangjun Kuang 2 anos atrás
pai
commit
ae52002faf

+ 98 - 0
.github/workflows/csharp-api-test.yaml

@@ -0,0 +1,98 @@
+name: swift-api-test
+
+on:
+  push:
+    branches:
+      - master
+      - csharp-api
+    paths:
+      - '.github/workflows/csharp-api-test.yaml'
+      - '.github/scripts/csharp-api-test.sh'
+      - 'CMakeLists.txt'
+      - 'cmake/**'
+      - 'sherpa-ncnn/csrc/*'
+      - 'sherpa-ncnn/csharp-api/*'
+      - 'sherpa-ncnn/c-api/*'
+      - 'sherpa-ncnn/csharp-api-examples/*'
+  pull_request:
+    branches:
+      - master
+    paths:
+      - '.github/workflows/swift-api-test.yaml'
+      - '.github/scripts/swift-api-test.sh'
+      - 'CMakeLists.txt'
+      - 'cmake/**'
+      - 'sherpa-ncnn/csrc/*'
+      - 'sherpa-ncnn/csharp-api/*'
+      - 'sherpa-ncnn/c-api/*'
+      - 'sherpa-ncnn/csharp-api-examples/*'
+
+concurrency:
+  group: csharp-api-test-${{ github.ref }}
+  cancel-in-progress: true
+
+permissions:
+  contents: read
+
+jobs:
+  csharp-api-test:
+    runs-on: ${{ matrix.os }}
+    name: ${{ matrix.vs-version }}
+    strategy:
+      fail-fast: false
+      matrix:
+        include:
+          - vs-version: vs2015
+            toolset-version: v140
+            os: windows-2019
+
+          - vs-version: vs2017
+            toolset-version: v141
+            os: windows-2019
+
+          - vs-version: vs2019
+            toolset-version: v142
+            os: windows-2022
+
+          - vs-version: vs2022
+            toolset-version: v143
+            os: windows-2022
+
+    steps:
+      - uses: actions/checkout@v2
+        with:
+          fetch-depth: 0
+
+      - name: Set up .NET Core
+        uses: actions/setup-dotnet@v1
+        with:
+          dotnet-version: '6.0.x'
+          include-prerelease: true
+
+      - name: Configure CMake
+        shell: bash
+        run: |
+          mkdir build
+          cd build
+          cmake \
+            -T ${{ matrix.toolset-version}},host=x64 \
+            -A x64 \
+            -D CMAKE_BUILD_TYPE=Release \
+            -D BUILD_SHARED_LIBS=ON \
+            ..
+
+      - name: Build sherpa-ncnn for windows
+        shell: bash
+        run: |
+          cd build
+          cmake --build . --config Release --target sherpa-ncnn-c-api -v
+
+      - name: Build C# exe
+        shell: bash
+        run: |
+          export PATH=$PWD/build/bin/Release:$PATH
+
+          csc ./csharp-api-examples/WaveReader.cs ./csharp-api-examples/DecodeFile.cs ./sherpa-ncnn/csharp-api/SherpaNcnn.cs
+          ls -lh ./DecodeFile.exe
+          ./DecodeFile.exe
+

+ 76 - 0
.github/workflows/dot-net-linux.yaml

@@ -0,0 +1,76 @@
+name: dot-net-linux
+
+on:
+  push:
+    branches:
+      - dot-net
+    tags:
+      - '*'
+
+concurrency:
+  group: dot-net-linux-${{ github.ref }}
+  cancel-in-progress: true
+
+permissions:
+  contents: read
+
+jobs:
+  dot-net-linux:
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        os: [ubuntu-latest]
+
+    steps:
+      - uses: actions/checkout@v2
+        with:
+          fetch-depth: 0
+
+      - name: Setup .NET Core 3.1
+        uses: actions/setup-dotnet@v1
+        with:
+          dotnet-version: 3.1.x
+
+      - name: Setup .NET 6.0
+        uses: actions/setup-dotnet@v1
+        with:
+          dotnet-version: 6.0.x
+
+      - name: Check dotnet
+        run: dotnet --info
+
+      - name: Configure CMake
+        shell: bash
+        run: |
+          mkdir build
+          cd build
+          cmake -D CMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DSHERPA_NCNN_ENABLE_DOT_NET_API=ON -DSHERPA_NCNN_ENABLE_C_API=ON -DSHERPA_NCNN_ENABLE_PORTAUDIO=OFF ..
+
+      - name: Build sherpa-ncnn for ubuntu
+        run: |
+          cd build
+          make sherpa-ncnn-native-dot-net-package
+
+          ls -lh packages
+
+      - name: Display .Net packages
+        run: |
+          cd build
+          ls -lh packages
+
+      - name: Upload .Net packages sherpa-ncnn
+        uses: actions/upload-artifact@v2
+        with:
+          name: sherpa-ncnn-pre-built-dot-net-packages-os-${{ matrix.os }}
+          path: ./build/packages
+
+      - name: publish .Net packages to nuget.org
+        if: github.repository == 'csukuangfj/sherpa-ncnn' || github.repository == 'k2-fsa/sherpa-ncnn'
+        shell: bash
+        env:
+          API_KEY: ${{ secrets.NUGET_API_KEY }}
+        run: |
+          # API_KEY is valid until 2024.05.02
+          cd build/packages
+          dotnet nuget push ./org.k2fsa.sherpa.ncnn.runtime.*.nupkg --api-key $API_KEY --source https://api.nuget.org/v3/index.json

+ 77 - 0
.github/workflows/dot-net-macos.yaml

@@ -0,0 +1,77 @@
+name: dot-net-macos
+
+on:
+  push:
+    branches:
+      - dot-net
+    tags:
+      - '*'
+
+concurrency:
+  group: dot-net-macos-${{ github.ref }}
+  cancel-in-progress: true
+
+permissions:
+  contents: read
+
+jobs:
+  dot-net-macos:
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        os: [macos-latest]
+
+    steps:
+      - uses: actions/checkout@v2
+        with:
+          fetch-depth: 0
+
+      - name: Setup .NET Core 3.1
+        uses: actions/setup-dotnet@v1
+        with:
+          dotnet-version: 3.1.x
+
+      - name: Setup .NET 6.0
+        uses: actions/setup-dotnet@v1
+        with:
+          dotnet-version: 6.0.x
+
+      - name: Check dotnet
+        run: dotnet --info
+
+      - name: Configure CMake
+        shell: bash
+        run: |
+          mkdir build
+          cd build
+          cmake -D CMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DSHERPA_NCNN_ENABLE_DOT_NET_API=ON -DSHERPA_NCNN_ENABLE_C_API=ON -DSHERPA_NCNN_ENABLE_PORTAUDIO=OFF ..
+
+      - name: Build sherpa-ncnn for macOS
+        shell: bash
+        run: |
+          cd build
+          make sherpa-ncnn-native-dot-net-package
+
+          ls -lh packages
+
+      - name: Display .Net packages
+        run: |
+          cd build
+          ls -lh packages
+
+      - name: Upload .Net packages sherpa-ncnn
+        uses: actions/upload-artifact@v2
+        with:
+          name: sherpa-ncnn-pre-built-dot-net-packages-os-${{ matrix.os }}
+          path: ./build/packages
+
+      - name: publish .Net packages to nuget.org
+        if: github.repository == 'csukuangfj/sherpa-ncnn' || github.repository == 'k2-fsa/sherpa-ncnn'
+        shell: bash
+        env:
+          API_KEY: ${{ secrets.NUGET_API_KEY }}
+        run: |
+          # API_KEY is valid until 2024.05.02
+          cd build/packages
+          dotnet nuget push ./org.k2fsa.sherpa.ncnn.runtime.*.nupkg --api-key $API_KEY --source https://api.nuget.org/v3/index.json

+ 82 - 0
.github/workflows/dot-net-windows-x64.yaml

@@ -0,0 +1,82 @@
+name: dot-net-windows-x64
+
+on:
+  push:
+    branches:
+      - dot-net
+    tags:
+      - '*'
+
+concurrency:
+  group: dot-net-windows-x64-${{ github.ref }}
+  cancel-in-progress: true
+
+permissions:
+  contents: read
+
+jobs:
+  dot-net-windows-x64:
+    runs-on: ${{ matrix.os }}
+    name: ${{ matrix.vs-version }}
+    strategy:
+      fail-fast: false
+      matrix:
+        include:
+          - vs-version: vs2015
+            toolset-version: v140
+            os: windows-2019
+
+    steps:
+      - uses: actions/checkout@v2
+        with:
+          fetch-depth: 0
+
+      - name: Setup .NET Core 3.1
+        uses: actions/setup-dotnet@v1
+        with:
+          dotnet-version: 3.1.x
+
+      - name: Setup .NET 6.0
+        uses: actions/setup-dotnet@v1
+        with:
+          dotnet-version: 6.0.x
+
+      - name: Check dotnet
+        run: dotnet --info
+
+      - name: Configure CMake
+        shell: bash
+        run: |
+          mkdir build
+          cd build
+          cmake -T ${{ matrix.toolset-version}},host=x64 -A x64 -D CMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DSHERPA_NCNN_ENABLE_DOT_NET_API=ON -DSHERPA_NCNN_ENABLE_C_API=ON -DSHERPA_NCNN_ENABLE_PORTAUDIO=OFF ..
+
+      - name: Build sherpa-ncnn for windows x64
+        shell: bash
+        run: |
+          cd build
+          cmake --build . --config Release --target sherpa-ncnn-native-dot-net-package
+
+          ls -lh packages
+
+      - name: Display .Net packages
+        shell: bash
+        run: |
+          cd build
+          ls -lh packages
+
+      - name: Upload .Net packages sherpa-ncnn
+        uses: actions/upload-artifact@v2
+        with:
+          name: sherpa-ncnn-pre-built-dot-net-packages-os-${{ matrix.os }}
+          path: ./build/packages
+
+      - name: publish .Net packages to nuget.org
+        if: github.repository == 'csukuangfj/sherpa-ncnn' || github.repository == 'k2-fsa/sherpa-ncnn'
+        shell: bash
+        env:
+          API_KEY: ${{ secrets.NUGET_API_KEY }}
+        run: |
+          # API_KEY is valid until 2024.05.02
+          cd build/packages
+          dotnet nuget push ./org.k2fsa.sherpa.ncnn.runtime.*.nupkg --api-key $API_KEY --source https://api.nuget.org/v3/index.json

+ 1 - 1
.github/workflows/linux.yaml

@@ -48,7 +48,7 @@ jobs:
           cd build
           cmake -D CMAKE_BUILD_TYPE=Release -DSHERPA_NCNN_ENABLE_FFMPEG_EXAMPLES=OFF ..
 
-      - name: Build sherpa for ubuntu
+      - name: Build sherpa-ncnn for ubuntu
         run: |
           cd build
           make -j2

+ 1 - 1
.github/workflows/macos.yaml

@@ -47,7 +47,7 @@ jobs:
           cd build
           cmake -D CMAKE_BUILD_TYPE=Release ..
 
-      - name: Build sherpa for macos
+      - name: Build sherpa-ncnn for macos
         run: |
           cd build
           make -j2

+ 64 - 0
.github/workflows/test-dot-net.yaml

@@ -0,0 +1,64 @@
+name: test-dot-net
+
+on:
+  push:
+    branches:
+      - master
+    paths:
+      - '.github/workflows/test-dot-net'
+      - 'dotnet-examples/**'
+
+  pull_request:
+    branches:
+      - master
+    paths:
+      - '.github/workflows/test-dot-net'
+      - 'dotnet-examples/**'
+
+  schedule:
+    # minute (0-59)
+    # hour (0-23)
+    # day of the month (1-31)
+    # month (1-12)
+    # day of the week (0-6)
+    # nightly build at 23:50 UTC time every day
+    - cron: "50 23 * * *"
+
+concurrency:
+  group: test-dot-net
+  cancel-in-progress: true
+
+permissions:
+  contents: read
+
+jobs:
+  test-dot-net:
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        os: [ubuntu-latest, macos-latest, windows-latest]
+
+    steps:
+      - uses: actions/checkout@v2
+        with:
+          fetch-depth: 0
+
+      - name: Setup .NET Core 3.1
+        uses: actions/setup-dotnet@v1
+        with:
+          dotnet-version: 3.1.x
+
+      - name: Setup .NET 6.0
+        uses: actions/setup-dotnet@v1
+        with:
+          dotnet-version: 6.0.x
+
+      - name: Check dotnet
+        run: dotnet --info
+
+      - name: Decode a file
+        shell: bash
+        run: |
+          cd dotnet-examples/decode-file
+          ./run.sh

+ 1 - 1
.github/workflows/windows-x64.yaml

@@ -63,7 +63,7 @@ jobs:
           cd build
           cmake -T ${{ matrix.toolset-version}},host=x64 -A x64 -D CMAKE_BUILD_TYPE=Release ..
 
-      - name: Build sherpa for windows
+      - name: Build sherpa-ncnn for windows
         shell: bash
         run: |
           cd build

+ 8 - 2
CMakeLists.txt

@@ -1,7 +1,7 @@
 cmake_minimum_required(VERSION 3.13 FATAL_ERROR)
 project(sherpa-ncnn)
 
-set(SHERPA_NCNN_VERSION "1.8.1")
+set(SHERPA_NCNN_VERSION "1.8.6")
 
 # Disable warning about
 #
@@ -29,6 +29,10 @@ endif()
 set(CMAKE_INSTALL_RPATH ${SHERPA_NCNN_RPATH_ORIGIN})
 set(CMAKE_BUILD_RPATH ${SHERPA_NCNN_RPATH_ORIGIN})
 
+# You will find a file compile_commands.json in the build directory
+# if it is enabled
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+
 option(BUILD_SHARED_LIBS "Whether to build shared libraries" OFF)
 option(SHERPA_NCNN_ENABLE_PYTHON "Whether to build Python" OFF)
 option(SHERPA_NCNN_ENABLE_PORTAUDIO "Whether to build with portaudio" ON)
@@ -36,6 +40,7 @@ option(SHERPA_NCNN_ENABLE_JNI "Whether to build JNI internface" OFF)
 option(SHERPA_NCNN_ENABLE_BINARY "Whether to build the binary sherpa-ncnn" ON)
 option(SHERPA_NCNN_ENABLE_TEST "Whether to build tests" OFF)
 option(SHERPA_NCNN_ENABLE_C_API "Whether to build C API" ON)
+option(SHERPA_NCNN_ENABLE_DOT_NET_API "Whether to build .Net API" OFF)
 option(SHERPA_NCNN_ENABLE_GENERATE_INT8_SCALE_TABLE "Whether to generate-int8-scale-table" ON)
 option(SHERPA_NCNN_ENABLE_FFMPEG_EXAMPLES "Whether to enable ffmpeg-examples" OFF)
 
@@ -49,7 +54,7 @@ endif()
 if(BUILD_SHARED_LIBS AND MSVC)
   set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
 endif()
-
+message(STATUS "CMAKE_EXPORT_COMPILE_COMMANDS: ${CMAKE_EXPORT_COMPILE_COMMANDS}")
 message(STATUS "BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS}")
 message(STATUS "SHERPA_NCNN_ENABLE_PYTHON ${SHERPA_NCNN_ENABLE_PYTHON}")
 message(STATUS "SHERPA_NCNN_ENABLE_PORTAUDIO ${SHERPA_NCNN_ENABLE_PORTAUDIO}")
@@ -57,6 +62,7 @@ message(STATUS "SHERPA_NCNN_ENABLE_JNI ${SHERPA_NCNN_ENABLE_JNI}")
 message(STATUS "SHERPA_NCNN_ENABLE_BINARY ${SHERPA_NCNN_ENABLE_BINARY}")
 message(STATUS "SHERPA_NCNN_ENABLE_TEST ${SHERPA_NCNN_ENABLE_TEST}")
 message(STATUS "SHERPA_NCNN_ENABLE_C_API ${SHERPA_NCNN_ENABLE_C_API}")
+message(STATUS "SHERPA_NCNN_ENABLE_DOT_NET_API ${SHERPA_NCNN_ENABLE_DOT_NET_API}")
 message(STATUS "SHERPA_NCNN_ENABLE_GENERATE_INT8_SCALE_TABLE ${SHERPA_NCNN_ENABLE_GENERATE_INT8_SCALE_TABLE}")
 message(STATUS "SHERPA_NCNN_ENABLE_FFMPEG_EXAMPLES ${SHERPA_NCNN_ENABLE_FFMPEG_EXAMPLES}")
 

+ 17 - 6
cmake/ncnn.cmake

@@ -29,12 +29,23 @@ function(download_ncnn)
     endif()
   endforeach()
 
-  FetchContent_Declare(ncnn
-    URL
-      ${ncnn_URL}
-      ${ncnn_URL2}
-    URL_HASH          ${ncnn_HASH}
-  )
+  if(NOT WIN32)
+    FetchContent_Declare(ncnn
+      URL
+        ${ncnn_URL}
+        ${ncnn_URL2}
+      URL_HASH          ${ncnn_HASH}
+      PATCH_COMMAND
+        sed -i.bak "/ncnn PROPERTIES VERSION/d" "src/CMakeLists.txt"
+    )
+  else()
+    FetchContent_Declare(ncnn
+      URL
+        ${ncnn_URL}
+        ${ncnn_URL2}
+      URL_HASH          ${ncnn_HASH}
+    )
+  endif()
 
   set(NCNN_PIXEL OFF CACHE BOOL "" FORCE)
   set(NCNN_PIXEL_ROTATE OFF CACHE BOOL "" FORCE)

+ 2 - 0
dotnet-examples/.gitignore

@@ -0,0 +1,2 @@
+obj
+bin

+ 79 - 0
dotnet-examples/decode-file/DecodeFile.cs

@@ -0,0 +1,79 @@
+// Copyright (c)  2023  Xiaomi Corporation (authors: Fangjun Kuang)
+using System;
+
+class DecodeFile {
+  public static void Main(String[] args) {
+    String usage = @"
+      ./DecodeFile.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 \
+         /path/to/foo.wav [<num_threads> [decode_method]]
+
+      num_threads: Default to 2
+      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;
+    }
+
+    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);
+  }
+}

+ 151 - 0
dotnet-examples/decode-file/WaveReader.cs

@@ -0,0 +1,151 @@
+// Copyright (c)  2023  Xiaomi Corporation (authors: Fangjun Kuang)
+using System;
+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!");
+    }
+
+    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;
+        }
+      }
+    }
+  }
+
+  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();
+
+    return header;
+  }
+
+  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;
+  }
+
+  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}");
+  }
+}
+
+}

+ 15 - 0
dotnet-examples/decode-file/decode-file.csproj

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

+ 27 - 0
dotnet-examples/decode-file/run.sh

@@ -0,0 +1,27 @@
+#!/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 \
+  ./sherpa-ncnn-streaming-zipformer-bilingual-zh-en-2023-02-13/test_wavs/0.wav \
+  2 \
+  modified_beam_search

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

@@ -0,0 +1,22 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+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
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{5F363113-5F81-4C73-9D96-D26BCFC6F2A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{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
+	EndGlobalSection
+EndGlobal

+ 4 - 0
sherpa-ncnn/CMakeLists.txt

@@ -4,6 +4,10 @@ if(SHERPA_NCNN_ENABLE_C_API)
   add_subdirectory(c-api)
 endif()
 
+if(SHERPA_NCNN_ENABLE_DOT_NET_API)
+  add_subdirectory(csharp-api)
+endif()
+
 if(SHERPA_NCNN_ENABLE_JNI)
   add_subdirectory(jni)
 endif()

+ 5 - 0
sherpa-ncnn/c-api/CMakeLists.txt

@@ -2,6 +2,11 @@ include_directories(${CMAKE_SOURCE_DIR})
 add_library(sherpa-ncnn-c-api c-api.cc)
 target_link_libraries(sherpa-ncnn-c-api sherpa-ncnn-core)
 
+if(BUILD_SHARED_LIBS)
+  target_compile_definitions(sherpa-ncnn-c-api PRIVATE SHERPA_NCNN_BUILD_SHARED_LIBS=1)
+  target_compile_definitions(sherpa-ncnn-c-api PRIVATE SHERPA_NCNN_BUILD_MAIN_LIB=1)
+endif()
+
 install(TARGETS sherpa-ncnn-c-api DESTINATION lib)
 
 install(FILES c-api.h

+ 0 - 7
sherpa-ncnn/c-api/c-api.cc

@@ -27,21 +27,14 @@
 #include "sherpa-ncnn/csrc/model.h"
 #include "sherpa-ncnn/csrc/recognizer.h"
 
-#ifdef __cplusplus
-#define SHERPA_NCNN_EXTERN_C extern "C"
-#endif
-
-SHERPA_NCNN_EXTERN_C
 struct SherpaNcnnRecognizer {
   std::unique_ptr<sherpa_ncnn::Recognizer> recognizer;
 };
 
-SHERPA_NCNN_EXTERN_C
 struct SherpaNcnnStream {
   std::unique_ptr<sherpa_ncnn::Stream> stream;
 };
 
-SHERPA_NCNN_EXTERN_C
 struct SherpaNcnnDisplay {
   std::unique_ptr<sherpa_ncnn::Display> impl;
 };

+ 51 - 25
sherpa-ncnn/c-api/c-api.h

@@ -31,11 +31,34 @@
 extern "C" {
 #endif
 
+// See https://github.com/pytorch/pytorch/blob/main/c10/macros/Export.h
+// We will set SHERPA_NCNN_BUILD_SHARED_LIBS and SHERPA_NCNN_BUILD_MAIN_LIB in
+// CMakeLists.txt
+
+#if defined(_WIN32)
+#if defined(SHERPA_NCNN_BUILD_SHARED_LIBS)
+#define SHERPA_NCNN_EXPORT __declspec(dllexport)
+#define SHERPA_NCNN_IMPORT __declspec(dllimport)
+#else
+#define SHERPA_NCNN_EXPORT
+#define SHERPA_NCNN_IMPORT
+#endif
+#else  // WIN32
+#define SHERPA_NCNN_EXPORT __attribute__((__visibility__("default")))
+#define SHERPA_NCNN_IMPORT SHERPA_NCNN_EXPORT
+#endif
+
+#if defined(SHERPA_NCNN_BUILD_MAIN_LIB)
+#define SHERPA_NCNN_API SHERPA_NCNN_EXPORT
+#else
+#define SHERPA_NCNN_API SHERPA_NCNN_IMPORT
+#endif
+
 /// Please refer to
 /// https://k2-fsa.github.io/sherpa/ncnn/pretrained_models/index.html
 /// to download pre-trained models. That is, you can find .ncnn.param,
 /// .ncnn.bin, and tokens.txt for this struct from there.
-typedef struct SherpaNcnnModelConfig {
+SHERPA_NCNN_API typedef struct SherpaNcnnModelConfig {
   /// Path to encoder.ncnn.param
   const char *encoder_param;
 
@@ -66,7 +89,7 @@ typedef struct SherpaNcnnModelConfig {
   int32_t num_threads;
 } SherpaNcnnModelConfig;
 
-typedef struct SherpaNcnnDecoderConfig {
+SHERPA_NCNN_API typedef struct SherpaNcnnDecoderConfig {
   /// Decoding method. Supported values are:
   /// greedy_search, modified_beam_search
   const char *decoding_method;
@@ -76,7 +99,7 @@ typedef struct SherpaNcnnDecoderConfig {
   int32_t num_active_paths;
 } SherpaNcnnDecoderConfig;
 
-typedef struct SherpaNcnnFeatureExtractorConfig {
+SHERPA_NCNN_API typedef struct SherpaNcnnFeatureExtractorConfig {
   // Sampling rate of the input audio samples. MUST match the one
   // expected by the model. For instance, it should be 16000 for models
   // from icefall.
@@ -87,7 +110,7 @@ typedef struct SherpaNcnnFeatureExtractorConfig {
   int32_t feature_dim;
 } SherpaNcnnFeatureExtractorConfig;
 
-typedef struct SherpaNcnnRecognizerConfig {
+SHERPA_NCNN_API typedef struct SherpaNcnnRecognizerConfig {
   SherpaNcnnFeatureExtractorConfig feat_config;
   SherpaNcnnModelConfig model_config;
   SherpaNcnnDecoderConfig decoder_config;
@@ -112,7 +135,7 @@ typedef struct SherpaNcnnRecognizerConfig {
   float rule3_min_utterance_length;
 } SherpaNcnnRecognizerConfig;
 
-typedef struct SherpaNcnnResult {
+SHERPA_NCNN_API typedef struct SherpaNcnnResult {
   // Recognized text
   const char *text;
 
@@ -122,36 +145,36 @@ typedef struct SherpaNcnnResult {
 
   // Pointer to continuous memory which holds timestamps which
   // are seperated by \0
-  float* timestamps;
+  float *timestamps;
 
   // The number of tokens/timestamps in above pointer
   int32_t count;
 } SherpaNcnnResult;
 
-typedef struct SherpaNcnnRecognizer SherpaNcnnRecognizer;
-typedef struct SherpaNcnnStream SherpaNcnnStream;
+SHERPA_NCNN_API typedef struct SherpaNcnnRecognizer SherpaNcnnRecognizer;
+SHERPA_NCNN_API typedef struct SherpaNcnnStream SherpaNcnnStream;
 
 /// Create a recognizer.
 ///
 /// @param config  Config for the recognizer.
 /// @return Return a pointer to the recognizer. The user has to invoke
 //          DestroyRecognizer() to free it to avoid memory leak.
-SherpaNcnnRecognizer *CreateRecognizer(
+SHERPA_NCNN_API SherpaNcnnRecognizer *CreateRecognizer(
     const SherpaNcnnRecognizerConfig *config);
 
 /// Free a pointer returned by CreateRecognizer()
 ///
 /// @param p A pointer returned by CreateRecognizer()
-void DestroyRecognizer(SherpaNcnnRecognizer *p);
+SHERPA_NCNN_API void DestroyRecognizer(SherpaNcnnRecognizer *p);
 
 /// Create a stream for accepting audio samples
 ///
 /// @param p A pointer returned by CreateRecognizer
 /// @return Return a pointer to a stream. The caller MUST invoke
 ///         DestroyStream at the end to avoid memory leak.
-SherpaNcnnStream *CreateStream(SherpaNcnnRecognizer *p);
+SHERPA_NCNN_API SherpaNcnnStream *CreateStream(SherpaNcnnRecognizer *p);
 
-void DestroyStream(SherpaNcnnStream *s);
+SHERPA_NCNN_API void DestroyStream(SherpaNcnnStream *s);
 
 /// Accept input audio samples and compute the features.
 ///
@@ -163,8 +186,8 @@ void DestroyStream(SherpaNcnnStream *s);
 /// @param samples A pointer to a 1-D array containing audio samples.
 ///                The range of samples has to be normalized to [-1, 1].
 /// @param n  Number of elements in the samples array.
-void AcceptWaveform(SherpaNcnnStream *s, float sample_rate,
-                    const float *samples, int32_t n);
+SHERPA_NCNN_API void AcceptWaveform(SherpaNcnnStream *s, float sample_rate,
+                                    const float *samples, int32_t n);
 
 /// Test if the stream has enough frames for decoding.
 ///
@@ -176,14 +199,14 @@ void AcceptWaveform(SherpaNcnnStream *s, float sample_rate,
 /// @param s A pointer returned by CreateStream()
 /// @return Return 1 if the given stream is ready for decoding.
 ///         Return 0 otherwise.
-int32_t IsReady(SherpaNcnnRecognizer *p, SherpaNcnnStream *s);
+SHERPA_NCNN_API int32_t IsReady(SherpaNcnnRecognizer *p, SherpaNcnnStream *s);
 
 /// Pre-condition for this function:
 ///   You must ensure that IsReady(p, s) return 1 before calling this function.
 ///
 /// @param p A pointer returned by CreateRecognizer()
 /// @param s A pointer returned by CreateStream()
-void Decode(SherpaNcnnRecognizer *p, SherpaNcnnStream *s);
+SHERPA_NCNN_API void Decode(SherpaNcnnRecognizer *p, SherpaNcnnStream *s);
 
 /// Get the decoding results so far.
 ///
@@ -191,24 +214,25 @@ void Decode(SherpaNcnnRecognizer *p, SherpaNcnnStream *s);
 /// @param s A pointer returned by CreateStream()
 /// @return A pointer containing the result. The user has to invoke
 ///         DestroyResult() to free the returned pointer to avoid memory leak.
-SherpaNcnnResult *GetResult(SherpaNcnnRecognizer *p, SherpaNcnnStream *s);
+SHERPA_NCNN_API SherpaNcnnResult *GetResult(SherpaNcnnRecognizer *p,
+                                            SherpaNcnnStream *s);
 
 /// Destroy the pointer returned by GetResult().
 ///
 /// @param r A pointer returned by GetResult()
-void DestroyResult(const SherpaNcnnResult *r);
+SHERPA_NCNN_API void DestroyResult(const SherpaNcnnResult *r);
 
 /// Reset a stream
 ///
 /// @param p A pointer returned by CreateRecognizer().
 /// @param s A pointer returned by CreateStream().
-void Reset(SherpaNcnnRecognizer *p, SherpaNcnnStream *s);
+SHERPA_NCNN_API void Reset(SherpaNcnnRecognizer *p, SherpaNcnnStream *s);
 
 /// Signal that no more audio samples would be available.
 /// After this call, you cannot call AcceptWaveform() any more.
 ///
 /// @param s A pointer returned by CreateStream()
-void InputFinished(SherpaNcnnStream *s);
+SHERPA_NCNN_API void InputFinished(SherpaNcnnStream *s);
 
 /// Return 1 is an endpoint has been detected.
 ///
@@ -219,19 +243,21 @@ void InputFinished(SherpaNcnnStream *s);
 ///
 /// @param p A pointer returned by CreateRecognizer()
 /// @return Return 1 if an endpoint is detected. Return 0 otherwise.
-int32_t IsEndpoint(SherpaNcnnRecognizer *p, SherpaNcnnStream *s);
+SHERPA_NCNN_API int32_t IsEndpoint(SherpaNcnnRecognizer *p,
+                                   SherpaNcnnStream *s);
 
 // for displaying results on Linux/macOS.
-typedef struct SherpaNcnnDisplay SherpaNcnnDisplay;
+SHERPA_NCNN_API typedef struct SherpaNcnnDisplay SherpaNcnnDisplay;
 
 /// Create a display object. Must be freed using DestroyDisplay to avoid
 /// memory leak.
-SherpaNcnnDisplay *CreateDisplay(int32_t max_word_per_line);
+SHERPA_NCNN_API SherpaNcnnDisplay *CreateDisplay(int32_t max_word_per_line);
 
-void DestroyDisplay(SherpaNcnnDisplay *display);
+SHERPA_NCNN_API void DestroyDisplay(SherpaNcnnDisplay *display);
 
 /// Print the result.
-void SherpaNcnnPrint(SherpaNcnnDisplay *display, int32_t idx, const char *s);
+SHERPA_NCNN_API void SherpaNcnnPrint(SherpaNcnnDisplay *display, int32_t idx,
+                                     const char *s);
 
 #ifdef __cplusplus
 } /* extern "C" */

+ 120 - 0
sherpa-ncnn/csharp-api/CMakeLists.txt

@@ -0,0 +1,120 @@
+find_program(DOTNET_EXECUTABLE NAMES dotnet)
+if(NOT DOTNET_EXECUTABLE)
+  message(FATAL_ERROR "Cannot find the executable dotnet.")
+endif()
+
+message(STATUS "DOTNET_EXECUTABLE: ${DOTNET_EXECUTABLE}")
+
+# Runtime IDentifier
+# see: https://docs.microsoft.com/en-us/dotnet/core/rid-catalog
+if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|arm64)")
+  set(SHERPA_NCNN_DOTNET_PLATFORM arm64)
+else()
+  set(SHERPA_NCNN_DOTNET_PLATFORM x64)
+endif()
+
+if(APPLE)
+  set(SHERPA_NCNN_DOTNET_RID osx-${SHERPA_NCNN_DOTNET_PLATFORM})
+elseif(UNIX)
+  set(SHERPA_NCNN_DOTNET_RID linux-${SHERPA_NCNN_DOTNET_PLATFORM})
+elseif(WIN32)
+  set(SHERPA_NCNN_DOTNET_RID win-${SHERPA_NCNN_DOTNET_PLATFORM})
+else()
+  message(FATAL_ERROR "Unsupported system ${CMAKE_SYSTEM_NAME}!")
+endif()
+
+message(STATUS ".Net RID: ${SHERPA_NCNN_DOTNET_RID}")
+set(MY_CURRENT_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_BUILD_TYPE}")
+
+configure_file(
+  ${CMAKE_CURRENT_LIST_DIR}/sherpa-ncnn.csproj.runtime.in
+  ${MY_CURRENT_BINARY_DIR}/${SHERPA_NCNN_DOTNET_RID}/sherpa-ncnn.csproj.in
+  @ONLY)
+
+file(GENERATE
+  OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>/${SHERPA_NCNN_DOTNET_RID}/sherpa-ncnn.csproj
+  INPUT ${MY_CURRENT_BINARY_DIR}/${SHERPA_NCNN_DOTNET_RID}/sherpa-ncnn.csproj.in)
+
+add_custom_command(
+  OUTPUT ${MY_CURRENT_BINARY_DIR}/${SHERPA_NCNN_DOTNET_RID}/timestamp
+  COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_LIST_DIR}/README.md ${MY_CURRENT_BINARY_DIR}/${SHERPA_NCNN_DOTNET_RID}/README.md
+  COMMAND ${CMAKE_COMMAND} -E env --unset=TARGETNAME ${DOTNET_EXECUTABLE} build -c Release /p:Platform=${SHERPA_NCNN_DOTNET_PLATFORM} sherpa-ncnn.csproj
+  COMMAND ${CMAKE_COMMAND} -E env --unset=TARGETNAME ${DOTNET_EXECUTABLE} pack -c Release sherpa-ncnn.csproj
+  COMMAND ${CMAKE_COMMAND} -E touch ${MY_CURRENT_BINARY_DIR}/${SHERPA_NCNN_DOTNET_RID}/timestamp
+  DEPENDS
+    ${MY_CURRENT_BINARY_DIR}/${SHERPA_NCNN_DOTNET_RID}/sherpa-ncnn.csproj
+    ${CMAKE_CURRENT_LIST_DIR}/README.md
+    sherpa-ncnn-c-api
+  BYPRODUCTS
+    ${MY_CURRENT_BINARY_DIR}/${SHERPA_NCNN_DOTNET_RID}/bin
+    ${MY_CURRENT_BINARY_DIR}/${SHERPA_NCNN_DOTNET_RID}/obj
+  VERBATIM
+  COMMENT "Generate .Net native package ${SHERPA_NCNN_DOTNET_RID} (${MY_CURRENT_BINARY_DIR}/${SHERPA_NCNN_DOTNET_RID}/timestamp)"
+  WORKING_DIRECTORY ${MY_CURRENT_BINARY_DIR}/${SHERPA_NCNN_DOTNET_RID})
+
+add_custom_target(_sherpa-ncnn-native-dot-net-package
+  DEPENDS
+    ${MY_CURRENT_BINARY_DIR}/${SHERPA_NCNN_DOTNET_RID}/timestamp
+  )
+
+set(SHERPA_NCNN_DOTNET_PACKAGES_DIR "${PROJECT_BINARY_DIR}/packages")
+message(STATUS "SHERPA_NCNN_DOTNET_PACKAGES_DIR ${SHERPA_NCNN_DOTNET_PACKAGES_DIR}")
+file(MAKE_DIRECTORY ${SHERPA_NCNN_DOTNET_PACKAGES_DIR})
+
+add_custom_command(
+  OUTPUT ${SHERPA_NCNN_DOTNET_PACKAGES_DIR}/org.k2fsa.sherpa.ncnn.runtime.${SHERPA_NCNN_DOTNET_RID}.${SHERPA_NCNN_VERSION}.nupkg
+  COMMAND
+    ${CMAKE_COMMAND} -E copy bin/$<CONFIG>/org.k2fsa.sherpa.ncnn.runtime.${SHERPA_NCNN_DOTNET_RID}.${SHERPA_NCNN_VERSION}.nupkg ${SHERPA_NCNN_DOTNET_PACKAGES_DIR}/org.k2fsa.sherpa.ncnn.runtime.${SHERPA_NCNN_DOTNET_RID}.${SHERPA_NCNN_VERSION}.nupkg
+  DEPENDS
+    ${MY_CURRENT_BINARY_DIR}/${SHERPA_NCNN_DOTNET_RID}/timestamp
+  WORKING_DIRECTORY ${MY_CURRENT_BINARY_DIR}/${SHERPA_NCNN_DOTNET_RID})
+
+add_custom_target(sherpa-ncnn-native-dot-net-package
+  DEPENDS
+    ${SHERPA_NCNN_DOTNET_PACKAGES_DIR}/org.k2fsa.sherpa.ncnn.runtime.${SHERPA_NCNN_DOTNET_RID}.${SHERPA_NCNN_VERSION}.nupkg)
+
+# Now for the wrapper
+
+file(MAKE_DIRECTORY ${MY_CURRENT_BINARY_DIR}/wrapper)
+
+configure_file(
+  ${CMAKE_CURRENT_LIST_DIR}/sherpa-ncnn.csproj.in
+  ${MY_CURRENT_BINARY_DIR}/wrapper/sherpa-ncnn.csproj
+  @ONLY)
+
+add_custom_command(
+  OUTPUT ${MY_CURRENT_BINARY_DIR}/wrapper/timestamp
+  COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_LIST_DIR}/README.md ${MY_CURRENT_BINARY_DIR}/wrapper/README.md
+  COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_LIST_DIR}/sherpa-ncnn.cs ${MY_CURRENT_BINARY_DIR}/wrapper/sherpa-ncnn.cs
+  COMMAND ${CMAKE_COMMAND} -E env --unset=TARGETNAME ${DOTNET_EXECUTABLE} build -c Release sherpa-ncnn.csproj
+  COMMAND ${CMAKE_COMMAND} -E env --unset=TARGETNAME ${DOTNET_EXECUTABLE} pack -c Release sherpa-ncnn.csproj
+  COMMAND ${CMAKE_COMMAND} -E touch ${MY_CURRENT_BINARY_DIR}/wrapper/timestamp
+  DEPENDS
+    ${CMAKE_CURRENT_LIST_DIR}/sherpa-ncnn.csproj.in
+    ${CMAKE_CURRENT_LIST_DIR}/README.md
+    ${CMAKE_CURRENT_LIST_DIR}/sherpa-ncnn.cs
+    _sherpa-ncnn-native-dot-net-package
+  BYPRODUCTS
+    ${MY_CURRENT_BINARY_DIR}/wrapper/bin
+    ${MY_CURRENT_BINARY_DIR}/wrapper/obj
+  VERBATIM
+  COMMENT "Generate .Net package (${MY_CURRENT_BINARY_DIR}/wrapper/timestamp)"
+  WORKING_DIRECTORY ${MY_CURRENT_BINARY_DIR}/wrapper)
+
+add_custom_target(_sherpa-ncnn-dot-net-package
+  DEPENDS
+    ${MY_CURRENT_BINARY_DIR}/wrapper/timestamp
+  )
+
+add_custom_command(
+  OUTPUT ${SHERPA_NCNN_DOTNET_PACKAGES_DIR}/org.k2fsa.sherpa.ncnn.${SHERPA_NCNN_VERSION}.nupkg
+  COMMAND
+    ${CMAKE_COMMAND} -E copy bin/$<CONFIG>/org.k2fsa.sherpa.ncnn.${SHERPA_NCNN_VERSION}.nupkg ${SHERPA_NCNN_DOTNET_PACKAGES_DIR}/org.k2fsa.sherpa.ncnn.${SHERPA_NCNN_VERSION}.nupkg
+  DEPENDS
+    ${MY_CURRENT_BINARY_DIR}/wrapper/timestamp
+    sherpa-ncnn-native-dot-net-package
+  WORKING_DIRECTORY ${MY_CURRENT_BINARY_DIR}/wrapper)
+
+add_custom_target(sherpa-ncnn-dot-net-package
+  DEPENDS
+    ${SHERPA_NCNN_DOTNET_PACKAGES_DIR}/org.k2fsa.sherpa.ncnn.${SHERPA_NCNN_VERSION}.nupkg)

+ 13 - 0
sherpa-ncnn/csharp-api/README.md

@@ -0,0 +1,13 @@
+# Introduction
+
+[sherpa-ncnn](https://github.com/k2-fsa/sherpa-ncnn) is an open-source
+real-time speech recognition toolkit developed
+by the Next-gen Kaldi team.
+
+It supports streaming recognition on a variety of
+platforms such as Android, iOS, Raspberry, Linux, Windows, macOS, etc.
+
+It does not require Internet connection during recognition.
+
+See the documentation https://k2-fsa.github.io/sherpa/ncnn/index.html
+for details.

+ 100 - 0
sherpa-ncnn/csharp-api/notes.md

@@ -0,0 +1,100 @@
+# Introduction
+
+This directory is for .Net.
+
+We use <https://github.com/Mizux/dotnet-native>
+as a reference to build native Nuget packages for various platforms
+such as Linux, macOS, and Windows.
+
+# Install .Net on Linux
+
+Please see <https://learn.microsoft.com/en-us/dotnet/core/install/linux-scripted-manual>
+for details.
+
+The following is just some notes about the installation:
+
+```bash
+wget https://dot.net/v1/dotnet-install.sh -O dotnet-install.sh
+chmod +x dotnet-install.sh
+./dotnet-install.sh --help
+./dotnet-install.sh  --install-dir /star-fj/fangjun/software/dotnet
+export PATH=/star-fj/fangjun/software/dotnet:$PATH
+
+# Check that the installation is successful
+which dotnet
+dotnet --info
+
+# To install the runtime, use
+./dotnet-install.sh --runtime dotnet --install-dir /star-fj/fangjun/software/dotnet/
+```
+
+# Build Nuget packages
+```bash
+cd /tmp
+git clone http://github.com/k2-fsa/sherpa-ncnn
+cd sherpa-ncnn
+
+mkdir build
+cd build
+cmake -DBUILD_SHARED_LIBS=ON -DSHERPA_NCNN_ENABLE_DOT_NET_API=ON -DSHERPA_NCNN_ENABLE_PORTAUDIO=OFF ..
+make -j 6 sherpa-ncnn-dot-net-package
+
+ls -lh packages
+
+dotnet nuget add source --name my-source $PWD/packages
+
+dotnet nuget list source
+```
+
+If you are using Linux, you will see the following output:
+```bash
+$ ls -lh  packages/
+total 2.3M
+-rw-r--r-- 1 kuangfangjun root  14K May  2 17:42 org.k2fsa.sherpa.ncnn.1.8.1.nupkg
+-rw-r--r-- 1 kuangfangjun root 2.3M May  2 17:42 org.k2fsa.sherpa.ncnn.runtime.linux-x64.1.8.1.nupkg
+```
+
+If you are using macOS, you will see the following output:
+```bash
+$ ls -lh packages/
+total 5200
+-rw-r--r--  1 fangjun  staff    13K May  2 17:17 org.k2fsa.sherpa.ncnn.1.8.1.nupkg
+-rw-r--r--  1 fangjun  staff   2.5M May  2 17:17 org.k2fsa.sherpa.ncnn.runtime.osx-x64.1.8.1.nupkg
+```
+
+# Test the generated packages
+```bash
+cd /tmp
+mkdir hello
+cd hello
+dotnet new sln
+
+dotnet new console -o test-sherpa-ncnn
+
+
+dotnet sln add ./test-sherpa-ncnn/test-sherpa-ncnn.csproj
+cd test-sherpa-ncnn
+dotnet add package org.k2fsa.sherpa.ncnn -v 1.8.1
+```
+
+# Notes about dotnet
+
+To list available nuget sources:
+
+```bash
+dotnet nuget list source
+```
+
+To publish a package:
+
+```bash
+export MY_API_KEY=xxxxxx
+dotnet nuget push ./org.k2fsa.sherpa.ncnn.runtime.osx-x64.1.8.2.nupkg --api-key $MY_API_KEY --source https://api.nuget.org/v3/index.json
+dotnet nuget push ./org.k2fsa.sherpa.ncnn.1.8.2.nupkg --api-key $MY_API_KEY --source https://api.nuget.org/v3/index.json
+```
+
+To clear all caches:
+
+```bash
+dotnet nuget locals all --clear
+```

+ 208 - 0
sherpa-ncnn/csharp-api/sherpa-ncnn.cs

@@ -0,0 +1,208 @@
+// Copyright (c)  2023  Xiaomi Corporation (authors: Fangjun Kuang)
+
+using System.Runtime.InteropServices;
+using System;
+
+namespace SherpaNcnn {
+
+[StructLayout(LayoutKind.Sequential)]
+public struct TransducerModelConfig {
+  [MarshalAs(UnmanagedType.LPStr)]
+  public string EncoderParam;
+
+  [MarshalAs(UnmanagedType.LPStr)]
+  public string EncoderBin;
+
+  [MarshalAs(UnmanagedType.LPStr)]
+  public string DecoderParam;
+
+  [MarshalAs(UnmanagedType.LPStr)]
+  public string DecoderBin;
+
+  [MarshalAs(UnmanagedType.LPStr)]
+  public string JoinerParam;
+
+  [MarshalAs(UnmanagedType.LPStr)]
+  public string JoinerBin;
+
+  [MarshalAs(UnmanagedType.LPStr)]
+  public string Tokens;
+
+  public int UseVulkanCompute;
+
+  public int NumThreads;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+public struct TransducerDecoderConfig {
+  [MarshalAs(UnmanagedType.LPStr)]
+  public string DecodingMethod;
+
+  public int NumActivePaths;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+public struct FeatureConfig {
+  public float SampleRate;
+  public int FeatureDim;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+public struct OnlineRecognizerConfig {
+  public FeatureConfig FeatConfig;
+  public TransducerModelConfig ModelConfig;
+  public TransducerDecoderConfig DecoderConfig;
+
+  public int EnableEndpoit;
+  public float Rule1MinTrailingSilence;
+  public float Rule2MinTrailingSilence;
+  public float Rule3MinUtteranceLength;
+}
+
+// please see
+// https://www.mono-project.com/docs/advanced/pinvoke/#gc-safe-pinvoke-code
+// https://www.mono-project.com/docs/advanced/pinvoke/#properly-disposing-of-resources
+public class OnlineRecognizer : IDisposable {
+ public OnlineRecognizer(OnlineRecognizerConfig config) {
+    IntPtr h = CreateOnlineRecognizer(ref config);
+    _handle = new HandleRef(this, h);
+  }
+
+  public OnlineStream CreateStream() {
+    IntPtr p = CreateOnlineStream(_handle.Handle);
+    return new OnlineStream(p);
+  }
+
+  public bool IsReady(OnlineStream stream) {
+    return IsReady(_handle.Handle, stream.Handle) != 0;
+  }
+
+  public void Decode(OnlineStream stream) {
+    Decode(_handle.Handle, stream.Handle);
+  }
+
+  public OnlineRecognizerResult GetResult(OnlineStream stream) {
+    IntPtr h = GetResult(_handle.Handle, stream.Handle);
+    OnlineRecognizerResult result = new OnlineRecognizerResult(h);
+    DestroyResult(h);
+    return result;
+  }
+
+  public void Dispose() {
+    Cleanup();
+    // Prevent the object from being placed on the
+    // finalization queue
+    System.GC.SuppressFinalize(this);
+  }
+
+  ~OnlineRecognizer() {
+    Cleanup();
+  }
+
+  private void Cleanup() {
+    DestroyOnlineRecognizer(_handle.Handle);
+
+    // Don't permit the handle to be used again.
+    _handle = new HandleRef(this, IntPtr.Zero);
+  }
+
+  private HandleRef _handle;
+
+#if LINUX
+  private const string dllName = "libsherpa-ncnn-c-api.so";
+#else
+  private const string dllName = "sherpa-ncnn-c-api";
+#endif
+
+  [DllImport(dllName, EntryPoint="CreateRecognizer")]
+  public static extern IntPtr CreateOnlineRecognizer(ref OnlineRecognizerConfig config);
+
+  [DllImport(dllName, EntryPoint="DestroyRecognizer")]
+  public static extern void DestroyOnlineRecognizer(IntPtr handle);
+
+  [DllImport(dllName, EntryPoint="CreateStream")]
+  public static extern IntPtr CreateOnlineStream(IntPtr handle);
+
+  [DllImport(dllName)]
+  public static extern int IsReady(IntPtr handle, IntPtr stream);
+
+  [DllImport(dllName, EntryPoint="Decode")]
+  public static extern void Decode(IntPtr handle, IntPtr stream);
+
+  [DllImport(dllName)]
+  public static extern IntPtr GetResult(IntPtr handle, IntPtr stream);
+
+  [DllImport(dllName)]
+  public static extern void DestroyResult(IntPtr result);
+}
+
+
+public class OnlineStream : IDisposable {
+  public OnlineStream(IntPtr p) {
+    _handle = new HandleRef(this, p);
+  }
+
+  public void AcceptWaveform(float sampleRate, float[] samples) {
+    AcceptWaveform(Handle, sampleRate, samples, samples.Length);
+  }
+  public void InputFinished() {
+    InputFinished(Handle);
+  }
+
+  ~OnlineStream() {
+    Cleanup();
+  }
+
+  public void Dispose() {
+    Cleanup();
+    // Prevent the object from being placed on the
+    // finalization queue
+    System.GC.SuppressFinalize(this);
+  }
+
+  private void Cleanup() {
+    DestroyOnlineStream(Handle);
+
+    // Don't permit the handle to be used again.
+    _handle = new HandleRef(this, IntPtr.Zero);
+  }
+
+  private HandleRef _handle;
+  public IntPtr Handle => _handle.Handle;
+
+#if LINUX
+  private const string dllName = "libsherpa-ncnn-c-api.so";
+#else
+  private const string dllName = "sherpa-ncnn-c-api";
+#endif
+
+  [DllImport(dllName, EntryPoint="DestroyStream")]
+  public static extern void DestroyOnlineStream(IntPtr handle);
+
+  [DllImport(dllName)]
+  public static extern void AcceptWaveform(IntPtr handle, float sampleRate, float[] samples, int n);
+
+  [DllImport(dllName)]
+  public static extern void InputFinished(IntPtr handle);
+}
+
+public class OnlineRecognizerResult {
+  public OnlineRecognizerResult(IntPtr handle) {
+    Impl impl = (Impl)Marshal.PtrToStructure(handle, typeof(Impl));
+    _text = Marshal.PtrToStringAnsi(impl.Text);
+  }
+
+  [StructLayout(LayoutKind.Sequential)]
+  struct Impl {
+    public IntPtr Text;
+    public IntPtr Tokens;
+    public IntPtr Timestamps;
+    int Count;
+  }
+
+  private String _text;
+  public String Text => _text;
+
+}
+
+}

+ 58 - 0
sherpa-ncnn/csharp-api/sherpa-ncnn.csproj.in

@@ -0,0 +1,58 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
+    <PackageReadmeFile>README.md</PackageReadmeFile>
+    <OutputType>Library</OutputType>
+    <LangVersion>8.0</LangVersion>
+    <TargetFrameworks>netstandard2.0;netcoreapp3.1;net6.0</TargetFrameworks>
+    <RuntimeIdentifiers>linux-x64;osx-x64;win-x64</RuntimeIdentifiers>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <AssemblyName>sherpa-ncnn</AssemblyName>
+    <Version>@SHERPA_NCNN_VERSION@</Version>
+
+    <Authors>The Next-gen Kaldi development team</Authors>
+    <Owners>The Next-gen Kaldi development team</Owners>
+    <Company>Xiaomi Corporation</Company>
+    <Copyright>Copyright 2019-2023 Xiaomi Corporation</Copyright>
+    <Description>sherpa-ncnn is an open-source real-time speech recognition toolkit developed
+    by the Next-gen Kaldi team. It supports streaming recognition on a variety of
+    platforms such as Android, iOS, Raspberry, Linux, Windows, macOS, etc.
+
+    It does not require Internet connection during recognition.
+
+    See the documentation https://k2-fsa.github.io/sherpa/ncnn/index.html
+    for details.
+    </Description>
+
+    <!-- Pack Option -->
+    <Title>sherpa-ncnn v@SHERPA_NCNN_VERSION@</Title>
+    <PackageId>org.k2fsa.sherpa.ncnn</PackageId>
+    <IncludeSymbols>true</IncludeSymbols>
+    <SymbolPackageFormat>snupkg</SymbolPackageFormat>
+
+    <!-- Signing -->
+    <SignAssembly>false</SignAssembly>
+    <PublicSign>false</PublicSign>
+    <DelaySign>false</DelaySign>
+  </PropertyGroup>
+
+  <PropertyGroup>
+    <IsLinux Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true'">true</IsLinux>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(IsLinux)'=='true'">
+   <DefineConstants>Linux</DefineConstants>
+  </PropertyGroup>
+
+
+  <ItemGroup>
+    <None Include="./README.md" Pack="true" PackagePath="/"/>
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="org.k2fsa.sherpa.ncnn.runtime.linux-x64" Version="@SHERPA_NCNN_VERSION@" />
+    <PackageReference Include="org.k2fsa.sherpa.ncnn.runtime.osx-x64"   Version="@SHERPA_NCNN_VERSION@" />
+    <PackageReference Include="org.k2fsa.sherpa.ncnn.runtime.win-x64"   Version="@SHERPA_NCNN_VERSION@" />
+  </ItemGroup>
+
+</Project>

+ 43 - 0
sherpa-ncnn/csharp-api/sherpa-ncnn.csproj.runtime.in

@@ -0,0 +1,43 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
+    <PackageReadmeFile>README.md</PackageReadmeFile>
+    <OutputType>Library</OutputType>
+    <TargetFrameworks>netstandard2.0;netcoreapp3.1;net6.0</TargetFrameworks>
+    <RuntimeIdentifier>@SHERPA_NCNN_DOTNET_RID@</RuntimeIdentifier>
+    <AssemblyName>sherpa-ncnn</AssemblyName>
+    <Version>@SHERPA_NCNN_VERSION@</Version>
+
+    <!-- Nuget Properties -->
+    <Description>.NET native wrapper for the sherpa-ncnn project</Description>
+    <IncludeBuildOutput>false</IncludeBuildOutput>
+
+    <!-- Pack Option -->
+    <Title>sherpa-ncnn @SHERPA_NCNN_DOTNET_RID@ v@SHERPA_NCNN_VERSION@</Title>
+    <PackageId>org.k2fsa.sherpa.ncnn.runtime.@SHERPA_NCNN_DOTNET_RID@</PackageId>
+
+    <!-- Signing -->
+    <SignAssembly>false</SignAssembly>
+    <PublicSign>false</PublicSign>
+    <DelaySign>false</DelaySign>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <None Include="./README.md" Pack="true" PackagePath="/"/>
+  </ItemGroup>
+
+  <ItemGroup>
+    <!-- Native library must be in native directory... -->
+    <!-- If project is built as a STATIC_LIBRARY (e.g. Windows) then we don't have to include it -->
+    <Content Include="
+      $<TARGET_FILE:ncnn>
+      ;$<TARGET_FILE:kaldi-native-fbank-core>
+      ;$<TARGET_FILE:sherpa-ncnn-c-api>
+      ;$<TARGET_FILE:sherpa-ncnn-core>
+    ">
+      <PackagePath>runtimes/@SHERPA_NCNN_DOTNET_RID@/native/%(Filename)%(Extension)</PackagePath>
+      <Pack>true</Pack>
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+  </ItemGroup>
+</Project>

+ 1 - 0
sherpa-ncnn/csrc/wave-reader.cc

@@ -41,6 +41,7 @@ struct WaveHeader {
       return false;
     }
 
+    //                       t m f
     if (subchunk1_id != 0x20746d66) {
       return false;
     }