基于kaldi的iOS语音识别(本地)+04+自定义解码器插件

iOS在线识别:https://www.jianshu.com/u/3c2a0bd52ebc

因为GStreamer的操作几乎都是在一个“黑盒”里面进行操作,所以它的这一套操作也有它自己的一个标准,就是一个个插件,我们要用到GStreamer,所以我们需要把解码器那部分也做成一个GStreamer插件,加入到管道(pipeline)中,这样就让GStreamer自己去处理传输和识别解码。

接下来我们就说说该项目最难的一部分了。

因为有些内容涉及到GStreamer自己的东西,比如制作模板要求和规范,我这里就不展开阐述了,也不是本内容的重点,这里只会讲解解码器在插件中的使用。

插件模板

插件名称kaldidecoder

初始化

/* the capabilities of the inputs and outputs.
 *
 */
static GstStaticPadTemplate sink_template =
GST_STATIC_PAD_TEMPLATE("sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS(
        "audio/x-raw, "
        "format = (string) S16LE, "
        "channels = (int) 1, "
        "rate = (int) [ 1, MAX ]"));

static GstStaticPadTemplate src_template =
GST_STATIC_PAD_TEMPLATE("src",
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS("text/x-raw, format= { utf8 }"));

static guint gst_ kaldidecoder_signals[LAST_SIGNAL];

#define gst_ kaldidecoder_parent_class parent_class
G_DEFINE_TYPE(Gst kaldidecoder, gst_ kaldidecoder,
              GST_TYPE_ELEMENT);

static void gst_ kaldidecoder_load_phone_syms(Gst kaldidecoder * filter,
                                                        const GValue * value);

static void gst_kaldidecoder_load_word_syms(Gst kaldidecoder * filter,
                                                       const GValue * value);

static void gst_ kaldidecoder_load_model(Gst kaldidecoder * filter,
                                                   const GValue * value);

static void gst_ kaldidecoder_load_fst(Gst kaldidecoder * filter,
                                                 const GValue * value);

static void gst_ kaldidecoder_load_lm_fst(Gst kaldidecoder * filter,
                                                    const GValue * value);

static void gst_ kaldidecoder_load_big_lm(Gst kaldidecoder * filter,
                                                    const GValue * value);

static void gst_ kaldidecoder_load_word_boundary_info(Gst kaldidecoder * filter,
                                                                const GValue * value);


static void gst_ kaldidecoder_set_property(GObject * object,
                                                     guint prop_id,
                                                     const GValue * value,
                                                     GParamSpec * pspec);

static void gst_ kaldidecoder_get_property(GObject * object,
                                                     guint prop_id,
                                                     GValue * value,
                                                     GParamSpec * pspec);

static gboolean gst_ kaldidecoder_sink_event(GstPad * pad,
                                                       GstObject * parent,
                                                       GstEvent * event);

static GstFlowReturn gst_ kaldidecoder_chain(GstPad * pad,
                                                       GstObject * parent,
                                                       GstBuffer * buf);

static GstStateChangeReturn gst_ kaldidecoder_change_state(
    GstElement *element, GstStateChange transition);

static gboolean gst_ kaldidecoder_query(GstPad *pad, GstObject * parent, GstQuery * query);

static void gst_ kaldidecoder_finalize(GObject * object);

这些pad模板都需要通过gst_element_class_add_pad_template ()_class_init方法里面注册。
_class_init:

/* GObject vmethod implementations */

/* initialize the kaldidecoder's class */
static void gst_kaldidecoder_class_init(
    GstkaldidecoderClass * klass) {
  GObjectClass *gobject_class;
  GstElementClass *gstelement_class;

  gobject_class = (GObjectClass *) klass;
  gstelement_class = (GstElementClass *) klass;

  gobject_class->set_property = gst_kaldidecoder_set_property;
  gobject_class->get_property = gst_kaldidecoder_get_property;
  gobject_class->finalize = gst_kaldidecoder_finalize;

  gstelement_class->change_state = gst_kaldidecoder_change_state;

  g_object_class_install_property(
      ...
  );
  ...

  gst_kaldidecoder_signals[PARTIAL_RESULT_SIGNAL] = g_signal_new(
      "partial-result", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST,
      G_STRUCT_OFFSET(GstkaldidecoderClass, partial_result),
      NULL,
      NULL, kaldi_marshal_VOID__STRING, G_TYPE_NONE, 1,
      G_TYPE_STRING);

  gst_kaldidecoder_signals[FINAL_RESULT_SIGNAL] = g_signal_new(
      "final-result", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST,
      G_STRUCT_OFFSET(GstkaldidecoderClass, final_result),
      NULL,
      NULL, kaldi_marshal_VOID__STRING, G_TYPE_NONE, 1,
      G_TYPE_STRING);

  gst_kaldidecoder_signals[FULL_FINAL_RESULT_SIGNAL] = g_signal_new(
      "full-final-result", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST,
      G_STRUCT_OFFSET(GstkaldidecoderClass, full_final_result),
      NULL,
      NULL, kaldi_marshal_VOID__STRING, G_TYPE_NONE, 1,
      G_TYPE_STRING);

  gst_element_class_set_details_simple(
      gstelement_class, "KaldiDecoder", "Speech/Audio",
      "Convert speech to text", "changfengfuyun");

  gst_element_class_add_pad_template(gstelement_class,
                                     gst_static_pad_template_get(&src_template));

  gst_element_class_add_pad_template(
      gstelement_class, gst_static_pad_template_get(&sink_template));
}

我们定义插件所有部分的代码,我们需要有_init()方法。

/* entry point to initialize the plug-in
 * initialize the plug-in itself
 * register the element factories and other features
 */
static gboolean kaldidecoder_init(
    GstPlugin * kaldidecoder) {
  /* debug category for fltering log messages
   *
   * exchange the string 'Template kaldidecoder' with your description
   */
  GST_DEBUG_CATEGORY_INIT(gst_kaldidecoder_debug,
                          "kaldidecoder", 0,
                          "Template kaldidecoder");

  return gst_element_register(kaldidecoder
                              "kaldidecoder", GST_RANK_NONE,
                              GST_TYPE_KALDIDECODER);
}

至此,就完成了解码器模板的初始化

指定pads

pads是数据进出元素的端口,这使得它们在元素创建过程中成为非常重要的项。在模板代码中,我们已经看到了静态pad模板如何负责将pad模板注册到元素类中。在这里,我们将看到如何创建实际的元素,如何使用_event()-函数来配置特定的格式,以及如何注册函数来让数据流经元素。

创建pad:

/* initialize the new element
 * instantiate pads and add them to element
 * set pad calback functions
 * initialize instance structure
 */
static void gst_kaldidecoder_init(
    Gstkaldidecoder * filter) {
  ...
  filter->sinkpad = NULL;

  filter->sinkpad = gst_pad_new_from_static_template(&sink_template, "sink");
  gst_pad_set_event_function(
      filter->sinkpad,
      GST_DEBUG_FUNCPTR(gst_kaldidecoder_sink_event));
  gst_pad_set_chain_function(
      filter->sinkpad, GST_DEBUG_FUNCPTR(gst_kaldidecoder_chain));
  gst_pad_set_query_function(
      filter->sinkpad, GST_DEBUG_FUNCPTR(gst_kaldidecoder_query));
  gst_pad_use_fixed_caps(filter->sinkpad);
  gst_element_add_pad(GST_ELEMENT(filter), filter->sinkpad);

  filter->srcpad = gst_pad_new_from_static_template(&src_template, "src");
  gst_pad_use_fixed_caps(filter->srcpad);
  gst_element_add_pad(GST_ELEMENT(filter), filter->srcpad);

  // 解码相关的初始化
  ...
}

这里也是每次生成元件的时候都会调用的方法

_chain方法:

  • 用于接收和处理sinkpad上的输入数据。
/* chain function
 * this function does the actual processing
 */
static GstFlowReturn gst_kaldidecoder_chain(GstPad * pad,
                                                       GstObject * parent,
                                                       GstBuffer * buf) {
  Gstkaldidecoder *filter = GST_KALDIDECODER(parent);

  if (G_UNLIKELY(!filter->audio_source))
    goto not_negotiated;
  if (!filter->silent) {
    filter->audio_source->PushBuffer(buf);
  }
  gst_buffer_unref(buf);
  return GST_FLOW_OK;

  /* special cases */
  not_negotiated: {
    GST_ELEMENT_ERROR(filter, CORE, NEGOTIATION, (NULL),
                      ("decoder wasn't allocated before chain function"));

    gst_buffer_unref(buf);
    return GST_FLOW_NOT_NEGOTIATED;
  }
}

_event方法:

  • 该方法通知你在传输数据流中发生的特殊事件(如caps, end-of-stream, newsegment, tags等)。
/* this function handles sink events */
static gboolean gst_kaldidecoder_sink_event(GstPad * pad,
                                                       GstObject * parent,
                                                       GstEvent * event) {
  gboolean ret;
  Gstkaldidecoder *filter;

  filter = GST_KALDIDECODER(parent);

  GST_DEBUG_OBJECT(filter, "Handling %s event", GST_EVENT_TYPE_NAME(event));

  switch (GST_EVENT_TYPE(event)) {
    case GST_EVENT_SEGMENT: {
      GST_DEBUG_OBJECT(filter, "Starting decoding task");
      filter->decoding = true;
      gst_pad_start_task(filter->srcpad,
                         (GstTaskFunction) gst_ kaldidecoder_loop,
                         filter, NULL);

      GST_DEBUG_OBJECT(filter, "Started decoding task");
      ret = TRUE;
      break;
    }
    case GST_EVENT_CAPS: {
      ret = TRUE;
      break;
    }
    case GST_EVENT_EOS: {
      /* end-of-stream, we should close down all stream leftovers here */
      GST_DEBUG_OBJECT(filter, "EOS received");
      if (filter->decoding) {
        filter->audio_source->SetEnded(true);
      } else {
        GST_DEBUG_OBJECT(filter, "EOS received while not decoding, pushing EOS out");
        gst_pad_push_event(filter->srcpad, gst_event_new_eos());
      }
      ret = TRUE;
      break;
    }
    default:
      ret = gst_pad_event_default(pad, parent, event);
      break;
  }
  return ret;
}

_query方法:

  • 元件接收queries必须回复的内容
/* GstElement vmethod implementations */

static gboolean
gst_kaldidecoder_query (GstPad *pad, GstObject * parent, GstQuery * query) {
  gboolean ret;
  Gstkaldidecoder *filter;

  filter = GST_KALDIDECODER(parent);

  switch (GST_QUERY_TYPE (query)) {
    case GST_QUERY_CAPS: {
      if (filter->feature_info == NULL) {
        filter->feature_info = new OnlineNnet2FeaturePipelineInfo(*(filter->feature_config));
    if (strcmp((filter->feature_config->feature_type).c_str(), "plp") == 0)
      filter->sample_rate = (int) filter->feature_info->plp_opts.frame_opts.samp_freq;
    else
      filter->sample_rate = (int) filter->feature_info->mfcc_opts.frame_opts.samp_freq;
      }
      GstCaps *new_caps = gst_caps_new_simple ("audio/x-raw",
            "format", G_TYPE_STRING, "S16LE",
            "rate", G_TYPE_INT, filter->sample_rate,
            "channels", G_TYPE_INT, 1, NULL);
      GST_DEBUG_OBJECT (filter, "Setting caps query result: %" GST_PTR_FORMAT, new_caps);
      gst_query_set_caps_result (query, new_caps);
      gst_caps_unref (new_caps);
      ret = TRUE;
      break;
    }
    default:
      ret = gst_pad_query_default (pad, parent, query);
      break;
  }
  return ret;
}

接下来讲解kaldi的解码。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容