现在很多企业都采用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, , 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, , 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 != )
{
return false;
}
//获取设备卡数量
int cardCount = CardService.GetCardCount(Convert.ToInt32(ECardType.CARD_TYPE_PCM));
if (cardCount == )
{
return false;
}
//初始化通道
int channelCount = ChannelService.GetChannelCount(Convert.ToInt32(EChannelType.CH_TRUNK)); for (int i = ; i < channelCount - ; i++)
{
ChannelModel channel = new ChannelModel()
{
ChannelId = i,
ChannelState = EChannelState.STATE_IDLE,
ChannelType = EChannelType.CH_TRUNK,
GroupId = ,
StreamNo = ,
TimeSlot = ,
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 = ;
int pStream = ;
int pTimeSlot = ; //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 =
};
ChannelModel channel = CallService.TaskCall(task);
Console.WriteLine(System.DateTime.Now.ToString());
//如果用户没挂机,强制挂机
if (!isMultiplayer)
{
//等待呼叫结束,获取通道状态
while (channel.ChannelState != EChannelState.STATE_IDLE)
{ }
}
return channel;
}