zoukankan      html  css  js  c++  java
  • Windows 下音频数据采集和播放

    音频操作所需头文件和链接库

                  

    #include<mmsystem.h>
    #include<mmreg.h>
    #pragma  comment(lib"winmm.lib")

            由于音频采集过程是一个持续过程,所以建议为它们各自分配一个线程,而使用MFC的 CWinThread 类是一个不错的选择,笔者就是利用CWinThread类将这两个功能封装成了两个独立的类,为以后的使用提供了很大的便利性。笔者在此为读者提供本人写好的一个工程,此工程为视频语音采集的不完善版,目前实现语音本地采集与播放,VFW视频采集与显示(视频不清晰),在后续章节会将VFW视频采集进行总结,敬请期待。。。。。

    工程下载地址:http://pan.baidu.com/share/link?shareid=190628&uk=2735225556 中选择  VideoPlay.rar 下载,此项目是用vs2010编译

    一、音频采集

    操作步骤:

    1、分配数据buffer,通过WAVEHDR结构体保存,准备存储采集到的音频数据,此处应该根据采集频率设置足量的buffer

    void CRecodeSound::PreCreateHeader()
    {
        for(int i=0;i<MAXRECBUFFER;i++)
            m_RecHead[i]=CreateWaveHeader();
        m_IsAllocated = 1;
    }
    LPWAVEHDR  CRecodeSound::CreateWaveHeader()
    {
        LPWAVEHDR lpHdr = new WAVEHDR;

        if(lpHdr==NULL)
        {
            m_RecodeLog.WriteString(TEXT(" Unable to allocate the memory"));
            return NULL;
        }

        ZeroMemory(lpHdr, sizeof(WAVEHDR));
        char* lpByte = new char[RECBUFFER];//m_WaveFormatEx.nBlockAlign*SOUNDSAMPLES)];

        if(lpByte==NULL)
        {
            m_RecodeLog.WriteString(TEXT(" Unable to allocate the memory"));
            return NULL;
        }
        lpHdr->lpData =  lpByte;
        lpHdr->dwBufferLength =RECBUFFER;   // (m_WaveFormatEx.nBlockAlign*SOUNDSAMPLES);
        return lpHdr;

    }

    2、初始化音频格式结构体 WAVEFORMATEX。

    memset(&m_WaveFormatEx, 0, sizeof(m_WaveFormatEx));
    m_WaveFormatEx.wFormatTag = WAVE_FORMAT_PCM;//声音格式为PCM        
    m_WaveFormatEx.nChannels = 1;    //采样声道数,对于单声道音频设置为1,立体声设置为2
    m_WaveFormatEx.wBitsPerSample = 8;//采样比特  8bits/次
    m_WaveFormatEx.cbSize = 0;//一般为0
    m_WaveFormatEx.nSamplesPerSec = 8000; //采样率 16000 次/秒
    m_WaveFormatEx.nBlockAlign = 1; //一个块的大小,采样bit的字节数乘以声道数
    m_WaveFormatEx.nAvgBytesPerSec = 8000; //每秒的数据率,就是每秒能采集多少字节的数据

    3、waveInOpen打开音频输入设备准备开始采集

    //开启音频采集
    MMRESULT mmReturn = ::waveInOpen( &m_hRecord, WAVE_MAPPER,
        &m_WaveFormatEx, ::GetCurrentThreadId(), 0, CALLBACK_THREAD);

    //Error has occured while opening device

    if(mmReturn != MMSYSERR_NOERROR ) //打开采集失败
    {
        displayError(mmReturn,"Open");
        return ;//FALSE;
    }

    4、waveInPrepareHeader 和 waveInAddBuffer 配合将准备好的buffer提供给设备

    //将准备好的buffer提供给音频输入设备
    for(int i=0; i < MAXRECBUFFER ; i++)
    {
        //准备一个bufrer给输入设备
        mmReturn = ::waveInPrepareHeader(m_hRecord,m_RecHead[i], sizeof(WAVEHDR));
        //发送一个buffer给指定的输入设备,当buffer填满将会通知程序
        mmReturn = ::waveInAddBuffer(m_hRecord, m_RecHead[i], sizeof(WAVEHDR));
    }

    5、waveInStart正式开始采集

    //开启指定的输入采集设备
    mmReturn = ::waveInStart(m_hRecord);

    if(mmReturn!=MMSYSERR_NOERROR )  //开始采集失败
        displayError(mmReturn,"Start");
    else
        m_IsRecoding = TRUE;

    6、每当一个buffer数据填满时,会触发 MM_WIM_DATA 消息,在程序中捕获此消息,通过消息传递过来的 lParam,为指向数据buffer的WAVEHDR指针。采集到此数据时可以根据程序需要对其做相应的处理。本程序是直接将采集到的数据提供给播放线程直接播放,你也可以通过socket发送到远端在播放,就可以网络语音了。

    void CRecodeSound::OnSoundData(WPARAM wParam, LPARAM lParam)
    {
        m_RecodeLog.WriteString(TEXT(" In the onsound data"));

        if(m_IsRecoding==FALSE) //如果当前不在采集状态
            return ;//FALSE;

        LPWAVEHDR lpHdr = (LPWAVEHDR) lParam;

        if(lpHdr->dwBytesRecorded==0 || lpHdr==NULL)
            return ;//ERROR_SUCCESS;

        //使采集过程,知道此buffer已经沾满,不能再填充
        ::waveInUnprepareHeader(m_hRecord, lpHdr, sizeof(WAVEHDR));

        //将采集到的声音发送给播放线程
        ((CVideoPlayDlg *)m_pDlg)->m_pPlaySound->PostThreadMessage(WM_PLAYSOUND_PLAYBLOCK, lpHdr->dwBytesRecorded, (LPARAM)lpHdr->lpData);
        
        // Send recorded audio to remote host…
        /*
        if(lpHdr->lpData!=NULL )
            ( (CVideoNetDlg*) dlg )->daudio.SendAudioData((unsigned char *)lpHdr->lpData,lpHdr->dwBytesRecorded);
        */

        if(m_IsRecoding)
        {
            //重新将buffer恢复到准备填充状态
            ::waveInPrepareHeader(m_hRecord, lpHdr, sizeof(WAVEHDR));
            ::waveInAddBuffer(m_hRecord, lpHdr, sizeof(WAVEHDR));
        }
    }

    7、在要停止采集是使用waveInStop停止采集数据。

    mmReturn = ::waveInStop(m_hRecord);

    8、停止采集成功,立即waveInReset重置设备,重置设备将会导致所有的采集buffer反馈给程序。

    if(!mmReturn) //停止采集成功,立即重置设备,重置设备将会导致所有的buffer反馈给程序
    {
        m_IsRecoding = FALSE;
        mmReturn = ::waveInReset(m_hRecord);  //重置设备
    }

    9、延时一段时间,等待所有的数据buffer都被程序处理完成

    Sleep(500); //等待一段时间,使buffer反馈完成

    10、waveInClose关闭设备

    if(!mmReturn) //重置设备成功,立即关闭设备
            mmReturn = ::waveInClose(m_hRecord); //关闭设备

    二、音频播放

    操作步骤:

    1、初始化音频数据格式结构体 WAVEFORMATEX

    //初始化音频格式结构体
    memset(&m_WaveFormatEx, 0, sizeof(m_WaveFormatEx));
    m_WaveFormatEx.wFormatTag = WAVE_FORMAT_PCM;
    m_WaveFormatEx.nChannels = 1;
    m_WaveFormatEx.wBitsPerSample = 8;
    m_WaveFormatEx.cbSize = 0;
    m_WaveFormatEx.nSamplesPerSec = 8000;
    m_WaveFormatEx.nAvgBytesPerSec = 8000 ;
    m_WaveFormatEx.nBlockAlign = 1;

    2、打开音频输出设备

    //打开音频输出设备
        mmReturn = ::waveOutOpen( &m_hPlay, WAVE_MAPPER,
            &m_WaveFormatEx, ::GetCurrentThreadId(), 0, CALLBACK_THREAD);

    3、设置音频输出音量

    if(mmReturn) //打开设备失败
        displayError(mmReturn,"PlayStart");    
    else
    {    
        m_IsPlaying = TRUE;
        DWORD volume = 0xffffffff;
        waveOutSetVolume(m_hPlay, volume);//设置输出设备的输出量
    }

    4、等待要输出的数据,通过waveOutPrepareHeader将数据提交给设备准备输出,通过waveOutWrite将提交给设备的数据输出。

    void CPlaySound::OnWriteSoundData(WPARAM wParam, LPARAM lParam)
    {
        MMRESULT mmResult = 0;
        int length=(int) wParam;
        
        if(m_IsPlaying == FALSE)
            return ; //FALSE;

        m_PlayLog.WriteString(TEXT(" playing sound data…."));

        // Prepare wave header for playing 
        WAVEHDR *lpHdr=new WAVEHDR;
        memset(lpHdr,0,sizeof(WAVEHDR));
        lpHdr->lpData=(char *)lParam;
        lpHdr->dwBufferLength=length;

        //将要输出的数据写入buffer
        mmResult = ::waveOutPrepareHeader(m_hPlay, lpHdr, sizeof(WAVEHDR));

        if(mmResult)
        {
            m_PlayLog.WriteString(TEXT(" Error while preparing header"));
            return ;//ERROR_SUCCESS;
        }

        //将输出数据发送给输出设备
        mmResult = ::waveOutWrite(m_hPlay, lpHdr, sizeof(WAVEHDR));

        if(mmResult)
        {
            m_PlayLog.WriteString(TEXT(" Error while writing to device"));
            return ;//ERROR_SUCCESS;                
        }
        return ;//ERROR_SUCCESS;
    }

    5、当提交给设备的数据输出结束,设备会发送一条MM_WOM_DONE消息反馈给设备,设备应该用waveOutUnprepareHeader将提交给设备输出的数据清除。

    void CPlaySound::OnEndPlaySoundData(WPARAM wParam, LPARAM lParam)
    {
        LPWAVEHDR lpHdr = (LPWAVEHDR) lParam;

        if(lpHdr)
        {
            ::waveOutUnprepareHeader(m_hPlay, lpHdr, sizeof(WAVEHDR));//音频输出结束,清空buffer
        }
        return ;//ERROR_SUCCESS;
    }

    6、结束输出前先用waveOutReset重置输出设备,重置能够使输出设备全部buffer输出结束,所以在waveOutReset后要延迟一段时间,然后调用waveOutClose关闭设备。

    void CPlaySound::OnStopPlaying(WPARAM wParam, LPARAM lParam)
    {

        MMRESULT mmReturn = 0;

        if(m_IsPlaying == FALSE)
            return;// FALSE;

        m_PlayLog.WriteString(TEXT(" Stopped  playing"));

        mmReturn = ::waveOutReset(m_hPlay);//重置输出设备,重置能够使输出设备全部buffer输出结束

        if(!mmReturn)
        {
            m_IsPlaying = FALSE;
            Sleep(500); //等待所有buffer输出完成
            mmReturn = ::waveOutClose(m_hPlay);//关闭设备
        }
    }

     from:http://xzben.com/windows-%E4%B8%8B%E9%9F%B3%E9%A2%91%E6%95%B0%E6%8D%AE%E9%87%87%E9%9B%86%E5%92%8C%E6%92%AD%E6%94%BE/

     
  • 相关阅读:
    关于添加类目
    关于协议传值
    Block传值
    UIImagePickerController(相册的使用)
    Handler(单例传值)
    UISearchController
    org.apache.commons.dbcp.SQLNestedException: Cannot load JDBC driver class 'com.microsoft.sqlserver.jdbc.SQLServerDriver '
    vs2015 打开项目自动运行 npm install
    Apktool下载与安装 windows环境
    React-Native 问题随记2: com.android.builder.testing.api.DeviceException
  • 原文地址:https://www.cnblogs.com/lidabo/p/3701959.html
Copyright © 2011-2022 走看看