瀏覽代碼

Change ffmpeg code from c to c++. (#152)

Winlin 2 年之前
父節點
當前提交
e66478cbd4
共有 4 個文件被更改,包括 355 次插入420 次删除
  1. 3 3
      .github/workflows/linux.yaml
  2. 0 57
      ffmpeg-examples/Makefile
  3. 0 49
      ffmpeg-examples/run.sh
  4. 352 311
      ffmpeg-examples/sherpa-ncnn-ffmpeg.cc

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

@@ -84,8 +84,8 @@ jobs:
           ls -lh bin/decode-file-c-api
           file bin/decode-file-c-api
 
-          cd ../ffmpeg-examples
-          make
+          ls -lh bin/sherpa-ncnn-ffmpeg
+          file bin/sherpa-ncnn-ffmpeg
 
       - name: Upload binary sherpa-ncnn and sherpa-ncnn-microphone
         uses: actions/upload-artifact@v2
@@ -95,7 +95,7 @@ jobs:
 
       - name: Test sherpa-ncnn-ffmpeg
         run: |
-          export PATH=$PWD/ffmpeg-examples:$PATH
+          export PATH=$PWD/build/bin:$PATH
           export EXE=sherpa-ncnn-ffmpeg
 
           .github/scripts/run-test.sh

+ 0 - 57
ffmpeg-examples/Makefile

@@ -1,57 +0,0 @@
-CC=g++
-GDB ?= FALSE
-
-# use pkg-config for getting CFLAGS and LDLIBS
-SHARED_LIBS=libavdevice                          \
-            libavformat                          \
-            libavfilter                          \
-            libavcodec                           \
-            libswresample                        \
-            libswscale                           \
-            libavutil
-
-ifeq ($(GDB), TRUE)
-	OPTFLAG += -g
-endif
-
-CFLAGS := $(shell pkg-config --cflags $(SHARED_LIBS)) -I.. -Wall -std=c++11 ${OPTFLAG}
-LDLIBS := $(shell pkg-config --libs $(SHARED_LIBS)) -L../build/lib -lsherpa-ncnn-c-api -lsherpa-ncnn-core -lkaldi-native-fbank-core -lncnn -lportaudio -lpthread -lm
-
-# Set the flag if not macOS, or it fails with "DSO missing from command line".
-ifneq ($(shell uname -s), Darwin)
-  CFLAGS += -fopenmp
-endif
-
-#Get libavutil version and extract major, minor and micro
-LIBAVUTIL_VERSION := $(shell pkg-config --modversion libavutil)
-LIBAVUTIL_MAJOR := $(shell echo "$(LIBAVUTIL_VERSION)" | awk -F. '{print $$1}')
-LIBAVUTIL_MINOR := $(shell echo "$(LIBAVUTIL_VERSION)" | awk -F. '{print $$2}')
-LIBAVUTIL_MICRO := $(shell echo "$(LIBAVUTIL_VERSION)" | awk -F. '{print $$3}')
-#Check if libavutil version is 57.28.100 or above
-FFMPEG_51_AND_ABOVE = $(shell echo "$(LIBAVUTIL_MAJOR) $(LIBAVUTIL_MINOR) $(LIBAVUTIL_MICRO)" | awk '{if ($$1 > 57 || ($$1 == 57 && $$2 > 28) || ($$1 == 57 && $$2 == 28 && $$3 >= 100)) print "TRUE"; else print "FALSE"}')
-ifeq ($(FFMPEG_51_AND_ABOVE), FALSE)
-$(error FFmpeg version should be n5.1 or above!)
-endif
-
-EXAMPLES=sherpa-ncnn-ffmpeg
-
-OBJS=$(addsuffix .o,$(EXAMPLES))
-
-.phony: all clean
-
-all: $(EXAMPLES)
-	@echo $(EXAMPLES)
-	$(RM) $(OBJS)
-
-$(EXAMPLES): $(OBJS)
-	$(CC) $(addsuffix .o,$@) $(CFLAGS) $(LDLIBS) -o $@
-
-%.o : %.cc
-	${CC} ${CFLAGS} -c -o $@ $<
-
-clean:
-	$(RM) $(EXAMPLES) $(OBJS)
-
-build_info:
-	@echo "libavutil version: $(LIBAVUTIL_VERSION)"
-	@echo "Supported examples: $(EXAMPLES)"

+ 0 - 49
ffmpeg-examples/run.sh

@@ -1,49 +0,0 @@
-#!/usr/bin/env bash
-
-set -ex
-
-if [ ! -d ./sherpa-ncnn-conv-emformer-transducer-2022-12-06 ]; 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/conv-emformer-transducer-models.html#csukuangfj-sherpa-ncnn-conv-emformer-transducer-2022-12-06-chinese-english"
-  echo "for help"
-  exit 1
-fi
-
-if [ ! -f ../build/lib/libsherpa-ncnn-core.a ]; then
-  echo "Please build sherpa-ncnn first. You can use"
-  echo ""
-  echo "  cd /path/to/sherpa-ncnn"
-  echo "  mkdir build"
-  echo "  cd build"
-  echo "  cmake .."
-  echo "  make -j4"
-  exit 1
-fi
-
-if [ ! -f ./sherpa-ncnn-ffmpeg ]; then
-  make
-fi
-
-../ffmpeg-examples/sherpa-ncnn-ffmpeg \
-  ./sherpa-ncnn-conv-emformer-transducer-2022-12-06/tokens.txt \
-  ./sherpa-ncnn-conv-emformer-transducer-2022-12-06/encoder_jit_trace-pnnx.ncnn.param \
-  ./sherpa-ncnn-conv-emformer-transducer-2022-12-06/encoder_jit_trace-pnnx.ncnn.bin \
-  ./sherpa-ncnn-conv-emformer-transducer-2022-12-06/decoder_jit_trace-pnnx.ncnn.param \
-  ./sherpa-ncnn-conv-emformer-transducer-2022-12-06/decoder_jit_trace-pnnx.ncnn.bin \
-  ./sherpa-ncnn-conv-emformer-transducer-2022-12-06/joiner_jit_trace-pnnx.ncnn.param \
-  ./sherpa-ncnn-conv-emformer-transducer-2022-12-06/joiner_jit_trace-pnnx.ncnn.bin \
-  ./sherpa-ncnn-conv-emformer-transducer-2022-12-06/test_wavs/0.wav
-
-
-../ffmpeg-examples/sherpa-ncnn-ffmpeg \
-  ./sherpa-ncnn-conv-emformer-transducer-2022-12-06/tokens.txt \
-  ./sherpa-ncnn-conv-emformer-transducer-2022-12-06/encoder_jit_trace-pnnx.ncnn.param \
-  ./sherpa-ncnn-conv-emformer-transducer-2022-12-06/encoder_jit_trace-pnnx.ncnn.bin \
-  ./sherpa-ncnn-conv-emformer-transducer-2022-12-06/decoder_jit_trace-pnnx.ncnn.param \
-  ./sherpa-ncnn-conv-emformer-transducer-2022-12-06/decoder_jit_trace-pnnx.ncnn.bin \
-  ./sherpa-ncnn-conv-emformer-transducer-2022-12-06/joiner_jit_trace-pnnx.ncnn.param \
-  ./sherpa-ncnn-conv-emformer-transducer-2022-12-06/joiner_jit_trace-pnnx.ncnn.bin \
-  https://huggingface.co/csukuangfj/sherpa-ncnn-conv-emformer-transducer-2022-12-06/resolve/main/test_wavs/0.wav
-

+ 352 - 311
ffmpeg-examples/sherpa-ncnn-ffmpeg.cc

@@ -15,14 +15,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+#include <signal.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 
-#include "sherpa-ncnn/c-api/c-api.h"
+#include <string>
+#include <cctype>  // std::tolower
 
+#include "sherpa-ncnn/csrc/display.h"
+#include "sherpa-ncnn/csrc/recognizer.h"
 
 /*
+ * The MIT License (MIT)
+ *
  * Copyright (c) 2010 Nicolas George
  * Copyright (c) 2011 Stefano Sabatini
  * Copyright (c) 2012 Clément Bœsch
@@ -58,18 +64,19 @@
 #ifdef __cplusplus
 extern "C" {
 #endif
-#include <libavutil/samplefmt.h>
 #include <libavcodec/avcodec.h>
-#include <libavformat/avformat.h>
 #include <libavfilter/buffersink.h>
 #include <libavfilter/buffersrc.h>
+#include <libavformat/avformat.h>
 #include <libavutil/channel_layout.h>
 #include <libavutil/opt.h>
+#include <libavutil/samplefmt.h>
 #ifdef __cplusplus
 }
 #endif
 
-static const char *filter_descr = "aresample=16000,aformat=sample_fmts=s16:channel_layouts=mono";
+static const char *filter_descr =
+    "aresample=16000,aformat=sample_fmts=s16:channel_layouts=mono";
 
 static AVFormatContext *fmt_ctx;
 static AVCodecContext *dec_ctx;
@@ -78,352 +85,386 @@ AVFilterContext *buffersrc_ctx;
 AVFilterGraph *filter_graph;
 static int audio_stream_index = -1;
 
-static int open_input_file(const char *filename)
-{
-    const AVCodec *dec;
-    int ret;
+static int open_input_file(const char *filename) {
+  const AVCodec *dec;
+  int ret;
 
-    if ((ret = avformat_open_input(&fmt_ctx, filename, NULL, NULL)) < 0) {
-        av_log(NULL, AV_LOG_ERROR, "Cannot open input file %s\n", filename);
-        return ret;
-    }
+  if ((ret = avformat_open_input(&fmt_ctx, filename, NULL, NULL)) < 0) {
+    av_log(NULL, AV_LOG_ERROR, "Cannot open input file %s\n", filename);
+    return ret;
+  }
 
-    if ((ret = avformat_find_stream_info(fmt_ctx, NULL)) < 0) {
-        av_log(NULL, AV_LOG_ERROR, "Cannot find stream information\n");
-        return ret;
-    }
+  if ((ret = avformat_find_stream_info(fmt_ctx, NULL)) < 0) {
+    av_log(NULL, AV_LOG_ERROR, "Cannot find stream information\n");
+    return ret;
+  }
 
-    /* select the audio stream */
-    ret = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, &dec, 0);
-    if (ret < 0) {
-        av_log(NULL, AV_LOG_ERROR, "Cannot find an audio stream in the input file\n");
-        return ret;
-    }
-    audio_stream_index = ret;
-
-    /* create decoding context */
-    dec_ctx = avcodec_alloc_context3(dec);
-    if (!dec_ctx)
-        return AVERROR(ENOMEM);
-    avcodec_parameters_to_context(dec_ctx, fmt_ctx->streams[audio_stream_index]->codecpar);
-
-    /* init the audio decoder */
-    if ((ret = avcodec_open2(dec_ctx, dec, NULL)) < 0) {
-        av_log(NULL, AV_LOG_ERROR, "Cannot open audio decoder\n");
-        return ret;
-    }
+  /* select the audio stream */
+  ret = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, &dec, 0);
+  if (ret < 0) {
+    av_log(NULL, AV_LOG_ERROR,
+           "Cannot find an audio stream in the input file\n");
+    return ret;
+  }
+  audio_stream_index = ret;
+
+  /* create decoding context */
+  dec_ctx = avcodec_alloc_context3(dec);
+  if (!dec_ctx) return AVERROR(ENOMEM);
+  avcodec_parameters_to_context(dec_ctx,
+                                fmt_ctx->streams[audio_stream_index]->codecpar);
+
+  /* init the audio decoder */
+  if ((ret = avcodec_open2(dec_ctx, dec, NULL)) < 0) {
+    av_log(NULL, AV_LOG_ERROR, "Cannot open audio decoder\n");
+    return ret;
+  }
 
-    return 0;
+  return 0;
 }
 
-static int init_filters(const char *filters_descr)
-{
-    char args[512];
-    int ret = 0;
-    const AVFilter *abuffersrc  = avfilter_get_by_name("abuffer");
-    const AVFilter *abuffersink = avfilter_get_by_name("abuffersink");
-    AVFilterInOut *outputs = avfilter_inout_alloc();
-    AVFilterInOut *inputs  = avfilter_inout_alloc();
-    static const enum AVSampleFormat out_sample_fmts[] = { AV_SAMPLE_FMT_S16, AV_SAMPLE_FMT_NONE };
-    static const int out_sample_rates[] = { 16000, -1 };
-    const AVFilterLink *outlink;
-    AVRational time_base = fmt_ctx->streams[audio_stream_index]->time_base;
-
-    filter_graph = avfilter_graph_alloc();
-    if (!outputs || !inputs || !filter_graph) {
-        ret = AVERROR(ENOMEM);
-        goto end;
-    }
+static int init_filters(const char *filters_descr) {
+  char args[512];
+  int ret = 0;
+  const AVFilter *abuffersrc = avfilter_get_by_name("abuffer");
+  const AVFilter *abuffersink = avfilter_get_by_name("abuffersink");
+  AVFilterInOut *outputs = avfilter_inout_alloc();
+  AVFilterInOut *inputs = avfilter_inout_alloc();
+  static const enum AVSampleFormat out_sample_fmts[] = {AV_SAMPLE_FMT_S16,
+                                                        AV_SAMPLE_FMT_NONE};
+  static const int out_sample_rates[] = {16000, -1};
+  const AVFilterLink *outlink;
+  AVRational time_base = fmt_ctx->streams[audio_stream_index]->time_base;
+
+  filter_graph = avfilter_graph_alloc();
+  if (!outputs || !inputs || !filter_graph) {
+    ret = AVERROR(ENOMEM);
+    goto end;
+  }
+
+  /* buffer audio source: the decoded frames from the decoder will be inserted
+   * here. */
+  if (dec_ctx->ch_layout.order == AV_CHANNEL_ORDER_UNSPEC)
+    av_channel_layout_default(&dec_ctx->ch_layout,
+                              dec_ctx->ch_layout.nb_channels);
+  ret = snprintf(args, sizeof(args),
+                 "time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=",
+                 time_base.num, time_base.den, dec_ctx->sample_rate,
+                 av_get_sample_fmt_name(dec_ctx->sample_fmt));
+  av_channel_layout_describe(&dec_ctx->ch_layout, args + ret,
+                             sizeof(args) - ret);
+  ret = avfilter_graph_create_filter(&buffersrc_ctx, abuffersrc, "in", args,
+                                     NULL, filter_graph);
+  if (ret < 0) {
+    av_log(NULL, AV_LOG_ERROR, "Cannot create audio buffer source\n");
+    goto end;
+  }
+
+  /* buffer audio sink: to terminate the filter chain. */
+  ret = avfilter_graph_create_filter(&buffersink_ctx, abuffersink, "out", NULL,
+                                     NULL, filter_graph);
+  if (ret < 0) {
+    av_log(NULL, AV_LOG_ERROR, "Cannot create audio buffer sink\n");
+    goto end;
+  }
+
+  ret = av_opt_set_int_list(buffersink_ctx, "sample_fmts", out_sample_fmts, -1,
+                            AV_OPT_SEARCH_CHILDREN);
+  if (ret < 0) {
+    av_log(NULL, AV_LOG_ERROR, "Cannot set output sample format\n");
+    goto end;
+  }
+
+  ret =
+      av_opt_set(buffersink_ctx, "ch_layouts", "mono", AV_OPT_SEARCH_CHILDREN);
+  if (ret < 0) {
+    av_log(NULL, AV_LOG_ERROR, "Cannot set output channel layout\n");
+    goto end;
+  }
+
+  ret = av_opt_set_int_list(buffersink_ctx, "sample_rates", out_sample_rates,
+                            -1, AV_OPT_SEARCH_CHILDREN);
+  if (ret < 0) {
+    av_log(NULL, AV_LOG_ERROR, "Cannot set output sample rate\n");
+    goto end;
+  }
+
+  /*
+   * Set the endpoints for the filter graph. The filter_graph will
+   * be linked to the graph described by filters_descr.
+   */
+
+  /*
+   * The buffer source output must be connected to the input pad of
+   * the first filter described by filters_descr; since the first
+   * filter input label is not specified, it is set to "in" by
+   * default.
+   */
+  outputs->name = av_strdup("in");
+  outputs->filter_ctx = buffersrc_ctx;
+  outputs->pad_idx = 0;
+  outputs->next = NULL;
+
+  /*
+   * The buffer sink input must be connected to the output pad of
+   * the last filter described by filters_descr; since the last
+   * filter output label is not specified, it is set to "out" by
+   * default.
+   */
+  inputs->name = av_strdup("out");
+  inputs->filter_ctx = buffersink_ctx;
+  inputs->pad_idx = 0;
+  inputs->next = NULL;
+
+  if ((ret = avfilter_graph_parse_ptr(filter_graph, filters_descr, &inputs,
+                                      &outputs, NULL)) < 0)
+    goto end;
+
+  if ((ret = avfilter_graph_config(filter_graph, NULL)) < 0) goto end;
+
+  /* Print summary of the sink buffer
+   * Note: args buffer is reused to store channel layout string */
+  outlink = buffersink_ctx->inputs[0];
+  av_channel_layout_describe(&outlink->ch_layout, args, sizeof(args));
+  av_log(NULL, AV_LOG_INFO, "Output: srate:%dHz fmt:%s chlayout:%s\n",
+         (int)outlink->sample_rate,
+         (char *)av_x_if_null(
+             av_get_sample_fmt_name((AVSampleFormat)outlink->format), "?"),
+         args);
 
-    /* buffer audio source: the decoded frames from the decoder will be inserted here. */
-    if (dec_ctx->ch_layout.order == AV_CHANNEL_ORDER_UNSPEC)
-        av_channel_layout_default(&dec_ctx->ch_layout, dec_ctx->ch_layout.nb_channels);
-    ret = snprintf(args, sizeof(args),
-            "time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=",
-             time_base.num, time_base.den, dec_ctx->sample_rate,
-             av_get_sample_fmt_name(dec_ctx->sample_fmt));
-    av_channel_layout_describe(&dec_ctx->ch_layout, args + ret, sizeof(args) - ret);
-    ret = avfilter_graph_create_filter(&buffersrc_ctx, abuffersrc, "in",
-                                       args, NULL, filter_graph);
-    if (ret < 0) {
-        av_log(NULL, AV_LOG_ERROR, "Cannot create audio buffer source\n");
-        goto end;
-    }
+end:
+  avfilter_inout_free(&inputs);
+  avfilter_inout_free(&outputs);
 
-    /* buffer audio sink: to terminate the filter chain. */
-    ret = avfilter_graph_create_filter(&buffersink_ctx, abuffersink, "out",
-                                       NULL, NULL, filter_graph);
-    if (ret < 0) {
-        av_log(NULL, AV_LOG_ERROR, "Cannot create audio buffer sink\n");
-        goto end;
-    }
+  return ret;
+}
 
-    ret = av_opt_set_int_list(buffersink_ctx, "sample_fmts", out_sample_fmts, -1,
-                              AV_OPT_SEARCH_CHILDREN);
-    if (ret < 0) {
-        av_log(NULL, AV_LOG_ERROR, "Cannot set output sample format\n");
-        goto end;
-    }
+static void sherpa_decode_frame(const AVFrame *frame,
+                                const sherpa_ncnn::Recognizer &recognizer,
+                                sherpa_ncnn::Stream *s,
+                                sherpa_ncnn::Display &display,
+                                std::string &last_text,
+                                int32_t &segment_index) {
+#define N 3200  // 0.2 s. Sample rate is fixed to 16 kHz
+  static float samples[N];
+  static int nb_samples = 0;
+  const int16_t *p = (int16_t *)frame->data[0];
 
-    ret = av_opt_set(buffersink_ctx, "ch_layouts", "mono",
-                              AV_OPT_SEARCH_CHILDREN);
-    if (ret < 0) {
-        av_log(NULL, AV_LOG_ERROR, "Cannot set output channel layout\n");
-        goto end;
-    }
+  if (frame->nb_samples + nb_samples >= N) {
+    s->AcceptWaveform(16000, samples, nb_samples);
 
-    ret = av_opt_set_int_list(buffersink_ctx, "sample_rates", out_sample_rates, -1,
-                              AV_OPT_SEARCH_CHILDREN);
-    if (ret < 0) {
-        av_log(NULL, AV_LOG_ERROR, "Cannot set output sample rate\n");
-        goto end;
+    while (recognizer.IsReady(s)) {
+      recognizer.DecodeStream(s);
     }
 
-    /*
-     * Set the endpoints for the filter graph. The filter_graph will
-     * be linked to the graph described by filters_descr.
-     */
-
-    /*
-     * The buffer source output must be connected to the input pad of
-     * the first filter described by filters_descr; since the first
-     * filter input label is not specified, it is set to "in" by
-     * default.
-     */
-    outputs->name       = av_strdup("in");
-    outputs->filter_ctx = buffersrc_ctx;
-    outputs->pad_idx    = 0;
-    outputs->next       = NULL;
-
-    /*
-     * The buffer sink input must be connected to the output pad of
-     * the last filter described by filters_descr; since the last
-     * filter output label is not specified, it is set to "out" by
-     * default.
-     */
-    inputs->name       = av_strdup("out");
-    inputs->filter_ctx = buffersink_ctx;
-    inputs->pad_idx    = 0;
-    inputs->next       = NULL;
-
-    if ((ret = avfilter_graph_parse_ptr(filter_graph, filters_descr,
-                                        &inputs, &outputs, NULL)) < 0)
-        goto end;
-
-    if ((ret = avfilter_graph_config(filter_graph, NULL)) < 0)
-        goto end;
-
-    /* Print summary of the sink buffer
-     * Note: args buffer is reused to store channel layout string */
-    outlink = buffersink_ctx->inputs[0];
-    av_channel_layout_describe(&outlink->ch_layout, args, sizeof(args));
-    av_log(NULL, AV_LOG_INFO, "Output: srate:%dHz fmt:%s chlayout:%s\n",
-           (int)outlink->sample_rate,
-           (char *)av_x_if_null(av_get_sample_fmt_name((AVSampleFormat)outlink->format), "?"),
-           args);
-
-end:
-    avfilter_inout_free(&inputs);
-    avfilter_inout_free(&outputs);
+    bool is_endpoint = recognizer.IsEndpoint(s);
+    auto text = recognizer.GetResult(s).text;
 
-    return ret;
-}
+    if (!text.empty() && last_text != text) {
+      last_text = text;
 
-static void sherpa_decode_frame(const AVFrame *frame, SherpaNcnnRecognizer *recognizer, SherpaNcnnStream *s, SherpaNcnnDisplay * display, int *segment_id)
-{
-#define N 3200  // 0.2 s. Sample rate is fixed to 16 kHz
-    static float samples[N];
-    static int nb_samples = 0;
-    const int16_t *p = (int16_t*)frame->data[0];
-
-    if (frame->nb_samples + nb_samples >= N) {
-        AcceptWaveform(s, 16000, samples, nb_samples);
-        while (IsReady(recognizer, s)) {
-          Decode(recognizer, s);
-        }
+      std::transform(text.begin(), text.end(), text.begin(),
+                     [](auto c) { return std::tolower(c); });
 
-        SherpaNcnnResult *r = GetResult(recognizer, s);
-        if (strlen(r->text)) {
-            SherpaNcnnPrint(display, *segment_id, r->text);
-        }
+      display.Print(segment_index, text);
+    }
 
-        if (IsEndpoint(recognizer, s)) {
-          Reset(recognizer, s);
-          if (strlen(r->text)) {
-            ++(*segment_id);
-          }
-        }
+    if (is_endpoint) {
+      if (!text.empty()) {
+        ++segment_index;
+      }
 
-        DestroyResult(r);
-        nb_samples = 0;
+      recognizer.Reset(s);
     }
 
-    for (int i = 0; i < frame->nb_samples; i++) {
-        samples[nb_samples++] = p[i] / 32768.;
-    }
-}
+    nb_samples = 0;
+  }
 
-static inline char *__av_err2str(int errnum)
-{
-    static char str[AV_ERROR_MAX_STRING_SIZE];
-    memset(str, 0, sizeof(str));
-    return av_make_error_string(str, AV_ERROR_MAX_STRING_SIZE, errnum);
+  for (int i = 0; i < frame->nb_samples; i++) {
+    samples[nb_samples++] = p[i] / 32768.;
+  }
 }
 
-int main(int argc, char **argv)
-{
-    int ret;
-    int num_threads = 4;
-    AVPacket *packet = av_packet_alloc();
-    AVFrame *frame = av_frame_alloc();
-    AVFrame *filt_frame = av_frame_alloc();
-    const char *kUsage =
-        "\n"
-        "Usage:\n"
-        "  ./sherpa-ncnn-ffmpeg \\\n"
-        "    /path/to/tokens.txt \\\n"
-        "    /path/to/encoder.ncnn.param \\\n"
-        "    /path/to/encoder.ncnn.bin \\\n"
-        "    /path/to/decoder.ncnn.param \\\n"
-        "    /path/to/decoder.ncnn.bin \\\n"
-        "    /path/to/joiner.ncnn.param \\\n"
-        "    /path/to/joiner.ncnn.bin \\\n"
-        "    /path/to/foo.wav [<num_threads> [decode_method, can be "
-        "greedy_search/modified_beam_search]]"
-        "\n\n"
-        "Please refer to \n"
-        "https://k2-fsa.github.io/sherpa/ncnn/pretrained_models/index.html\n"
-        "for a list of pre-trained models to download.\n";
-
-
-    if (!packet || !frame || !filt_frame) {
-        fprintf(stderr, "Could not allocate frame or packet\n");
-        exit(1);
-    }
+static inline char *__av_err2str(int errnum) {
+  static char str[AV_ERROR_MAX_STRING_SIZE];
+  memset(str, 0, sizeof(str));
+  return av_make_error_string(str, AV_ERROR_MAX_STRING_SIZE, errnum);
+}
 
-    if (argc < 9 || argc > 11) {
-        fprintf(stderr, "%s\n", kUsage);
-        return -1;
+static void Handler(int32_t sig) {
+  fprintf(stderr, "\nCaught Ctrl + C. Exiting...\n");
+  signal(sig, SIG_DFL);
+  raise(sig);
+};
+
+int main(int argc, char **argv) {
+  if (argc < 9 || argc > 11) {
+    const char *usage = R"usage(
+Usage:
+  ./bin/sherpa-ncnn-microphone \
+    /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 \
+    ffmpeg-input-url \
+    [num_threads] [decode_method, can be greedy_search/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.
+)usage";
+    fprintf(stderr, "%s\n", usage);
+    fprintf(stderr, "argc, %d\n", argc);
+
+    return -1;
+  }
+  signal(SIGINT, Handler);
+
+  AVPacket *packet = av_packet_alloc();
+  AVFrame *frame = av_frame_alloc();
+  AVFrame *filt_frame = av_frame_alloc();
+  if (!packet || !frame || !filt_frame) {
+    fprintf(stderr, "Could not allocate frame or packet\n");
+    exit(1);
+  }
+
+  sherpa_ncnn::RecognizerConfig config;
+  config.model_config.tokens = argv[1];
+  config.model_config.encoder_param = argv[2];
+  config.model_config.encoder_bin = argv[3];
+  config.model_config.decoder_param = argv[4];
+  config.model_config.decoder_bin = argv[5];
+  config.model_config.joiner_param = argv[6];
+  config.model_config.joiner_bin = argv[7];
+  int32_t num_threads = 4;
+  if (argc >= 9 && atoi(argv[8]) > 0) {
+    num_threads = atoi(argv[8]);
+  }
+  config.model_config.encoder_opt.num_threads = num_threads;
+  config.model_config.decoder_opt.num_threads = num_threads;
+  config.model_config.joiner_opt.num_threads = num_threads;
+
+  const float expected_sampling_rate = 16000;
+  if (argc == 11) {
+    std::string method = argv[10];
+    if (method.compare("greedy_search") ||
+        method.compare("modified_beam_search")) {
+      config.decoder_config.method = method;
     }
+  }
 
-    SherpaNcnnRecognizerConfig config;
-    config.model_config.tokens = argv[1];
-    config.model_config.encoder_param = argv[2];
-    config.model_config.encoder_bin = argv[3];
-    config.model_config.decoder_param = argv[4];
-    config.model_config.decoder_bin = argv[5];
-    config.model_config.joiner_param = argv[6];
-    config.model_config.joiner_bin = argv[7];
-
-    if (argc >= 10 && atoi(argv[9]) > 0) {
-        num_threads = atoi(argv[9]);
-    }
-    config.model_config.num_threads = num_threads;
-    config.model_config.use_vulkan_compute = 0;
+  config.enable_endpoint = true;
 
-    config.decoder_config.decoding_method = "greedy_search";
+  config.endpoint_config.rule1.min_trailing_silence = 2.4;
+  config.endpoint_config.rule2.min_trailing_silence = 1.2;
+  config.endpoint_config.rule3.min_utterance_length = 300;
 
-    if (argc == 11) {
-        config.decoder_config.decoding_method = argv[10];
-    }
-    config.decoder_config.num_active_paths = 4;
-    config.enable_endpoint = 1;
-    config.rule1_min_trailing_silence = 2.4;
-    config.rule2_min_trailing_silence = 1.2;
-    config.rule3_min_utterance_length = 300;
+  config.feat_config.sampling_rate = expected_sampling_rate;
+  config.feat_config.feature_dim = 80;
 
-    config.feat_config.sampling_rate = 16000;
-    config.feat_config.feature_dim = 80;
+  fprintf(stderr, "%s\n", config.ToString().c_str());
 
-    SherpaNcnnRecognizer *recognizer = CreateRecognizer(&config);
+  sherpa_ncnn::Recognizer recognizer(config);
+  auto s = recognizer.CreateStream();
 
-    SherpaNcnnStream *s = CreateStream(recognizer);
+  int ret;
+  if ((ret = open_input_file(argv[8])) < 0) {
+    fprintf(stderr, "Open input file %s failed, r0=%d\n", argv[8], ret);
+    exit(1);
+  }
 
-    SherpaNcnnDisplay *display = CreateDisplay(60);
-    int segment_id = 0;
+  if ((ret = init_filters(filter_descr)) < 0) {
+    fprintf(stderr, "Init filters %s failed, r0=%d\n", filter_descr, ret);
+    exit(1);
+  }
 
-    if ((ret = open_input_file(argv[8])) < 0)
-        exit(1);
+  std::string last_text;
+  int32_t segment_index = 0;
+  sherpa_ncnn::Display display;
+  while (1) {
+    if ((ret = av_read_frame(fmt_ctx, packet)) < 0) {
+      break;
+    }
 
-    if ((ret = init_filters(filter_descr)) < 0)
-        exit(1);
+    if (packet->stream_index == audio_stream_index) {
+      ret = avcodec_send_packet(dec_ctx, packet);
+      if (ret < 0) {
+        av_log(NULL, AV_LOG_ERROR,
+               "Error while sending a packet to the decoder\n");
+        break;
+      }
+
+      while (ret >= 0) {
+        ret = avcodec_receive_frame(dec_ctx, frame);
+        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
+          break;
+        } else if (ret < 0) {
+          av_log(NULL, AV_LOG_ERROR,
+                 "Error while receiving a frame from the decoder\n");
+          exit(1);
+        }
 
-    /* read all packets */
-    while (1) {
-        if ((ret = av_read_frame(fmt_ctx, packet)) < 0)
+        if (ret >= 0) {
+          /* push the audio data from decoded frame into the filtergraph */
+          if (av_buffersrc_add_frame_flags(buffersrc_ctx, frame,
+                                           AV_BUFFERSRC_FLAG_KEEP_REF) < 0) {
+            av_log(NULL, AV_LOG_ERROR,
+                   "Error while feeding the audio filtergraph\n");
             break;
+          }
 
-        if (packet->stream_index == audio_stream_index) {
-            ret = avcodec_send_packet(dec_ctx, packet);
-            if (ret < 0) {
-                av_log(NULL, AV_LOG_ERROR, "Error while sending a packet to the decoder\n");
-                break;
+          /* pull filtered audio from the filtergraph */
+          while (1) {
+            ret = av_buffersink_get_frame(buffersink_ctx, filt_frame);
+            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
+              break;
             }
-
-            while (ret >= 0) {
-                ret = avcodec_receive_frame(dec_ctx, frame);
-                if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
-                    break;
-                } else if (ret < 0) {
-                    av_log(NULL, AV_LOG_ERROR, "Error while receiving a frame from the decoder\n");
-                    exit(1);
-                }
-
-                if (ret >= 0) {
-                    /* push the audio data from decoded frame into the filtergraph */
-                    if (av_buffersrc_add_frame_flags(buffersrc_ctx, frame, AV_BUFFERSRC_FLAG_KEEP_REF) < 0) {
-                        av_log(NULL, AV_LOG_ERROR, "Error while feeding the audio filtergraph\n");
-                        break;
-                    }
-
-                    /* pull filtered audio from the filtergraph */
-                    while (1) {
-                        ret = av_buffersink_get_frame(buffersink_ctx, filt_frame);
-                        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
-                            break;
-                        if (ret < 0)
-                            exit(1);
-                        sherpa_decode_frame(filt_frame, recognizer, s, display, &segment_id);
-                        av_frame_unref(filt_frame);
-                    }
-                    av_frame_unref(frame);
-                }
+            if (ret < 0) {
+              exit(1);
             }
+            sherpa_decode_frame(filt_frame, recognizer, s.get(), display,
+                                last_text, segment_index);
+            av_frame_unref(filt_frame);
+          }
+          av_frame_unref(frame);
         }
-        av_packet_unref(packet);
+      }
     }
-
-    // add some tail padding
-    float tail_paddings[4800] = {0};  // 0.3 seconds at 16 kHz sample rate
-    AcceptWaveform(s, 16000, tail_paddings, 4800);
-
-    InputFinished(s);
-
-    while (IsReady(recognizer, s)) {
-      Decode(recognizer, s);
-    }
-    SherpaNcnnResult *r = GetResult(recognizer, s);
-    if(strlen(r->text)) {
-      SherpaNcnnPrint(display, segment_id, r->text);
-    }
-    DestroyResult(r);
-
-    DestroyDisplay(display);
-    DestroyStream(s);
-    DestroyRecognizer(recognizer);
-    avfilter_graph_free(&filter_graph);
-    avcodec_free_context(&dec_ctx);
-    avformat_close_input(&fmt_ctx);
-    av_packet_free(&packet);
-    av_frame_free(&frame);
-    av_frame_free(&filt_frame);
-
-    if (ret < 0 && ret != AVERROR_EOF) {
-        fprintf(stderr, "Error occurred: %s\n", __av_err2str(ret));
-        exit(1);
-    }
-
-    fprintf(stderr, "\n");
-
-    return 0;
+    av_packet_unref(packet);
+  }
+
+  // add some tail padding
+  float tail_paddings[4800] = {0};  // 0.3 seconds at 16 kHz sample rate
+  s->AcceptWaveform(16000, tail_paddings, 4800);
+
+  s->InputFinished();
+
+  while (recognizer.IsReady(s.get())) {
+    recognizer.DecodeStream(s.get());
+  }
+
+  auto text = recognizer.GetResult(s.get()).text;
+  if (!text.empty() && last_text != text) {
+    last_text = text;
+    std::transform(text.begin(), text.end(), text.begin(),
+                   [](auto c) { return std::tolower(c); });
+    display.Print(segment_index, text);
+  }
+
+  avfilter_graph_free(&filter_graph);
+  avcodec_free_context(&dec_ctx);
+  avformat_close_input(&fmt_ctx);
+  av_packet_free(&packet);
+  av_frame_free(&frame);
+  av_frame_free(&filt_frame);
+
+  if (ret < 0 && ret != AVERROR_EOF) {
+    fprintf(stderr, "Error occurred: %s\n", __av_err2str(ret));
+    exit(1);
+  }
+
+  return 0;
 }