Chinaunix首页 | 论坛 | 博客
  • 博客访问: 537189
  • 博文数量: 576
  • 博客积分: 40000
  • 博客等级: 大将
  • 技术积分: 5020
  • 用 户 组: 普通用户
  • 注册时间: 2008-10-13 14:47
文章分类

全部博文(576)

文章存档

2011年(1)

2008年(575)

我的朋友

分类:

2008-10-14 14:55:00

Skype录音答录机编程思路和代码

作者:

版本:1.0.3
操作系统:Windows 桌面操作系统
开发日期:2006-11-20 03:05
文档日期:2008-06-24 18:33

摘要

  我常常使用Skype和台湾同胞还有国外的朋友进行联系,有时因为业务需要需要将语音通话录音并保留下来,在我有这个想法的那个时候(2006年)Skype官方并没有提供录音功能,咱们是做程序的嘛,没有的功能可以自己来添加啊,这也是为什么我酷爱编程的原因。

关键字:skype,audio

  Skype是免费的语音通话软件,不但可以点对点用电脑进行免费的语音通话,而且只需花费低廉的费用就可以直接呼叫固定电话或手机,Skype以优秀的通话质量而赢得了全世界不少用户的亲睐,我就是Skype的忠实用户,下图就是我的Skype截图:

  我常常使用Skype和台湾同胞还有国外的朋友进行联系,有时因为业务需要需要将语音通话录音并保留下来,在我有这个想法的那个时候(2006年)Skype官方并没有提供录音功能,咱们是做程序的嘛,没有的功能可以自己来添加啊,这也是为什么我酷爱编程的原因。
  应广大网友的要求,现将该程序的编程思路和源代码贡献出来与大家共勉,希望能给对音频编程有兴趣的朋友提供一点点帮助,那我就心满意足了。
  刚开始编写这个程序的时候,我试着用常规的录音方式对声卡进行录音,既然是通话录音,我们希望能将自己的声音和对方的声音同时纪录下来。首先,我们要将对方的声音录下来,那就只能选取“立体声混音”通道进行录音,但此时“麦克风”通道的声音将被丢弃,也就是说在Skype里对方将听不到我说话了;其次,如果我们还要将我自己的声音录下来,就得开启“麦克风”通道录音,但是在Skype通话过程中,“麦克风”通道已经被Skype占用了,我们的程序无法再次进行录音,看来常规的录音方式行不通。
  于是,我想到了Windows音频的底层处理机制,任何语音软件的音频数据处理到最后都离不开 Windows 的底层音频 Win32 API 函数,查一下MSDN 库就能得知,这些函数都在 MultiMed.chm 帮助文件中:

Waveform Functions
The following functions are used with waveform audio.
auxGetDevCaps
auxGetNumDevs
auxGetVolume
auxOutMessage
auxSetVolume
PlaySound
sndPlaySound
waveInAddBuffer
waveInClose
waveInGetDevCaps
waveInGetErrorText
waveInGetID
waveInGetNumDevs
waveInGetPosition
waveInMessage
waveInOpen
waveInPrepareHeader
waveInProc
waveInReset
waveInStart
waveInStop
waveInUnprepareHeader
waveOutBreakLoop
waveOutClose
waveOutGetDevCaps
waveOutGetErrorText
waveOutGetID
waveOutGetNumDevs
waveOutGetPitch
waveOutGetPlaybackRate
waveOutGetPosition
waveOutGetVolume
waveOutMessage
waveOutOpen
waveOutPause
waveOutPrepareHeader
waveOutProc
waveOutReset
waveOutRestart
waveOutSetPitch
waveOutSetPlaybackRate
waveOutSetVolume
waveOutUnprepareHeader
waveOutWrite

  有了这些函数,我就想到了一个办法,那就是用系统钩子改变这些函数的原地址,在Skype调用这些Win32 API函数之前先进入我的程序,我将Skype的音频数据“偷偷地”拷贝一份传递给我自己的应用程序,再还给Skype,这样就可以神不知鬼不觉地将通话中的语音数据取出来,再加上自己的mp3压缩保存到硬盘文件即可。

以上便是整个Skype录音的全部思路,现在开始介绍代码。

在本程序中需要监视的Win32 API函数有:

waveInOpen – 打开一个音频输入设备(录音)
waveInClose – 关闭一个音频输入设备(录音)
waveOutOpen – 打开一个音频输出设备(回放)
waveOutClose – 关闭一个音频输出设备(回放)
waveInPrepareHeader – 为音频输入设备准备一个内存数据缓冲(录音)
waveOutWrite – 将语音数据块发送至音频输出设备进行播放(回放)

  由于我们的程序需要嵌入到Skype程序中,所以我们只能使用dll的形式来编写这个程序,我现在需要写一个修改Win32 API函数地址的类,在这里我直接引用了《Windows 核心编程》随书代码中的 CAPIHook 类,我提供的源代码里就有这个类,这个类可以修改Win32 API函数的地址,当我们修改好API函数地址以后,Skype调用前面所说的6个函数时系统会自动调用我们的函数,请看代码:

//
// 定义函数变量
//
typedef MMRESULT (WINAPI *PFN_waveInOpen) ( LPHWAVEIN phwi, 
          	          UINT uDeviceID, 
					LPWAVEFORMATEX pwfx, 
					DWORD dwCallback, 
					DWORD dwCallbackInstance, 
					DWORD fdwOpen );
typedef MMRESULT (WINAPI *PFN_waveInClose) ( HWAVEIN hwi );
typedef MMRESULT (WINAPI *PFN_waveOutOpen) ( LPHWAVEOUT phwo, 
					UINT uDeviceID, 
					LPWAVEFORMATEX pwfx, 
					DWORD dwCallback, 
					DWORD dwCallbackInstance, 
					DWORD fdwOpen );
typedef MMRESULT (WINAPI *PFN_waveOutClose) ( HWAVEOUT hwo );
typedef MMRESULT (WINAPI *PFN_waveInPrepareHeader) ( HWAVEIN hwi, LPWAVEHDR pwh, UINT cbwh );
typedef MMRESULT (WINAPI *PFN_waveOutWrite) ( HWAVEOUT hwo, LPWAVEHDR pwh, UINT cbwh );

//
// 修改Win32 API函数地址
//
CAPIHook g_waveInOpen("winmm.dll", "waveInOpen", (PROC) Hook_waveInOpen, TRUE);
CAPIHook g_waveInClose("winmm.dll", "waveInClose", (PROC) Hook_waveInClose, TRUE);
CAPIHook g_waveOutOpen("winmm.dll", "waveOutOpen", (PROC) Hook_waveOutOpen, TRUE);
CAPIHook g_waveOutClose("winmm.dll", "waveOutClose", (PROC) Hook_waveOutClose, TRUE);
CAPIHook g_waveInPrepareHeader("winmm.dll", "waveInPrepareHeader", (PROC) Hook_waveInPrepareHeader, TRUE);
CAPIHook g_waveOutWrite("winmm.dll", "waveOutWrite", (PROC) Hook_waveOutWrite, TRUE);

说明:

CAPIHook g_waveInOpen("winmm.dll", "waveInOpen", (PROC) Hook_waveInOpen, TRUE);

  这段代码实现了 "winmm.dll" 库中 "waveInOpen"函数地址的修改,修改后的地址为“Hook_waveInOpen”,也就是说,以后Skype调用函数“waveInOpen”系统会自动先调用我们的函数“Hook_waveInOpen”。其他几个函数修改原理相同。

  至此我们已经成功地修改了Win32 API函数,由于Skype在调用这些API函数时会将音频数据传递给系统,刚好系统又先调用我们的函数,那我们就可以得到Skype的音频数据,看下面代码:

//
// 修改 waveOutWrite 函数地址
//
CAPIHook g_waveOutWrite("winmm.dll", "waveOutWrite", (PROC) Hook_waveOutWrite, TRUE);

//
// This is the waveOutWrite replacement function
//
MMRESULT WINAPI Hook_waveOutWrite ( HWAVEOUT hwo, LPWAVEHDR pwh, UINT cbwh )
{
//
// Skype 在调用 waveOutWrite 函数进行音频回放(播放对方的声音)时我们将得
// 到这些音频数据,这些数据就存放在 pwh->lpData 缓冲中,我们调用 
// SendDataToMainWnd 函数将数据发送至主窗口进行压缩和保存处理
//
SendDataToMainWnd ( pwh->lpData,
pwh->dwBytesRecorded > 0 ? pwh->dwBytesRecorded : pwh->dwBufferLength,
ENUM_CATCHSOUNDTYPE_waveOutWrite );

// Call the original waveOutWrite function
MMRESULT nResult = ((PFN_waveOutWrite)(PROC) g_waveOutWrite )
(hwo, pwh, cbwh);

// Return the result back to the caller
return(nResult);
}

以上代码中将“偷取”到的音频数据通过 SendDataToMainWnd 函数发送给主窗口,至此,回放音频数据偷取成功了。

接下来我们在“偷取”录音数据(即通话中我说话的音频数据),看代码:

//
// 修改 waveInPrepareHeader 函数地址
//
CAPIHook g_waveInPrepareHeader("winmm.dll", "waveInPrepareHeader", (PROC) Hook_waveInPrepareHeader, TRUE);

//
// This is the waveInPrepareHeader replacement function
//
MMRESULT WINAPI Hook_waveInPrepareHeader ( HWAVEIN hwi, LPWAVEHDR pwh, UINT cbwh )
{
// 我们都知道,常规录音是用多个录音缓冲轮流交替使用的方式来得到来自
// 硬件设备的语音数据的,当任何一个录音缓冲数据满的时候,录音程序将
// 调用 Win32 API waveInPrepareHeader 函数来准备下一个录音缓冲,调用这
// 个函数时传递的 LPWAVEHDR 指针中刚好有已经录好的音频数据,我们
// 用同样的方式将数据取走。
SendDataToMainWnd ( pwh->lpData,
pwh->dwBytesRecorded > 0 ? pwh->dwBytesRecorded : pwh->dwBufferLength,
ENUM_CATCHSOUNDTYPE_waveInPrepareHeader );

// Call the original waveInPrepareHeader function
MMRESULT nResult = ((PFN_waveInPrepareHeader)(PROC) g_waveInPrepareHeader )
(hwi, pwh, cbwh);

// Return the result back to the caller
return(nResult);
}

  至此,Skype通话过程中音频输入和输出的数据(即对方讲话和我自己讲话的声音)已经全部“偷取”到了,接下来只要压缩成mp3格式即可,mp3压缩代码网上很多,随便下载一个来用就可以了,我用的是“hw_mp3_enc.dll library”,效果一般,但用做电话录音足亦。
  一个有趣的功能:我们录音后的mp3文件播放时,我可以让对方的声音在左声道,我自己的声音在右声道,好像两个人面对面在对话一样。其实做起来并不难,从上面的代码我们知道,其实输入和输出的音频数据是独立获取的,我们在合并到mp3文件时,将输入的数据存为左声道,输出的数据存为右声道即可。
既然叫“Skype答录机”,除了有录音功能外,还应该有自动应答功能,要实现这个功能有两个办法:

  • a) 当来电震铃超过规定的次数时自动提机,将录音通道切换到“立体声混音”,然后播放之前准备好的一个语音文件(如:您好,我现在不在电脑旁,有事请留言),本软件使用的就是这种方式;
  • b) 当来电震铃超过规定的次数时自动提机,然后播放之前准备好的一个语音文件(如:您好,我现在不在电脑旁,有事请留言)数据直接传递至上面的.dll文件相关函数中,然后 waveInPrepareHeader 函数中将系统从麦克风中录制的声音替换掉,这种方式比较难控制,但可以实 现很多奇怪的效果,比如通话变声等。

  需要注意的地方:该程序是通过钩子方式截取Skype的音频数据,所以程序的执行效率要求很高,对于慢速处理的操作(如:压缩mp3数据、数据存盘等)最好是放到其他线程中处理,否则会影响Skype通话质量,造成通话断断续续的感觉,录音数据也可能会丢失。

软件执行界面

主界面:
 

☆ 配置界面:

说明:

  由于该软件为“深圳市伟信科技开发有限公司”的商业软件,这个软件是为了“无线Skype话机”而开发的,使用该话机可以离开电脑像操作普通手机一样进行免费的Skype通话了。

详情请见:

出于商业道德的考虑,我不能将整个工程源代码全部公开,但有关Skype录音和应答方面的关键性代码已经全部包含在文件包里了,只要稍加修改就可以添加到自己的工程项目中了。
  以上程序在Windows XP/ Skype2.5 上测试通过,但Skype3.0 以后的版本录音好像有点问题,因为时间的关系,我尚未查找其中的原因,我初步猜测 Skype 在调用 waveInPrepareHeader 函数前将数据清除掉了,可以试着捕捉 MM_WIM_DATA 消息来获取录音数据,因为工作比较忙,所以没时间来做尝试,有兴趣的朋友可以来完成它,如果有朋友完成了请将你的方法email给我,多谢。

谢谢曹昌利、陈容清、周伟波等几位同志对本软件的严格测试。
--------------------next---------------------


阅读(290) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~