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

现在很多企业都采用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;

        }

 

你可能感兴趣的:(C#)