文章目录

https://mediaarea.net/en/MediaInfo
https://sourceforge.net/projects/audacity/

正常人听力范围在: 20~20KHz. 根据那奎斯特定理,采样率应该在40KHz左右.

frame

一般一帧的时间长度在10-30ms(要包含声音的基本周期), 该时间内包含的采样点数即为帧长!

码率= 采样率(每一秒采样点(sample)个数) x 位深度 x 声道; 文件大小 = 码率 x 时长

pcm

在电话领域有两种压缩方法(算法): a-law, u-law(美,日). 都是有损压缩,将 12-13bit LPCM数据 → 压缩为→ 8bit

编解码器体现为G711,有时G711也被广义的理解为PCM!

WAV

RIFF **

struct RIFF_HEADER { //RIFF
    char szRiffID[4]; //'R','I','F','F'
    DWORD dwSize;     //文件的总大小 - 4 - 4
    char szRiffFormat[4]; //'W','A','V','E'
};
struct FMT_BLOCK { //Chunk format
    char szFmtID[4]; //'f','m','t',' '
    DWORDdwFmtSize;
    WAVE_FORMAT wavFormat;
};

struct WAVE_FORMAT { //16B
    WORD wFormatTag; //一般为 WAVE_FORMAT_PCM 即脉冲编码调制
    WORD wChannels;
    DWORD dwSamplesPerSec;
    DWORD dwAvgBytesPerSec;
    WORD wBlockAlign;
    WORD wBitsPerSample; //WAVEFORMATEX则在此项之后多加了一个附加项
};
// 可选字段,一般当wav文件由某些软件转化而成,则包含该Chunk,但是一般都不包含该块
struct FACT_BLOCK { //chunk fact
    char szFactID[4]; //'f','a','c','t'
    DWORDdwFactSize;
};
// 是真正保存wav数据的地方,以'data'作为该Chunk的标示.然后是数据的大小,紧跟后面的即wav数据.
struct DATA_BLOCK { //chunk data
    char szDataID[4]; //'d','a','t','a'
    DWORD dwDataSize;
};
// 获得录音设备的性能参数,使用如下结构体和函数

typedef struct tagWAVEINCAPSA {
    WORD  wMid; /* manufacturer ID */
    WORD  wPid; /* product ID */
    MMVERSION vDriverVersion; /* version of the driver */
    CHAR  szPname[MAXPNAMELEN]; /* product name (NULL terminated string) */
    DWORD dwFormats; /* formats supported */
    WORD  wChannels; /* number of channels supported */
    WORD  wReserved1; /* structure packing */
} WAVEINCAPS;
MMRESULT waveInGetDevCaps(
    UINT_PTR     uDeviceID, // 一般是 WAVE_MAPPER
    LPWAVEINCAPS pwic,      // WAVEINCAPS*
    UINT // sizeof(WAVEINCAPS)
);
// 用于向录音设备或放音设备 指定录音或回放 所需要的音频格式
typedef struct {
    WORD wFormatTag;// 数据格式,一般为 WAVE_FORMAT_PCM
    WORD nChannels; // 声道
    DWORD nSamplesPerSec; // 采样频率
    DWORD nAvgBytesPerSec;// 码率:采样频率 * 块对齐
    WORD nBlockAlign;    // 块对齐:     采样深度 * 声道数 /8
    WORD wBitsPerSample; // 采样深度:
    WORD cbSize; // 附加信息,一般为 0
} WAVEFORMATEX;
// 音频数据操作的基本单元
typedef struct {
    LPSTR lpData; //指向音频数据的指针,一般为一个缓冲区的地址 char*
    // the new value must be less than the prepared value.
    // buffer's data length,tmp, need to adjust to the real data length
    DWORD dwBufferLength;
    DWORD dwBytesRecorded; //已录音的字节数
    DWORD dwUser; //0,或者为封装类的this指针(做强制转换(DWORD)(void*)this)
    // 0,当录音的buffer被填充时该项被置为 WHDR_DONE,
    // Before calling waveOutWrite you should technically set the flag value to
    // WHDR_PREPARED not 0, otherwise the call may fail on some OS's.
    DWORD dwFlags;
    DWORD dwLoops; //0, 配合 dwFlags(WHDR_BEGINLOOP,WHDR_ENDLOOP) 使用,但循环次数必须在开始数据块上使用
    struct wavehdr_tag* lpNext; //NULL or 循环队列中的下一个WHDR指针
    DWORD reserved; //0
} WAVEHDR;

// message
afx_msg void OnMM_WOM_DONE(UINT parm1, LONG parm2);
BEGIN_MESSAGE_MAP(CCamViewDlg, CDialog)
    ON_THREAD_MESSAGE(MM_WOM_DONE, OnMM_WOM_DONE)
END_MESSAGE_MAP()
// Windows建立了一个特殊的线程来管理音频播放, 在此回调函数中你可以做的事情有很多限制
void CALLBACK waveOutProc(HWAVEOUT hwo, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)

// record
waveInOpen() // 打开录音设备(产生消息:MM_WIM_OPEN)
// 使WAVEHDR与录音设备关联,
// 其结构体可以被录音设备(驱动)作为录音数据的存储结构
// 里面包含了buffer==lpData
waveInPrepareHeader()
// 给该结构体中的音频数据存储的指针分配空间
// 并将WAVEHDR添加到录音设备可用的结构体序列中
waveInAddBuffer()
// 开始录音(调用此函数后,等待录音数据的到来,每当录音缓冲区被填满的时候便产生:MM_WIM_DATA)
// 当录音设备已经将音频数据录好后放到了WAVEHDR中,便产生了MM_WIM_DATA消息,(在此消息的处理函数中)
// 该消息的 wParam 表示了录音设备的句柄, lParam 表示了当前录音所关联的WAVEHDR结构
// (在消息的处理函数中)此时如果要使用WAVEHDR中的数据必须先调用waveInUnprepareHerder函数,
// 以便将WAVEHDR与录音设备取消关联,该调用也指示了该结构体从当前调用处到下一个waveInPrepareHeader之间不再用于录音
// 当对该结构体处理完成后需要再次调用waveInPrepareHeader,waveInAddBuffer以便准备WAVEHDR提供下次录音
waveInStart()
// 停止波形文件的输入并回滚当前位置(产生消息:MM_WIM_DATA)
waveInReset()
waveInUnprepareHerder()
waveInClose() // 关闭录音设备(产生消息:MM_WIM_CLOSE)
waveInGetNumDevs() // 获得录音设备的数量,必须>0才开始

// playsound
waveOutOpen() // 打开放音设备(产生消息:MM_WOM_OPEN)
// prepare的作用就是将缓冲区等参数(操作单元)与设备句柄做关联
// The lpData, dwBufferLength, and dwFlags members of the WAVEHDR structure
// must be set before calling this function (dwFlags must be zero).
waveOutPrepareHeader()
// 播放放音设备所关联的WAVEHDR中的音频数据,当WAVEHDR中的数据被回放完成后便产生:MM_WOM_DONE消息,
// 该消息的 wParam 表示了放音设备的句柄, lParam 表示了当前放音所关联的WAVEHDR结构
// Before calling waveOutWrite you should technically set the flag value to
// WHDR_PREPARED not 0, otherwise the call may fail on some OS's.
waveOutWrite()

// stopsound
// 释放放音设备对应的缓存和其他资源
// reparing a buffer that has not been prepared has no effect, and the function returns zero.
// 息的处理函数中)需要调用waveOutUnprepareHeader以便取消WAVEHDR与放音设备的关联
// 取消之后方可以free放音的数据
waveOutUnprepareHeader()
// 停止播放并回滚当前位置(产生消息:MM_WOM_DONE)
// waveOutReset不是立即返回的函数,
// 而需要等待驻留在WAVEDEV里的音频BUFFER全部标记为WOM_DONE
// 经过callback proc处理完毕后, waveOutReset才会返回
// 所以很容易造成死锁!
waveOutReset()
waveOutClose() // 关闭放音设备(产生消息:MM_WOM_CLOSE)
waveOutGetNumDevs()
HWAVEIN hWaveIn; //输入设备
WAVEFORMATEX waveform; //采集音频的格式,结构体
BYTE *pBuffer1, *pBuffer2; //采集音频时的数据缓存
WAVEHDR wHdr1, wHdr2; //采集音频时包含数据缓存的结构体
static DWORD CALLBACK MicCallback(HWAVEIN hWaveIn, UINT uMsg, DWORD dwInstance, DWORD param1,DWORD param2);

waveform.wFormatTag = WAVE_FORMAT_PCM;//声音格式为PCM
waveform.nSamplesPerSec = 16000; //采样率,16000次/秒
waveform.wBitsPerSample = 16; //采样比特,16bits/次
waveform.nChannels = 2; //采样声道数,2声道
waveform.nAvgBytesPerSec = 16000 * 4; //每秒的数据率,就是每秒能采集多少字节的数据
waveform.nBlockAlign = 4; //一个块的大小,采样bit的字节数乘以声道数
waveform.cbSize = 0; //一般为0

// 使用waveInOpen函数开启音频采集
MMRESULT mmr = waveInOpen(&hWaveIn, WAVE_MAPPER, &waveform, (DWORD)(MicCallback), DWORD(this), CALLBACK_FUNCTION);
if (mmr != MMSYSERR_NOERROR) return false;

// 建立两个数组(这里可以建立多个数组)用来缓冲音频数据
DWORD bufsize = 8 * 1024;
pBuffer1 = new BYTE[bufsize];
pBuffer2 = new BYTE[bufsize];
wHdr1.lpData = (LPSTR)pBuffer1;
wHdr1.dwBufferLength = bufsize;
wHdr1.dwBytesRecorded = 0;
wHdr1.dwUser = 0;
wHdr1.dwFlags = 0;
wHdr1.dwLoops = 1;
wHdr1.lpNext = NULL;
wHdr1.reserved = 0;

// 将建立好的wHdr1做为备用
waveInPrepareHeader(hWaveIn, &wHdr1, sizeof(WAVEHDR));
wHdr2.lpData = (LPSTR)pBuffer2;
wHdr2.dwBufferLength = bufsize;
wHdr2.dwBytesRecorded = 0;
wHdr2.dwUser = 0;
wHdr2.dwFlags = 0;
wHdr2.dwLoops = 1;
wHdr2.lpNext = NULL;
wHdr2.reserved = 0;

// 将建立好的wHdr2做为备用
waveInPrepareHeader(hWaveIn, &wHdr2, sizeof(WAVEHDR));
// 将两个wHdr添加到waveIn中去
waveInAddBuffer(hWaveIn, &wHdr1, sizeof(WAVEHDR));
waveInAddBuffer(hWaveIn, &wHdr2, sizeof(WAVEHDR));
// 开始音频采集
waveInStart(hWaveIn);

// 下面这个是callback函数,对于采集到的音频数据都在这个函数中处理
DWORD CALLBACK CDlg::MicCallback(HWAVEIN hwavein, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)
{
    CIMAADPCMDlg* pWnd = (CIMAADPCMDlg*)dwInstance ;
    int re(0);
    switch (uMsg)
    {
    case WIM_OPEN:
        TRACE("WIM_OPEN\n");
        break;
    case WIM_DATA:
        TRACE("WIM_DATA\n");
        // 这里就是对采集到的数据做处理的地方,我是做了发送处理
        // ((PWAVEHDR)dwParam1)->lpData这就是采集到的数据指针
        // ((PWAVEHDR)dwParam1)->dwBytesRecorded这就是采集到的数据长度
        re = send(pWnd->sends, ((PWAVEHDR)dwParam1)->lpData, ((PWAVEHDR)dwParam1)->dwBytesRecorded, 0);
        // 处理完了之后还要再把这个缓冲数组加回去
        // pWnd->win代表是否继续采集,因为当停止采集的时候,只有当所有的
        // 缓冲数组都腾出来之后才能close,所以需要停止采集时就不要再waveInAddBuffer了
        if (pWnd->win) waveInAddBuffer(hwavein, (PWAVEHDR)dwParam1, sizeof(WAVEHDR));
        break;
    case WIM_CLOSE:
        TRACE("WIM_CLOSE\n");
        break;
    default:
        break;
    }
    return 0;
}
// 最后是结束采集
win = false;
// 在这里可以等待一定的时间,确保所有缓冲数组全都退出来了
// 也可以在WIM_DATA消息处理中加个计数器,计算全部buffer都
// 退出来了,就可以调用下面的close了
waveInClose(hWaveIn);