Debian switched to PocketSphinx 5 a few months ago so the gnustep-gui
package was automatically rebuilt for the new version of the library.
As a result, GSSpeechRecognitionServer was not built at all since
configure checks for a header that has been renamed in the new
PocketSphinx version.

The SphinxBase utility library has been deprecated by PocketSphinx
upstream and is scheduled for removal in Debian, so it can't be used
to record audio input.  Unfortunately, the other sound libraries that
GUI uses (sndfile and libao) are also unsuitable for the task at hand.

Upstream recommends SOX but they explicitly mention that their
sox-based example (examples/live.c) doesn't work on Windows.
Additionally, I think it would be a bit fragile to use NSTask to spawn
an external process for this purpose (IIRC, popen has some quirks on
Windows) so that's why I chose PortAudio.

The attached patch works for me although the speech recognition
results are a bit messy.  I also tried live.c and live_portaudio.c
without modifications and I get similar results with them, so I guess
that's due to the "capability" of the PocketSphinx engine (or my poor
English pronunciation, or both).

The alternative code for the old PocketSphinx version can be dropped
in a few years when the new version becomes widely available.
>From efe6e38d85afc50d642f734d0548b416c1bda5c9 Mon Sep 17 00:00:00 2001
From: Yavor Doganov <[email protected]>
Date: Wed, 10 Jun 2026 18:39:14 +0300
Subject: [PATCH] Port GSSpeechRecognitionServer to PocketSphinx 5

The implementation requires PortAudio since the SphinxBase library has
been deprecated upstream.
---
 Tools/speech_recognizer/GNUmakefile           |   1 +
 .../PocketsphinxSpeechRecognitionEngine.m     | 164 ++++++++++++++++++
 configure.ac                                  |  12 +-
 3 files changed, 174 insertions(+), 3 deletions(-)

diff --git a/Tools/speech_recognizer/GNUmakefile 
b/Tools/speech_recognizer/GNUmakefile
index 86456ac4b..103cdad35 100644
--- a/Tools/speech_recognizer/GNUmakefile
+++ b/Tools/speech_recognizer/GNUmakefile
@@ -20,6 +20,7 @@ GSSpeechRecognitionServer_OBJC_FILES = \
 # Add includes and link dirs.... 
 GSSpeechRecognitionServer_OBJC_FILES += $(RECOGNIZER_ENGINE_CLASS)
 GSSpeechRecognitionServer_INCLUDE_DIRS += $(RECOGNIZER_BASE_CFLAGS) \
+       -I../../Source \
        -I../../Headers \
        -I../../Headers/Additions
 GSSpeechRecognitionServer_LIB_DIRS += -L../../Source/$(GNUSTEP_OBJ_DIR) \
diff --git a/Tools/speech_recognizer/PocketsphinxSpeechRecognitionEngine.m 
b/Tools/speech_recognizer/PocketsphinxSpeechRecognitionEngine.m
index 82c78d31c..5e9b7131b 100644
--- a/Tools/speech_recognizer/PocketsphinxSpeechRecognitionEngine.m
+++ b/Tools/speech_recognizer/PocketsphinxSpeechRecognitionEngine.m
@@ -22,12 +22,19 @@
    Boston, MA 02110 USA.
 */
 
+#include <config.h>
+
 #import "GSSpeechRecognitionEngine.h"
 #import <Foundation/NSDistributedNotificationCenter.h>
 
+#if HAVE_POCKETSPHINX_EXPORT_H
+#include <pocketsphinx.h>
+#include <portaudio.h>
+#else
 #include <sphinxbase/err.h>
 #include <sphinxbase/ad.h>
 #include <pocketsphinx/pocketsphinx.h>
+#endif
 
 /**
  * Implementation of a speech engine using pocketsphinx.  This should be the 
default
@@ -36,6 +43,7 @@
 
 #define MODELDIR "/share/pocketsphinx/model"
 
+#if !HAVE_POCKETSPHINX_EXPORT_H
 static const arg_t cont_args_def[] = {
     POCKETSPHINX_OPTIONS,
     /* Argument file. */
@@ -61,11 +69,16 @@ static const arg_t cont_args_def[] = {
      "Print word times in file transcription."},
     CMDLN_EMPTY_OPTION
 };
+#endif
 
 @interface PocketsphinxSpeechRecognitionEngine : GSSpeechRecognitionEngine
 {
   ps_decoder_t *ps;
+#if HAVE_POCKETSPHINX_EXPORT_H
+  ps_config_t *config;
+#else
   cmd_ln_t *config;
+#endif
   FILE *fh;
   char const *uttid;
   int16 buf[512];
@@ -82,20 +95,30 @@ static const arg_t cont_args_def[] = {
 {
   if ((self = [super init]) != nil)
     {
+#if HAVE_POCKETSPHINX_EXPORT_H
+      config = ps_config_init(NULL);
+#else
       char *arg[3];
       arg[0] = "";
       arg[1] = "-inmic";
       arg[2] = "yes";
 
       config = cmd_ln_parse_r(NULL, cont_args_def, 3, arg, TRUE);
+#endif
       // turn off pocketsphinx output
       err_set_logfp(NULL);
+#if !HAVE_POCKETSPHINX_EXPORT_H
       err_set_debug_level(0);
+#endif
       ps_default_search_args(config);
       ps = ps_init(config);
       if (ps == NULL)
         {
+#if HAVE_POCKETSPHINX_EXPORT_H
+          ps_config_free(config);
+#else
           cmd_ln_free_r(config);
+#endif
           NSLog(@"Could not start server");
           return nil;
         }
@@ -112,6 +135,146 @@ static const arg_t cont_args_def[] = {
                 userInfo: nil];
 }
 
+/* NOTE: This method implementation is derived from PocketSphinx's
+   examples/live_portaudio.c; the original code is licensed under the
+   Expat license:
+
+   Copyright (c) 2022 David Huggins-Daines <[email protected]>
+
+   Permission is hereby granted, free of charge, to any person
+   obtaining a copy of this software and associated documentation
+   files (the "Software"), to deal in the Software without
+   restriction, including without limitation the rights to use, copy,
+   modify, merge, publish, distribute, sublicense, and/or sell copies
+   of the Software, and to permit persons to whom the Software is
+   furnished to do so, subject to the following conditions:
+
+   The above copyright notice and this permission notice shall be
+   included in all copies or substantial portions of the Software.
+
+   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+   BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+   ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+   SOFTWARE.  */
+#if HAVE_POCKETSPHINX_EXPORT_H
+- (void) recognize
+{
+  PaStream *stream;
+  PaError err;
+  ps_endpointer_t *ep;
+  int16 *frame;
+  size_t frame_size;
+
+  ENTER_POOL
+  NSDebugLog(@"** Starting speech recognition loop");
+  if ((err = Pa_Initialize()) != paNoError)
+    {
+      NSLog(@"Could not initialize PortAudio: %s", Pa_GetErrorText(err));
+      return;
+    }
+  if ((ep = ps_endpointer_init(0, 0, 0, 0, 0)) == NULL)
+    {
+      NSLog(@"Failed to initialize PocketSphinx endpointer");
+      Pa_Terminate();
+      return;
+    }
+
+  frame_size = ps_endpointer_frame_size(ep);
+  if ((frame = malloc(frame_size * sizeof(frame[0]))) == NULL)
+    {
+      NSLog(@"Failed to allocate frame");
+      Pa_Terminate();
+      ps_endpointer_free(ep);
+      return;
+    }
+
+  err = Pa_OpenDefaultStream(&stream, 1, 0, paInt16,
+                             ps_config_int(config, "samprate"),
+                             frame_size, NULL, NULL);
+  if (err != paNoError)
+    {
+      NSLog(@"Failed to open PortAudio stream: %s", Pa_GetErrorText(err));
+      Pa_Terminate();
+      ps_endpointer_free(ep);
+      free(frame);
+      return;
+    }
+  if ((err = Pa_StartStream(stream)) != paNoError)
+    {
+      NSLog(@"Failed to start PortAudio stream: %s", Pa_GetErrorText(err));
+      Pa_Terminate();
+      ps_endpointer_free(ep);
+      free(frame);
+      return;
+    }
+
+  NSDebugLog(@"Ready.... <%@, %d>", _listeningThread,
+             [_listeningThread isCancelled]);
+  while ([_listeningThread isCancelled] == NO && _listeningThread != nil)
+    {
+      const int16 *speech;
+      int in_speech = ps_endpointer_in_speech(ep);
+
+      if ((err = Pa_ReadStream(stream, frame, frame_size)) != paNoError)
+        {
+          NSLog(@"Failed to read audio: %s", Pa_GetErrorText(err));
+          break;
+        }
+
+      speech = ps_endpointer_process(ep, frame);
+      if (speech)
+        {
+          char const *hyp;
+
+          if (!in_speech)
+            {
+              if (ps_start_utt(ps) < 0)
+                {
+                  NSLog(@"Failed to start utterance");
+                  Pa_Terminate();
+                  ps_endpointer_free(ep);
+                  free(frame);
+                  return;
+                }
+              NSDebugLog(@"Listening...");
+            }
+          if (ps_process_raw(ps, speech, frame_size, FALSE, FALSE) < 0)
+            {
+              NSLog(@"Failed to process audio");
+              Pa_Terminate();
+              ps_endpointer_free(ep);
+              free(frame);
+              return;
+            }
+          if (!ps_endpointer_in_speech(ep))
+            {
+              ps_end_utt(ps);
+              if ((hyp = ps_get_hyp(ps, NULL)) != NULL)
+                {
+                  NSString *recognizedString
+                    = [NSString stringWithCString: hyp
+                                         encoding: NSUTF8StringEncoding];
+                  [self performSelectorOnMainThread: 
@selector(_recognizedWord:)
+                                         withObject: recognizedString
+                                      waitUntilDone: NO];
+                  NSDebugLog(@"Word: %s", hyp);
+                }
+            }
+        }
+      [NSThread sleepForTimeInterval: 0.01];
+    }
+
+  Pa_StopStream(stream);
+  Pa_Terminate();
+  ps_endpointer_free(ep);
+  free(frame);
+  LEAVE_POOL
+}
+#else
 /*
  * NOTE: This code is derived from continuous.c under pocketsphinx 
  *       which is MIT licensed
@@ -201,6 +364,7 @@ static const arg_t cont_args_def[] = {
   ps_end_utt(ps);
   ad_close(ad);
 }
+#endif
 
 - (void) start
 {
diff --git a/configure.ac b/configure.ac
index 01decdbe1..2d38e7d43 100644
--- a/configure.ac
+++ b/configure.ac
@@ -587,11 +587,17 @@ BUILD_SPEECH_RECOGNIZER=
 
 # has pocketsphinx, for speech recognition.
 AC_CHECK_LIB(pocketsphinx, ps_start_utt, have_speech_recognizer=yes, 
have_speech_recognizer=no)
-AC_CHECK_HEADERS(pocketsphinx/pocketsphinx_export.h, have_pocketsphinx=yes, 
have_pocketsphinx=no)
+AC_CHECK_HEADERS([pocketsphinx/export.h],
+  [AC_CHECK_LIB([portaudio], [Pa_Initialize],
+                 [have_pocketsphinx=yes
+                  ADDLIBS=portaudio-2.0], [have_pocketsphinx=no])],
+  [AC_CHECK_HEADERS([pocketsphinx/pocketsphinx_export.h],
+                    [have_pocketsphinx=yes
+                     ADDLIBS=sphinxbase], [have_pocketsphinx=no])])
 if test "$have_pocketsphinx" = yes -a "$have_speech_recognizer" = yes -a 
"$enable_speech_recognizer" = yes; then
    BUILD_SPEECH_RECOGNIZER="speech_recognizer"
-   RECOGNIZER_BASE_LIBS=`$PKG_CONFIG --libs pocketsphinx sphinxbase`
-   RECOGNIZER_BASE_CFLAGS=`$PKG_CONFIG --cflags pocketsphinx sphinxbase`
+   RECOGNIZER_BASE_LIBS=`$PKG_CONFIG --libs pocketsphinx $ADDLIBS`
+   RECOGNIZER_BASE_CFLAGS=`$PKG_CONFIG --cflags pocketsphinx $ADDLIBS`
    RECOGNIZER_ENGINE_CLASS=PocketsphinxSpeechRecognitionEngine.m
 fi
 AC_SUBST(BUILD_SPEECH_RECOGNIZER)
-- 
2.53.0

Reply via email to