用c#實現與飛環語音卡的交互

如今不少企業都採用freeswitch搭建的軟交換來實現通話,主要優點成本低吞吐量大,可是語音卡的通話質量仍是瑞勝一籌。redis

去年有機會在朋友公司裏幫忙開發與軟交換交互的上層服務及接口,在開發過程當中稍微研究了下飛環的語音卡,並用c#實現了簡單的單方通話及雙向通話,下面就把單方通話的實現方法簡單說下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;
        }
相關文章
相關標籤/搜索