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
