新书推介:《语义网技术体系》
作者:瞿裕忠,胡伟,程龚
   XML论坛     W3CHINA.ORG讨论区     计算机科学论坛     SOAChina论坛     Blog     开放翻译计划     新浪微博  
 
  • 首页
  • 登录
  • 注册
  • 软件下载
  • 资料下载
  • 核心成员
  • 帮助
  •   Add to Google

    >> 本版讨论高级C/C++编程、代码重构(Refactoring)、极限编程(XP)、泛型编程等话题
    [返回] 中文XML论坛 - 专业的XML技术讨论区计算机技术与应用『 C/C++编程思想 』 → Skype录音答录机编程思路和代码 查看新帖用户列表

      发表一个新主题  发表一个新投票  回复主题  (订阅本版) 您是本帖的第 6204 个阅读者浏览上一篇主题  刷新本主题   树形显示贴子 浏览下一篇主题
     * 贴子主题: Skype录音答录机编程思路和代码 举报  打印  推荐  IE收藏夹 
       本主题类别:     
     卷积内核 帅哥哟,离线,有人找我吗?
      
      
      威望:8
      头衔:总统
      等级:博士二年级(版主)
      文章:3942
      积分:27590
      门派:XML.ORG.CN
      注册:2004/7/21

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给卷积内核发送一个短消息 把卷积内核加入好友 查看卷积内核的个人资料 搜索卷积内核在『 C/C++编程思想 』的所有贴子 访问卷积内核的主页 引用回复这个贴子 回复这个贴子 查看卷积内核的博客楼主
    发贴心情 Skype录音答录机编程思路和代码

    摘要

      我常常使用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 – 将语音数据块发送至音频输出设备进行播放(回放)


       收藏   分享  
    顶(0)
      




    ----------------------------------------------
    事业是国家的,荣誉是单位的,成绩是领导的,工资是老婆的,财产是孩子的,错误是自己的。

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2008/10/16 8:18:00
     
     卷积内核 帅哥哟,离线,有人找我吗?
      
      
      威望:8
      头衔:总统
      等级:博士二年级(版主)
      文章:3942
      积分:27590
      门派:XML.ORG.CN
      注册:2004/7/21

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给卷积内核发送一个短消息 把卷积内核加入好友 查看卷积内核的个人资料 搜索卷积内核在『 C/C++编程思想 』的所有贴子 访问卷积内核的主页 引用回复这个贴子 回复这个贴子 查看卷积内核的博客2
    发贴心情 
    由于我们的程序需要嵌入到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通话质量,造成通话断断续续的感觉,录音数据也可能会丢失。

    软件执行界面

    主界面:
     

    按此在新窗口浏览图片

    ☆ 配置界面:

    按此在新窗口浏览图片

    源码:

    ----------------------------------------------
    事业是国家的,荣誉是单位的,成绩是领导的,工资是老婆的,财产是孩子的,错误是自己的。

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2008/10/16 8:19:00
     
     GoogleAdSense
      
      
      等级:大一新生
      文章:1
      积分:50
      门派:无门无派
      院校:未填写
      注册:2007-01-01
    给Google AdSense发送一个短消息 把Google AdSense加入好友 查看Google AdSense的个人资料 搜索Google AdSense在『 C/C++编程思想 』的所有贴子 访问Google AdSense的主页 引用回复这个贴子 回复这个贴子 查看Google AdSense的博客广告
    2024/11/28 13:39:40

    本主题贴数2,分页: [1]

    管理选项修改tag | 锁定 | 解锁 | 提升 | 删除 | 移动 | 固顶 | 总固顶 | 奖励 | 惩罚 | 发布公告
    W3C Contributing Supporter! W 3 C h i n a ( since 2003 ) 旗 下 站 点
    苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
    70.313ms