熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Delphi編程 >> 正文

編程實現電話點歌-情人節特別禮物

2013-11-11 21:06:14  來源: Delphi編程 

  偶然的起因

  記得還是在去年情人節的時候當時一直在為給女朋友送什麼禮物而發愁覺得送花實在沒有什麼創意可又不知道什麼樣的禮物即能給她一個驚喜同事又不昂貴這時我的一個好朋友出了一個主意說不如電話點歌吧還比較特別可是如果是通過電台點歌後再告訴她收聽的話就起不到意外的效果了

  就在沒有什麼好辦法的時候我在Delphi論壇上瞎逛的時候一個人提出的問題突然啟發了我問題是關於如果編程實現語音留言和電話按鍵的記錄功能的我突然想為什麼我不能寫一個程序來控制電話然後再給女友打一個傳呼讓她回電話當電話接通後我的程序先播放一段事先錄制好的話提示她通過電話按鍵來選歌並能提供留言的功能呢主意一定我就趕忙查閱這方面的資料了一開始朋友們告訴可以通過語音卡來實現這些功能可是語音卡比較貴而且我買了後除了用一次以外以後也不會經常用到實在是有點浪費後來網友cced提到他聽人說TurboPower公司出的Async Professional控件提供了一組基於Telephone Api的控件可以通過語音Modem來實現類似的功能這個看來成本就低多了我的Modem正好是語音Modem於是我就下載了Async Professional(官方網wwwturbopowercom)試驗了一下果然不同反響便宜且簡單

  開始設計

  下面我們就來看看如何利用這組控件實現語音功能對於我們程序的應用來說只需要使用兩個TAPI控件TApdComPort和TApdTapiDevice即可其中TApdComPort控件是一個串口通訊控件因為Modem是同串口相連接的因此需要串口通訊控件來進行控制而TapdTapiDevice則是提供語音功能的核心控件

  首先新建一個程序項目在窗體上放置一個TApdComport控件設置其屬性為AutoOpen:=False;TapiMode=tmOn;這裡TapiMode 設定為tmOn 表明TApdComPort 將由同其關聯的TApdTapiDevice控件來控制而將AutoOpen設定為False 是因為串口的打開和關閉現在可以完全由TAPI來控制了

  然後在窗體上放置一個TApdTapiDevice控件設定其Comport屬性為前面的TApdComPort控件設定AnswerOnRing屬性為表明第一次振鈴後就開始由程序控制電話的應答設定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秒的沉默時間就掛斷

procedure TFrmMainFormCreate(Sender: TObject);
var
TeleIni: TIniFile;
begin
CurrentState := csIdle;
ApdTapiDeviceTrimSeconds := ; //錄音時有秒靜音就掛斷

CommandList := TStringListCreate;

TeleIni := TIniFileCreate(ExtractFilePath(ParamStr()) + Teleini);
TeleIniReadSectionvalues(Commands CommandList);
TeleIniFree;
WindowState := wsMaximized;
end;

  然後是將定義在TeleIni文件中的將要播放的聲音列表文件目錄加載到CommandList中

TeleIni的示例如下

[Commands]
#=wav
#=wav
#=wav
#=E:\Program Files\APRO\Examples\Beepwav

  其中#表示當用戶按下和#號按鍵後程序會播放其對應的wav文件接下來就是我們要提供兩個命令一個是監控電話一個是掛斷電話先在窗體上添加一個TlistBox起名為LBSysInfo然後添加兩個菜單項並同兩個Action連接編寫Action的OnExecute事件處理函數

//監控電話
procedure TFrmMainActionAnswerExecute(Sender: TObject);
begin
try
ApdTapiDeviceEnableVoice := True;
except
ApplicationMessageBox(當前設備不支持語音擴展 錯誤 MB_OK);
end;

if ApdTapiDeviceEnableVoice then
begin
ApdTapiDeviceAutoAnswer;
LBSysInfoItemsAdd(answer:接聽對方電話);
CurrentState := csWaiting;
end
end;

  因為不是所有的Modem都支持語音功能因此在監控電話接入前應該先判斷設置ApdTapiDeviceEnableVoice := True;如果出現異常表明Modem不支持語音功能如果支持的話就調用AutoAnswer方法等待接入同時設置狀態為csWaiting並在列表框中寫入日志

//掛斷電話
procedure TFrmMainActionCancelExecute(Sender: TObject);
begin
ApdTapiDeviceCancelCall;
LBSysInfoItemsAdd(cancel:掛斷對方電話);
CurrentState := csIdle;
end;

  掛斷電話就簡單多了只要簡單的調用TApdTapiDevice控件的CancelCall方法就可以了還需要設置當前狀態為csIdle

  如果系統中存在多個TAPI設備的時候我們還可以選擇使用哪一個來接聽電話下面是選擇設備的方法

//選擇設備
procedure TFrmMainActionSelDevExecute(Sender: TObject);
begin
ApdTapiDeviceSelectDevice;
ApdTapiDeviceEnableVoice := True;
end;

事件驅動
Telephone API是基於事件驅動的因此核心功能需要在事件處理函數中實現先來看程序的TApdTapiDevice的OnConnect事件處理函數代碼

procedure TFrmMainApdTapiDeviceTapiConnect(Sender: TObject);
begin
CurrentState := csConnected;
LBSysInfoItemsAdd(Connect:連接成功);
ApdTapiDevicePlayWaveFile(Greetingwav);//播放功能提示語音
LBSysInfoItemsAdd(connect:播放greetingwav);
end;

  當用戶打入被監控的電話後會激發這個事件程序應該在用戶接入後播放提示語音提示用戶按不同功能鍵來點歌或留言程序設置當前狀態為csConnected然後調用ApdTapiDevice的PlayWaveFile方法播放提示語音波文件

  要注意的是不同Modem支持播放的波文件的格式是不同的但它們都支持PCM 位單聲道的波文件但這種類型波文件的音質非常差用來播放歌曲效果實在糟糕不過大多數語音Modem都支持音質更好的波文件格式但通常都是 PCM格式的比如我的Lucent Voice Modem就支持PCM 位單聲道的波文件的播放歌曲轉化為波文件非常簡單我用Winamp將mp文件通過Winamp本身的Disk Writer Plugin插件直接將mp轉化成位的波文件(通常為M大小)然後再用一個叫goldwave的軟件(我忘了從什麼地方下載的了)將其轉化為位的單聲道波文件(通常M大小)至於提示語音我則是使用windows自帶的錄音機程序通過麥克風錄制的

  當用戶聽完提示語音後他們會按鍵來點歌或留言而用戶的按鍵會激發TApdTapiDevice的OnDTMF事件我們就可以在這個事件中對按鍵進行處理下面就是處理過程代碼

procedure TFrmMainApdTapiDeviceTapiDTMF(CP: TObject; Digit: Char;
ErrorCode: Integer);
begin
if (Digit = ) or (Digit = ) then
Exit;
LBSysInfoItemsAdd(dtmf:按鍵= + Digit);

CurrentCommand := CurrentCommand + Digit;
{簡單狀態機}
if Digit = # then
begin
if CurrentCommand = *# then
begin
CurrentCommand := ;
ApdTapiDeviceMaxMessageLength := ; //最長記錄時間
ApdTapiDeviceInterruptWave := False; //按鍵不能中斷提示語音的播放
ApdTapiDevicePlayWaveFile(recordhintwav);//播放錄音提示語音
CurrentState := csRecording;
Exit;
end;

if CommandListvalues[CurrentCommand] <> then
begin
ApdTapiDevicePlayWaveFile(CommandListvalues[CurrentCommand]);
LBSysInfoItemsAdd(Format(%s %s 正在播放 %s
[ApdTapiDevicecalleridname apdtapidevicecallerid
CommandListvalues[CurrentCommand]]));
end
else
begin
//播放錯誤提示語音並要求用戶重新輸入命令
ApdTapiDevicePlayWaveFile(errornowav);
LBSysInfoItemsAdd(Format(%s %s 輸入了錯誤的號碼
[ApdTapiDevicecalleridname apdtapidevicecallerid]));
end;
//重置命令為空
CurrentCommand := ;
end;
end;

  程序對按鍵進行判斷(按鍵對應於digit參數)如果輸入的為*#就進入錄音功能在錄音前先播放提示語音可以告訴用戶留言長度為然後設置當前狀態為csRecording有人可能要問沒看到用來錄音的代碼呀這部分其實是實現在另外的事件中的我們稍後就會講到再來看點歌部分同樣的根據按鍵的組合在先前加載進CommandList的字符串列表中查找相匹配的歌曲如果有相應的歌曲就播放否則播放錯誤提示語音提示用戶重新輸入命令然後將按鍵清空等待重新輸入另外注意在事件的日志記錄中我記錄了ApdTapiDevicecalleridname和CallerID的屬性它們對應的是打入電話的號碼不過這項功能只對開通了來電顯示功能的電話號碼才有效通過對打入電話號碼信息的處理我們可以提供一些額外的功能不過這是題外話了

  前面提到了在按鍵處理事件中我們並沒有進行留言的錄制功能這主要是因為我們要保證留言提示語音不被按鍵中斷(設定Interruptwave:=false)因此把留言錄制功能放到了TApdTapiDevice的OnWaveNotify事件中了這個事件可以提示波文件播放的狀態比如播放結束和錄音所需聲音數據准備狀態等在本程序中我們需要在提示語音播放結束後開始記錄留言並在留言聲音數據准備好後將其保存到磁盤文件中下面是處理過程的流程

procedure TFrmMainApdTapiDeviceTapiWaveNotify(CP: TObject;
Msg: TWaveMessage);
var
TimeStr: string;
FileName: string;
begin
//決不能在case外做耗時的操作
case Msg of
waPlayOpen: LBSysInfoItemsAdd(wavnotify:播放開始);
waPlayDone:
begin
LBSysInfoItemsAdd(wavnotify:播放結束);
if CurrentState = csRecording then
begin
try
      //等待波設備狀態為wsIdle再開始錄音
while ApdTapiDeviceWaveState <> wsIdle do
ApplicationProcessMessages;
ApdTapiDeviceInterruptWave := True;
ApdTapiDeviceStartWaveRecord;
LBSysInfoItemsAdd(dtmf:錄音成功);
except
LBSysInfoItemsAdd(dtmf:錄音失敗);
end;
end;
end;
waPlayClose: LBSysInfoItemsAdd(wavnotify:播放關閉);
waRecordOpen: LBSysInfoItemsAdd(wavnotify:錄音開始);
waDataReady:
begin
LBSysInfoItemsAdd(wavnotify:數據准備);
TimeSeparator := ;
FileName := DateTimeToStr(Now) + wav;
try
ApdTapiDeviceSaveWaveFile(ExtractFilePath(ParamStr()) + record\ +
FileName True);
LBSysInfoItemsAdd(wavNotify:保存聲音文件 + FileName);
except
LBSysInfoItemsAdd(wavnotify:保存聲音文件失敗);
end;
end;
waRecordClose:
begin
LBSysInfoItemsAdd(wavnotify:記錄聲音結束);
CurrentState := csWaiting;
ActionCancelExecute(nil);
TimerEnabled := True;
end;
end;
end;

  整個流程就是通過一個Case語句來判斷當前聲音狀態如果為waPlayDone(播放完畢)同時CurrentStatus為csRecording的話就調用StartWaveRecord方法來記錄聲音而當Msg為waDataReady狀態時表明錄音數據已經可以存盤了這時根據當前時間生成一個文件名並將數據保存為波文件而當錄音結束後我們就需要調用ActionCancelExecute(nil)來掛斷電話並將狀態設置為csWaiting來等待下次接入注意在代碼最後我們將一個TTimer控件激活了這個TTimer控件的時間間隔Interval設置為同時其OnTimer事件代碼如下

procedure TFrmMainTimerTimer(Sender: TObject);
begin
try
  //應答電話
ActionAnswerExecute(nil);
CurrentState := csWaiting;
TimerEnabled := False;
except
end;
end;

  這樣設置的原因在於當調用CancelCall方法來掛斷電話後TAPI設備需要秒來恢復正常狀態如果立刻執行AutoAnswer的話這個方法就會失效無法正確監控電話接入因此要用TTimer來控制恢復電話應答的時間

  四異常處理

  要想程序非常健壯的反復應答電話接入我們必須對用戶突然掛斷電話進行處理用戶斷開的事件會激發控件的OnTapiStatus事件當用戶掛斷電話時我們要做的是如果當前還在錄音就停止錄音如果是在播放歌曲就掛斷電話然後設置TTimer生效重新進入電話應答狀態下面就是整個處理過程的代碼

procedure TFrmMainApdTapiDeviceTapiStatus(CP: TObject; First
Last: Boolean; Device Message Param Param Param: Cardinal);
begin
if (Message = Line_CallState) then
begin
case Param of
LineCallState_Disconnected:
begin
LBSysInfoItemsAdd(status:disconnected from remote modem);
if CurrentState = csRecording then
begin
ApdTapiDeviceStopWaveRecord;
Exit;
end;
CurrentState := csDisconnected;
ActionCancelExecute(nil);
TimerEnabled := True;
end;
end;
end;
end;

  進一步完善

  當錄音完畢後我們想聽一下電話留言的話可以在窗體上放置一個打開文件對話框用下面代碼實現

procedure TFrmMainActionPlayRecExecute(Sender: TObject);
var
FrmPlay: TFrmPlayRec;
begin
DlgOpenRecInitialDir := ExtractFilePath(ParamStr()) + Record\;
if DlgOpenRecExecute then
//播放聲音記錄文件
ShellExecute(ApplicationHandle PChar(open) PChar(DlgOpenRecFileName)
nil nil SW_SHOW);
end;

  另外如果大家自信自己的歌喉不比那些歌星差的話完全可以錄制自己的歌聲然後播放給你的女朋友或朋友聽也許效果更棒

  最後我要說的就是Telephone API所能提供的功能遠遠不止本文中所提到的感興趣的朋友可以進一步查閱相關資料來研究還有Turbo Power已經不再開發Async Pro了它把所有的源碼都放到了Sourceforge上共享大家可以到SourceForge上下載


From:http://tw.wingwit.com/Article/program/Delphi/201311/8505.html
  • 上一篇文章:

  • 下一篇文章:
  • 推薦文章
    Copyright © 2005-2013 電腦知識網 Computer Knowledge   All rights reserved.