用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, , 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;
}
用c#实现与飞环语音卡的交互的更多相关文章
- dialogic d300语音卡驱动重装后启动报错问题解决方法
dialogic d300 驱动重装后 dlstart 报错解决 问题描述:dlstart 后如下报错 [root@BJAPQ091 data]#dlstop Stopping Dialogic ...
- Android 离线语音用法(讯飞语音)
这次给大家带来的是项目的离线语音功能. 讯飞开放平台中的离线语音 首先创建开放平台的账号.这个不必多说 然后创建新应用 选择我的应用,例如以下图,注意下我打马赛克的地方,这个appId非常重要 点击进 ...
- android用讯飞实现TTS语音合成 实现中文版
Android系统从1.6版本开始就支持TTS(Text-To-Speech),即语音合成.但是android系统默认的TTS引擎:Pic TTS不支持中文.所以我们得安装自己的TTS引擎和语音包. ...
- 基于科大讯飞语音云windows平台开发
前记: 前段时间公司没事干,突发奇想想做一个语音识别系统,看起来应该非常easy的,但做起来却是各种问题,这个对电气毕业的我,却是挺为难的.谷姐已经离我们而去,感谢度娘,感谢CSDN各位大神,好歹也做 ...
- C# 实现语音听写
本文系原创,禁止转载. 分享如何使用c#对接科大讯飞语音听写服务,简单高效地实现语音听写. 实现语音听写主要分为录音和语音识别两部分:录音是指获取设备声卡端口的音频数据并将之保存为音频文件,语音识别就 ...
- 小打卡PRD
目标:打造一款不同于市场上的公开打卡app的产品 理念:通过监督和鼓励,和相同圈子的人一起互相鼓励.分享及监督,共同进步. 优点: 模板消息通知,网上基本通过小程序中逻辑层JS完成推送的请求,小打卡在 ...
- BZOJ.4500.矩阵(差分约束 SPFA判负环 / 带权并查集)
BZOJ 差分约束: 我是谁,差分约束是啥,这是哪 太真实了= = 插个广告:这里有差分约束详解. 记\(r_i\)为第\(i\)行整体加了多少的权值,\(c_i\)为第\(i\)列整体加了多少权值, ...
- 语音-数字中继-E1-学习帖
1.电话线,俗称数字中继,模拟线路,一门电话线只能跑一个电话号码,用模拟语音卡:2.光纤,信令有两种30B+D(也叫pri信令)或者7号信令,数字线路,一路可以跑30路电话,用数字语音卡:数字语音卡 ...
- SIM卡读卡器的研究与设计
SIM卡(Subscriber Identity Module).即用户识别模块,是一张符合GSM规范的"智慧卡".SIM卡可以插入任何一部符合GSM规范的移动电话中," ...
随机推荐
- c# p2p 穿透(源码加密)
http://blog.oraycn.com/ESFramework_Demo_P2P.aspx 测试,完全OK! 我很喜欢这个.可以源码是加密的!我希望实现 web 版本的p2p视频观看,aehy ...
- 移动应用跨平台框架江湖将现终结者?速来参拜来自Facebook的React Native
React Native使用初探 February 06 2015 Facebook让所有React Conf的参与人员都可以初尝React Native的源码---一个编写原生移动应用的方法.该方法 ...
- 移植MonkeyRunner的图片对比和获取子图功能的实现-UiAutomator/Robotium篇
根据前一篇文章<移植MonkeyRunner的图片对比和获取子图功能的实现-Appium篇>所述,因为Appium和MonkeyRunner有一个共同点--代码控制流程都是在客户端实现的. ...
- POJ 1284 Primitive Roots 原根
题目来源:POJ 1284 Primitive Roots 题意:求奇素数的原根数 思路:一个数n是奇素数才有原根 原根数是n-1的欧拉函数 #include <cstdio> const ...
- 快速构建Windows 8风格应用25-数据绑定
原文:快速构建Windows 8风格应用25-数据绑定 本篇博文主要介绍如何将UI元素与数据进行绑定.数据绑定的方向.数据更改通知.数据转换.数据绑定支持的绑定方案. 数据绑定是一种简单方式来显示数据 ...
- 快速构建Windows 8风格应用29-捕获图片与视频
原文:快速构建Windows 8风格应用29-捕获图片与视频 引言 本篇博文主要介绍Windows 8中相机的概念.捕获图片与视频的基本原理.如何实现捕获图片与视频.相机最佳实践. 一.相机 关于相机 ...
- leetcode[94] Unique Binary Search Trees
给定n,那么从1,2,3...n总共可以构成多少种二叉查找数呢.例如给定3 Given n = 3, there are a total of 5 unique BST's. 1 3 3 2 1 \ ...
- Protocol Buffer和JSON性能比较
JSON PB 数据结构支持 简单结构 较复杂结构 数据格式 文本 二进制 数据大小 一般 小,json大小的1/3左右 解析效率 一般 快,是json解析速度的3-10倍 可读性 好,自描述的 ...
- 个推A/B测试评测
A/B测试在各类网站设计中已经是比较常见的,本文着重讲讲A/B测试在应用推送领域的作用. 目前国外开通A/B测试的推送服务商只有swrve,而国内的个推也在前不久发布的smart push 2.0中集 ...
- 自己动手实现Expression翻译器 – Part Ⅲ
上一节实现了对TableExpression的解析,通过反射创建实例以及构建该实例的成员访问表达式生成了一个TableExpression,并将其遍历格式化为”Select * From TableN ...