代码改变世界

用c#实现与飞环语音卡的交互

2015-03-05 16:38  风来之东林  阅读(546)  评论(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
    }
View Code
  • 接着创建通道服务
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);
        }
       
    }
View Code
  • 在交互过程中通道的状态改变会触发相应的事件,我们需要在驱动打开后注册事件回调
    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));
        }
    }
View Code
  •  现在我们可以打开驱动了,打开驱动以后初始化通道信息及注册事件回调
        /// 打开和初始化设备
        /// </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;
        }        
View Code
  • 通道初始化完毕之后就可以利用通道来进行呼叫了
    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)
            {

            }

        }
    }
View Code
  • 下面是调用方法
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;
        }