我在Xamarin Android应用程序(使用MVVMCross)中使用pocketsphinx进行语音识别。当我在View中使用它时效果很好,当我将它从View移动到插件中时它很有用,这样我就可以在ViewModel中使用它(使用回调事件处理程序)。
现在我正在重构(再次!)使用async / await,并且app无法加载pocketsphinx库。我的代码如下。当我接到电话时
Java.Lang.JavaSystem.LoadLibrary("pocketsphinx_jni");
我得到一个例外:
Java.Lang.UnsatisfiedLinkError: Library pocketsphinx_jni not found; tried [/vendor/lib/libpocketsphinx_jni.so, /system/lib/libpocket...
我使用Application.Context.ApplicationInfo.NativeLibraryDir
和Java.Lang.JavaSystem.Load
尝试了一些组合,但没有快乐。
关于为什么现在会发生这种情况的任何想法?大概与线程有关,但我对如何解决这个问题感到难过。在我开始重构以使用async / await之前,所有这些代码都运行良好。
我的ViewModel中的调用是:
var result = await _speechRecognitionService.ListenAsync(delay);
这是我的SpeechRecognitionService类:
public class SpeechRecognitionService : Java.Lang.Object,
ISpeechRecognitionService, IRecognitionListener
{
private SpeechRecognizer _recognizer;
private Java.IO.File _assetDir;
private bool _isHandlingResponse;
private bool _isInitialised;
private static EventWaitHandle _waitHandle = new AutoResetEvent(false);
private string _response;
public event EventHandler HeardSpeechEventHandler;
public bool IsInitialised()
{
return _isInitialised;
}
/// <summary>
/// Hopefully fire off the listen asynchronously
/// </summary>
/// <param name="delay"></param>
/// <returns></returns>
public async Task<string> ListenAsync(int delay)
{
return await Task.Run(() => DoListen(delay));
}
/// <summary>
/// Initialise if required then fire up the listener
/// </summary>
/// <param name="delay"></param>
/// <returns></returns>
private string DoListen(int delay)
{
if (!IsInitialised())
{
Init();
}
Listen(delay);
return "";
}
/// <summary>
/// Set up the config for the listener
/// </summary>
public void Init()
{
Console.WriteLine("SpeechRecognitionIntentService ====== Init");
try
{
// THIS CALL FAILS!
Java.Lang.JavaSystem.LoadLibrary("pocketsphinx_jni");
_assetDir = Edu.Cmu.Pocketsphinx.Assets.SyncAssets(Application.Context);
}
catch (IOException e)
{
throw new Exception("Failed to synchronize assets when attempting to load voice recognition software", e);
}
var config = Decoder.DefaultConfig();
config.SetString("-dict", JoinPath(_assetDir, "models/lm/cmu07a.dic"));
config.SetString("-hmm", JoinPath(_assetDir, "models/hmm/en-us-semi"));
config.SetString("-rawlogdir", _assetDir.AbsolutePath);
config.SetInt("-maxhmmpf", 10000);
config.SetBoolean("-fwdflat", false);
config.SetBoolean("-bestpath", false);
config.SetFloat("-kws_threshold", 1e-5);
//config.SetFloat("-samprate", 8000);
//config.SetBoolean("-remove_noise", false);
_recognizer = new SpeechRecognizer(config);
_recognizer.AddListener(this);
// Create keyword-activation search.
//_recognizer.SetKws(KwsSearchName, Keyphrase);
// Create grammar-based search for guide.
var lw = config.GetInt("-lw");
AddSearch("guide", "guide.gram", "<guide.command>", lw);
// Create language model search.
//var path = JoinPath(_assetDir, "models/lm/weather.dmp");
//var lm = new NGramModel(config, _recognizer.Logmath, path);
//_recognizer.SetLm("forecast", lm);
_isInitialised = true;
Console.WriteLine("InitVoiceRecognition done.");
}
/// <summary>
/// Listen for "delay" seconds. Use a timer. The timer and the speech recognition callbacks both use the _isHandlingResponse flag to decide
/// who is handling the response. If the user speaks before the timer elapses then the speech recognizer callbacks set the flag and handle
/// it by returning the spoken string to the HeardSpeechEventHandler. If the timer elapses first, then it sets the flag and sends an empty string
/// back to the HeardSpeechEventHandler.
/// </summary>
/// <param name="delay"></param>
public string Listen(int delay)
{
Console.WriteLine("Start listening for {0} seconds.", delay);
//_recognizer.StopListening();
try
{
_isHandlingResponse = false;
_recognizer.SetSearch("guide");
var timer = new Timer(_ =>
{
// If the speech recognizer is not handling the response already (i.e. the user hasn't spoken) then
// set the flag and return an empty response
if (!_isHandlingResponse)
{
_isHandlingResponse = true;
_recognizer.StopListening();
//Debug.WriteLine("The timer elapsed for the delay of {0} seconds with NO RESPONSE ({0})", delay, CurrentObject.Label);
//if (HeardSpeechEventHandler != null)
//{
// HeardSpeechEventHandler(this, new SpeechResult() {Result = string.Empty});
//}
Console.Write("Timer elapsed so setting response to '' and setting wait handle");
_response = string.Empty;
_waitHandle.Set();
}
}, null, delay * 1000, Timeout.Infinite);
_recognizer.StartListening();
Console.WriteLine("Started listening, now waiting for wait handle...");
_waitHandle.WaitOne();
return _response;
}
catch (Exception ex)
{
Console.WriteLine("An exception occurred attempting to listen: {0}", ex.Message);
return "";
}
}
//public IntPtr Handle { get; private set; }
/// <summary>
///
/// </summary>
/// <param name="p0"></param>
public void OnPartialResult(Hypothesis p0)
{
Console.WriteLine("Partial result: " + p0.Hypstr);
// If the timer is not handling the response already (i.e. timer hasn't timed out yet) then
// set the flag and stop listening. This will kick off the OnResult function.
if (!_isHandlingResponse)
{
_isHandlingResponse = true;
_recognizer.StopListening();
Console.WriteLine("Partial result: " + p0.Hypstr);
}
}
//http://www.albahari.com/
/// <summary>
///
/// </summary>
/// <param name="p0"></param>
public void OnResult(Hypothesis p0)
{
//if (!_isHandlingResponse)
//{
// _isHandlingResponse = true;
Console.WriteLine("Full result, setting response to '{0}' and setting wait handle", p0.Hypstr);
//if (HeardSpeechEventHandler != null)
//{
// HeardSpeechEventHandler(this, new SpeechResult() {Result = p0.Hypstr});
//}
_response = p0.Hypstr;
_waitHandle.Set();
//}
}
public void OnVadStateChanged(bool p0)
{
//throw new NotImplementedException();
//Console.WriteLine("OnVadStateChanged: {0}", p0);
}
/// <summary>
///
/// </summary>
/// <param name="name"></param>
/// <param name="path"></param>
/// <param name="ruleName"></param>
/// <param name="lw"></param>
private void AddSearch(String name, String path, String ruleName, int lw)
{
//File grammarParent = new File(Path.Combine(_assetDir.AbsolutePath, "grammar"));
var grammarParent = JoinPath(_assetDir, "grammar");
var jsgf = new Jsgf(Path.Combine(grammarParent, path));
var rule = jsgf.GetRule(ruleName);
var fsg = jsgf.BuildFsg(rule, _recognizer.Logmath, lw);
_recognizer.SetFsg(name, fsg);
Console.WriteLine("AddSearch done.");
}
/// <summary>
///
/// </summary>
/// <param name="parent"></param>
/// <param name="path"></param>
/// <returns></returns>
private static String JoinPath(Java.IO.File parent, String path)
{
return new Java.IO.File(parent, path).AbsolutePath;
}
}
答案 0 :(得分:1)
我通过获取当前活动的UI线程来加载Pocket Sphinx库来实现这一点,如下所示:
Mvx.Resolve<IMvxAndroidCurrentTopActivity>().Activity.RunOnUiThread(() =>
Java.Lang.JavaSystem.LoadLibrary("pocketsphinx_jni"));
这样做感觉有点不对(为什么我的插件需要知道当前UI线程的任何内容?) - 但它确实有效。