用VC自己动手做个录音机

用VC自己动手做个录音机

搞了很久的程序,都是做业务系统之类的,还没有搞过多媒体方面的编程,今天动手做个录音机,了解一下声音相关的API,

window下面声音的编程主要有三种方式,

  • MCI,这种方式很简单,但是不够灵活
  • waveXXXX等低阶的声音API
  • 还有就是DirectSound

个人感觉使用waveXXX函数应该是最方便和最灵活的,也是比较简单的,

随便创建一个MFC基于对话框的工程,在窗口类里面增加几个成员

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--> DWORD m_dwDataLength;         // 数据长度
WAVEFORMATEX m_waveform;     // 声音格式
HWAVEIN m_hWaveIn;             // 音频输入句柄
HWAVEOUT m_hWaveOut;         // 音频输出句柄
PBYTE m_pSaveBuffer;         // 音频存储内存

WAVEHDR m_WAVEHDR1;
WAVEHDR m_WAVEHDR2;

char  m_cbBuffer1[INP_BUFFER_SIZE];     // 声音临时缓存1
char  m_cbBuffer2[INP_BUFFER_SIZE];     // 声音临时缓存2

 

 在构造函数对成员进行初始化

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--> // 录音机状态初始化
m_nRecordState  =  RECORD_STATE_INIT;
m_dwDataLength 
=   0 ;

// open waveform audo for input
// 为声音输入设置格式
m_waveform.wFormatTag = WAVE_FORMAT_PCM;
m_waveform.nChannels
= 1 ;
m_waveform.nSamplesPerSec
= 11025 ;
m_waveform.nAvgBytesPerSec
= 11025 ;
m_waveform.nBlockAlign
= 1 ;
m_waveform.wBitsPerSample
= 8 ;
m_waveform.cbSize
= 0 ;    
    
m_pSaveBuffer 
=  NULL;

 看看我的窗体

 

录音的处理函数

 

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->
void  CWaveRecordDlg::OnBnClickedBtnRecord()
{
    
//打开声音设备
    if(waveInOpen(&m_hWaveIn,WAVE_MAPPER,&m_waveform,
            (DWORD)m_hWnd,NULL,CALLBACK_WINDOW))
{
        MessageBeep(MB_ICONEXCLAMATION);
        MessageBox(_T(
"录制声音失败!"),
                    _T(
"错误"),MB_ICONEXCLAMATION|MB_OK);
        
return;
    }


    
//设置缓冲区
    m_WAVEHDR1.lpData = (LPTSTR)m_cbBuffer1;
    m_WAVEHDR1.dwBufferLength 
= INP_BUFFER_SIZE;
    m_WAVEHDR1.dwBytesRecorded 
= 0;
    m_WAVEHDR1.dwUser
=0;
    m_WAVEHDR1.dwFlags
=0;
    m_WAVEHDR1.dwLoops
=1;
    m_WAVEHDR1.lpNext
=NULL;
    m_WAVEHDR1.reserved
=0;

    m_WAVEHDR2.lpData
=(LPTSTR)m_cbBuffer2;
    m_WAVEHDR2.dwBufferLength 
= INP_BUFFER_SIZE;
    m_WAVEHDR2.dwBytesRecorded 
= 0;
    m_WAVEHDR2.dwUser
=0;
    m_WAVEHDR2.dwFlags
=0;
    m_WAVEHDR2.dwLoops
=1;
    m_WAVEHDR2.lpNext
=NULL;
    m_WAVEHDR2.reserved
=0;

    
//设置双缓冲
    waveInPrepareHeader(m_hWaveIn,&m_WAVEHDR1,sizeof(WAVEHDR));
    waveInPrepareHeader(m_hWaveIn,
&m_WAVEHDR2,sizeof(WAVEHDR));

    waveInAddBuffer(m_hWaveIn,
&m_WAVEHDR1,sizeof(WAVEHDR));
    waveInAddBuffer(m_hWaveIn,
&m_WAVEHDR2,sizeof(WAVEHDR));

    
//先随便分配个内存
    if(m_pSaveBuffer == NULL){
        free(m_pSaveBuffer);
        m_pSaveBuffer 
= NULL;
    }

    m_pSaveBuffer 
= (PBYTE)malloc(1);
    m_dwDataLength 
= 0;

    
//开始录音
    
// Begin sampling    
    waveInStart(m_hWaveIn);
}

 然后增加三个消息进行处理

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->
LRESULT CWaveRecordDlg::OnMM_WIM_CLOSE(UINT wParam, LONG lParam)
{
    waveInUnprepareHeader(m_hWaveIn, 
&m_WAVEHDR1, sizeof (WAVEHDR)) ;
    waveInUnprepareHeader(m_hWaveIn, 
&m_WAVEHDR2, sizeof (WAVEHDR)) ;
    
    m_nRecordState 
= RECORD_STATE_STOP;
    SetButtonState();

    
return NULL;
}


LRESULT CWaveRecordDlg::OnMM_WIM_DATA(UINT wParam, LONG lParam)
{
    PWAVEHDR pWaveHdr 
= (PWAVEHDR)lParam;
    
    
if(pWaveHdr->dwBytesRecorded > 0){
        m_pSaveBuffer 
= (PBYTE)realloc(m_pSaveBuffer,m_dwDataLength + pWaveHdr->dwBytesRecorded);
        
if(m_pSaveBuffer == NULL){
            waveInClose (m_hWaveIn);
            MessageBeep (MB_ICONEXCLAMATION) ;
            AfxMessageBox(
"erro memory");
            
return NULL;
        }


        memcpy(m_pSaveBuffer
+m_dwDataLength , pWaveHdr->lpData,pWaveHdr->dwBytesRecorded);

        m_dwDataLength 
+= pWaveHdr->dwBytesRecorded;
    }


    
if(m_nRecordState == RECORD_STATE_STOPING){
        waveInClose(m_hWaveIn);        
    }


    waveInAddBuffer(m_hWaveIn, pWaveHdr, 
sizeof (WAVEHDR));

    
return NULL;
}


LRESULT CWaveRecordDlg::OnMM_WIM_OPEN(UINT wParam, LONG lParam)
{
    m_nRecordState 
= RECORD_STATE_RECING;
    SetButtonState();
    
return NULL;
}

 对录音处理的基本原理简单的谈一下,

先调用waveInOpen打开声音设备,设置WAVEHDR类型的缓冲区,其中lpData成员指向缓冲区的内存块,

可以分配动态内存也可以像我这里一样,分配在栈区的内存。接着就可以调用waveInPrepareHeader以及waveInAddBuffer,

调用 waveInStart开始录音,然后会发出消息MM_WIM_OPEN,如果一人缓冲区满了以后就会发出消息MM_WIM_DATA,

我们就可以在这个消息处理函数将声音内容拷到我们自己内存块保存起来,以及增加m_dwDataLength的长度,如果要停止录音,

只要调用一下waveInReset,然后会发出消息MM_WIM_DATA作最后处理,并且发出消息OnMM_WIM_CLOSE作点善后工作,

如在这里调用waveInUnprepareHeader去掉缓冲区,并且设置按钮的状态,lpData指向的是动态内存也可以在这里释放。

注:这里用了两个缓冲区,可以使声音连续,如果只使用一个缓冲区会导致声音有顿

现在看看声音播放,先看代码

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--> void  CWaveRecordDlg::OnBnClickedBtnPlay()
{
    
//打开声音播放句柄
    if(waveOutOpen(&m_hWaveOut,WAVE_MAPPER,&m_waveform,(DWORD)m_hWnd, NULL, CALLBACK_WINDOW)){
        MessageBeep(MB_ICONEXCLAMATION);
        MessageBox(_T(
"录制声音失败!"),_T("错误"),MB_ICONEXCLAMATION|MB_OK);
        
return;
    }
    
}


LRESULT CWaveRecordDlg::OnMM_WON_OPEN(UINT wParam,LONG lParam)
{
    memset(
&m_WAVEHDR1,0,sizeof(WAVEHDR));
    m_WAVEHDR1.lpData 
= (char *)m_pSaveBuffer;
//指向要播放的内存
    m_WAVEHDR1.dwBufferLength 
= m_dwDataLength;
//播放的长度
    m_WAVEHDR1.dwFlags 
= WHDR_BEGINLOOP | WHDR_ENDLOOP;
    m_WAVEHDR1.dwLoops 
= 1;

    waveOutPrepareHeader(m_hWaveOut,
&m_WAVEHDR1,sizeof(WAVEHDR));

    waveOutWrite(m_hWaveOut,
&m_WAVEHDR1,sizeof(WAVEHDR));

    m_nRecordState 
= RECORD_STATE_PLAYING;
    SetButtonState();
    
return NULL;
}


LRESULT CWaveRecordDlg::OnMM_WOM_DONE(UINT wParam,LONG lParam)
{
    PWAVEHDR pWaveHdr 
= (PWAVEHDR)lParam;
    waveOutUnprepareHeader (m_hWaveOut, pWaveHdr, 
sizeof (WAVEHDR)) ;
    waveOutClose (m_hWaveOut);
    
return NULL;
}


LRESULT CWaveRecordDlg::OnMM_WOM_CLOSE(UINT wParam,LONG lParam)
{
    m_nRecordState 
= RECORD_STATE_STOP;
    SetButtonState();
    
return NULL;
}

 声音的播放相对简单一点,在这里这种情况不需要用到双缓存技术,只要调用 waveOutOpen打开声音播放句柄,

然后响应 MM_WON_OPEN消息,设置缓冲区,就可以了

当然,如果是网络聊天的时候发出来的声音包应该不是这样一大块的内存,而是一小块一小块就得用到双缓存技术,

需要处理MM_WOM_DONE,以便重新加入声音包,可以顺畅的播放;在这里只是简单的去掉缓冲区

 然后现在就剩下保存成wav,以后读取wav文件了

首先看一下wav文件的格式,以下内容部分摘自window程序设计

 

表22-1 .WAV档案格式
偏移量位元组资料
00004「RIFF」
00044波形块的大小(档案大小减8)
00084「WAVE」
000C4「fmt 」
00104格式块的大小(16位元组)
00142wf.wFormatTag = WAVE_FORMAT_PCM = 1
00162wf.nChannels
00184wf.nSamplesPerSec
001C4wf.nAvgBytesPerSec
00202wf.nBlockAlign
00222wf.wBitsPerSample
00244「data」
00284波形资料的大小
002C 波形资料

 

这是一种扩充自RIFF(Resource Interchange File Format:资源交换档案格式)的格式。RIFF是用於多媒体资料档案的万用格式,它是一种标记档案格式。在这种格式下,档案由资料「块」组成,而这些资料块则由前面4个字元的ASCII名称和4位元组(32位元)的资料块大小来确认。资料块大小值不包括名称和大小所需要的8位元组。

波形声音档案以文字字串「RIFF」开始,用来标识这是一个RIFF档案。字串後面是一个32位元的资料块大小,表示档案其余部分的大小,或者是小於8位元组的档案大小。

资料块以文字字串「WAVE」开始,用来标识这是一个波形声音块,後面是文字字串「fmt」-注意用空白使之成为4字元的字串-用来标识包含波形声音资料格式的子资料块。「fmt」字串的後面是格式资讯大小,这里是16位元组。格式资讯是WAVEFORMATEX结构的前16个位元组,或者,像最初定义时一样,是包含WAVEFORMAT结构的PCMWAVEFORMAT结构。

 格式资讯的後面是文字字串「data」,然後是32位元的资料大小,最後是波形资料本身。这些资料是按相同格式进行简单连结的样本,这与低阶波形声音设备上所使用的格式相同。如果样本大小是8位元,或者更少,那么每个样本有1位元组用於单声道,或者有2位元组用於立体声。如果样本大小在9到16位元之间,则每个样本就有2位元组用於单声道,或者4位元组用於立体声。对於立体声波形资料,每个样本都由左值及其後面的右值组成。

--------------------------------

基于上面的了解,就可以写一个读取wav包头的函数了

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--> #define  WAVE_HEADER_SIZE 44

int  CWaveRecordDlg::read_wav_head(WAVEFORMATEX  * wf, char   ** out_buffer, int   * out_len, char   * in_buffer, int  in_len)
{
    
char *lp_pos;
    
int itmp;
    
    lp_pos 
= in_buffer;
    
if(in_buffer == NULL || in_len == 0 || in_len < WAVE_HEADER_SIZE || wf==NULL)
        
return 1;

    
if(strncmp(lp_pos,"RIFF",4)!=0)
        
return -1;
    lp_pos 
+= 4;

    itmp 
= *((int*)lp_pos);
    
if(itmp != (in_len-8))
        
return -1;
    lp_pos 
+= 4;

    
if(strncmp(lp_pos,"WAVEfmt ",8)!=0)
        
return -1;
    lp_pos 
+= 8;

    itmp 
= *((int*)lp_pos);
    
if(itmp != 16)
        
return -1;
    lp_pos 
+= 4;

    
/**//*格式信息*/
    memcpy(wf,lp_pos,
16);
    lp_pos 
+= 16;

    
if(strncmp(lp_pos,"data",4)!=0)
        
return -1;
    lp_pos 
+= 4;

    
//真正的数据长度
    *out_len = *((int*)lp_pos);
    lp_pos 
+= 4;

    
if(*out_len != (in_len - WAVE_HEADER_SIZE))
        
return 1;

    
*out_buffer = (char*)malloc(*out_len);
    
if(*out_buffer == NULL)
        
return -2;

    memcpy(
*out_buffer,lp_pos,*out_len);    
    
    
return 0;
}

 以及写wav头媒体信息

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--> int  CWaveRecordDlg::write_wav_head(WAVEFORMATEX  * wf, char   * in_buffer, int  in_len, char   ** out_buffer,  int   * out_len)
{
    
char *buffer;
    
int *int_tmp,pos=0;

    
*out_len = WAVE_HEADER_SIZE + in_len;
    buffer 
= (char*)malloc(*out_len);
    
if(buffer == NULL) 
        
return -1;
    memcpy(buffer,
"RIFF",4);
    pos 
= 4;

    int_tmp 
= (int*)(buffer+pos);
    
*int_tmp = WAVE_HEADER_SIZE + in_len - 8;/**//*波形块的大小(档案大小减8)*/
    pos 
+= 4;

    memcpy(buffer
+pos,"WAVEfmt ",8);
    pos 
+= 8;

    int_tmp 
= (int*)(buffer+pos);
    
*int_tmp = 16;
    pos 
+= 4;

    
/**//*格式信息*/
    memcpy(buffer
+pos,wf,16);
    pos 
+= 16;

    memcpy(buffer
+pos,"data",4);
    pos 
+= 4;

    int_tmp 
= (int*)(buffer+pos);
    
*int_tmp = in_len;
    pos 
+= 4;

    memcpy(buffer
+pos,in_buffer,in_len);

    
*out_buffer = buffer;

    
return 0;
}

 

现在还有一点,就是 这个录音机还没有加入声音的压缩,算了,等有时间再弄吧

 

这样用waveXXX函数播录放声音的用法基本上描述清楚了 

 

录音程序,可在DEC++或vc++6.0编译环境下成功运行 部分代码: int main() { creat_file(); //新建文件,原文件数据被删除 RecordWave(); //录音函数 simplest_pcm16le_to_wave("NocturneNo2inEflat_44.1k_s16le.pcm", 1, 44100, "output_nocture.wav"); //将二进制录音信息从内存中提取,并生成 wav 文件 测控 1602 DEV C ++ 环境下 控制台应用程序 善解人意 成员:王帅、赵永玻、侯雅茹 3 return 0; } void RecordWave() { int count = waveInGetNumDevs(); //检测录音设备 printf("\n 音频输入数量:%d\n", count); WAVEINCAPS waveIncaps; MMRESULT mmResult = waveInGetDevCaps(0, &waveIncaps;, sizeof(WAVEINCAPS)); printf("\n 音频输入设备:%s\n", waveIncaps.szPname); if (MMSYSERR_NOERROR == mmResult) { //HWAVEIN phwi; WAVEFORMATEX pwfx; //录音格式指针 WaveInitFormat (&pwfx;, //波形声音的格式,单声道双声道使用 WAVE_FORMAT_PCM.当包含在 WAVEFORMATEXTENSIBLE 结构中时,使用 WAVE_FORMAT_EXTENSIBLE 1, //声道数量 44100, //采样率 16 // 采样位数 ); printf("\n 正在打开音频输入设备"); printf("\n 采样参数:声道 44.1kHz 16bit\n"); mmResult = waveInOpen( &phwi;, WAVE_MAPPER, &pwfx;, (DWORD)(MicCallback), NULL, CALLBACK_FUNCTION );//3 if (MMSYSERR_NOERROR == mmResult) { //WAVEHDR pwh1; char buffer1[10240]; pwh1.lpData = buffer1; pwh1.dwBufferLength = 10240; pwh1.dwUser = 1; pwh1.dwFlags = 0; 测控 1602 DEV C ++ 环境下 控制台应用程序 善解人意 成员:王帅、赵永玻、侯雅茹 4 mmResult = waveInPrepareHeader(phwi, &pwh1;, sizeof(WAVEHDR));//为波形输 入设备准备缓冲区 printf("\n 准备缓冲区 1"); //WAVEHDR pwh2; char buffer2[10240]; pwh2.lpData = buffer2; pwh2.dwBufferLength = 10240; pwh2.dwUser = 2; pwh2.dwFlags = 0; mmResult = waveInPrepareHeader(phwi, &pwh2;, sizeof(WAVEHDR));//为波形输 入设备准备缓冲区 printf("\n 准备缓冲区 2\n"); // WAVEHDR pwh3; char buffer3[10240]; pwh3.lpData = buffer3; pwh3.dwBufferLength = 10240; pwh3.dwUser = 3; pwh3.dwFlags = 0; mmResult = waveInPrepareHeader(phwi, &pwh3;, sizeof(WAVEHDR));//为波形输 入设备准备缓冲区 printf("准备缓冲区 3\n"); if (MMSYSERR_NOERROR == mmResult) { mmResult = waveInAddBuffer(phwi, &pwh1;, sizeof(WAVEHDR));//给输入设 备增加一个缓存 printf("\n 将缓冲区 1 加入音频输入设备"); mmResult = waveInAddBuffer(phwi, &pwh2;, sizeof(WAVEHDR));//给输入设 备增加一个缓存 printf("\n 将缓冲区 2 加入音频输入设备\n"); mmResult = waveInAddBuffer(phwi, &pwh3;, sizeof(WAVEHDR));//给输入设 备增加一个缓存 printf("将缓冲区 3 加入音频输入设备\n");
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值