~ubuntu-branches/ubuntu/saucy/hud/saucy-proposed

« back to all changes in this revision

Viewing changes to src/hudsphinx.c

  • Committer: Package Import Robot
  • Author(s): Ubuntu daily release
  • Date: 2013-06-05 12:33:44 UTC
  • mto: This revision was merged to the branch mainline in revision 5.
  • Revision ID: package-import@ubuntu.com-20130605123344-cpp4to647tyfv7kr
Tags: upstream-13.10.1daily13.06.05.1
ImportĀ upstreamĀ versionĀ 13.10.1daily13.06.05.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Copyright Ā© 2012 Canonical Ltd.
 
3
 *
 
4
 * This program is free software: you can redistribute it and/or modify it
 
5
 * under the terms of the GNU General Public License version 3, as
 
6
 * published by the Free Software Foundation.
 
7
 *
 
8
 * This program is distributed in the hope that it will be useful, but
 
9
 * WITHOUT ANY WARRANTY; without even the implied warranties of
 
10
 * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
 
11
 * PURPOSE.  See the GNU General Public License for more details.
 
12
 *
 
13
 * You should have received a copy of the GNU General Public License along
 
14
 * with this program.  If not, see <http://www.gnu.org/licenses/>.
 
15
 */
 
16
 
 
17
#define G_LOG_DOMAIN "hudsphinx"
 
18
 
 
19
#include "hudvoice.h"
 
20
#include "hudsphinx.h"
 
21
#include "hud-query-iface.h"
 
22
#include "hudsource.h"
 
23
#include "pronounce-dict.h"
 
24
 
 
25
/* Pocket Sphinx */
 
26
#include "pocketsphinx.h"
 
27
#include <sphinxbase/ad.h>
 
28
#include <sphinxbase/cont_ad.h>
 
29
 
 
30
#include <gio/gunixoutputstream.h>
 
31
#include <glib/gstdio.h>
 
32
 
 
33
static GQuark
 
34
hud_sphinx_error_quark(void)
 
35
{
 
36
  static GQuark quark = 0;
 
37
  if (quark == 0)
 
38
    quark = g_quark_from_static_string ("hud-sphinx-error-quark");
 
39
  return quark;
 
40
}
 
41
 
 
42
static GRegex *
 
43
hud_sphinx_alphanumeric_regex_new (void)
 
44
{
 
45
  GRegex *alphanumeric_regex = NULL;
 
46
 
 
47
  GError *error = NULL;
 
48
  alphanumeric_regex = g_regex_new("ā€¦|\\.\\.\\.", 0, 0, &error);
 
49
  if (alphanumeric_regex == NULL) {
 
50
    g_error("Compiling regex failed: [%s]", error->message);
 
51
    g_error_free(error);
 
52
  }
 
53
 
 
54
  return alphanumeric_regex;
 
55
}
 
56
 
 
57
static arg_t sphinx_cmd_ln[] = {
 
58
  POCKETSPHINX_OPTIONS,
 
59
  { "-adcdev", ARG_STRING, NULL, "Name of audio device to use for input." },
 
60
  CMDLN_EMPTY_OPTION
 
61
};
 
62
 
 
63
struct _HudSphinx
 
64
{
 
65
  GObject parent_instance;
 
66
 
 
67
  HudQueryIfaceComCanonicalHudQuery *skel;
 
68
  GRegex * alphanumeric_regex;
 
69
  cmd_ln_t *config;
 
70
  ps_decoder_t *ps;
 
71
};
 
72
 
 
73
typedef GObjectClass HudSphinxClass;
 
74
 
 
75
static void hud_sphinx_finalize (GObject *object);
 
76
 
 
77
static gboolean hud_sphinx_voice_query (HudVoice *self, HudSource *source,
 
78
    gchar **result, GError **error);
 
79
 
 
80
static void hud_sphinx_iface_init (HudVoiceInterface *iface);
 
81
 
 
82
G_DEFINE_TYPE_WITH_CODE (HudSphinx, hud_sphinx, G_TYPE_OBJECT,
 
83
                         G_IMPLEMENT_INTERFACE (HUD_TYPE_VOICE, hud_sphinx_iface_init))
 
84
 
 
85
static void hud_sphinx_iface_init (HudVoiceInterface *iface)
 
86
{
 
87
  iface->query = hud_sphinx_voice_query;
 
88
}
 
89
 
 
90
static void
 
91
hud_sphinx_class_init (GObjectClass *klass)
 
92
{
 
93
  klass->finalize = hud_sphinx_finalize;
 
94
}
 
95
 
 
96
static void
 
97
hud_sphinx_init (HudSphinx *self)
 
98
{
 
99
  self->alphanumeric_regex = hud_sphinx_alphanumeric_regex_new();
 
100
}
 
101
 
 
102
static void
 
103
hud_sphinx_finalize (GObject *object)
 
104
{
 
105
  HudSphinx *self = HUD_SPHINX (object);
 
106
 
 
107
  g_clear_object(&self->skel);
 
108
  g_clear_pointer(&self->alphanumeric_regex, g_regex_unref);
 
109
 
 
110
  g_clear_pointer(&self->ps, ps_free);
 
111
 
 
112
  G_OBJECT_CLASS (hud_sphinx_parent_class)
 
113
    ->finalize (object);
 
114
}
 
115
 
 
116
HudSphinx *
 
117
hud_sphinx_new (HudQueryIfaceComCanonicalHudQuery *skel, const gchar *device, GError **error)
 
118
{
 
119
  HudSphinx *self = g_object_new (HUD_TYPE_SPHINX, NULL);
 
120
  self->skel = g_object_ref(skel);
 
121
 
 
122
  gchar *hmm = HMM_PATH;
 
123
  gchar *dict = DICT_PATH;
 
124
 
 
125
  if (device != NULL) { 
 
126
    self->config = cmd_ln_init(NULL, sphinx_cmd_ln, TRUE,
 
127
                                     "-hmm", hmm,
 
128
                                     "-dict", dict,
 
129
                                     "-adcdev", device,
 
130
                                     NULL);
 
131
  } else {
 
132
    self->config = cmd_ln_init(NULL, sphinx_cmd_ln, TRUE,
 
133
                                     "-hmm", hmm,
 
134
                                     "-dict", dict,
 
135
                                     NULL);
 
136
  }
 
137
 
 
138
  if (self->config == NULL) {
 
139
    g_warning("Sphinx command line arguments failed to initialize");
 
140
    g_set_error_literal (error, hud_sphinx_error_quark (), 0,
 
141
        "Sphinx command line arguments failed to initialize");
 
142
    return NULL;
 
143
  }
 
144
 
 
145
  self->ps = ps_init(self->config);
 
146
  if (self->ps == NULL) {
 
147
    g_warning("Unable to initialize Sphinx decoder");
 
148
    g_set_error_literal (error, hud_sphinx_error_quark (), 0,
 
149
        "Unable to initialize Sphinx decoder");
 
150
    return NULL;
 
151
  }
 
152
 
 
153
  return self;
 
154
}
 
155
 
 
156
/* Start code taken from PocketSphinx */
 
157
 
 
158
/* Sleep for specified msec */
 
159
static void
 
160
sleep_msec(int32 ms)
 
161
{
 
162
#if (defined(WIN32) && !defined(GNUWINCE)) || defined(_WIN32_WCE)
 
163
    Sleep(ms);
 
164
#else
 
165
    /* ------------------- Unix ------------------ */
 
166
    struct timeval tmo;
 
167
 
 
168
    tmo.tv_sec = 0;
 
169
    tmo.tv_usec = ms * 1000;
 
170
 
 
171
    select(0, NULL, NULL, NULL, &tmo);
 
172
#endif
 
173
}
 
174
 
 
175
static gboolean
 
176
hud_sphinx_utterance_loop(HudSphinx *self, gchar **result, GError **error)
 
177
{
 
178
  ad_rec_t *ad;
 
179
  int16 adbuf[4096];
 
180
  int32 k, ts, rem;
 
181
  char const *hyp;
 
182
  char const *uttid;
 
183
  cont_ad_t *cont;
 
184
 
 
185
  cmd_ln_t *config = self->config;
 
186
  ps_decoder_t *ps = self->ps;
 
187
 
 
188
  if ((ad = ad_open_dev (cmd_ln_str_r (config, "-adcdev"),
 
189
      (int) cmd_ln_float32_r(config, "-samprate"))) == NULL )
 
190
  {
 
191
    g_warning("Failed to open audio device");
 
192
    *result = NULL;
 
193
    g_set_error_literal (error, hud_sphinx_error_quark (), 0,
 
194
        "Failed to open audio device");
 
195
    return FALSE;
 
196
  }
 
197
 
 
198
  /* Initialize continuous listening module */
 
199
  if ((cont = cont_ad_init (ad, ad_read)) == NULL )
 
200
  {
 
201
    g_warning("Failed to initialize voice activity detection");
 
202
    *result = NULL;
 
203
    g_set_error_literal (error, hud_sphinx_error_quark (), 0,
 
204
        "Failed to initialize voice activity detection");
 
205
    return FALSE;
 
206
  }
 
207
  if (ad_start_rec (ad) < 0)
 
208
  {
 
209
    g_warning("Failed to start recording");
 
210
    *result = NULL;
 
211
    g_set_error_literal (error, hud_sphinx_error_quark (), 0,
 
212
        "Failed to start recording");
 
213
    return FALSE;
 
214
  }
 
215
 
 
216
  /* Indicate listening for next utterance */
 
217
  g_debug("Voice query is listening");
 
218
  hud_query_iface_com_canonical_hud_query_emit_voice_query_listening (
 
219
      HUD_QUERY_IFACE_COM_CANONICAL_HUD_QUERY (self->skel));
 
220
 
 
221
  /* Wait data for next utterance */
 
222
  while ((k = cont_ad_read (cont, adbuf, 4096)) == 0)
 
223
    sleep_msec (100);
 
224
 
 
225
  if (k < 0)
 
226
  {
 
227
    g_warning("Failed to read audio");
 
228
    *result = NULL;
 
229
    g_set_error_literal (error, hud_sphinx_error_quark (), 0,
 
230
        "Failed to read audio");
 
231
    return FALSE;
 
232
  }
 
233
 
 
234
  /*
 
235
   * Non-zero amount of data received; start recognition of new utterance.
 
236
   * NULL argument to uttproc_begin_utt => automatic generation of utterance-id.
 
237
   */
 
238
  if (ps_start_utt (ps, NULL ) < 0)
 
239
    g_error("Failed to start utterance");
 
240
  g_debug("Voice query has heard something");
 
241
  hud_query_iface_com_canonical_hud_query_emit_voice_query_heard_something(
 
242
              HUD_QUERY_IFACE_COM_CANONICAL_HUD_QUERY (self->skel));
 
243
  ps_process_raw (ps, adbuf, k, FALSE, FALSE);
 
244
 
 
245
  /* Note timestamp for this first block of data */
 
246
  ts = cont->read_ts;
 
247
 
 
248
  /* Decode utterance until end (marked by a "long" silence, >1sec) */
 
249
  for (;;)
 
250
  {
 
251
    /* Read non-silence audio data, if any, from continuous listening module */
 
252
    if ((k = cont_ad_read (cont, adbuf, 4096)) < 0)
 
253
      g_error("Failed to read audio");
 
254
    if (k == 0)
 
255
    {
 
256
      /*
 
257
       * No speech data available; check current timestamp with most recent
 
258
       * speech to see if more than 1 sec elapsed.  If so, end of utterance.
 
259
       */
 
260
      if ((cont->read_ts - ts) > DEFAULT_SAMPLES_PER_SEC)
 
261
        break;
 
262
    }
 
263
    else
 
264
    {
 
265
      /* New speech data received; note current timestamp */
 
266
      ts = cont->read_ts;
 
267
    }
 
268
 
 
269
    /*
 
270
     * Decode whatever data was read above.
 
271
     */
 
272
    rem = ps_process_raw (ps, adbuf, k, FALSE, FALSE);
 
273
 
 
274
    /* If no work to be done, sleep a bit */
 
275
    if ((rem == 0) && (k == 0))
 
276
      sleep_msec (20);
 
277
  }
 
278
 
 
279
  /*
 
280
   * Utterance ended; flush any accumulated, unprocessed A/D data and stop
 
281
   * listening until current utterance completely decoded
 
282
   */
 
283
  ad_stop_rec (ad);
 
284
  while (ad_read (ad, adbuf, 4096) >= 0);
 
285
  cont_ad_reset (cont);
 
286
 
 
287
  g_debug("Voice query has stopped listening, processing...");
 
288
  fflush (stdout);
 
289
  /* Finish decoding, obtain and print result */
 
290
  ps_end_utt (ps);
 
291
  hyp = ps_get_hyp (ps, NULL, &uttid);
 
292
  fflush (stdout);
 
293
 
 
294
  cont_ad_close (cont);
 
295
  ad_close (ad);
 
296
 
 
297
  if (hyp)
 
298
  {
 
299
    *result = g_strdup (hyp);
 
300
  }
 
301
  else
 
302
  {
 
303
    *result = NULL;
 
304
  }
 
305
 
 
306
  return TRUE;
 
307
 
 
308
}
 
309
/* End code taken from PocketSphinx */
 
310
 
 
311
/* Actually recognizing the Audio */
 
312
static gboolean
 
313
hud_sphinx_listen (HudSphinx *self, fsg_model_t* fsg,
 
314
    gchar **result, GError **error)
 
315
{
 
316
  // Get the fsg set or create one if none
 
317
  fsg_set_t *fsgs = ps_get_fsgset(self->ps);
 
318
  if (fsgs == NULL)
 
319
    fsgs = ps_update_fsgset(self->ps);
 
320
 
 
321
  // Remove the old fsg
 
322
  fsg_model_t * old_fsg = fsg_set_get_fsg(fsgs, fsg_model_name(fsg));
 
323
  if (old_fsg)
 
324
  {
 
325
    fsg_set_remove(fsgs, old_fsg);
 
326
    fsg_model_free(old_fsg);
 
327
  }
 
328
 
 
329
  // Add the new fsg
 
330
  fsg_set_add(fsgs, fsg_model_name(fsg), fsg);
 
331
  fsg_set_select (fsgs, fsg_model_name(fsg));
 
332
 
 
333
  ps_update_fsgset (self->ps);
 
334
 
 
335
  gboolean success = hud_sphinx_utterance_loop (self, result, error);
 
336
 
 
337
  if (success) {
 
338
    g_debug("Recognized: %s", *result);
 
339
  } else {
 
340
    g_warning("Utterance loop failed");
 
341
  }
 
342
 
 
343
  return success;
 
344
}
 
345
 
 
346
static void
 
347
free_func (gpointer data)
 
348
{
 
349
  g_ptr_array_free((GPtrArray*) data, TRUE);
 
350
}
 
351
 
 
352
static gint
 
353
hud_sphinx_number_of_states(GPtrArray *command_list)
 
354
{
 
355
  gint number_of_states = 0;
 
356
 
 
357
  guint i;
 
358
  for (i = 0; i < command_list->len; ++i)
 
359
  {
 
360
    GPtrArray *command = g_ptr_array_index(command_list, i);
 
361
    number_of_states += command->len;
 
362
  }
 
363
 
 
364
  // the number of states calculated above doesn't include the start and end
 
365
  return number_of_states + 2;
 
366
}
 
367
 
 
368
static gint
 
369
hud_sphinx_write_command (fsg_model_t *fsg, GPtrArray *command,
 
370
    gint state_num, gfloat command_probability)
 
371
{
 
372
  // the first transition goes from the state 0
 
373
  // it's probability depends on how many commands there are
 
374
  if (command->len > 0)
 
375
  {
 
376
    const gchar *word = g_ptr_array_index(command, 0);
 
377
    gchar *lower = g_utf8_strdown(word, -1);
 
378
    gint wid = fsg_model_word_add (fsg, lower);
 
379
    fsg_model_trans_add (fsg, 0, ++state_num,
 
380
        command_probability, wid);
 
381
    g_free(lower);
 
382
  }
 
383
 
 
384
  // the rest of the transitions are certain (straight path)
 
385
  // so have probability 1.0
 
386
  guint i;
 
387
  for (i = 1; i < command->len; ++i)
 
388
  {
 
389
    const gchar *word = g_ptr_array_index(command, i);
 
390
    gchar *lower = g_utf8_strdown(word, -1);
 
391
    gint wid = fsg_model_word_add (fsg, lower);
 
392
    fsg_model_trans_add (fsg, state_num, state_num + 1,
 
393
        1.0, wid);
 
394
    ++state_num;
 
395
    g_free(lower);
 
396
  }
 
397
 
 
398
  // null transition to exit state
 
399
  fsg_model_null_trans_add (fsg, state_num, 1, 0);
 
400
 
 
401
  return state_num;
 
402
}
 
403
 
 
404
static gboolean
 
405
hud_sphinx_build_grammar (HudSphinx *self, GList *items,
 
406
    fsg_model_t **fsg, GError **error)
 
407
{
 
408
  PronounceDict *dict = pronounce_dict_get_sphinx(error);
 
409
  if (dict == NULL)
 
410
  {
 
411
    return FALSE;
 
412
  }
 
413
 
 
414
  /* Get the pronounciations for the items */
 
415
  GHashTable *pronounciations = g_hash_table_new_full (g_str_hash, g_str_equal,
 
416
      g_free, (GDestroyNotify) g_strfreev);
 
417
  GPtrArray *command_list = g_ptr_array_new_with_free_func (free_func);
 
418
  GHashTable *unique_commands = g_hash_table_new(g_str_hash, g_str_equal);
 
419
  HudItemPronunciationData pronounciation_data =
 
420
  { pronounciations, self->alphanumeric_regex, command_list, dict, unique_commands };
 
421
  g_list_foreach (items, (GFunc) hud_item_insert_pronounciation,
 
422
      &pronounciation_data);
 
423
  g_hash_table_destroy(unique_commands);
 
424
 
 
425
  if (command_list->len == 0)
 
426
  {
 
427
    g_set_error_literal (error, hud_sphinx_error_quark(), 0, "Could not build Sphinx grammar. Is sphinx-voxforge installed?");
 
428
    g_clear_pointer(&pronounciations, g_hash_table_destroy);
 
429
    g_ptr_array_free (command_list, TRUE);
 
430
    return FALSE;
 
431
  }
 
432
 
 
433
  gint number_of_states = hud_sphinx_number_of_states(command_list);
 
434
  gfloat command_probability = 1.0f / command_list->len;
 
435
 
 
436
  g_debug("Number of states [%d]", number_of_states);
 
437
 
 
438
  *fsg = fsg_model_init ("<hud.GRAM>", ps_get_logmath (self->ps),
 
439
      cmd_ln_float32_r(self->config, "-lw"), number_of_states);
 
440
  (*fsg)->start_state = 0;
 
441
  (*fsg)->final_state = 1;
 
442
 
 
443
  // starting at state 2 (0 is start and 1 is exit)
 
444
  gint state_num = 1;
 
445
  guint i;
 
446
  for (i = 0; i < command_list->len; ++i)
 
447
  {
 
448
    GPtrArray *command = g_ptr_array_index(command_list, i);
 
449
    // keep a record of the number of states so far
 
450
    state_num = hud_sphinx_write_command(*fsg, command, state_num, command_probability);
 
451
  }
 
452
 
 
453
  glist_t nulls = fsg_model_null_trans_closure (*fsg, NULL );
 
454
  glist_free (nulls);
 
455
 
 
456
  g_clear_pointer(&pronounciations, g_hash_table_destroy);
 
457
  g_ptr_array_free (command_list, TRUE);
 
458
 
 
459
  return TRUE;
 
460
}
 
461
 
 
462
static gboolean
 
463
hud_sphinx_voice_query (HudVoice *voice, HudSource *source, gchar **result, GError **error)
 
464
{
 
465
  g_return_val_if_fail(HUD_IS_SPHINX(voice), FALSE);
 
466
  HudSphinx *self = HUD_SPHINX(voice);
 
467
 
 
468
  if (source == NULL) {
 
469
    /* No active window, that's fine, but we'll just move on */
 
470
    *result = NULL;
 
471
    g_set_error_literal (error, hud_sphinx_error_quark(), 0, "Active source is null");
 
472
    return FALSE;
 
473
  }
 
474
 
 
475
  GList *items = hud_source_get_items(source);
 
476
  if (items == NULL) {
 
477
    /* The active window doesn't have items, that's cool.  We'll move on. */
 
478
    *result = NULL;
 
479
    return TRUE;
 
480
  }
 
481
 
 
482
  fsg_model_t *fsg = NULL;
 
483
  if (!hud_sphinx_build_grammar(self, items, &fsg, error))
 
484
  {
 
485
    g_list_free_full(items, g_object_unref);
 
486
    return FALSE;
 
487
  }
 
488
 
 
489
  gboolean success = hud_sphinx_listen (self, fsg, result, error);
 
490
 
 
491
  g_list_free_full(items, g_object_unref);
 
492
 
 
493
  return success;
 
494
}