用c#实现与飞环语音卡的交互
2015-03-05 16:38 风来之东林 阅读(561) 评论(0) 编辑 收藏 举报现在很多企业都采用freeswitch搭建的软交换来实现通话,主要优势成本低吞吐量大,但是语音卡的通话质量还是瑞胜一筹。
去年有机会在朋友公司里帮忙开发与软交换交互的上层服务及接口,在开发过程中稍微研究了下飞环的语音卡,并用c#实现了简单的单方通话及双向通话,下面就把单方通话的实现方法简单说下
- 开发时只需要用到PhonicDT.dll,如果需要发布到测试环境,那就需要注册OCX,这里就不说了可以参考官方文档
- 在与语音卡交互前必须确认设备驱动是否打开,利用DllImport来访问动态链接库的API
//打开设备驱动 [DllImport("PhonicDT.dll")] public extern static int tpi_OpenDevice();
- 驱动打开后就能访问语音卡的通道了,不同版本的卡通道数量是不一样的
/// <summary> /// 获取指定类型的通道的数量 /// </summary> /// <param name="channelType">通道类型,可以是EChannelType所定义的任一种,参看类型定义</param> /// <returns>大于0 为通道数,0表示没有该类型通道,小于0 表示出错的错误码,参看EDtvcErrorCode 定义</returns> [DllImport(DllName.PhonicName)] public extern static int tpi_GetChannelCount(int channelType);
- 为了方便监控通道的状态,我们可以将通道信息缓存起来或者用redis持久化,持久化的主要目的是减少过多的调用api去访问驱动,如何用redis持久化我就不说了这里就用单例来实现通道信息的保存
- 首先我们需要一个通道实体 ChannelModel
/// <summary> /// 通道实体 /// </summary> public class ChannelModel { #region proptery /// <summary> /// 通道Id(通道号) /// </summary> public int ChannelId { get; set; } /// <summary> /// 所属组Id /// </summary> public int GroupId { get; set; } /// <summary> /// 通道类型 /// </summary> public EChannelType ChannelType { get; set; } /// <summary> /// 通道状态 /// </summary> public EChannelState ChannelState { get; set; } /// <summary> /// 通道属性 /// </summary> public EVoiceChannelAttrib VoiceChannelAttrib { get; set; } /// <summary> /// 通道所属的流号 /// </summary> public long StreamNo { get; set; } /// <summary> /// 通道所属的时序 /// </summary> public long TimeSlot { get; set; } /// <summary> /// 是否正在放音 /// </summary> public bool IsPlay { get; set; } /// <summary> /// 通道任务 /// </summary> public TaskModel Task { get; set; } /// <summary> /// 通道触发的事件 /// </summary> public EPhonicEvent EventState { get; set; } /// <summary> /// 录音文件地址 /// </summary> public string VoiceFilePath { get; set; } #endregion #region func /// <summary> /// 呼叫 /// </summary> /// <param name="isTo">true呼叫客户,false呼叫坐席</param> public void MakeCall(bool isTo) { string callStr = this.Task.ToCall; if (!isTo) { callStr = this.Task.Agent; } CallImport.tpi_MakeCall( Convert.ToInt32(this.ChannelType) , this.ChannelId , new StringBuilder(this.Task.FromCall) , new StringBuilder(callStr) , this.Task.CallRingingTime); //修改通道状态 this.ChannelState = EChannelState.STATE_OUT_CALLING; } /// <summary> /// 根据文件放音 /// </summary> public void Play() { //"E:\\test\\financial.vox" StringBuilder filename = new StringBuilder(this.VoiceFilePath);//物理路径 VoiceService.PlayFile(Convert.ToInt32(this.ChannelType), this.ChannelId, filename, 0, this.Task.PlayTime); this.IsPlay = true; } /// <summary> /// 从内存放音 /// </summary> public void PlayMemory() { //将语音流存入内存中 "E:\\test\\financial.vox" using (FileStream fs = new FileStream("E:\\test\\financial.vox", FileMode.Open)) { byte[] array = new byte[fs.Length];//初始化字节数组 fs.Read(array, 0, array.Length);//读取流中数据把它写到字节数组中 ASCIIEncoding encoding = new ASCIIEncoding(); string pVoiceBuffer = System.Text.Encoding.Default.GetString(array); //encoding.GetString(array); //Console.WriteLine(pVoiceBuffer); // VoiceService.PlayMemory(Convert.ToInt32(this.ChannelType), this.ChannelId, ref pVoiceBuffer, 0); this.IsPlay = true; } } /// <summary> /// 停止放音 /// </summary> public void StopPlay() { VoiceImport.tpi_StopPlay(Convert.ToInt32(this.ChannelType), this.ChannelId); this.IsPlay = false; } /// <summary> /// 释放通道 /// </summary> public void Destroy() { this.ChannelState = EChannelState.STATE_IDLE; this.Task = null; this.EventState = EPhonicEvent.eventIdle; } #endregion }
- 接着创建通道服务
public class ChannelService { private static ChannelService channelService = new ChannelService(); private List<ChannelModel> channelModels; public static ChannelService getInstance() { return channelService; } /// <summary> /// 通道集合 /// </summary> public List<ChannelModel> ChannelModels { get { if (this.channelModels == null) { this.channelModels = new List<ChannelModel>(); } return this.channelModels; } set { this.channelModels = value; } } /// <summary> /// 获取通道状态 /// </summary> /// <param name="channelType"></param> /// <param name="channelID"></param> /// <returns></returns> public static EChannelState GetChannelState(int channelType, int channelID) { return ChannelService.getInstance().ChannelModels.Find(c => Convert.ToInt32(c.ChannelType) == channelType && c.ChannelId == channelID).ChannelState; } /// <summary> /// 修改通道状态 /// </summary> /// <param name="channelType"></param> /// <param name="channelID"></param> public static void UpdateChannelState(int channelType, int channelID, EChannelState channelState) { ChannelService.getInstance().ChannelModels.Find(c => Convert.ToInt32(c.ChannelType) == channelType && c.ChannelId == channelID).ChannelState = channelState; Console.WriteLine(string.Format("[修改了通道状态]{0}" , ChannelService.getInstance().ChannelModels.Find(c => Convert.ToInt32(c.ChannelType) == channelType && c.ChannelId == channelID).ChannelState)); } /// <summary> /// 获取通道 /// </summary> /// <param name="channelID"></param> /// <returns></returns> public static ChannelModel GetChannelById(int channelID) { return ChannelService.getInstance().ChannelModels.Find(c => c.ChannelId == channelID); } /// <summary> /// 获取空闲通道 /// </summary> /// <returns></returns> public static ChannelModel GetChannelByIdle() { return ChannelService.getInstance().ChannelModels.Find(c => c.ChannelState == EChannelState.STATE_IDLE); } /// <summary> /// 获取指定类型的通道数量 /// </summary> /// <param name="channelType"></param> /// <returns></returns> public static int GetChannelCount(int channelType) { return ChannelImport.tpi_GetChannelCount(channelType); } /// <summary> /// 获取通道的时序,流号 /// </summary> /// <param name="channelType"></param> /// <param name="channelID"></param> /// <param name="pStream"></param> /// <param name="pTimeSlot"></param> /// <returns></returns> public static int GetChannelTimeSlot(int channelType, int channelID, out int pStream, out int pTimeSlot) { return ChannelImport.tpi_GetChannelTimeSlot(channelType, channelID, out pStream, out pTimeSlot); } /// <summary> /// 建立两个通道间的双向连接 /// </summary> /// <param name="destType"></param> /// <param name="destID"></param> /// <param name="srcType"></param> /// <param name="srcID"></param> /// <returns></returns> public static int TalkWith(int destType, int destID, int srcType, int srcID) { return ChannelImport.tpi_TalkWith(destType, destID, srcType, srcID); } /// <summary> /// 挂机 /// </summary> /// <param name="channelType"></param> /// <param name="channelID"></param> /// <param name="cause"></param> /// <returns></returns> public static int Hangup(int channelType, int channelID, int cause) { return CallImport.tpi_Hangup(channelType, channelID, cause); } }
- 在交互过程中通道的状态改变会触发相应的事件,我们需要在驱动打开后注册事件回调
public class EventService { private static void SetEventNotifyCallBackProc(ProcPhonicDTFireEventCallBack callBack) { EventImport.tpi_SetEventNotifyCallBackProc(callBack); } public static void InitCallBack() { //加载事件回调 SetEventNotifyCallBackProc(delegate(EPhonicEvent eventType, int channelType, int channelID, int iParam1, int iParam2) { Console.Write(eventType); switch (eventType) { case EPhonicEvent.eventState: OnState(channelType, channelID, iParam1, iParam2); break; case EPhonicEvent.eventDeviceTimer: OnTimer(channelType, channelID); break; case EPhonicEvent.eventSignal: //OnSignal(channelType, channelID, iParam1); break; case EPhonicEvent.eventAlarm: //OnAlarm(channelType, channelID, iParam1); break; case EPhonicEvent.eventIdle: OnIdle(channelType, channelID); break; case EPhonicEvent.eventCallIn: OnCallIn(channelType, channelID, new StringBuilder(iParam1.ToString()), new StringBuilder(iParam2.ToString())); break; case EPhonicEvent.eventAnswer: OnAnswer(channelType, channelID); break; case EPhonicEvent.eventCallOutFinish: OnCallOutFinish(channelType, channelID); break; case EPhonicEvent.eventCallFail: OnCallFail(channelType, channelID, iParam1); break; case EPhonicEvent.eventHangup: OnHangup(channelType, channelID, iParam1); break; case EPhonicEvent.eventDTMF: OnDTMF(channelType, channelID, iParam1); break; case EPhonicEvent.eventPlayEnd: OnPlayEnd(channelType, channelID, iParam1); break; case EPhonicEvent.eventRecordEnd: OnRecordEnd(channelType, channelID, iParam1); break; } }); } public static void OnState(int channelType, int channelID, int newChannelState, int oldChannelState) { Console.WriteLine(string.Format("[通道ID]{0} [新状态]{1} [旧状态]{2}", channelID, newChannelState, oldChannelState)); EChannelState channelState = (EChannelState)Enum.Parse(typeof(EChannelState), newChannelState.ToString()); //ChannelService.UpdateChannelState(channelType, channelID, channelState); ChannelModel channel = ChannelService.GetChannelById(channelID); channel.ChannelState = channelState; if (channelState == EChannelState.STATE_OUT_RELEASE) { channel.Destroy(); } } /// <summary> /// 通道定时 /// </summary> /// <param name="channelType"></param> /// <param name="channelID"></param> public static void OnTimer(int channelType, int channelID) { Console.WriteLine(string.Format("OnTimer [通道ID]{0}", channelID)); } /// <summary> /// 通道信令发生变化 /// </summary> /// <param name="channelType"></param> /// <param name="channelID"></param> /// <param name="Signal"></param> public static void OnSignal(int channelType, int channelID, int Signal) { Console.WriteLine(string.Format("OnSignal [通道ID]{0} [通道信令]{1}", channelID, Signal)); } /// <summary> /// 中继告警 /// </summary> /// <param name="channelType"></param> /// <param name="channelID"></param> /// <param name="Alarm"></param> public static void OnAlarm(int channelType, int channelID, int Alarm) { Console.WriteLine(string.Format("OnAlarm [通道ID]{0} [告警值]{1}", channelID, Alarm)); } /// <summary> /// 通道进入空闲状态 /// </summary> /// <param name="channelType"></param> /// <param name="channelID"></param> public static void OnIdle(int channelType, int channelID) { Console.WriteLine(string.Format("OnIdle [通道ID]{0}", channelID)); ChannelModel channel = ChannelService.GetChannelById(channelID); channel.EventState = EPhonicEvent.eventIdle; channel.ChannelState = EChannelState.STATE_IDLE; } /// <summary> /// 通道有电话呼入 /// </summary> /// <param name="channelType"></param> /// <param name="channelID"></param> /// <param name="callerID"></param> /// <param name="phoneNumber"></param> public static void OnCallIn(int channelType, int channelID, StringBuilder callerID, StringBuilder phoneNumber) { Console.WriteLine(string.Format("OnCallIn [通道ID]{0} [主叫电话]{1} [被叫电话]{2}" , channelID, callerID.ToString(), phoneNumber.ToString())); //ChannelService.UpdateChannelState(channelType, channelID, EChannelState.STATE_IN_CALLING); ChannelModel channel = ChannelService.GetChannelById(channelID); channel.EventState = EPhonicEvent.eventCallIn; channel.ChannelState = EChannelState.STATE_IN_CALLING; } /// <summary> /// 用户已应答呼叫 /// </summary> /// <param name="channelType"></param> /// <param name="channelID"></param> public static void OnAnswer(int channelType, int channelID) { ChannelModel channel = ChannelService.GetChannelById(channelID); channel.EventState = EPhonicEvent.eventAnswer; channel.ChannelState = EChannelState.STATE_IN_TALK; Console.WriteLine(string.Format("OnAnswer [通道ID]{0}", channelID)); } /// <summary> /// 对指定通道的呼叫已完成 /// </summary> /// <param name="channelType"></param> /// <param name="channelID"></param> public static void OnCallOutFinish(int channelType, int channelID) { ChannelModel channel = ChannelService.GetChannelById(channelID); channel.EventState = EPhonicEvent.eventCallOutFinish; channel.ChannelState = EChannelState.STATE_OUT_RINGING; Console.WriteLine(string.Format("OnCallOutFinish [通道ID]{0}", channelID)); //ChannelService.UpdateChannelState(channelType, channelID, EChannelState.STATE_OUT_RINGING); } /// <summary> /// 对指定通道的呼叫失败 /// </summary> /// <param name="channelType"></param> /// <param name="channelID"></param> /// <param name="cause"></param> public static void OnCallFail(int channelType, int channelID, int cause) { ChannelModel channel = ChannelService.GetChannelById(channelID); channel.EventState = EPhonicEvent.eventCallFail; Console.WriteLine(string.Format("OnCallFail [通道ID]{0} [挂机原因]{1}", channelID, cause)); Console.WriteLine(System.DateTime.Now.ToString()); } /// <summary> /// 通道已挂机 /// </summary> /// <param name="channelType"></param> /// <param name="channelID"></param> /// <param name="cause"></param> public static void OnHangup(int channelType, int channelID, int cause) { ChannelModel channel = ChannelService.GetChannelById(channelID); channel.EventState = EPhonicEvent.eventHangup; channel.ChannelState = EChannelState.STATE_OUT_HANGUP; Console.WriteLine(string.Format("OnHangup [通道ID]{0} [挂机原因]{1}" , channelID, cause)); //ChannelService.UpdateChannelState(channelType, channelID, EChannelState.STATE_OUT_HANGUP); } /// <summary> /// 用户已按键 /// </summary> /// <param name="channelType"></param> /// <param name="channelID"></param> /// <param name="dtmfCode"></param> public static void OnDTMF(int channelType, int channelID, int dtmfCode) { ChannelModel channel = ChannelService.GetChannelById(channelID); channel.EventState = EPhonicEvent.eventDTMF; Console.WriteLine(string.Format("OnDTMF [通道ID]{0} [用户所按键的ASCII码]{1}" , channelID, dtmfCode)); } /// <summary> /// 触发信令跟踪事件 /// </summary> /// <param name="channelType"></param> /// <param name="channelID"></param> /// <param name="signalType"></param> /// <param name="signalCode"></param> public static void OnSignalMonitor(int channelType, int channelID, int signalType, int signalCode) { Console.WriteLine(string.Format("OnSignalMonitor [通道ID]{0} [信令类型]{1} [信令码]{2}" , channelID, signalType, signalCode)); } /// <summary> /// 对通道的放音已结束 /// </summary> /// <param name="channelType"></param> /// <param name="channelID"></param> /// <param name="completeSize"></param> public static void OnPlayEnd(int channelType, int channelID, int completeSize) { ChannelModel channel = ChannelService.GetChannelById(channelID); channel.EventState = EPhonicEvent.eventPlayEnd; Console.WriteLine(string.Format("OnPlayEnd [通道ID]{0} [完成播放的字节数]{1}" , channelID, completeSize)); } /// <summary> /// 通道的录音已结束 /// </summary> /// <param name="channelType"></param> /// <param name="channelID"></param> /// <param name="completeSize"></param> public static void OnRecordEnd(int channelType, int channelID, int completeSize) { ChannelModel channel = ChannelService.GetChannelById(channelID); channel.EventState = EPhonicEvent.eventRecordEnd; Console.WriteLine(string.Format("OnRecordEnd [通道ID]{0} [完成录音的字节数]{1}" , channelID, completeSize)); } }
- 现在我们可以打开驱动了,打开驱动以后初始化通道信息及注册事件回调
/// 打开和初始化设备 /// </summary> /// <returns>0 表示成功,其他值表示出错的错误码,含义参看类型定义的EDtvcErrorCode 定义</returns> public static bool OpenDevice() { //询问manage运行模式及运行状态 //打开设备驱动 int isOpen = DeviceImport.tpi_OpenDevice(); if (isOpen != 0) { return false; } //获取设备卡数量 int cardCount = CardService.GetCardCount(Convert.ToInt32(ECardType.CARD_TYPE_PCM)); if (cardCount == 0) { return false; } //初始化通道 int channelCount = ChannelService.GetChannelCount(Convert.ToInt32(EChannelType.CH_TRUNK)); for (int i = 0; i < channelCount - 1; i++) { ChannelModel channel = new ChannelModel() { ChannelId = i, ChannelState = EChannelState.STATE_IDLE, ChannelType = EChannelType.CH_TRUNK, GroupId = 0, StreamNo = 0, TimeSlot = 0, VoiceChannelAttrib = EVoiceChannelAttrib.ATTRIB_VOICE_PLAY_ONLY }; ChannelService.getInstance().ChannelModels.Add(channel); } //加载事件回调 EventService.InitCallBack(); return true; }
- 通道初始化完毕之后就可以利用通道来进行呼叫了
public class CallService { /// <summary> /// 在通道上建立任务呼叫 /// </summary> /// <param name="task"></param> public static ChannelModel TaskCall(TaskModel task) { ChannelModel channel = null; switch (task.TaskType) { case TaskType.voice: channel = VoiceCall(task); break; case TaskType.ansThr: break; case TaskType.key: break; case TaskType.keyThr: break; } return channel; } /// <summary> /// 纯语音呼叫 /// </summary> private static ChannelModel VoiceCall(TaskModel task) { //获取空闲通道 ChannelModel channel = ChannelService.GetChannelByIdle(); channel.Task = task; //建立呼叫 channel.MakeCall(true); //等待通道执行呼叫 while (channel.ChannelState != EChannelState.STATE_IDLE) { switch (channel.EventState) { case EPhonicEvent.eventAnswer: if (!channel.IsPlay) { Console.WriteLine(channel.IsPlay); channel.Play(); } break; case EPhonicEvent.eventHangup: if (channel.IsPlay) { channel.StopPlay(); } break; } } return channel; } private static void VoiceToCall() { ChannelModel channel = ChannelService.GetChannelByIdle(); channel.MakeCall(true); while (channel.ChannelState != EChannelState.STATE_OUT_HANGUP && channel.ChannelState != EChannelState.STATE_IN_CALLING) { } } }
- 下面是调用方法
private static ChannelModel SingleCall(bool isMultiplayer) { Console.WriteLine("请输入电话号码"); string phone = Console.ReadLine(); int channelType = 0; int pStream = 0; int pTimeSlot = 0; //ChannelService.GetChannelTimeSlot(channelType, channel.ChannelId, out pStream, out pTimeSlot); //Console.WriteLine(string.Format("通道[流号]{0},[时序]{1}", pStream, pTimeSlot)); Console.WriteLine("正在呼叫..."); TaskModel task = new TaskModel { TaskType = TaskType.voice, FromCall = "400*******", ToCall = phone, CallRingingTime = 10000 }; ChannelModel channel = CallService.TaskCall(task); Console.WriteLine(System.DateTime.Now.ToString()); //如果用户没挂机,强制挂机 if (!isMultiplayer) { //等待呼叫结束,获取通道状态 while (channel.ChannelState != EChannelState.STATE_IDLE) { } } return channel; }