65.9K
CodeProject 正在变化。 阅读更多。
Home

AISpeech API ASSDK 教程 2:一个更完整的版本

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2012年4月10日

CPOL

8分钟阅读

viewsIcon

11721

在本文中,我们将构建一个更完整的版本。除了查看 ASSDK 返回和响应的内容外,我们还将探索 ASSDK 提供的几乎所有主要方法。

引言

上一篇文章中,我们快速回顾了使用 AISpeech API 云服务及其 ASSDK (ActionScript 3.0) 构建语音英语测评应用的过程。

在本文中,我们将构建一个更完整的版本。除了查看 ASSDK 返回和响应的内容外,我们还将探索 ASSDK 提供的几乎所有主要方法。

本教程的源代码可在 github 上找到。

目标

我们将构建一款语音英语测评应用。这是一个 Flex 应用。用户界面如图 1 所示。

图 1 应用线框图

分数显示

这次,我们将参考文本的每个单词着色,以指示发音分数。绿色 (#00FF00) 表示非常好。黄色 (#FFD800) 表示良好。橙色 (#FF6A00) 表示差。红色 (#FF0000) 表示非常差。我们给每个单词一个 <span> HTML 标签,并为其分配颜色属性。然后,我们使用 Flex TextArea (spark) 组件显示 HTML 内容。

我们从一个 Flex TextArea (spark) 组件开始:

<s:TextArea id="txtScores" fontSize="30" editable="false"/>

然后我们添加以下脚本:

import spark.utils.TextFlowUtil; 
private var test:String =
    "<span color='#00ff00'>I </span> " +
    "<span color='#ffd800'>like </span> " +
    "<span color='#ffd800'>to </span> " +
    "<span color='#ff6a00'>play </span> " +
    "<span color='#ff0000'>piano </span> ";
private function init():void
{
    txtScores.textFlow = TextFlowUtil.importFromString(test);
}

我们需要将 init() 方法分配给应用程序的 creationComplete 回调方法。还要注意我们附加到每个单词的空格。它显示为

Figure 2 Colourful score output 

图 2 彩色分数输出

其他控件

如图 1 所示,我们还需要其他几个控件。脚本如下:

<s:VGroup> 
    <s:HGroup>
        <s:Label text="Reference Text"/>
        <s:TextInput id="txtRefText" text="I like to play piano" fontSize="30" width="627"/>
    </s:HGroup>
    <s:HGroup>
        <s:Button label="Start Record" fontSize="20" />
        <s:Button label="Stop Record" fontSize="20" />
        <s:CheckBox label="Play 'ding'" id="chboxPlayDing" selected="true" />
        <s:Button label="Replay the latest" fontSize="20" />
    </s:HGroup>
    <s:HGroup>
        <s:Label text="Scores" />
        <s:TextArea id="txtScores" width="666" height="50" editable="false" fontSize="30" />
    </s:HGroup>
    <s:HGroup>
        <s:Label text="RecorderLib returns and events"/>
        <s:TextArea id="txtReturns" width="531" height="100"/>
    </s:HGroup>
    <s:Label text="RecordLib components states"/>
    <s:Label text="Microphone: " id="txtMicrophoneState" fontSize="20"/>
    <s:Label text="Connection: " id="txtConnectionState" fontSize="20"/>
    <s:Label text="Core requester: " id="txtCoreRequesterState" fontSize="20"/>
    <s:Label text="Recorder: " id="txtRecorderState" fontSize="20"/>
</s:VGroup> 

完成的布局如下所示。

图 3 完成的布局

使用 AISpeech API ASSDK 录制用户语音

如前一篇文章所述,AISpeech API ASSDK 以 AISpeechLib.swc 的形式提供。将 Flex 应用链接到此库。以下代码准备并初始化一个 RecorderLib 实例。

import com.aispeech.RecorderLib; 
private static const RECORDERLIB_PARAMS:Object = {
    appKey:"your application ID", 
    secretKey:"your secret Key", 
    serverList:["rtmp://demo.aispeech.com:80/v2.0/aistream","rtmpt://demo.aispeech.com:433/v2.0/aistream"]
}; 
private var _coreRequesterParams:Object = {
    refText:"past", // this to be updated for each core request (record)
    rank:4,        // we use four-level-scores this time
    coreType:"en.sent.score",  // request the English-Senetence core 
    userId:"xxxxxx",
    applicationId:"your application ID"  // application ID again 
}; 
private var _recorder:RecorderLib; 
private function init():void
{
    _recorder = new RecorderLib();
    _recorder.init(RECORDERLIB_PARAMS);
} 

为了使本教程能够独立完成,有几点需要注意,尽管这些内容已在上一教程中介绍过。

  • RECORDERLIB_PARAMS 用于初始化 RecorderLib 实例。appKeysecretKey 由 AISpeech 提供,需要向其申请。serverLists 包含 AISpeech API 云访问点的列表。
  • _coreRequesterParams 用于向语音核心发出请求。每次录音都对应一个核心请求。refText 可能会针对每个请求/录音进行更新。

运行应用程序,如图所示,您应该会看到一个请求用户使用麦克风设备的权限的对话框。我们说“这个提示是一个很好的指标,表明 RecorderLib 已成功加载”,但这并不完全准确。稍后我们将找出原因。

图 4 请求用户权限的对话框

然后我们添加 startRecord()stopRecord() 方法,如下所示:

private function startRecord():void 
{ 
    _coreRequesterParams.refText = txtRefText.text; 
    var recordLength:int = 2500 + txtRefText.text.split(" ").length * 450; 
    var recorderParams:Object = 
        { 
            serverParam:_coreRequesterParams, 
            recordLength:recordLength // ms  
        }; 
    _recorder.startRecord(recorderParams); 
} 
private function stopRecord():void 
{ 
    _recorder.stopRecord(); 
} 

有几点需要注意:

  • 在将 _coreRequesterParams.refText 发送到 _recorderstartRecord() 方法之前,我们会根据用户的输入 (txtRefText.txt) 更新它。
  • 我们根据参考文本中的单词数量计算录音时长(毫秒)。
  • 然后,我们将 startRecord() 方法分配给“开始录音”按钮的点击回调,并将 stopRecord() 方法分配给“停止录音”按钮的点击回调。

运行项目。单击“开始”按钮。如果听到“叮”的一声,则表示录音现在工作正常。

显示分数

在本节中,我们将分三个步骤进行:获取 API 响应、构建 HTML 字符串和显示分数。

以下脚本提供了一个事件处理程序来解析分数结果:

private function coreRequesterEventHandler(event:CoreRequesterEvent):void
{
    if (event.type == CoreRequesterEvent.RESULT)
    {
        // parse results
        var details:Array = event.data.result.details;
        trace("length of details: " + details.length);
    }
} 

以下脚本(位于 init() 方法内)让 RecorderLib 实例监听 CoreRequesterEvent.RESULT 事件。请注意,addEventListener() 方法在 RecorderLib 实例被创建后、初始化之前调用。

_recorder = new RecorderLib(); 
_recorder.addEventListener(CoreRequesterEvent.RESULT, coreRequesterEventHandler); 
_recorder.init(RECORDERLIB_PARAMS); 

coreResultEventHandler() 方法中,我们输出了 details 的长度。调试项目,检查控制台,可以看到 details 的长度为 5,给定参考文本为“I like to play piano”,这是正确的。

在调用 trace() 方法的那一行设置一个断点。再次调试项目。对着麦克风说话,程序会停止。如图所示,details 数组元素的 score 属性为“4”。由于我们将 _coreRequesterParams 对象的 rank 属性设置为 4,因此语音核心现在返回 4 级分数。4 是最高分,1 是最低分。

Figure 5 Results

图 5 结果

以下代码提供了一个辅助函数,用于构建 HTML 字符串,该字符串根据单词的分数对其进行彩色标记。请记住规则:红色表示非常差,绿色表示非常好。介于两者之间的是橙色和黄色。

private function colourfulScoreHelper(refText:String, details:Array):String
{
    // red, orange, yellow, green
    var colourArray:Array = ["#FF0000", "#FF6A00", "#FFD800", "#00FF00"];
    var refTextArray:Array = refText.split(" ");
    var htmlString:String = "";
    var temp:String = "";
    for (var i:int = 0; i < refTextArray.length; i ++)
    {
        // recall the example: "<span color='#00ff00'>I </span>"
        temp = "<span color='" +
            colourArray[details[i].score - 1] +
            "'>" +
            refTextArray[i] +
            " </span>";
        htmlString += temp;
    }
    return htmlString;
} 

思路是:我们使用单词的分数作为索引,从 colourArray 中选择颜色值。其余的只是简单的字符串拼接。我们可以进一步将 colourArray 设为常量成员变量。

回到 coreRequesterEventHandler() 方法。以下代码调用 HTML 字符串辅助函数并更新 txtScores TextArea 组件。

private function coreRequesterEventHandler(event:CoreRequesterEvent):void
{
    if (event.type == CoreRequesterEvent.RESULT)
    {
        // parse results
        var details:Array = event.data.result.details;
        var htmlString:String = colourfulScoreHelper(_coreRequesterParams.refText, details);
        txtScores.textFlow = TextFlowUtil.importFromString(htmlString);
    }
} 

项目应该可以工作,并显示彩色且有意义的语音评估结果。

到目前为止,我们几乎重复了上一教程中的内容。但这次,我们实现了一个新的分数视图,并尝试了 4 级分数。在下一节中,我们将更深入地研究 RecorderLib

RecorderLib 组件

尽管 RecorderLib 只暴露了少量方法和属性,但其内部相当复杂。简单来说,RecorderLib 包含四个主要组件:麦克风、录音器、连接和核心请求器。我们不会深入研究它们的实现。(实际上,AI Speech Ltd 及其工程师计划开源 ASSDK,可能在下一次主要更新之后。)在演示中,我们将看到 RecorderLib 除了我们处理过的核心结果之外,还会返回哪些其他信息。

我们从方法返回值开始。

根据 ASSDK 文档,init()startRecord() 等方法都返回一个 StatusCode。我们现在将它们显示在 txtReturns 中,这是一个我们在布局中定义的 TextArea 实例。

我们首先提供一个将字符串附加到 txtReturns 的函数:

private function appendReturns(message:String):void
{ 
    txtReturns.text += (message + "\n");
} 

然后我们将 appendReturns() 方法应用于调用 RecorderLibinit()startRecord()stopRecord() 方法(到目前为止我们只知道这三个方法)。例如:

appendReturns(_recorder.init(RECORDERLIB_PARAMS));

运行应用程序,并进行一些操作(例如,单击“开始录音”和“停止录音”按钮)。这次,“RecorderLib 返回和事件”显示了一些“日志”。其中大部分是 StatusCode 或数字。根据 ASSDK 文档,我列出了一些 StatusCode 及其含义如下:

  • 50004:成功
  • 50005:参数错误
  • 50003:麦克风设备不可用
  • ……

现在我们知道 RecorderLib 方法返回 StatusCode。让我们看看 RecordeLib 分派了哪些事件。

我们已经知道了 CoreRequesterEvent.RESULT。此事件对于捕获 API 响应和解析结果(分数)至关重要。RecorderLib 分派许多其他事件。以下是完整列表:

  • FactoryEvent
    • READY
    • EXCEPTION_TIMEOUT
  • NetEvent
    • EXCEPTION_CLOSED
  • CoreRequesterEvent
    • RESULT
    • EXCEPTION_TIMEOUT
    • EXCEPTION_PARAMETERS_ERROR
    • EXCEPTION_RESPONSE_ERROR
  • MicrophoneDeviceEvent
    • MIC_ALLOWED
    • EXCEPTION_MIC_NOT_ALLOWED
    • EXCEPTION_MIC_NOT_FOUND
  • RecorderEvent
    • RECORD_STARTED
    • RECORD_STOPPED
    • REPLAY_STARTED
    • REPLAY_STOPPED
    • RECORDID_GOT
    • EXCEPTION_NO_RECORD

请注意,RecorderLib 有一个工厂组件,它初始化 RecorderLib 实例,连接到服务器,并自动连接上述四个主要组件。在工厂完成工作之前,RecorderLib 实例不应进行录音或请求语音核心。因此,出现了 FactoryEvent.READY 事件。这就是为什么我们指出,看到请求用户权限的对话框并不一定意味着 RecorderLib 实例已成功初始化。事件不会撒谎。

在以下脚本中,我们添加了几个事件监听器来监听所有定义的事件。请注意,上面列出的所有事件都扩展自 AIEvent 类。除了 CoreRequesterEvent.RESULT,我们只使用一个事件处理程序来处理其他事件:将它们打印出来。这仅用于演示目的。在实际应用程序中,每个事件都应仔细处理。例如,RecorderEvent.RECORD_STARTEDRecorderEvent.RECORD_STOPPED 可以组合起来控制录音进度条。作为另一个例子,RecorderEvent.RECORDID_GOT 可用于检索本地缓存录音的 recordId

_recorder = new RecorderLib(); 
_recorder.addEventListener(CoreRequesterEvent.RESULT, coreRequesterEventHandler);
// add the other event listeners
_recorder.addEventListener(FactoryEvent.READY, eventHandler);
_recorder.addEventListener(FactoryEvent.EXCEPTION_TIMEOUT, eventHandler);
_recorder.addEventListener(NetEvent.EXCEPTION_CLOSED, eventHandler);
_recorder.addEventListener(CoreRequesterEvent.EXCEPTION_PARAMETERS_ERROR, eventHandler);
_recorder.addEventListener(CoreRequesterEvent.EXCEPTION_RESPONSE_ERROR, eventHandler);
_recorder.addEventListener(CoreRequesterEvent.EXCEPTION_TIMEOUT, eventHandler);
_recorder.addEventListener(MicrophoneDeviceEvent.MIC_ALLOWED, eventHandler);
_recorder.addEventListener(MicrophoneDeviceEvent.EXCEPTION_MIC_NOT_ALLOWED, eventHandler);
_recorder.addEventListener(MicrophoneDeviceEvent.EXCEPTION_MIC_NOT_FOUND, eventHandler);
_recorder.addEventListener(RecorderEvent.EXCEPTION_NO_RECORD, eventHandler);
_recorder.addEventListener(RecorderEvent.RECORD_STARTED, eventHandler);
_recorder.addEventListener(RecorderEvent.RECORD_STOPPED, eventHandler);
_recorder.addEventListener(RecorderEvent.RECORDID_GOT, eventHandler);
_recorder.addEventListener(RecorderEvent.REPLAY_STARTED, eventHandler);
_recorder.addEventListener(RecorderEvent.REPLAY_STOPPED, eventHandler);
appendReturns(_recorder.init(RECORDERLIB_PARAMS)); 

请注意,我们在初始化 RecorderLib 实例之前添加了事件监听器。

eventHandler() 方法简单地将 event.type 附加到 txtReturns TextArea:

private function eventHandler(event:AIEvent):void 
{
    appendReturns(event.type);
}

现在,我们可以看到更多的日志输出。例如:

microphone.device.exception.mic.not.allowed 
[{"message":"","statusCode":"50004"}] 
factory.ready 
microphone.device.mic.allowed 
recorder.recordID.got 
recorder.record.started 
[{"message":"start recording","statusCode":"50004"}] 
recorder.record.stopped 
[{"message":"stop record","statusCode":"50004"}] 

除了方法返回值和事件之外,RecorderLib 还允许应用程序查看其组件的状态。

在以下代码中,我们设置了一个计时器,自动检查 RecorderLib 实例的状态,并将它们打印到屏幕上。首先是 checkStates() 方法:

private function checkStates(event:TimerEvent):void
{
    txtMicrophoneState.text = "Microphone: " + _recorder.microphoneDeviceState;
    txtRecorderState.text = "Recorder: " + _recorder.recorderState;
    txtConnectionState.text = "Connection: " + _recorder.connectionState;
    txtCoreRequesterState.text = "Core requester: " + _recorder.coreRequesterState;
} 

然后在 init() 方法中,我们设置了一个计时器:

_timer = new Timer(2000); 
_timer.addEventListener(TimerEvent.TIMER, checkStates);
_timer.start()

请注意,_timer 是一个 Timer 实例,定义为私有成员变量。

现在,应用程序每 2 秒检查一次 RecorderLib 实例的组件状态,并显示它们(作为 String),如下所示:

图 6 组件状态

另外两个 RecorderLib 功能。

我们想收听最新的录音。将以下代码分配给“重播最新录音”按钮的点击回调:

appendReturns(_recorder.startReplay({}));

几点说明:

  • 我们调用 startReplay() 方法进行重播。我们给这个方法一个空对象“{}”,以便播放最新的本地缓存录音。
  • 我们将 startReplay() 方法用 appendReturns() 包装起来,以便我们能在屏幕上看到它的返回值。

回想图 1。在该线框图中,我们设计了一个复选框“播放叮声”。我们注意到 RecorderLib 会播放一声“叮”来通知用户开始说话。这是一个很好的语音用户界面设计,系统应该响应用户的操作(单击“开始录音”按钮)。这也有助于确保用户在实际录音功能开始后才说话。但是,如果您不想播放“叮”声,请随意这样做。然而,响应用户的操作是一个最佳实践,因此录音进度条可能是一个很好的补充。

回想 startRecord() 方法,我们在 recorderParam 变量中设置了两个属性(serverParamrecordLength)。RecorderLib.startRecord() 方法接受另一个属性:playDing。请看以下代码:

private function startRecord():void
{
    _coreRequesterParams.refText = txtRefText.text;
    var recordLength:int = 2500 + txtRefText.text.split(" ").length * 450;
    _playDing = chboxPlayDing.selected;
    var recorderParams:Object =
        {
            serverParam:_coreRequesterParams,
            recordLength:recordLength, // ms 
            playDing:_playDing
        };
    appendReturns(_recorder.startRecord(recorderParams));
}  

结论

好了,这篇文章很长。但我们现在都完成了。

在本教程中,在“RecorderLib 组件”部分之前,与上一教程相比,有两件新事物:

  1. 我们使用 Flex TextArea 渲染 HTML 字符串,以彩色显示分数。
  2. 我们要求语音核心返回 4 级分数。

在“RecorderLib 组件”及后续部分,我们捕获了 RecorderLib 的方法返回值、事件和组件状态。我们只是将它们打印在屏幕上。但这些信息对于构建一个防错且用户友好的应用程序非常重要。

如我所承诺的,我已向 AI Speech Ltd 索取了 ASSDK 文档的英文版本。据我所知,在撰写本文时,他们正在为源代码添加 ASDoc 注释。我可能会在某个地方上传 PDF 版本。

另外,本教程的源代码可以在 github 上找到。

感谢阅读。

历史

这是本文的第一个版本。

© . All rights reserved.