DELPHI下基于APRO控件的语音系统开发

开始设计
下面我们就来看看如何利用这组控件实现语音功能,对于我们程序的应用来说,只需要使用两个 TAPI 控件 TApdComPort TApdTapiDevice 即可,其中 TApdComPort 控件是一个串口通讯控件,因为 Modem 是同串口相连接的,因此需要串口通讯控件来进行控制。而 TapdTapiDevice 则是提供语音功能的核心控件。
首先,新建一个程序项目,在窗体上放置一个 TApdComport 控件,设置其属性为 AutoOpen:=False;TapiMode=tmOn; 这里 TapiMode 设定为 tmOn 表明 TApdComPort 将由同其关联的 TApdTapiDevice. 控件来控制,而将 AutoOpen 设定为 False 是因为串口的打开和关闭现在可以完全由 TAPI 来控制了。
然后,在窗体上放置一个 TApdTapiDevice 控件,设定其 Comport 属性为前面的 TApdComPort 控件。设定 AnswerOnRing 属性为 1 ,表明第一次振铃后就开始由程序控制电话的应答。设定 ShowTapiDevices True 表明当调用控件的 SelectDevice 方法时,会显示一个选择 TAPI 设备的对话框。 ShowPorts 属性为 false ,表明调用 SelectDevice 方法不会显示串行口列表。
接下来,本程序主要是采用有限状态机来控制流程的,下面我们来定义枚举状态
 Type
TCurrentState = (csIdle, csWaiting, csConnected, csPlaying, csRecording, sDisconnected);
 
其中 csIdle 状态表示电话处于空闲状态,正等待接入。 csWaiting 则表示电话处于程序控制下,等待接入,如果有电话打入,程序会自动应答。 csConnected 则表示有电话打入,处于连接状态, csRecording 则用来表示当前处于记录电话留言状态。 csDisconnected 则表示当前连接挂断了。
 
程序初始化
下面就是程序的 OnCreate 的事件处理函数,非常简单,就是先设置当前状态为 csIdle ,并设置 ApdTapiDevice 控件的 TrimSeconds 属性为 5 ,表示当录音时如果有5秒的沉默时间就挂断。
procedure TFrmMain.FormCreate(Sender: TObject);
var
TeleIni: TIniFile;
begin
CurrentState := csIdle;
ApdTapiDevice.TrimSeconds := 5; //
录音时有 5 秒静音就挂断
 CommandList := TStringList.Create;
 TeleIni := TIniFile.Create(ExtractFilePath(ParamStr(0)) + 'Tele.ini');
TeleIni.ReadSectionvalues('Commands', CommandList);
TeleIni.Free;
WindowState := wsMaximized;
end;
然后是将定义在 Tele.Ini 文件中的将要播放的声音列表文件目录加载到 CommandList 中。 Tele.Ini 的示例如下:
[Commands]
1#=1.wav
2#=2.wav
3#=3.wav
123#=E:/Program Files/APRO/Examples/Beep.wav
其中 1# ,表示当用户按下 1 # 号按键后,程序会播放其对应的 1.wav 文件。接下来就是我们要提供两个命令,一个是监控电话,一个是挂断电话,先在窗体上添加一个 TlistBox, 起名为 LBSysInfo ,然后添加两个菜单项,并同两个 Action 连接,编写 Action OnExecute 事件处理函数:
 //
监控电话
procedure TFrmMain.ActionAnswerExecute(Sender: TObject);
begin
try
ApdTapiDevice.EnableVoice := True;
except
Application.MessageBox('
当前设备不支持语音扩展 ', ' 错误 ', MB_OK);
end;
 if ApdTapiDevice.EnableVoice then
begin
ApdTapiDevice.AutoAnswer;
LBSysInfo.Items.Add('answer:
接听对方电话 ');
CurrentState := csWaiting;
end
end;
 
因为不是所有的 Modem 都支持语音功能,因此在监控电话接入前应该先判断设置 ApdTapiDevice.EnableVoice := True; ,如果出现异常,表明 Modem 不支持语音功能。如果支持的话,就调用 AutoAnswer 方法等待接入同时设置状态为 csWaiting ,并在列表框中写入日志。
 //
挂断电话
procedure TFrmMain.ActionCancelExecute(Sender: TObject);
begin
ApdTapiDevice.CancelCall;
LBSysInfo.Items.Add('cancel:
挂断对方电话 ');
CurrentState := csIdle;
end;
 
挂断电话就简单多了,只要简单的调用 TApdTapiDevice 控件的 CancelCall 方法就可以了,还需要设置当前状态为 csIdle
 
如果系统中存在多个 TAPI 设备的时候,我们还可以选择使用哪一个来接听电话,下面是选择设备的方法:
 //
选择设备
procedure TFrmMain.ActionSelDevExecute(Sender: TObject);
begin
ApdTapiDevice.SelectDevice;
ApdTapiDevice.EnableVoice := True;
end;
 
事件驱动
Telephone API
是基于事件驱动的,因此核心功能需要在事件处理函数中实现,先来看程序的 TApdTapiDevice OnConnect 事件处理函数代码:
 procedure TFrmMain.ApdTapiDeviceTapiConnect(Sender: TObject);
begin
CurrentState := csConnected;
LBSysInfo.Items.Add('Connect:
连接成功 ');
ApdTapiDevice.PlayWaveFile('Greeting.wav');//
播放功能提示语音
LBSysInfo.Items.Add('connect:
播放 greeting.wav');
end;
 
当用户打入被监控的电话后,会激发这个事件,程序应该在用户接入后播放提示语音,提示用户按不同功能键来点歌或留言。程序设置当前状态为 csConnected ,然后调用 ApdTapiDevice PlayWaveFile 方法播放提示语音波文件。
要注意的是:不同 Modem 支持播放的波文件的格式是不同的,但它们都支持 PCM 8 位单声道的波文件,但这种类型波文件的音质非常差,用来播放歌曲效果实在糟糕,不过大多数语音 Modem 都支持音质更好的波文件格式,但通常都是 PCM 格式的,比如我的 Lucent Voice Modem 就支持 PCM 16 位 单声道的波文件的播放。歌曲转化为波文件非常简单,我用 Winamp mp3 文件通过 Winamp 本身的 Disk Writer Plug-in 插件直接将 mp3 转化成 44 位的波文件(通常为 40-70M 大小),然后在用一个叫 goldwave 的软件(我忘了从什么地方下载的了)将其转化为 16 位的单声道波文件(通常 4-7M 大小)。至于提示语音,我则是使用 windows 自带的录音机程序通过麦克风录制的。  
当用户听完提示语音后,他们会按键来点歌或留言,而用户的按键会激发 TApdTapiDevice OnDTMF 事件,我们就可以在这个事件中对按键进行处理,下面就是处理过程代码:
 procedure TFrmMain.ApdTapiDeviceTapiDTMF(CP: TObject; Digit: Char;
ErrorCode: Integer);
begin
if (Digit = '') or (Digit = ' ') then
Exit;
LBSysInfo.Items.Add('dtmf:
按键 =' + Digit);
 CurrentCommand := CurrentCommand + Digit;
{
简单状态机 }
if Digit = '#' then
begin
if CurrentCommand = '*#' then
begin
CurrentCommand := '';
ApdTapiDevice.MaxMessageLength := 30; //
最长记录时间 30
ApdTapiDevice.InterruptWave := False; //
按键不能中断提示语音的播放  
ApdTapiDevice.PlayWaveFile('recordhint.wav');//
播放录音提示语音
CurrentState := csRecording;
Exit;
end;
 if CommandList.values[CurrentCommand] <> '' then
begin
ApdTapiDevice.PlayWaveFile(CommandList.values[CurrentCommand]);
LBSysInfo.Items.Add(Format('%s %s
正在播放 %s',
[ApdTapiDevice.calleridname, apdtapidevice.callerid,
CommandList.values[CurrentCommand]]));
end
else
begin
//
播放错误提示语音,并要求用户重新输入命令
ApdTapiDevice.PlayWaveFile('errorno.wav');
LBSysInfo.Items.Add(Format('%s %s
输入了错误的号码 ',
[ApdTapiDevice.calleridname, apdtapidevice.callerid]));
end;
//
重置命令为空
CurrentCommand := '';
end;
end;
 
程序对按键进行判断(按键对应于 digit 参数),如果输入的为 '*#' 键,就进入录音功能,在录音前先播放提示语音,可以告诉用户留言长度为 30 秒,然后设置当前状态为 csRecording ,有人可能要问,没看到用来录音的代码呀,这部分其实是实现在另外的事件中的,我们稍后就会讲到。再来看点歌部分,同样的根据按键的组合在先前加载进 CommandList 的字符串列表中查找相匹配的歌曲,如果有相应的歌曲就播放,否则播放错误提示语音,提示用户重新输入命令,然后将按键清空等待重新输入。另外注意在事件的日志记录中我记录了 ApdTapiDevice.calleridname CallerID 的属性,它们对应的是打入电话的号码,不过这项功能只对开通了来电显示功能的电话号码才有效,通过对打入电话号码信息的处理,我们可以提供一些额外的功能,不过这是题外话了。
前面提到了在按键处理事件中我们并没有进行留言的录制功能,这主要是因为我们要保证留言提示语音不被按键中断(设定 Interruptwave:=false ),因此把留言录制功能放到了 TApdTapiDevice OnWaveNotify 事件中了,这个事件可以提示波文件播放的状态,比如播放结束和录音所需声音数据准备状态等,在本程序中我们需要在提示语音播放结束后,开始记录留言,并在留言声音数据准备好后,将其保存到磁盘文件中。下面是处理过程的流程:  
 procedure TFrmMain.ApdTapiDeviceTapiWaveNotify(CP: TObject;
Msg: TWaveMessage);
var
TimeStr: string;
FileName: string;
begin
//
决不能在 case 外做耗时的操作
case Msg of
waPlayOpen: LBSysInfo.Items.Add('wavnotify:
播放开始 ');
waPlayDone:
begin
LBSysInfo.Items.Add('wavnotify:
播放结束 ');
if CurrentState = csRecording then
begin
try
       // 等待波设备状态为 wsIdle 再开始录音
while ApdTapiDevice.WaveState <> wsIdle do
Application.ProcessMessages;  
ApdTapiDevice.InterruptWave := True;
ApdTapiDevice.StartWaveRecord;
LBSysInfo.Items.Add('dtmf:
录音成功 ');
except
LBSysInfo.Items.Add('dtmf:
录音失败 ');
end;
end;
end;
waPlayClose: LBSysInfo.Items.Add('wavnotify:
播放关闭 ');
waRecordOpen: LBSysInfo.Items.Add('wavnotify:
录音开始 ');
waDataReady:
begin
LBSysInfo.Items.Add('wavnotify:
数据准备 ');
TimeSeparator := '-';
FileName := DateTimeToStr(Now) + '.wav';
try
ApdTapiDevice.SaveWaveFile(ExtractFilePath(ParamStr(0)) + 'record/' +
FileName, True);
LBSysInfo.Items.Add('wavNotify:
保存声音文件 ' + FileName);
except
LBSysInfo.Items.Add('wavnotify:
保存声音文件失败 ');
end;
end;
waRecordClose:
begin
LBSysInfo.Items.Add('wavnotify:
记录声音结束 ');
CurrentState := csWaiting;
ActionCancelExecute(nil);
Timer1.Enabled := True;
end;
end;
end;
 
整个流程就是通过一个 Case 语句来判断当前声音状态,如果为 waPlayDone( 播放完毕 ) ,同事 CurrentStatus csRecording 的话,就调用 StartWaveRecord 方法来记录声音。而当 Msg waDataReady 状态时,表明录音数据已经可以存盘了,这时根据当前时间生成一个文件名,并将数据保存为波文件。而当录音结束后,我们就需要调用 ActionCancelExecute(nil) 来挂断电话,并将状态设置为 csWaiting 来等待下次接入,注意的在代码最后,我们将一个 TTimer 控件激活了。这个 TTimer 控件的时间间隔 Interval 设置为 8 秒,同时其 OnTimer 事件代码如下:  
 procedure TFrmMain.Timer1Timer(Sender: TObject);
begin
try
   // 应答电话
ActionAnswerExecute(nil);
CurrentState := csWaiting;
Timer1.Enabled := False;
except
end;
end;
 
这样设置的原因在于,当调用 CancelCall 方法来挂断电话后, TAPI 设备需要 8 秒来恢复正常状态,如果立刻执行 AutoAnswer 的话,这个方法就会失效,无法正确监控电话接入,因此要用 TTimer 来控制恢复电话应答的时间。
 
异常处理
要想程序非常健壮的反复应答电话接入,我们必须对用户突然挂断电话进行处理,用户断开的事件会激发控件的 OnTapiStatus 事件,当用户挂断电话时,我们要做的是如果当前还在录音,就停止录音,如果是在播放歌曲,就挂断电话,然后设置 TTimer 生效,重新进入电话应答状态。下面就是整个处理过程的代码:
 procedure TFrmMain.ApdTapiDeviceTapiStatus(CP: TObject; First,
Last: Boolean; Device, Message, Param1, Param2, Param3: Cardinal);
begin
if (Message = Line_CallState) then
begin
case Param1 of
LineCallState_Disconnected:
begin
LBSysInfo.Items.Add('status:disconnected from remote modem');
if CurrentState = csRecording then
begin
ApdTapiDevice.StopWaveRecord;
Exit;
end;
CurrentState := csDisconnected;
ActionCancelExecute(nil);
Timer1.Enabled := True;
end;
end;
end;
end;
 
进一步完善
当录音完毕后,我们想听一下电话留言的话,可以在窗体上放置一个打开文件对话框,用下面代码实现:
 procedure TFrmMain.ActionPlayRecExecute(Sender: TObject);
var
FrmPlay: TFrmPlayRec;
begin
DlgOpenRec.InitialDir := ExtractFilePath(ParamStr(0)) + 'Record/';
if DlgOpenRec.Execute then
//
播放声音记录文件
ShellExecute(Application.Handle, PChar('open'), PChar(DlgOpenRec.FileName),
nil, nil, SW_SHOW);
end;
 
另外,如果大家自信自己的歌喉不比那些歌星差的话,完全可以录制自己的歌声,然后播放给你的女朋友或朋友听,也许效果更棒:)。
最后,我要说的就是 Telephone API 所能提供的功能远远不止本文中所提到的,感兴趣的朋友可以进一步查阅相关资料来研究。
 
最后,要说的是 Turbo Power 已经不再开发 Async Pro 了,它把所有的源码都放到了 Sourceforge 上共享,大家可以到 SourceForge 上下载。  

 
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值