Ver Fonte

Add examples for Golang api (#244)

Fangjun Kuang há 2 anos atrás
pai
commit
3d48f0ebb0

+ 131 - 0
.github/workflows/go.yaml

@@ -0,0 +1,131 @@
+name: test-go
+
+on:
+  push:
+    branches:
+      - master
+    tags:
+      - '*'
+  pull_request:
+    branches:
+      - master
+
+  workflow_dispatch:
+
+concurrency:
+  group: go-${{ github.ref }}
+  cancel-in-progress: true
+
+jobs:
+  go:
+    name: ${{ matrix.os }} ${{matrix.arch }}
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        include:
+          - os: ubuntu-latest
+            arch: amd64
+          - os: macos-latest
+            arch: amd64
+          - os: windows-latest
+            arch: x64
+          - os: windows-latest
+            arch: x86 # use 386 for GOARCH
+
+    steps:
+      - uses: actions/checkout@v2
+        with:
+          fetch-depth: 0
+      - uses: actions/setup-go@v4
+        with:
+          go-version: '>=1.20'
+
+      - name: Display go version
+        shell: bash
+        run: |
+          go version
+          go env GOPATH
+          go env GOARCH
+
+      - name: Set up MinGW
+        if: matrix.os == 'windows-latest'
+        uses: egor-tensin/setup-mingw@v2
+        with:
+          platform: ${{ matrix.arch }}
+
+      - name: Show gcc
+        if: matrix.os == 'windows-latest'
+        run: |
+          gcc --version
+
+      - name: Test decoding file (Linux/macOS)
+        if: matrix.os != 'windows-latest'
+        shell: bash
+        run: |
+          cd go-api-examples/decode-file
+          ls -lh
+          go mod tidy
+          cat go.mod
+          go build -x
+          ls -lh
+
+          git lfs install
+
+          git clone https://huggingface.co/csukuangfj/sherpa-ncnn-streaming-zipformer-bilingual-zh-en-2023-02-13
+          ./run.sh
+          rm -rf sherpa-ncnn-streaming-zipformer-bilingual-zh-en-2023-02-13
+
+      - name: Test decoding file (Win64)
+        if: matrix.os == 'windows-latest' && matrix.arch == 'x64'
+        shell: bash
+        run: |
+          cd go-api-examples/decode-file
+          ls -lh
+          go mod tidy
+          cat go.mod
+          go build
+          ls -lh
+
+          echo $PWD
+          ls -lh /C/Users/runneradmin/go/pkg/mod/github.com/k2-fsa/
+          ls -lh /C/Users/runneradmin/go/pkg/mod/github.com/k2-fsa/*
+          cp -v /C/Users/runneradmin/go/pkg/mod/github.com/k2-fsa/sherpa-ncnn-go-windows*/lib/x86_64-pc-windows-gnu/*.dll .
+          ls -lh
+
+          git lfs install
+
+          git clone https://huggingface.co/csukuangfj/sherpa-ncnn-streaming-zipformer-bilingual-zh-en-2023-02-13
+          ./run.sh
+          rm -rf sherpa-ncnn-streaming-zipformer-bilingual-zh-en-2023-02-13
+
+      - name: Test decoding file (Win32)
+        if: matrix.os == 'windows-latest' && matrix.arch == 'x86'
+        shell: bash
+        run: |
+          cd go-api-examples/decode-file
+          ls -lh
+          go mod tidy
+          cat go.mod
+          ls -lh
+
+          go env GOARCH
+          go env
+          echo "------------------------------"
+          go env -w GOARCH=386
+          go env -w CGO_ENABLED=1
+          go env
+
+          go clean
+          go build -x
+
+          echo $PWD
+          ls -lh /C/Users/runneradmin/go/pkg/mod/github.com/k2-fsa/
+          cp -v /C/Users/runneradmin/go/pkg/mod/github.com/k2-fsa/sherpa-ncnn-go-windows*/lib/i686-pc-windows-gnu/*.dll .
+          ls -lh
+
+          git lfs install
+
+          git clone https://huggingface.co/csukuangfj/sherpa-ncnn-streaming-zipformer-bilingual-zh-en-2023-02-13
+          ./run.sh
+          rm -rf sherpa-ncnn-streaming-zipformer-bilingual-zh-en-2023-02-13

+ 226 - 0
.github/workflows/release-go.yaml

@@ -0,0 +1,226 @@
+name: release-go
+
+on:
+  push:
+    branches:
+      - master
+    tags:
+      - '*'
+
+  workflow_dispatch:
+    inputs:
+      version:
+        description: "Version information(e.g., 2.0.1) or auto"
+        required: true
+
+env:
+  VERSION:
+    |- # Enter release tag name or version name in workflow_dispatch. Appropriate version if not specified
+    ${{ github.event.release.tag_name || github.event.inputs.version }}
+
+concurrency:
+  group: release-go-${{ github.ref }}
+  cancel-in-progress: true
+
+jobs:
+  linux:
+    if: github.repository_owner == 'k2-fsa' || github.repository_owner == 'csukuangfj'
+    name: Linux ${{ matrix.arch }}
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        os: [ubuntu-latest]
+        arch: [x86_64, aarch64]
+
+    steps:
+      - uses: actions/checkout@v2
+
+      - name: SSH to GitHub
+        run: |
+          mkdir -p ~/.ssh/
+          cp scripts/go/ssh_config ~/.ssh/config
+          echo "${{ secrets.MY_GITHUB_SSH_KEY }}" > ~/.ssh/github && chmod 600 ~/.ssh/github
+          ssh github.com || true
+          rm ~/.ssh/github
+
+      - name: Set up QEMU
+        if: matrix.arch == 'aarch64'
+        uses: docker/setup-qemu-action@v2
+        with:
+          platforms: all
+
+      # see https://cibuildwheel.readthedocs.io/en/stable/changelog/
+      # for a list of versions
+      - name: Build wheels x86_64
+        if: matrix.arch == 'x86_64'
+        uses: pypa/cibuildwheel@v2.11.4
+        env:
+          CIBW_BEFORE_BUILD: "pip install -U cmake numpy"
+          CIBW_BUILD: "cp38-manylinux_x86_64"
+          CIBW_BUILD_VERBOSITY: 3
+
+      - name: Build wheels aarch64
+        if: matrix.arch == 'aarch64'
+        uses: pypa/cibuildwheel@v2.11.4
+        env:
+          CIBW_BEFORE_BUILD: "pip install -U cmake numpy"
+          CIBW_ENVIRONMENT: SHERPA_NCNN_CMAKE_ARGS='-DCMAKE_C_FLAGS="-march=armv8-a" -DCMAKE_CXX_FLAGS="-march=armv8-a"'
+          CIBW_BUILD: "cp38-manylinux_aarch64"
+          CIBW_BUILD_VERBOSITY: 3
+          CIBW_ARCHS_LINUX: aarch64
+
+      - name: Display wheels
+        shell: bash
+        run: |
+          ls -lh ./wheelhouse/*.whl
+          unzip -l ./wheelhouse/*.whl
+
+      - uses: actions/upload-artifact@v2
+        with:
+          name: ${{ matrix.os }}-wheels-for-go-${{ matrix.arch }}
+          path: ./wheelhouse/*.whl
+
+  macOS:
+    name: macOS ${{ matrix.arch }}
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        os: [macos-latest]
+        arch: [x86_64, arm64]
+
+    steps:
+      - uses: actions/checkout@v2
+      - name: Configure CMake
+        shell: bash
+        run: |
+          mkdir build
+          cd build
+          cmake -D CMAKE_BUILD_TYPE=${{ matrix.build_type }} -D BUILD_SHARED_LIBS=ON -DCMAKE_OSX_ARCHITECTURES=${{ matrix.arch }} -DCMAKE_INSTALL_PREFIX=./install ..
+
+      - name: Build sherpa-ncnn for macOS ${{ matrix.arch }}
+        shell: bash
+        run: |
+          cd build
+          make -j2
+          make install
+
+          ls -lh lib
+          ls -lh bin
+
+          file install/lib/lib*
+
+      - uses: actions/upload-artifact@v2
+        with:
+          name: ${{ matrix.os }}-for-${{ matrix.arch }}
+          path: ./build/install/lib/
+
+  windows:
+    name: Windows ${{ matrix.arch }}
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        os: [windows-latest]
+        arch: [x64, Win32]
+    steps:
+      - uses: actions/checkout@v2
+        with:
+          fetch-depth: 0
+
+      - name: Configure CMake
+        shell: bash
+        run: |
+          mkdir build
+          cd build
+          cmake -A ${{ matrix.arch }} -D CMAKE_BUILD_TYPE=Release -D BUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX=./install ..
+
+      - name: Build sherpa-ncnn for windows
+        shell: bash
+        run: |
+          cd build
+          cmake --build . --config Release -- -m:2
+          cmake --build . --config Release --target install -- -m:2
+
+          ls -lh install/*
+
+          ls -lh install/lib
+          ls -lh install/bin
+
+      - name: Upload artifact
+        uses: actions/upload-artifact@v2
+        with:
+          name: sherpa-ncnn-go-windows-${{ matrix.arch }}
+          path: ./build/install/lib/
+
+  Release:
+    name: Release
+    runs-on: ubuntu-latest
+    needs: [linux, macOS, windows]
+
+    steps:
+      - uses: actions/checkout@v2
+
+      - name: Add SSH key
+        run: |
+          mkdir -p ~/.ssh/
+          cp scripts/go/ssh_config ~/.ssh/config
+          echo "${{ secrets.MY_GITHUB_SSH_KEY }}" > ~/.ssh/github && chmod 600 ~/.ssh/github
+          ssh github.com || true
+
+      - name: Retrieve artifact Linux x86_64
+        uses: actions/download-artifact@v2
+        with:
+          name: ubuntu-latest-wheels-for-go-x86_64
+          path: ./linux_x86_64
+
+      - name: Retrieve artifact Linux aarch64
+        uses: actions/download-artifact@v2
+        with:
+          name: ubuntu-latest-wheels-for-go-aarch64
+          path: ./linux_aarch64
+
+      - name: Retrieve artifact from macos-latest (x86_64)
+        uses: actions/download-artifact@v2
+        with:
+          name: macos-latest-for-x86_64
+          path: ./macos-x86_64
+
+      - name: Retrieve artifact from macos-latest (arm64)
+        uses: actions/download-artifact@v2
+        with:
+          name: macos-latest-for-arm64
+          path: ./macos-arm64
+
+      - name: Retrieve artifact from windows-latest (x64)
+        uses: actions/download-artifact@v2
+        with:
+          name: sherpa-ncnn-go-windows-x64
+          path: ./windows-x64
+
+      - name: Retrieve artifact from windows-latest (Win32)
+        uses: actions/download-artifact@v2
+        with:
+          name: sherpa-ncnn-go-windows-Win32
+          path: ./windows-win32
+
+      - name: Unzip Ubuntu wheels
+        shell: bash
+        run: |
+          cd linux_x86_64
+          ls -lh
+          unzip ./*.whl
+          tree .
+
+          cd ../linux_aarch64
+          ls -lh
+          unzip ./*.whl
+          tree .
+
+      - name: Release go
+        # if: env.VERSION != ''
+        shell: bash
+        run: |
+          export VERSION=auto
+          ./scripts/go/release.sh

+ 1 - 0
go-api-examples/decode-file/.gitignore

@@ -0,0 +1 @@
+decode-file

+ 9 - 0
go-api-examples/decode-file/go.mod

@@ -0,0 +1,9 @@
+module decode-file
+
+go 1.12
+
+require (
+	github.com/k2-fsa/sherpa-ncnn-go v0.0.0-20230804012636-09edd8796764
+	github.com/spf13/pflag v1.0.5
+	github.com/youpy/go-wav v0.3.2
+)

+ 35 - 0
go-api-examples/decode-file/go.sum

@@ -0,0 +1,35 @@
+github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/k2-fsa/sherpa-ncnn-go v0.0.0-20230804012636-09edd8796764 h1:ibX9TRgwiy4ecqyVcTha0cT0rBqi341nIB3maMTFygM=
+github.com/k2-fsa/sherpa-ncnn-go v0.0.0-20230804012636-09edd8796764/go.mod h1:pM/h/5yDszOg/zRwJwiyr5p+1qfhB83roTofAPCPYOI=
+github.com/k2-fsa/sherpa-ncnn-go-linux v1.0.0 h1:5OsP3+R3WDNq5HZKawQQVcAw6p68kROc6YQgatkXQYI=
+github.com/k2-fsa/sherpa-ncnn-go-linux v1.0.0/go.mod h1:J5nhtRXymY4fcOQsq0dJyxYAISqq/FqcXHjV9FtKiMM=
+github.com/k2-fsa/sherpa-ncnn-go-macos v1.0.0 h1:8vAxiKIylQz3RJPgoPB09tVrJIPoldcg4F0K8yU5gxU=
+github.com/k2-fsa/sherpa-ncnn-go-macos v1.0.0/go.mod h1:zBUGOr6H3oXi2ys+vO3RE9hhd/hOBb2pMdhLxsGHxMY=
+github.com/k2-fsa/sherpa-ncnn-go-windows v1.0.2 h1:se/s+FTN54tf1mqHduiTC72aXSYbyavQPSMpJcrtHtg=
+github.com/k2-fsa/sherpa-ncnn-go-windows v1.0.2/go.mod h1:7xN0Ojf3wDkeHFNFsD1xgAunnehpjRKcL4/4egbrdDY=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/youpy/go-riff v0.1.0 h1:vZO/37nI4tIET8tQI0Qn0Y79qQh99aEpponTPiPut7k=
+github.com/youpy/go-riff v0.1.0/go.mod h1:83nxdDV4Z9RzrTut9losK7ve4hUnxUR8ASSz4BsKXwQ=
+github.com/youpy/go-wav v0.3.2 h1:NLM8L/7yZ0Bntadw/0h95OyUsen+DQIVf9gay+SUsMU=
+github.com/youpy/go-wav v0.3.2/go.mod h1:0FCieAXAeSdcxFfwLpRuEo0PFmAoc+8NU34h7TUvk50=
+github.com/zaf/g711 v0.0.0-20190814101024-76a4a538f52b h1:QqixIpc5WFIqTLxB3Hq8qs0qImAgBdq0p6rq2Qdl634=
+github.com/zaf/g711 v0.0.0-20190814101024-76a4a538f52b/go.mod h1:T2h1zV50R/q0CVYnsQOQ6L7P4a2ZxH47ixWcMXFGyx8=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
+gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=

+ 187 - 0
go-api-examples/decode-file/main.go

@@ -0,0 +1,187 @@
+package main
+
+import (
+	"bytes"
+	"encoding/binary"
+	"errors"
+	sherpa "github.com/k2-fsa/sherpa-ncnn-go/sherpa_ncnn"
+	flag "github.com/spf13/pflag"
+	"github.com/youpy/go-wav"
+	"log"
+	"os"
+	"strings"
+)
+
+func main() {
+	log.SetFlags(log.LstdFlags | log.Lmicroseconds)
+
+	config := sherpa.RecognizerConfig{}
+	config.Feat = sherpa.FeatureConfig{SampleRate: 16000, FeatureDim: 80}
+
+	flag.StringVar(&config.Model.EncoderParam, "encoder-param", "", "Path to the encoder.ncnn.param")
+	flag.StringVar(&config.Model.EncoderBin, "encoder-bin", "", "Path to the encoder.ncnn.bin")
+	flag.StringVar(&config.Model.DecoderParam, "decoder-param", "", "Path to the decoder.ncnn.param")
+	flag.StringVar(&config.Model.DecoderBin, "decoder-bin", "", "Path to the decoder.ncnn.bin")
+	flag.StringVar(&config.Model.JoinerParam, "joiner-param", "", "Path to the joiner.ncnn.param")
+	flag.StringVar(&config.Model.JoinerBin, "joiner-bin", "", "Path to the joiner.ncnn.bin")
+
+	flag.StringVar(&config.Model.Tokens, "tokens", "", "Path to the tokens file")
+	flag.IntVar(&config.Model.NumThreads, "num-threads", 1, "Number of threads for computing")
+	flag.StringVar(&config.Decoder.DecodingMethod, "decoding-method", "greedy_search", "Decoding method. Possible values: greedy_search, modified_beam_search")
+	flag.IntVar(&config.Decoder.NumActivePaths, "num-active-paths", 4, "Used only when --decoding-method is modified_beam_search")
+
+	flag.Parse()
+
+	if len(flag.Args()) != 1 {
+		log.Fatalf("Please provide one wave file")
+	}
+	checkConfig(&config)
+
+	log.Println("Reading", flag.Arg(0))
+
+	samples, sampleRate := readWave(flag.Arg(0))
+
+	log.Println("Initializing recognizer")
+	recognizer := sherpa.NewRecognizer(&config)
+	log.Println("Recognizer created!")
+	defer sherpa.DeleteRecognizer(recognizer)
+
+	log.Println("Start decoding!")
+	stream := sherpa.NewStream(recognizer)
+	defer sherpa.DeleteStream(stream)
+
+	stream.AcceptWaveform(sampleRate, samples)
+
+	tailPadding := make([]float32, int(float32(sampleRate)*0.3))
+	stream.AcceptWaveform(sampleRate, tailPadding)
+
+	for recognizer.IsReady(stream) {
+		recognizer.Decode(stream)
+	}
+
+	log.Println("Decoding done!")
+	result := recognizer.GetResult(stream)
+
+	log.Println(strings.ToLower(result.Text))
+	log.Printf("Wave duration: %v seconds", float32(len(samples))/float32(sampleRate))
+}
+
+func readWave(filename string) (samples []float32, sampleRate int) {
+	file, _ := os.Open(filename)
+	defer file.Close()
+
+	reader := wav.NewReader(file)
+	format, err := reader.Format()
+	if err != nil {
+		log.Fatalf("Failed to read wave format")
+	}
+
+	if format.AudioFormat != 1 {
+		log.Fatalf("Support only PCM format. Given: %v\n", format.AudioFormat)
+	}
+
+	if format.NumChannels != 1 {
+		log.Fatalf("Support only 1 channel wave file. Given: %v\n", format.NumChannels)
+	}
+
+	if format.BitsPerSample != 16 {
+		log.Fatalf("Support only 16-bit per sample. Given: %v\n", format.BitsPerSample)
+	}
+
+	reader.Duration() // so that it initializes reader.Size
+
+	buf := make([]byte, reader.Size)
+	n, err := reader.Read(buf)
+	if n != int(reader.Size) {
+		log.Fatalf("Failed to read %v bytes. Returned %v bytes\n", reader.Size, n)
+	}
+
+	samples = samplesInt16ToFloat(buf)
+	sampleRate = int(format.SampleRate)
+
+	return
+}
+
+func samplesInt16ToFloat(inSamples []byte) []float32 {
+	numSamples := len(inSamples) / 2
+	outSamples := make([]float32, numSamples)
+
+	for i := 0; i != numSamples; i++ {
+		s := inSamples[i*2 : (i+1)*2]
+
+		var s16 int16
+		buf := bytes.NewReader(s)
+		err := binary.Read(buf, binary.LittleEndian, &s16)
+		if err != nil {
+			log.Fatal("Failed to parse 16-bit sample")
+		}
+		outSamples[i] = float32(s16) / 32768
+	}
+
+	return outSamples
+}
+
+func checkConfig(config *sherpa.RecognizerConfig) {
+	// --encoder-param
+	if config.Model.EncoderParam == "" {
+		log.Fatal("Please provide --encoder-param")
+	}
+
+	if _, err := os.Stat(config.Model.EncoderParam); errors.Is(err, os.ErrNotExist) {
+		log.Fatalf("--encoder-param %v does not exist", config.Model.EncoderParam)
+	}
+
+	// --encoder-bin
+	if config.Model.EncoderBin == "" {
+		log.Fatal("Please provide --encoder-bin")
+	}
+
+	if _, err := os.Stat(config.Model.EncoderBin); errors.Is(err, os.ErrNotExist) {
+		log.Fatalf("--encoder-bin %v does not exist", config.Model.EncoderBin)
+	}
+
+	// --decoder-param
+	if config.Model.DecoderParam == "" {
+		log.Fatal("Please provide --decoder-param")
+	}
+
+	if _, err := os.Stat(config.Model.DecoderParam); errors.Is(err, os.ErrNotExist) {
+		log.Fatalf("--decoder-param %v does not exist", config.Model.DecoderParam)
+	}
+
+	// --decoder-bin
+	if config.Model.DecoderBin == "" {
+		log.Fatal("Please provide --decoder-bin")
+	}
+
+	if _, err := os.Stat(config.Model.DecoderBin); errors.Is(err, os.ErrNotExist) {
+		log.Fatalf("--decoder-bin %v does not exist", config.Model.DecoderBin)
+	}
+
+	// --joiner-param
+	if config.Model.JoinerParam == "" {
+		log.Fatal("Please provide --joiner-param")
+	}
+
+	if _, err := os.Stat(config.Model.JoinerParam); errors.Is(err, os.ErrNotExist) {
+		log.Fatalf("--joiner-param %v does not exist", config.Model.JoinerParam)
+	}
+
+	// --joiner-bin
+	if config.Model.JoinerBin == "" {
+		log.Fatal("Please provide --joiner-bin")
+	}
+
+	if _, err := os.Stat(config.Model.JoinerBin); errors.Is(err, os.ErrNotExist) {
+		log.Fatalf("--joiner-bin %v does not exist", config.Model.JoinerBin)
+	}
+
+	// --tokens
+	if config.Model.Tokens == "" {
+		log.Fatal("Please provide --tokens")
+	}
+
+	if _, err := os.Stat(config.Model.Tokens); errors.Is(err, os.ErrNotExist) {
+		log.Fatalf("--tokens %v does not exist", config.Model.Tokens)
+	}
+}

+ 23 - 0
go-api-examples/decode-file/run.sh

@@ -0,0 +1,23 @@
+#!/usr/bin/env bash
+
+if [ ! -d ./sherpa-ncnn-streaming-zipformer-bilingual-zh-en-2023-02-13 ]; then
+  echo "Please download the pre-trained model for testing."
+  echo "You can refer to"
+  echo ""
+  echo "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"
+  echo ""
+  echo "for help"
+  exit 1
+fi
+
+go build
+
+./decode-file \
+  --encoder-param ./sherpa-ncnn-streaming-zipformer-bilingual-zh-en-2023-02-13/encoder_jit_trace-pnnx.ncnn.param \
+  --encoder-bin ./sherpa-ncnn-streaming-zipformer-bilingual-zh-en-2023-02-13/encoder_jit_trace-pnnx.ncnn.bin \
+  --decoder-param ./sherpa-ncnn-streaming-zipformer-bilingual-zh-en-2023-02-13/decoder_jit_trace-pnnx.ncnn.param \
+  --decoder-bin ./sherpa-ncnn-streaming-zipformer-bilingual-zh-en-2023-02-13/decoder_jit_trace-pnnx.ncnn.bin \
+  --joiner-param ./sherpa-ncnn-streaming-zipformer-bilingual-zh-en-2023-02-13/joiner_jit_trace-pnnx.ncnn.param \
+  --joiner-bin ./sherpa-ncnn-streaming-zipformer-bilingual-zh-en-2023-02-13/joiner_jit_trace-pnnx.ncnn.bin \
+  --tokens ./sherpa-ncnn-streaming-zipformer-bilingual-zh-en-2023-02-13/tokens.txt \
+  ./sherpa-ncnn-streaming-zipformer-bilingual-zh-en-2023-02-13/test_wavs/1.wav

+ 1 - 0
go-api-examples/real-time-speech-recognition-from-microphone/.gitignore

@@ -0,0 +1 @@
+real-time-speech-recognition-from-microphone

+ 10 - 0
go-api-examples/real-time-speech-recognition-from-microphone/go.mod

@@ -0,0 +1,10 @@
+module real-time-speech-recognition-from-microphone
+
+go 1.12
+
+require (
+	github.com/gordonklaus/portaudio v0.0.0-20230709114228-aafa478834f5
+	github.com/k2-fsa/sherpa-ncnn-go v0.0.0-20230804012636-09edd8796764
+	github.com/spf13/pflag v1.0.5
+	github.com/youpy/go-wav v0.3.2
+)

+ 37 - 0
go-api-examples/real-time-speech-recognition-from-microphone/go.sum

@@ -0,0 +1,37 @@
+github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/gordonklaus/portaudio v0.0.0-20230709114228-aafa478834f5 h1:5AlozfqaVjGYGhms2OsdUyfdJME76E6rx5MdGpjzZpc=
+github.com/gordonklaus/portaudio v0.0.0-20230709114228-aafa478834f5/go.mod h1:WY8R6YKlI2ZI3UyzFk7P6yGSuS+hFwNtEzrexRyD7Es=
+github.com/k2-fsa/sherpa-ncnn-go v0.0.0-20230804012636-09edd8796764 h1:ibX9TRgwiy4ecqyVcTha0cT0rBqi341nIB3maMTFygM=
+github.com/k2-fsa/sherpa-ncnn-go v0.0.0-20230804012636-09edd8796764/go.mod h1:pM/h/5yDszOg/zRwJwiyr5p+1qfhB83roTofAPCPYOI=
+github.com/k2-fsa/sherpa-ncnn-go-linux v1.0.0 h1:5OsP3+R3WDNq5HZKawQQVcAw6p68kROc6YQgatkXQYI=
+github.com/k2-fsa/sherpa-ncnn-go-linux v1.0.0/go.mod h1:J5nhtRXymY4fcOQsq0dJyxYAISqq/FqcXHjV9FtKiMM=
+github.com/k2-fsa/sherpa-ncnn-go-macos v1.0.0 h1:8vAxiKIylQz3RJPgoPB09tVrJIPoldcg4F0K8yU5gxU=
+github.com/k2-fsa/sherpa-ncnn-go-macos v1.0.0/go.mod h1:zBUGOr6H3oXi2ys+vO3RE9hhd/hOBb2pMdhLxsGHxMY=
+github.com/k2-fsa/sherpa-ncnn-go-windows v1.0.2 h1:se/s+FTN54tf1mqHduiTC72aXSYbyavQPSMpJcrtHtg=
+github.com/k2-fsa/sherpa-ncnn-go-windows v1.0.2/go.mod h1:7xN0Ojf3wDkeHFNFsD1xgAunnehpjRKcL4/4egbrdDY=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/youpy/go-riff v0.1.0 h1:vZO/37nI4tIET8tQI0Qn0Y79qQh99aEpponTPiPut7k=
+github.com/youpy/go-riff v0.1.0/go.mod h1:83nxdDV4Z9RzrTut9losK7ve4hUnxUR8ASSz4BsKXwQ=
+github.com/youpy/go-wav v0.3.2 h1:NLM8L/7yZ0Bntadw/0h95OyUsen+DQIVf9gay+SUsMU=
+github.com/youpy/go-wav v0.3.2/go.mod h1:0FCieAXAeSdcxFfwLpRuEo0PFmAoc+8NU34h7TUvk50=
+github.com/zaf/g711 v0.0.0-20190814101024-76a4a538f52b h1:QqixIpc5WFIqTLxB3Hq8qs0qImAgBdq0p6rq2Qdl634=
+github.com/zaf/g711 v0.0.0-20190814101024-76a4a538f52b/go.mod h1:T2h1zV50R/q0CVYnsQOQ6L7P4a2ZxH47ixWcMXFGyx8=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
+gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=

+ 180 - 0
go-api-examples/real-time-speech-recognition-from-microphone/main.go

@@ -0,0 +1,180 @@
+package main
+
+import (
+	"errors"
+	"fmt"
+	"github.com/gordonklaus/portaudio"
+	sherpa "github.com/k2-fsa/sherpa-ncnn-go/sherpa_ncnn"
+	flag "github.com/spf13/pflag"
+	"log"
+	"os"
+	"strings"
+)
+
+func main() {
+	err := portaudio.Initialize()
+	if err != nil {
+		log.Fatalf("Unable to initialize portaudio: %v\n", err)
+	}
+	defer portaudio.Terminate()
+
+	default_device, err := portaudio.DefaultInputDevice()
+	if err != nil {
+		log.Fatal("Failed to get default input device: %v\n", err)
+	}
+	fmt.Printf("Select default input device: %s\n", default_device.Name)
+	param := portaudio.StreamParameters{}
+	param.Input.Device = default_device
+	param.Input.Channels = 1
+	param.Input.Latency = default_device.DefaultLowInputLatency
+
+	param.SampleRate = 16000
+	param.FramesPerBuffer = 0
+	param.Flags = portaudio.ClipOff
+
+	config := sherpa.RecognizerConfig{}
+	config.Feat = sherpa.FeatureConfig{SampleRate: 16000, FeatureDim: 80}
+
+	flag.StringVar(&config.Model.EncoderParam, "encoder-param", "", "Path to the encoder.ncnn.param")
+	flag.StringVar(&config.Model.EncoderBin, "encoder-bin", "", "Path to the encoder.ncnn.bin")
+	flag.StringVar(&config.Model.DecoderParam, "decoder-param", "", "Path to the decoder.ncnn.param")
+	flag.StringVar(&config.Model.DecoderBin, "decoder-bin", "", "Path to the decoder.ncnn.bin")
+	flag.StringVar(&config.Model.JoinerParam, "joiner-param", "", "Path to the joiner.ncnn.param")
+	flag.StringVar(&config.Model.JoinerBin, "joiner-bin", "", "Path to the joiner.ncnn.bin")
+
+	flag.StringVar(&config.Model.Tokens, "tokens", "", "Path to the tokens file")
+	flag.IntVar(&config.Model.NumThreads, "num-threads", 1, "Number of threads for computing")
+	flag.StringVar(&config.Decoder.DecodingMethod, "decoding-method", "greedy_search", "Decoding method. Possible values: greedy_search, modified_beam_search")
+	flag.IntVar(&config.Decoder.NumActivePaths, "num-active-paths", 4, "Used only when --decoding-method is modified_beam_search")
+
+	flag.IntVar(&config.EnableEndpoint, "enable-endpoint", 1, "Whether to enable endpoint")
+	flag.Float32Var(&config.Rule1MinTrailingSilence, "rule1-min-trailing-silence", 2.4, "Threshold for rule1")
+	flag.Float32Var(&config.Rule2MinTrailingSilence, "rule2-min-trailing-silence", 1.2, "Threshold for rule2")
+	flag.Float32Var(&config.Rule3MinUtteranceLength, "rule3-min-utterance-length", 20, "Threshold for rule3")
+
+	flag.Parse()
+
+	checkConfig(&config)
+
+	log.Println("Initializing recognizer")
+	recognizer := sherpa.NewRecognizer(&config)
+	log.Println("Recognizer created!")
+	defer sherpa.DeleteRecognizer(recognizer)
+
+	stream := sherpa.NewStream(recognizer)
+	defer sherpa.DeleteStream(stream)
+
+	// you can choose another value for 0.1 if you want
+	samplesPerCall := int32(param.SampleRate * 0.1) // 0.1 second
+
+	samples := make([]float32, samplesPerCall)
+
+	s, err := portaudio.OpenStream(param, samples)
+	if err != nil {
+		log.Fatalf("Failed to open the stream")
+	}
+	defer s.Close()
+	chk(s.Start())
+
+	var last_text string
+
+	segment_idx := 0
+
+	fmt.Println("Started! Please speak")
+
+	for {
+		chk(s.Read())
+		stream.AcceptWaveform(int(param.SampleRate), samples)
+
+		for recognizer.IsReady(stream) {
+			recognizer.Decode(stream)
+		}
+
+		text := recognizer.GetResult(stream).Text
+		if len(text) != 0 && last_text != text {
+			last_text = strings.ToLower(text)
+			fmt.Printf("\r%d: %s", segment_idx, last_text)
+		}
+
+		if recognizer.IsEndpoint(stream) {
+			if len(text) != 0 {
+				segment_idx++
+				fmt.Println()
+			}
+			recognizer.Reset(stream)
+		}
+	}
+
+	chk(s.Stop())
+}
+
+func chk(err error) {
+	if err != nil {
+		panic(err)
+	}
+}
+
+func checkConfig(config *sherpa.RecognizerConfig) {
+	// --encoder-param
+	if config.Model.EncoderParam == "" {
+		log.Fatal("Please provide --encoder-param")
+	}
+
+	if _, err := os.Stat(config.Model.EncoderParam); errors.Is(err, os.ErrNotExist) {
+		log.Fatalf("--encoder-param %v does not exist", config.Model.EncoderParam)
+	}
+
+	// --encoder-bin
+	if config.Model.EncoderBin == "" {
+		log.Fatal("Please provide --encoder-bin")
+	}
+
+	if _, err := os.Stat(config.Model.EncoderBin); errors.Is(err, os.ErrNotExist) {
+		log.Fatalf("--encoder-bin %v does not exist", config.Model.EncoderBin)
+	}
+
+	// --decoder-param
+	if config.Model.DecoderParam == "" {
+		log.Fatal("Please provide --decoder-param")
+	}
+
+	if _, err := os.Stat(config.Model.DecoderParam); errors.Is(err, os.ErrNotExist) {
+		log.Fatalf("--decoder-param %v does not exist", config.Model.DecoderParam)
+	}
+
+	// --decoder-bin
+	if config.Model.DecoderBin == "" {
+		log.Fatal("Please provide --decoder-bin")
+	}
+
+	if _, err := os.Stat(config.Model.DecoderBin); errors.Is(err, os.ErrNotExist) {
+		log.Fatalf("--decoder-bin %v does not exist", config.Model.DecoderBin)
+	}
+
+	// --joiner-param
+	if config.Model.JoinerParam == "" {
+		log.Fatal("Please provide --joiner-param")
+	}
+
+	if _, err := os.Stat(config.Model.JoinerParam); errors.Is(err, os.ErrNotExist) {
+		log.Fatalf("--joiner-param %v does not exist", config.Model.JoinerParam)
+	}
+
+	// --joiner-bin
+	if config.Model.JoinerBin == "" {
+		log.Fatal("Please provide --joiner-bin")
+	}
+
+	if _, err := os.Stat(config.Model.JoinerBin); errors.Is(err, os.ErrNotExist) {
+		log.Fatalf("--joiner-bin %v does not exist", config.Model.JoinerBin)
+	}
+
+	// --tokens
+	if config.Model.Tokens == "" {
+		log.Fatal("Please provide --tokens")
+	}
+
+	if _, err := os.Stat(config.Model.Tokens); errors.Is(err, os.ErrNotExist) {
+		log.Fatalf("--tokens %v does not exist", config.Model.Tokens)
+	}
+}

+ 22 - 0
go-api-examples/real-time-speech-recognition-from-microphone/run.sh

@@ -0,0 +1,22 @@
+#!/usr/bin/env bash
+
+if [ ! -d ./sherpa-ncnn-streaming-zipformer-bilingual-zh-en-2023-02-13 ]; then
+  echo "Please download the pre-trained model for testing."
+  echo "You can refer to"
+  echo ""
+  echo "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"
+  echo ""
+  echo "for help"
+  exit 1
+fi
+
+go build
+
+./real-time-speech-recognition-from-microphone \
+  --encoder-param ./sherpa-ncnn-streaming-zipformer-bilingual-zh-en-2023-02-13/encoder_jit_trace-pnnx.ncnn.param \
+  --encoder-bin ./sherpa-ncnn-streaming-zipformer-bilingual-zh-en-2023-02-13/encoder_jit_trace-pnnx.ncnn.bin \
+  --decoder-param ./sherpa-ncnn-streaming-zipformer-bilingual-zh-en-2023-02-13/decoder_jit_trace-pnnx.ncnn.param \
+  --decoder-bin ./sherpa-ncnn-streaming-zipformer-bilingual-zh-en-2023-02-13/decoder_jit_trace-pnnx.ncnn.bin \
+  --joiner-param ./sherpa-ncnn-streaming-zipformer-bilingual-zh-en-2023-02-13/joiner_jit_trace-pnnx.ncnn.param \
+  --joiner-bin ./sherpa-ncnn-streaming-zipformer-bilingual-zh-en-2023-02-13/joiner_jit_trace-pnnx.ncnn.bin \
+  --tokens ./sherpa-ncnn-streaming-zipformer-bilingual-zh-en-2023-02-13/tokens.txt

+ 160 - 0
scripts/go/release.sh

@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+set -ex
+
+git config --global user.email "csukuangfj@gmail.com"
+git config --global user.name "Fangjun Kuang"
+
+SHERPA_NCNN_VERSION=v$(grep "SHERPA_NCNN_VERSION" ./CMakeLists.txt  | cut -d " " -f 2  | cut -d '"' -f 2)
+
+echo "========================================================================="
+
+git clone git@github.com:k2-fsa/sherpa-ncnn-go-linux.git
+
+echo "Copy libs for Linux x86_64"
+
+rm -rf sherpa-ncnn-go-linux/lib/x86_64-unknown-linux-gnu/lib*
+
+cp -v ./linux_x86_64/sherpa_ncnn/lib/libkaldi-native-fbank-core.so sherpa-ncnn-go-linux/lib/x86_64-unknown-linux-gnu/
+cp -v ./linux_x86_64/sherpa_ncnn/lib/libncnn.so sherpa-ncnn-go-linux/lib/x86_64-unknown-linux-gnu/
+cp -v ./linux_x86_64/sherpa_ncnn/lib/libsherpa-ncnn-c-api.so sherpa-ncnn-go-linux/lib/x86_64-unknown-linux-gnu/
+cp -v ./linux_x86_64/sherpa_ncnn/lib/libsherpa-ncnn-core.so sherpa-ncnn-go-linux/lib/x86_64-unknown-linux-gnu/
+cp -v ./linux_x86_64/sherpa_ncnn.libs/libgomp*.so* sherpa-ncnn-go-linux/lib/x86_64-unknown-linux-gnu/
+
+echo "Copy libs for Linux aarch64"
+
+rm -rf sherpa-ncnn-go-linux/lib/aarch64-unknown-linux-gnu/lib*
+
+cp -v ./linux_aarch64/sherpa_ncnn/lib/libkaldi-native-fbank-core.so sherpa-ncnn-go-linux/lib/aarch64-unknown-linux-gnu/
+cp -v ./linux_aarch64/sherpa_ncnn/lib/libncnn.so sherpa-ncnn-go-linux/lib/aarch64-unknown-linux-gnu/
+cp -v ./linux_aarch64/sherpa_ncnn/lib/libsherpa-ncnn-c-api.so sherpa-ncnn-go-linux/lib/aarch64-unknown-linux-gnu/
+cp -v ./linux_aarch64/sherpa_ncnn/lib/libsherpa-ncnn-core.so sherpa-ncnn-go-linux/lib/aarch64-unknown-linux-gnu/
+cp -v ./linux_aarch64/sherpa_ncnn.libs/libgomp*.so* sherpa-ncnn-go-linux/lib/aarch64-unknown-linux-gnu/
+
+echo "Copy sources for Linux"
+cp sherpa-ncnn/c-api/c-api.h sherpa-ncnn-go-linux/
+cp scripts/go/sherpa_ncnn.go sherpa-ncnn-go-linux/
+
+pushd sherpa-ncnn-go-linux
+tag=$(git describe --abbrev=0 --tags)
+if [[ x"$VERSION" == x"auto" ]]; then
+  # this is a pre-release
+  if [[ $tag == ${SHERPA_NCNN_VERSION}* ]]; then
+    # echo we have already release pre-release before, so just increment it
+    last=$(echo $tag | rev | cut -d'.' -f 1 | rev)
+    new_last=$((last+1))
+    new_tag=${SHERPA_NCNN_VERSION}-alpha.${new_last}
+  else
+    new_tag=${SHERPA_NCNN_VERSION}-alpha.1
+  fi
+else
+  new_tag=$VERSION
+fi
+
+echo "new_tag: $new_tag"
+git add .
+git status
+git commit -m "Release $new_tag" && \
+git push && \
+git tag $new_tag && \
+git push origin $new_tag || true
+
+popd
+echo "========================================================================="
+
+git clone git@github.com:k2-fsa/sherpa-ncnn-go-macos.git
+
+echo "Copy libs for macOS x86_64"
+rm -rf sherpa-ncnn-go-macos/lib/x86_64-apple-darwin/lib*
+cp -v ./macos-x86_64/libkaldi-native-fbank-core.dylib sherpa-ncnn-go-macos/lib/x86_64-apple-darwin
+cp -v ./macos-x86_64/libncnn.dylib sherpa-ncnn-go-macos/lib/x86_64-apple-darwin
+cp -v ./macos-x86_64/libsherpa-ncnn-c-api.dylib sherpa-ncnn-go-macos/lib/x86_64-apple-darwin
+cp -v ./macos-x86_64/libsherpa-ncnn-core.dylib sherpa-ncnn-go-macos/lib/x86_64-apple-darwin
+
+echo "Copy libs for macOS arm64"
+rm -rf sherpa-ncnn-go-macos/lib/aarch64-apple-darwin/lib*
+cp -v ./macos-arm64/libkaldi-native-fbank-core.dylib sherpa-ncnn-go-macos/lib/aarch64-apple-darwin
+cp -v ./macos-arm64/libncnn.dylib sherpa-ncnn-go-macos/lib/aarch64-apple-darwin
+cp -v ./macos-arm64/libsherpa-ncnn-c-api.dylib sherpa-ncnn-go-macos/lib/aarch64-apple-darwin
+cp -v ./macos-arm64/libsherpa-ncnn-core.dylib sherpa-ncnn-go-macos/lib/aarch64-apple-darwin
+
+echo "Copy sources for macOS"
+cp sherpa-ncnn/c-api/c-api.h sherpa-ncnn-go-macos/
+cp scripts/go/sherpa_ncnn.go sherpa-ncnn-go-macos/
+
+pushd sherpa-ncnn-go-macos
+tag=$(git describe --abbrev=0 --tags)
+if [[ x"$VERSION" == x"auto" ]]; then
+  # this is a pre-release
+  if [[ $tag == ${SHERPA_NCNN_VERSION}* ]]; then
+    # echo we have already release pre-release before, so just increment it
+    last=$(echo $tag | rev | cut -d'.' -f 1 | rev)
+    new_last=$((last+1))
+    new_tag=${SHERPA_NCNN_VERSION}-alpha.${new_last}
+  else
+    new_tag=${SHERPA_NCNN_VERSION}-alpha.1
+  fi
+else
+  new_tag=$VERSION
+fi
+
+echo "new_tag: $new_tag"
+git add .
+git status
+git commit -m "Release $new_tag" && \
+git push && \
+git tag $new_tag && \
+git push origin $new_tag || true
+
+popd
+echo "========================================================================="
+
+git clone git@github.com:k2-fsa/sherpa-ncnn-go-windows.git
+echo "Copy libs for Windows x86_64"
+rm -fv sherpa-ncnn-go-windows/lib/x86_64-pc-windows-gnu/*
+cp -v ./windows-x64/kaldi-native-fbank-core.dll sherpa-ncnn-go-windows/lib/x86_64-pc-windows-gnu
+cp -v ./windows-x64/ncnn.dll sherpa-ncnn-go-windows/lib/x86_64-pc-windows-gnu
+cp -v ./windows-x64/sherpa-ncnn-c-api.dll sherpa-ncnn-go-windows/lib/x86_64-pc-windows-gnu
+cp -v ./windows-x64/sherpa-ncnn-core.dll sherpa-ncnn-go-windows/lib/x86_64-pc-windows-gnu
+
+echo "Copy libs for Windows x86"
+rm -fv sherpa-ncnn-go-windows/lib/i686-pc-windows-gnu/*
+cp -v ./windows-win32/kaldi-native-fbank-core.dll sherpa-ncnn-go-windows/lib/i686-pc-windows-gnu
+cp -v ./windows-win32/ncnn.dll sherpa-ncnn-go-windows/lib/i686-pc-windows-gnu
+cp -v ./windows-win32/sherpa-ncnn-c-api.dll sherpa-ncnn-go-windows/lib/i686-pc-windows-gnu
+cp -v ./windows-win32/sherpa-ncnn-core.dll sherpa-ncnn-go-windows/lib/i686-pc-windows-gnu
+
+echo "Copy sources for Windows"
+cp sherpa-ncnn/c-api/c-api.h sherpa-ncnn-go-windows/
+cp scripts/go/sherpa_ncnn.go sherpa-ncnn-go-windows/
+
+pushd sherpa-ncnn-go-windows
+tag=$(git describe --abbrev=0 --tags)
+if [[ x"$VERSION" == x"auto" ]]; then
+  # this is a pre-release
+  if [[ $tag == ${SHERPA_NCNN_VERSION}* ]]; then
+    # echo we have already release pre-release before, so just increment it
+    last=$(echo $tag | rev | cut -d'.' -f 1 | rev)
+    new_last=$((last+1))
+    new_tag=${SHERPA_NCNN_VERSION}-alpha.${new_last}
+  else
+    new_tag=${SHERPA_NCNN_VERSION}-alpha.1
+  fi
+else
+  new_tag=$VERSION
+fi
+
+echo "new_tag: $new_tag"
+git add .
+git status
+git commit -m "Release $new_tag" && \
+git push && \
+git tag $new_tag && \
+git push origin $new_tag || true
+
+popd
+
+echo "========================================================================="
+
+
+rm -fv ~/.ssh/github

+ 243 - 0
scripts/go/sherpa_ncnn.go

@@ -0,0 +1,243 @@
+/*
+Speech recognition with [Next-gen Kaldi].
+
+[sherpa-ncnn] is an open-source speech recognition framework for [Next-gen Kaldi].
+It depends only on [ncnn], supporting both streaming and non-streaming
+speech recognition.
+
+It does not need to access the network during recognition and everything
+runs locally.
+
+It supports a variety of platforms, such as Linux (x86_64, aarch64, arm),
+Windows (x86_64, x86), macOS (x86_64, arm64), RISC-V, etc.
+
+Usage examples:
+
+ 1. Real-time speech recognition from a microphone
+
+    Please see
+    https://github.com/k2-fsa/sherpa-ncnn/tree/master/go-api-examples/real-time-speech-recognition-from-microphone
+
+ 2. Decode a file
+
+    Please see
+    https://github.com/k2-fsa/sherpa-ncnn/tree/master/go-api-examples/decode-file
+
+[sherpa-ncnn]: https://github.com/k2-fsa/sherpa-ncnn
+[ncnn]: https://github.com/tencent/ncnn
+[Next-gen Kaldi]: https://github.com/k2-fsa/
+*/
+package sherpa_ncnn
+
+// #include <stdlib.h>
+// #include "c-api.h"
+import "C"
+import "unsafe"
+
+// Please refer to
+// https://k2-fsa.github.io/sherpa/ncnn/pretrained_models/
+// to download pre-trained models
+type ModelConfig struct {
+	EncoderParam string // Path to the encoder.ncnn.param
+	EncoderBin   string // Path to the encoder.ncnn.bin
+	DecoderParam string // Path to the decoder.ncnn.param
+	DecoderBin   string // Path to the decoder.ncnn.bin
+	JoinerParam  string // Path to the joiner.ncnn.param
+	JoinerBin    string // Path to the joiner.ncnn.bin
+	Tokens       string // Path to tokens.txt
+	NumThreads   int    // Number of threads to use for neural network computation
+}
+
+// Configuration for the feature extractor
+type FeatureConfig struct {
+	// Sample rate expected by the model. It is 16000 for all
+	// pre-trained models provided by us
+	SampleRate int
+	// Feature dimension expected by the model. It is 80 for all
+	// pre-trained models provided by us
+	FeatureDim int
+}
+
+// Configuration for the beam search decoder
+type DecoderConfig struct {
+	// Decoding method. Supported values are:
+	// greedy_search, modified_beam_search
+	DecodingMethod string
+
+	// Number of active paths for modified_beam_search.
+	// It is ignored when decoding_method is greedy_search.
+	NumActivePaths int
+}
+
+// Configuration for the online/streaming recognizer.
+type RecognizerConfig struct {
+	Feat    FeatureConfig
+	Model   ModelConfig
+	Decoder DecoderConfig
+
+	EnableEndpoint int // 1 to enable endpoint detection.
+
+	// Please see
+	// https://k2-fsa.github.io/sherpa/ncnn/endpoint.html
+	// for the meaning of Rule1MinTrailingSilence, Rule2MinTrailingSilence
+	// and Rule3MinUtteranceLength.
+	Rule1MinTrailingSilence float32
+	Rule2MinTrailingSilence float32
+	Rule3MinUtteranceLength float32
+}
+
+// It contains the recognition result for a online stream.
+type RecognizerResult struct {
+	Text string
+}
+
+// The online recognizer class. It wraps a pointer from C.
+type Recognizer struct {
+	impl *C.struct_SherpaNcnnRecognizer
+}
+
+// The online stream class. It wraps a pointer from C.
+type Stream struct {
+	impl *C.struct_SherpaNcnnStream
+}
+
+// Free the internal pointer inside the recognizer to avoid memory leak.
+func DeleteRecognizer(recognizer *Recognizer) {
+	C.DestroyRecognizer(recognizer.impl)
+	recognizer.impl = nil
+}
+
+// The user is responsible to invoke [DeleteRecognizer]() to free
+// the returned recognizer to avoid memory leak
+func NewRecognizer(config *RecognizerConfig) *Recognizer {
+	c := C.struct_SherpaNcnnRecognizerConfig{}
+	c.feat_config.sampling_rate = C.float(config.Feat.SampleRate)
+	c.feat_config.feature_dim = C.int(config.Feat.FeatureDim)
+
+	c.model_config.encoder_param = C.CString(config.Model.EncoderParam)
+	defer C.free(unsafe.Pointer(c.model_config.encoder_param))
+
+	c.model_config.encoder_bin = C.CString(config.Model.EncoderBin)
+	defer C.free(unsafe.Pointer(c.model_config.encoder_bin))
+
+	c.model_config.decoder_param = C.CString(config.Model.DecoderParam)
+	defer C.free(unsafe.Pointer(c.model_config.decoder_param))
+
+	c.model_config.decoder_bin = C.CString(config.Model.DecoderBin)
+	defer C.free(unsafe.Pointer(c.model_config.decoder_bin))
+
+	c.model_config.joiner_param = C.CString(config.Model.JoinerParam)
+	defer C.free(unsafe.Pointer(c.model_config.joiner_param))
+
+	c.model_config.joiner_bin = C.CString(config.Model.JoinerBin)
+	defer C.free(unsafe.Pointer(c.model_config.joiner_bin))
+
+	c.model_config.tokens = C.CString(config.Model.Tokens)
+	defer C.free(unsafe.Pointer(c.model_config.tokens))
+
+	c.model_config.use_vulkan_compute = C.int(0)
+	c.model_config.num_threads = C.int(config.Model.NumThreads)
+
+	c.decoder_config.decoding_method = C.CString(config.Decoder.DecodingMethod)
+	defer C.free(unsafe.Pointer(c.decoder_config.decoding_method))
+
+	c.decoder_config.num_active_paths = C.int(config.Decoder.NumActivePaths)
+
+	c.enable_endpoint = C.int(config.EnableEndpoint)
+	c.rule1_min_trailing_silence = C.float(config.Rule1MinTrailingSilence)
+	c.rule2_min_trailing_silence = C.float(config.Rule2MinTrailingSilence)
+	c.rule3_min_utterance_length = C.float(config.Rule3MinUtteranceLength)
+
+	recognizer := &Recognizer{}
+	recognizer.impl = C.CreateRecognizer(&c)
+
+	return recognizer
+}
+
+// Delete the internal pointer inside the stream to avoid memory leak.
+func DeleteStream(stream *Stream) {
+	C.DestroyStream(stream.impl)
+	stream.impl = nil
+}
+
+// The user is responsible to invoke [DeleteStream]() to free
+// the returned stream to avoid memory leak
+func NewStream(recognizer *Recognizer) *Stream {
+	stream := &Stream{}
+	stream.impl = C.CreateStream(recognizer.impl)
+	return stream
+}
+
+// Input audio samples for the stream.
+//
+// sampleRate is the actual sample rate of the input audio samples. If it
+// is different from the sample rate expected by the feature extractor, we will
+// do resampling inside.
+//
+// samples contains audio samples. Each sample is in the range [-1, 1]
+func (s *Stream) AcceptWaveform(sampleRate int, samples []float32) {
+	C.AcceptWaveform(s.impl, C.float(sampleRate), (*C.float)(&samples[0]), C.int(len(samples)))
+}
+
+// Signal that there will be no incoming audio samples.
+// After calling this function, you cannot call [Stream.AcceptWaveform] any longer.
+//
+// The main purpose of this function is to flush the remaining audio samples
+// buffered inside for feature extraction.
+func (s *Stream) InputFinished() {
+	C.InputFinished(s.impl)
+}
+
+// Check whether the stream has enough feature frames for decoding.
+// Return true if this stream is ready for decoding. Return false otherwise.
+//
+// You will usually use it like below:
+//
+//	for recognizer.IsReady(s) {
+//	   recognizer.Decode(s)
+//	}
+func (recognizer *Recognizer) IsReady(s *Stream) bool {
+	return C.IsReady(recognizer.impl, s.impl) == 1
+}
+
+// Return true if an endpoint is detected.
+//
+// You usually use it like below:
+//
+//	if recognizer.IsEndpoint(s) {
+//	   // do your own stuff after detecting an endpoint
+//
+//	   recognizer.Reset(s)
+//	}
+func (recognizer *Recognizer) IsEndpoint(s *Stream) bool {
+	return C.IsEndpoint(recognizer.impl, s.impl) == 1
+}
+
+// After calling this function, the internal neural network model states
+// are reset and IsEndpoint(s) would return false. GetResult(s) would also
+// return an empty string.
+func (recognizer *Recognizer) Reset(s *Stream) {
+	C.Reset(recognizer.impl, s.impl)
+}
+
+// Decode the stream. Before calling this function, you have to ensure
+// that recognizer.IsReady(s) returns true. Otherwise, you will be SAD.
+//
+// You usually use it like below:
+//
+//	for recognizer.IsReady(s) {
+//	  recognizer.Decode(s)
+//	}
+func (recognizer *Recognizer) Decode(s *Stream) {
+	C.Decode(recognizer.impl, s.impl)
+}
+
+// Get the current result of stream since the last invoke of Reset()
+func (recognizer *Recognizer) GetResult(s *Stream) *RecognizerResult {
+	p := C.GetResult(recognizer.impl, s.impl)
+	defer C.DestroyResult(p)
+	result := &RecognizerResult{}
+	result.Text = C.GoString(p.text)
+
+	return result
+}

+ 5 - 0
scripts/go/ssh_config

@@ -0,0 +1,5 @@
+Host github.com
+  Hostname github.com
+  User git
+  IdentityFile ~/.ssh/github
+  StrictHostKeyChecking no

+ 1 - 1
sherpa-ncnn/csrc/alsa.cc

@@ -43,7 +43,7 @@ void ToFloat32(const std::vector<int32_t> &in, int32_t channel_to_use,
 
   int32_t n = in.size();
   for (int32_t i = 0, k = 0; i < n; i += num_channels, ++k) {
-    (*out)[k] = in[i + channel_to_use] / float(1 << 31);
+    (*out)[k] = in[i + channel_to_use] / static_cast<float>(1 << 31);
   }
 }
 

+ 6 - 6
sherpa-ncnn/csrc/conv-emformer-model.cc

@@ -31,9 +31,9 @@ ConvEmformerModel::ConvEmformerModel(const ModelConfig &config) {
     joiner_.opt.use_vulkan_compute = true;
     NCNN_LOGE("Use GPU");
   } else {
-    NCNN_LOGE("Don't Use GPU. has_gpu: %d, config.use_vulkan_compute: %d",
-              static_cast<int32_t>(has_gpu),
-              static_cast<int32_t>(config.use_vulkan_compute));
+    // NCNN_LOGE("Don't Use GPU. has_gpu: %d, config.use_vulkan_compute: %d",
+    //           static_cast<int32_t>(has_gpu),
+    //           static_cast<int32_t>(config.use_vulkan_compute));
   }
 
   InitEncoder(config.encoder_param, config.encoder_bin);
@@ -63,9 +63,9 @@ ConvEmformerModel::ConvEmformerModel(AAssetManager *mgr,
     joiner_.opt.use_vulkan_compute = true;
     NCNN_LOGE("Use GPU");
   } else {
-    NCNN_LOGE("Don't Use GPU. has_gpu: %d, config.use_vulkan_compute: %d",
-              static_cast<int32_t>(has_gpu),
-              static_cast<int32_t>(config.use_vulkan_compute));
+    // NCNN_LOGE("Don't Use GPU. has_gpu: %d, config.use_vulkan_compute: %d",
+    //           static_cast<int32_t>(has_gpu),
+    //           static_cast<int32_t>(config.use_vulkan_compute));
   }
 
   InitEncoder(mgr, config.encoder_param, config.encoder_bin);

+ 6 - 6
sherpa-ncnn/csrc/lstm-model.cc

@@ -41,9 +41,9 @@ LstmModel::LstmModel(const ModelConfig &config) {
     joiner_.opt.use_vulkan_compute = true;
     NCNN_LOGE("Use GPU");
   } else {
-    NCNN_LOGE("Don't Use GPU. has_gpu: %d, config.use_vulkan_compute: %d",
-              static_cast<int32_t>(has_gpu),
-              static_cast<int32_t>(config.use_vulkan_compute));
+    // NCNN_LOGE("Don't Use GPU. has_gpu: %d, config.use_vulkan_compute: %d",
+    //           static_cast<int32_t>(has_gpu),
+    //           static_cast<int32_t>(config.use_vulkan_compute));
   }
 
   InitEncoder(config.encoder_param, config.encoder_bin);
@@ -72,9 +72,9 @@ LstmModel::LstmModel(AAssetManager *mgr, const ModelConfig &config) {
     joiner_.opt.use_vulkan_compute = true;
     NCNN_LOGE("Use GPU");
   } else {
-    NCNN_LOGE("Don't Use GPU. has_gpu: %d, config.use_vulkan_compute: %d",
-              static_cast<int32_t>(has_gpu),
-              static_cast<int32_t>(config.use_vulkan_compute));
+    // NCNN_LOGE("Don't Use GPU. has_gpu: %d, config.use_vulkan_compute: %d",
+    //           static_cast<int32_t>(has_gpu),
+    //           static_cast<int32_t>(config.use_vulkan_compute));
   }
 
   InitEncoder(mgr, config.encoder_param, config.encoder_bin);

+ 6 - 6
sherpa-ncnn/csrc/zipformer-model.cc

@@ -31,9 +31,9 @@ ZipformerModel::ZipformerModel(const ModelConfig &config) {
     joiner_.opt.use_vulkan_compute = true;
     NCNN_LOGE("Use GPU");
   } else {
-    NCNN_LOGE("Don't Use GPU. has_gpu: %d, config.use_vulkan_compute: %d",
-              static_cast<int32_t>(has_gpu),
-              static_cast<int32_t>(config.use_vulkan_compute));
+    // NCNN_LOGE("Don't Use GPU. has_gpu: %d, config.use_vulkan_compute: %d",
+    //           static_cast<int32_t>(has_gpu),
+    //           static_cast<int32_t>(config.use_vulkan_compute));
   }
 
   InitEncoder(config.encoder_param, config.encoder_bin);
@@ -62,9 +62,9 @@ ZipformerModel::ZipformerModel(AAssetManager *mgr, const ModelConfig &config) {
     joiner_.opt.use_vulkan_compute = true;
     NCNN_LOGE("Use GPU");
   } else {
-    NCNN_LOGE("Don't Use GPU. has_gpu: %d, config.use_vulkan_compute: %d",
-              static_cast<int32_t>(has_gpu),
-              static_cast<int32_t>(config.use_vulkan_compute));
+    // NCNN_LOGE("Don't Use GPU. has_gpu: %d, config.use_vulkan_compute: %d",
+    //           static_cast<int32_t>(has_gpu),
+    //           static_cast<int32_t>(config.use_vulkan_compute));
   }
 
   InitEncoder(mgr, config.encoder_param, config.encoder_bin);

+ 9 - 4
sherpa-ncnn/python/csrc/recognizer.cc

@@ -20,6 +20,7 @@
 
 #include <memory>
 #include <string>
+#include <vector>
 
 #include "sherpa-ncnn/csrc/recognizer.h"
 
@@ -47,11 +48,15 @@ static void PybindRecognitionResult(py::module *m) {
       .def_property_readonly(
           "text", [](PyClass &self) -> std::string { return self.text; })
       .def_property_readonly(
-          "tokens", [](PyClass &self) -> std::vector<int> { return self.tokens; })
+          "tokens",
+          [](PyClass &self) -> std::vector<int> { return self.tokens; })
+      .def_property_readonly("stokens",
+                             [](PyClass &self) -> std::vector<std::string> {
+                               return self.stokens;
+                             })
       .def_property_readonly(
-          "stokens", [](PyClass &self) -> std::vector<std::string> { return self.stokens; })
-      .def_property_readonly(
-          "timestamps", [](PyClass &self) -> std::vector<float> { return self.timestamps; });
+          "timestamps",
+          [](PyClass &self) -> std::vector<float> { return self.timestamps; });
 }
 
 static void PybindRecognizerConfig(py::module *m) {