[转]c#编写网络电话

本文转自:http://www.cnblogs.com/dfsxh/archive/2008/12/30/1355886.html

原文如下:

摘要:语音通话已经是IM的基本功能了,qq,MSN甚至连刚出来的百度HI都自带语音聊天的功能,大家可能觉得很炫,其实大家都是用的windows平台上的API,懂了原理之后自己也可以做,再说了微软也提供了DirectSound的托管互操作程序集,使.net开发人员也很容易的介入到这个领域,甚至你还可以写一个能跑在window mobile上的语音电话,现在好多手机都支持wifi,这样一个简单的wifi电话就由你的手里诞生了。本帖来和大家一起看看如何来做网络电话。

思路:要想做一个网络电话,基本遵循以下步骤
1、一方实时的录音,把模拟信号转换成数字信号;
2、把声音实时压缩;
3、通过网络协议把压缩后的数据传输给接收方;
4、接收方解压缩接受到的音频数据;
5、实时的把接收到的数字信号转换成模拟信号并播放出来。

下面我们来看看每一步面临的挑战及其解决方案。
1、第一步,实时录音,DirectxSound有录音方面的API,托管的类分别是Microsoft.DirectX.DirectSound.CaptureDevicesCollection,Microsoft.DirectX.DirectSound.Capture和Microsoft.DirectX.DirectSound.CaptureBuffer,CaptureDevicesCollection用来枚举本机的可用的录音设备,Capture则表示一个录音设备,CaptureBuffer是用来存放录音数据的缓冲区,我们开始录音后,音频数据会不断的写入到环形的流式缓冲区,然后我们定期从缓冲区中把录音数据取出来返回给上层应用层就可以了。关于环形的流式缓冲区,可以看参考链接部分。
2、声音的压缩是一个很难抉择的步骤,默认的DirectSound只能播放和录制PCM格式(WAV)的音频数据,但这种声音格式特别大。常用的声音压缩格式有h.7231,gsm,amr,h.711等等,各种压缩算法都有自己的码率和适用范围。因为我们做的是互联网的语音电话,不考虑慢速网络和无线连接下的情况,也不用考虑终端设备的CPU能不能支持我们选用的压缩算法,我们做的语音电话双方都是PC机,应该什么解压缩算法都不会引起什么性能上的问题,所以只要网络快一些,选择哪个压缩算法都无所谓了,网上有h.711的压缩算法,我打算就采用这个,他的码率是64Kbps,比PCM的1.544Mbps和2.048Mbps要小的多。然后我们进行了音频数据压缩后,还可以对字节流进行GZIP或者7ZIP压缩,前者用SharpZip,后者7zip的官方有c#的使用代码,大家可以测试一下这两个算法的性能后做出适合自己的决定。关于各种压缩格式的特性可以参考我做的PPT及提供的参考链接。
3、网络电话注重实时性,而把声音从网络上传输就要走IP网络,而IP网络不是一个等时系统,所以我们就要尽量的去模拟实时的语音传输,提到实时,肯定UDP比TCP要实时,因为TCP要保证传输的可靠性,有序性等,而专门用于实时传输有一个应用层协议是RTP协议,这个协议一般就是建立在UDP基础上的,它在每个包头提供了一些序列号、时间戳等信息,但UDP本身并不会使用这些信息,这时候就有一个RTCP协议来用这些信息进行流量控制和拥塞控制,比如说RTCP检测到网络拥挤,会告诉发送方变换一种低码率的语音压缩算法来传输数据。这些大多都需要自己去实现,本文的源码没有去实现这些,关于RTP和RTCP可以参考相关资料或者我做的PPT。
4、每个压缩算法都有相应的解压缩算法,呵呵。
5、播放声音肯定也需要用到DS,也需要用到StreamBuffer,大致流程如下
1)创建一个声音设备Microsoft.DirectX.DirectSound.Device dev = new Microsoft.DirectX.DirectSound.Device();
2)设置协调级别dev.SetCooperativeLevel(this, Microsoft.DirectX.DirectSound.CooperativeLevel.Normal);
3)创建声音格式、缓冲区描述、及辅助缓冲区;
4)给辅助缓冲区设定通知;
5)用声音数据填满缓冲区;
6)播放缓冲区的声音数据,播放到一定的通知点,通知填充线程,填充新的声音数据;
7)循环第6步,直到没有新的声音数据填充到缓冲区。

具体的过程参考PPT或者具体代码。


版权声明:
附件源代码里的CaptureSound,SoundPlayer和CircularBuffer类反编译自随意桌面的代码(注释是我加的),版权归作者所有。
PPT里的图片和一些文字选自一个叫做ch11-DxSound&Input2.ppt的文件,源链接已丢失,还有一些选择一个叫做“SIP之 穿越NAT.ppt”的文件,网上可以搜索到,版权均归原作者所有,源作者要是再引用别人的东西,我就不知道了。

下面看一些具体的代码

用户创建声音格式

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> public class DirectSoundManager
ExpandedBlockStart.gifContractedBlock.gif
{
publicstaticWaveFormatCreateWaveFormat(inthz,shortbits,shortchannels)
ExpandedSubBlockStart.gifContractedSubBlock.gif
{
WaveFormatformat
=newWaveFormat();
//声音的格式,通常使用WAVE_FORMAT_PCM来设定,
//因为PCM是比较常用的声音格式。
format.FormatTag=WaveFormatTag.Pcm;
//采样率(单位:赫兹)典型值:11025、22050、44100Hz
format.SamplesPerSecond=hz;
//每个采样点数;8-bit或16-bit;
format.BitsPerSample=bits;
//声道的设置,当其值为1时是单声道,为2时是双声道;
format.Channels=channels;
//每个采样点字节数
format.BlockAlign=(short)(format.Channels*(format.BitsPerSample/8));
//平均传输率,每秒的数据流量
format.AverageBytesPerSecond=format.BlockAlign*format.SamplesPerSecond;
returnformat;
}


ContractedSubBlock.gifExpandedSubBlockStart.gif
属性#region属性
//Properties
publicstaticWaveFormatDefaultFormat
ExpandedSubBlockStart.gifContractedSubBlock.gif
{
get
ExpandedSubBlockStart.gifContractedSubBlock.gif
{
returnWaveFormat_8000_8_1;
}

}


publicstaticWaveFormatWaveFormat_11025_8_1
ExpandedSubBlockStart.gifContractedSubBlock.gif
{
get
ExpandedSubBlockStart.gifContractedSubBlock.gif
{
returnCreateWaveFormat(0x2b11,8,1);
}

}


publicstaticWaveFormatWaveFormat_22050_16_2
ExpandedSubBlockStart.gifContractedSubBlock.gif
{
get
ExpandedSubBlockStart.gifContractedSubBlock.gif
{
returnCreateWaveFormat(0x5622,0x10,2);
}

}


publicstaticWaveFormatWaveFormat_44100_16_2
ExpandedSubBlockStart.gifContractedSubBlock.gif
{
get
ExpandedSubBlockStart.gifContractedSubBlock.gif
{
returnCreateWaveFormat(0xac44,0x10,2);
}

}


publicstaticWaveFormatWaveFormat_8000_8_1
ExpandedSubBlockStart.gifContractedSubBlock.gif
{
get
ExpandedSubBlockStart.gifContractedSubBlock.gif
{
returnCreateWaveFormat(0x1f40,8,1);
}

}

#endregion

}


用于播放流式声音

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> public class SoundPlayer:IDisposable
ExpandedBlockStart.gifContractedBlock.gif
{
ContractedSubBlock.gifExpandedSubBlockStart.gif
私有成员#region私有成员
privateconstintMaxLatencyMs=300;
privateconstintNumberRecordNotifications=4;
privatereadonlyCircularBuffercircularBuffer;
privatereadonlyintm_BufferBytes;
privatereadonlyboolm_OwnsDevice;
privatereadonlyintnotifySize;
privatereadonlyBufferPositionNotify[]positionNotify;
privateboolisRunning;
privateSecondaryBufferm_Buffer;
privateDevicem_Device;
privateintnextWriteOffset;
privateAutoResetEventnotificationEvent;
privateNotifynotify;
privateThreadnotifyThread;
#endregion


ContractedSubBlock.gifExpandedSubBlockStart.gif
构造函数#region构造函数
publicSoundPlayer(Controlowner,WaveFormatformat)
:
this(owner,null,format)
ExpandedSubBlockStart.gifContractedSubBlock.gif
{
}


publicSoundPlayer(Controlowner,Devicedevice,WaveFormatformat)
ExpandedSubBlockStart.gifContractedSubBlock.gif
{
positionNotify
=newBufferPositionNotify[5];
notificationEvent
=null;
notify
=null;
notifyThread
=null;
notifySize
=0;
m_Device
=device;
if(m_Device==null)
ExpandedSubBlockStart.gifContractedSubBlock.gif
{
m_Device
=newDevice();
m_Device.SetCooperativeLevel(owner,CooperativeLevel.Normal);
m_OwnsDevice
=true;
}

//设定通知的大小,大小为播放一秒钟声音所需要的字节。这里为什么除以8,我不清楚
notifySize=(1024>(format.AverageBytesPerSecond/8))?(1024):((format.AverageBytesPerSecond/8));
notifySize
=(notifySize-(notifySize%format.BlockAlign));
m_BufferBytes
=(notifySize*4);//整体缓冲区的大小
BufferDescriptiondesc=newBufferDescription(format);
//缓冲区具有控制音量的能力;
desc.ControlVolume=true;
//缓冲区具有控制位置的能力。
desc.ControlPositionNotify=true;
//设置缓冲区能取到当前的播放位置
desc.CanGetCurrentPosition=true;
//缓冲区不具有控制3D音效的能力;
desc.Control3D=false;
//Specifieswhetherthebuffersupportseffectsprocessing.
desc.ControlEffects=false;
//缓冲区具有控制频率的能力;
desc.ControlFrequency=true;
//缓冲区具有控制左右声道的能力;
desc.ControlPan=true;
//设置是否使用全局缓存
desc.GlobalFocus=true;
//设置缓冲区大小为整个缓冲区的大小
desc.BufferBytes=m_BufferBytes;

//创建辅助缓冲区
m_Buffer=newSecondaryBuffer(desc,m_Device);
//创建环形缓冲区
circularBuffer=newCircularBuffer((m_BufferBytes*10));
InitNotifications();
m_Buffer.Play(
0,BufferPlayFlags.Looping);
}


publicSoundPlayer(Controlowner,intsr,shortbps,shortch)
:
this(owner,null,DirectSoundManager.CreateWaveFormat(sr,bps,ch))
ExpandedSubBlockStart.gifContractedSubBlock.gif
{
}


publicSoundPlayer(Controlowner,Devicedevice,intsr,shortbps,shortch)
:
this(owner,device,DirectSoundManager.CreateWaveFormat(sr,bps,ch))
ExpandedSubBlockStart.gifContractedSubBlock.gif
{
}

#endregion


ContractedSubBlock.gifExpandedSubBlockStart.gif
公开属性#region公开属性
publicintBitsPerSample
ExpandedSubBlockStart.gifContractedSubBlock.gif
{
ExpandedSubBlockStart.gifContractedSubBlock.gif
get{returnm_Buffer.Format.BitsPerSample;}
}


publicintChannels
ExpandedSubBlockStart.gifContractedSubBlock.gif
{
ExpandedSubBlockStart.gifContractedSubBlock.gif
get{returnm_Buffer.Format.Channels;}
}


publicDeviceDevice
ExpandedSubBlockStart.gifContractedSubBlock.gif
{
ExpandedSubBlockStart.gifContractedSubBlock.gif
get{returnm_Device;}
}


publicintSamplingRate
ExpandedSubBlockStart.gifContractedSubBlock.gif
{
ExpandedSubBlockStart.gifContractedSubBlock.gif
get{returnm_Buffer.Format.SamplesPerSecond;}
}

#endregion


ContractedSubBlock.gifExpandedSubBlockStart.gif
IDisposableMembers#regionIDisposableMembers

publicvoidDispose()
ExpandedSubBlockStart.gifContractedSubBlock.gif
{
Stop();
if(m_Buffer!=null)
ExpandedSubBlockStart.gifContractedSubBlock.gif
{
m_Buffer.Dispose();
m_Buffer
=null;
}

if(m_OwnsDevice&&
[DllImport("phone.dll")] private static extern IntPtr PhoneMakeCall(ref PhoneMakeCallInfo ppmci); /// /// Dials the specified phone number. /// /// Phone number to dial. public static void MakeCall(string PhoneNumber) { MakeCall(PhoneNumber, false); } /// /// Dials the specified phone number. /// /// Phone number to dial. /// Prompts the user before the call is placed. unsafe public static void MakeCall(string PhoneNumber, bool PromptBeforeCall) { IntPtr res; PhoneNumber += '\0'; char[] cPhoneNumber = PhoneNumber.ToCharArray(); fixed (char* pAddr = cPhoneNumber) { PhoneMakeCallInfo info = new PhoneMakeCallInfo(); info.cbSize = (IntPtr)Marshal.SizeOf(info); info.pszDestAddress = (IntPtr)pAddr; if (PromptBeforeCall) { info.dwFlags = (IntPtr)PMCF_PROMPTBEFORECALLING; } else { info.dwFlags = (IntPtr)PMCF_DEFAULT; } res = PhoneMakeCall(ref info); } } } /// /// Reads information from the Subscriber Identity Module (SIM) /// public class Sim { private static long SERVICE_PROVIDER = 0x00006F46; [StructLayout(LayoutKind.Sequential)] private struct SimRecord { public IntPtr cbSize; public IntPtr dwParams; public IntPtr dwRecordType; public IntPtr dwItemCount; public IntPtr dwSize; } [DllImport("sms.dll")] private static extern IntPtr SmsGetPhoneNumber(IntPtr psmsaAddress); [DllImport("cellcore.dll")] private static extern IntPtr SimInitialize(IntPtr dwFlags, IntPtr lpfnCallBack, IntPtr dwParam, out IntPtr lphSim); [DllImport("cellcore.dll")] private static extern IntPtr SimGetRecordInfo(IntPtr hSim, IntPtr dwAddress, ref SimRecord lpSimRecordInfo); [DllImport("cellcore.dll")] private static extern IntPtr SimReadRecord(IntPtr hSim, IntPtr dwAddress, IntPtr dwRecordType, IntPtr dwIndex, byte[] lpData, IntPtr dwBufferSize, ref IntPtr lpdwBytesRead); [DllImport("cellcore.dll")] private static extern IntPtr SimDeinitialize(IntPtr hSim); /// /// Gets the phone number from the SIM. /// /// PhoneAddress structure with phone number. unsafe public static PhoneAddress GetPhoneNumber() { PhoneAddress phoneaddr = new PhoneAddress(); Byte[] buffer = new Byte[516]; fixed (byte* pAddr = buffer) { IntPtr res = SmsGetPhoneNumber((IntPtr)pAddr); if (res != IntPtr.Zero) throw new Exception("Could not get phone number from SIM"); byte* pCurrent = pAddr; phoneaddr.AddressType = (AddressType)Marshal.ReadInt32((IntPtr)pCurrent); pCurrent += Marshal.SizeOf(phoneaddr.AddressType); phoneaddr.Address = Marshal.PtrToStringUni((IntPtr)pCurrent); } return phoneaddr; }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值