使用WaveIn录制音频并且使用WaveOut播放音频

在Windows下开发音频的方式有多种,但是最简单,也是最灵活的就是Wave系列API,今天我们一起用WaveIn和WaveOut实现一个音频录制和音频播放器,具体界面如下:

录制步骤如下:

void CcbdDlg::OnBnClickedButtonStartRec()
{
	// TODO:  在此添加控件通知处理程序代码
	m_iHour = 0;
	m_iMinute = 0;
	m_iSec = 0;

	m_pDataBuff1 = NULL;
	m_pDataBuff2 = NULL;
	m_hWaveDev = NULL;

	m_dwFileSize = 0;

	CStringA strPathA;

	strPathA = m_strPath;

	m_fSave = fopen(strPathA.GetBuffer(strPathA.GetLength()), "ab+");

	if (!m_fSave)
	{
		AfxMessageBox(_T("文件打开失败!"));
		return;
	}

	WAVEFORMATEX waveCtx;
	waveCtx.nSamplesPerSec = 44100; /* sample rate */
	waveCtx.wBitsPerSample = 16; /* sample size */
	waveCtx.nChannels = 2; /* channels*/
	waveCtx.cbSize = 0; /* size of _extra_ info */
	waveCtx.wFormatTag = WAVE_FORMAT_PCM;
	waveCtx.nBlockAlign = (waveCtx.wBitsPerSample * waveCtx.nChannels) >> 3;
	waveCtx.nAvgBytesPerSec = waveCtx.nBlockAlign * waveCtx.nSamplesPerSec;

	MMRESULT iRet = waveInOpen(&m_hWaveDev, m_comboDevList.GetCurSel()/*WAVE_MAPPER*/, &waveCtx, (DWORD)(UINT)this->m_hWnd, NULL, CALLBACK_WINDOW);

	if (MMSYSERR_NOERROR == iRet)
	{
		TRACE("yes,waveInOpen OK!\n");
	}
	else
	{
		AfxMessageBox(_T("设备打开失败!"));
		return;
	}

	//准备第一个缓冲

	m_pDataBuff1 = new BYTE[waveCtx.nAvgBytesPerSec];
	memset(m_pDataBuff1, 0, waveCtx.nAvgBytesPerSec);
	m_waveHdr1.lpData = (LPSTR)m_pDataBuff1;
	m_waveHdr1.dwBufferLength = waveCtx.nAvgBytesPerSec;
	m_waveHdr1.dwBytesRecorded = 0;
	m_waveHdr1.dwFlags = 0;
	iRet = waveInPrepareHeader(m_hWaveDev, &m_waveHdr1, sizeof(WAVEHDR));
	if (MMSYSERR_NOERROR == iRet)
	{
		TRACE("yes,waveInPrepareHeader OK!\n");
	}
	else
	{
		if (m_pDataBuff1)
		{
			delete [] m_pDataBuff1;
			m_pDataBuff1 = NULL;
		}
		AfxMessageBox(_T("内存分配失败!"));
		return;
	}
	iRet = waveInAddBuffer(m_hWaveDev, &m_waveHdr1, sizeof(WAVEHDR));
	if (MMSYSERR_NOERROR == iRet)
	{
		TRACE("yes,waveInAddBuffer OK!\n");
	}
	else
	{
		if (m_pDataBuff1)
		{
			delete [] m_pDataBuff1;
			m_pDataBuff1 = NULL;
		}

		AfxMessageBox(_T("录制准备失败!"));
		return;
	}


	//准备第二个缓冲
	m_pDataBuff2 = new BYTE[waveCtx.nAvgBytesPerSec];
	memset(m_pDataBuff2, 0, waveCtx.nAvgBytesPerSec);
	m_waveHdr2.lpData = (LPSTR)m_pDataBuff2;
	m_waveHdr2.dwBufferLength = waveCtx.nAvgBytesPerSec;
	m_waveHdr2.dwBytesRecorded = 0;
	m_waveHdr2.dwFlags = 0;
	iRet = waveInPrepareHeader(m_hWaveDev, &m_waveHdr2, sizeof(WAVEHDR));
	if (MMSYSERR_NOERROR == iRet)
	{
		TRACE("yes,waveInPrepareHeader OK!\n");
	}
	else
	{
		if (m_pDataBuff1)
		{
			delete[] m_pDataBuff1;
			m_pDataBuff1 = NULL;
		}
		if (m_pDataBuff2)
		{
			delete [] m_pDataBuff2;
			m_pDataBuff2 = NULL;
		}

		AfxMessageBox(_T("准备录制声音失败!"));
		return;
	}
	iRet = waveInAddBuffer(m_hWaveDev, &m_waveHdr2, sizeof(WAVEHDR));
	if (MMSYSERR_NOERROR == iRet)
	{
		TRACE("yes,waveInAddBuffer OK!\n");
	}
	else
	{
		if (m_pDataBuff1)
		{
			delete[] m_pDataBuff1;
			m_pDataBuff1 = NULL;
		}

		if (m_pDataBuff2)
		{
			delete [] m_pDataBuff2;
			m_pDataBuff2 = NULL;
		}

		AfxMessageBox(_T("缓冲添加失败!"));
		return;
	}

	iRet = waveInStart(m_hWaveDev);
	if (MMSYSERR_NOERROR == iRet)
	{
		TRACE("yes,waveInStart OK!\n");
	}
	else
	{
		if (m_pDataBuff1)
		{
			delete [] m_pDataBuff1;
			m_pDataBuff1 = NULL;
		}
		if (m_pDataBuff2)
		{
			delete [] m_pDataBuff2;
			m_pDataBuff2 = NULL;
		}
		AfxMessageBox(_T("录制启动失败!"));
		return;
	}

	GetDlgItem(IDC_BUTTON_START_REC)->EnableWindow(FALSE);
	GetDlgItem(IDC_STATIC_REC_TIMER)->ShowWindow(SW_SHOW);
	GetDlgItem(IDC_STATIC_REC_TIMER)->SetWindowText(_T("00:00:00"));
	SetTimer(MM_AUDIO_RECORD_TIMER, 1000, NULL);
	SetTimer(SCREEN_RECORD_TIMER, 33, NULL);
}

上面的代码我们已经准备好了录制,当麦克风有音频信息传入的时候,我们会收到MM_WIM_DATA消息,在这里我们把收到的数据保存为文件,具体代码如下:

LRESULT CcbdDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
	MMRESULT iRet = 0;
	switch (message)
	{
	case MM_WIM_OPEN:
		TRACE("Open!\n");
		break;
	case MM_WIM_CLOSE:
		TRACE("Close!\n");
		break;
	case MM_WIM_DATA:

		if (m_hWaveDev == NULL)
			break;

		LPWAVEHDR waveHdr = (LPWAVEHDR)lParam;

		if (!m_fSave)
		{
			TRACE("Error!\n");
			break;
		}

		BYTE bType = 1;//音频

		DWORD dwSize = waveHdr->dwBytesRecorded; //大小

		m_dwFileSize += dwSize;

		fwrite(&bType, 1, 1, m_fSave);

		fwrite(&dwSize, 1, 4, m_fSave);

		int nTotal = 0;

		while (nTotal < waveHdr->dwBytesRecorded)
		{
			int nTmp = fwrite(waveHdr->lpData, 1, waveHdr->dwBytesRecorded, m_fSave);
			nTotal += nTmp;
		}

		iRet = waveInAddBuffer(m_hWaveDev, waveHdr, sizeof(WAVEHDR));

		if (MMSYSERR_NOERROR != iRet)
		{
			if (m_pDataBuff1)
			{
				delete m_pDataBuff1;
				m_pDataBuff1 = NULL;
			}
			if (m_pDataBuff2)
			{
				delete m_pDataBuff2;
				m_pDataBuff2 = NULL;
			}
		}	

		break;
	}

	return CDialog::WindowProc(message, wParam, lParam);
}

当用户点击结束录制的时候,我们需要做一些善后的工作,具体如下:

void CcbdDlg::OnBnClickedButtonEndRec()
{
	// TODO:  在此添加控件通知处理程序代码

	waveInUnprepareHeader(m_hWaveDev, &m_waveHdr1, sizeof(WAVEHDR));
	waveInUnprepareHeader(m_hWaveDev, &m_waveHdr2, sizeof(WAVEHDR));
	MMRESULT iRet = waveInStop(m_hWaveDev);
	if (MMSYSERR_NOERROR == iRet)
	{
		TRACE("yes,waveInStop OK!\n");
	}
	else
	{
		TRACE("no,waveInStop Wrong!\n");
	}
	iRet = waveInReset(m_hWaveDev);
	if (MMSYSERR_NOERROR == iRet)
	{
		TRACE("yes,waveInReset OK!\n");
	}
	else
	{
		TRACE("no,waveInReset Wrong!\n");
	}
	iRet = waveInClose(m_hWaveDev);
	if (MMSYSERR_NOERROR == iRet)
	{
		TRACE("yes,waveInClose OK!\n");
	}
	else
	{
		TRACE("no,waveInClose Wrong!\n");
	}
	m_hWaveDev = NULL;
	if (m_pDataBuff1)
	{
		delete m_pDataBuff1;
		m_pDataBuff1 = NULL;
	}
	if (m_pDataBuff2)
	{
		delete m_pDataBuff2;
		m_pDataBuff2 = NULL;
	}
	GetDlgItem(IDC_BUTTON_START_REC)->EnableWindow(TRUE);
	GetDlgItem(IDC_STATIC_REC_TIMER)->ShowWindow(SW_HIDE);
	KillTimer(MM_AUDIO_RECORD_TIMER);

	if (m_fSave)
	{
		fwrite(&m_dwFileSize, 1, 4, m_fSave);
	}

	if (m_fSave)
	{
		fclose(m_fSave);
		return;
	}
}

上面我们完成了录制,下面我们一起看一下播放。

播放器的具体开发步骤如下:

当用户点击播放的时候,我们首先需要读取音频文件,用音频文件的数据初始化两个缓冲区,然后提交给声卡,让其播放,具体代码如下:

void CcbdplayerDlg::OnBnClickedButtonPlay()
{
	// TODO:  在此添加控件通知处理程序代码

	m_bStartPlay = true;

	CStringA strTmpA;
	strTmpA = m_strPath;
	m_fTmp = fopen(strTmpA.GetBuffer(strTmpA.GetLength()), "rb+");
	if (!m_fTmp)
	{
		AfxMessageBox(_T("文件打开失败!"));
		return;
	}

	fseek(m_fTmp, -4, SEEK_END);

	fread(&m_dwFileSize, 1, 4, m_fTmp);

	fseek(m_fTmp, 0, SEEK_SET);

	int nTmp = m_dwFileSize;
	
	m_PlayProgress.SetRange32(0, nTmp);

	m_PlayProgress.SetPos(0);

	MMRESULT mmresult = 0;
	mmresult = waveOutOpen(&m_hWaveOut, WAVE_MAPPER, &m_WaveFormat, (DWORD)m_hWnd, (DWORD_PTR)0, CALLBACK_WINDOW);
	if (mmresult != MMSYSERR_NOERROR)
	{
		AfxMessageBox(_T("打开设备失败!"));
		return;
	}
	
	if (!m_pDataBuff1)
	{
		AfxMessageBox(_T("内存分配失败!"));
		return;
	}

	int nReadSize = 0;

	BYTE bType = 0;

	DWORD dwSize = 0;

	fread(&bType, 1, 1, m_fTmp);

	fread(&dwSize, 1, 4, m_fTmp);

	memset(m_pDataBuff1, 0, m_WaveFormat.nAvgBytesPerSec);

	nReadSize = fread(m_pDataBuff1, 1, dwSize, m_fTmp);

	m_WaveHead1.lpData = (LPSTR)m_pDataBuff1;
	m_WaveHead1.dwBufferLength = nReadSize;
	m_WaveHead1.dwBytesRecorded = 0;
	m_WaveHead1.dwUser = 0;
	m_WaveHead1.dwFlags = 0;
	m_WaveHead1.dwLoops = 1;
	m_WaveHead1.lpNext = NULL;
	m_WaveHead1.reserved = 0;

	mmresult = waveOutPrepareHeader(m_hWaveOut, &m_WaveHead1, sizeof(WAVEHDR));
	if (mmresult != MMSYSERR_NOERROR)
	{
		if (m_pDataBuff1)
			delete[] m_pDataBuff1;
		if (m_pDataBuff2)
			delete[] m_pDataBuff2;

		AfxMessageBox(_T("准备失败!"));

		return;
	}

	memset(m_pDataBuff2, 0, m_WaveFormat.nAvgBytesPerSec);

	fread(&bType, 1, 1, m_fTmp);

	fread(&dwSize, 1, 4, m_fTmp);

	nReadSize = fread(m_pDataBuff2, 1, dwSize, m_fTmp);

	if (!m_pDataBuff2)
	{
		if (m_pDataBuff1)
			delete[] m_pDataBuff1;

		AfxMessageBox(_T("内存分配失败!"));
		return;
	}

	m_WaveHead2.lpData = (LPSTR)m_pDataBuff2;
	m_WaveHead2.dwBufferLength = nReadSize;
	m_WaveHead2.dwBytesRecorded = 0;
	m_WaveHead2.dwUser = 0;
	m_WaveHead2.dwFlags = 0;
	m_WaveHead2.dwLoops = 1;
	m_WaveHead2.lpNext = NULL;
	m_WaveHead2.reserved = 0;	

	mmresult = waveOutPrepareHeader(m_hWaveOut, &m_WaveHead2, sizeof(WAVEHDR));

	if (mmresult != MMSYSERR_NOERROR)
	{
		if (m_pDataBuff1)
			delete[] m_pDataBuff1;
		if (m_pDataBuff2)
			delete[] m_pDataBuff2;

		AfxMessageBox(_T("准备失败!"));

		return;
	}
	
	waveOutWrite(m_hWaveOut, &m_WaveHead1, sizeof(WAVEHDR));

	waveOutWrite(m_hWaveOut, &m_WaveHead2, sizeof(WAVEHDR));
	
	GetDlgItem(IDC_BUTTON_PLAY)->EnableWindow(FALSE);

	GetDlgItem(IDC_BUTTON_STOP_PLAY)->EnableWindow(TRUE);
}

当声卡播放完我们提交给它的数据后,它会发送通知WOM_DONE给我们,我们需要做的是继续从文件读取音频,然后再次提交给声卡,具体代码如下:

LRESULT CcbdplayerDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
	MMRESULT iRet = 0;

	switch (message)
	{
	case WOM_OPEN:
		break;
	case WOM_DONE:
	{
		if (m_bStartPlay == false)
			break;

		LPWAVEHDR pWaveHeader = (LPWAVEHDR)lParam;

		if (pWaveHeader)
		{
			int nPos = m_PlayProgress.GetPos();

			if (nPos >= m_dwFileSize)
			{
				break;
			}

			nPos += pWaveHeader->dwBufferLength;
			m_PlayProgress.SetPos(nPos);

			waveOutUnprepareHeader(m_hWaveOut, pWaveHeader, sizeof(WAVEHDR));

			int nReadSize = 0;

			BYTE bType = 0;

			DWORD dwSize = 0;

			fread(&bType, 1, 1, m_fTmp);			

			if (feof(m_fTmp))
				break;

			if (bType != 1)
				break;

			fread(&dwSize, 1, 4, m_fTmp);

			if (feof(m_fTmp))
				break;

			memset(pWaveHeader->lpData, 0, m_WaveFormat.nAvgBytesPerSec);

			nReadSize = fread(pWaveHeader->lpData, 1, dwSize, m_fTmp);

			if (feof(m_fTmp))
				break;

			pWaveHeader->dwBufferLength = nReadSize;

			MMRESULT mmresult = waveOutPrepareHeader(m_hWaveOut, pWaveHeader, sizeof(WAVEHDR));

			if (mmresult != MMSYSERR_NOERROR)
			{
				AfxMessageBox(_T("准备失败!"));
				break;
			}

			waveOutWrite(m_hWaveOut, pWaveHeader, sizeof(WAVEHDR));
		}

	}
		break;
	}

	return CDialog::WindowProc(message, wParam, lParam);
}

当用户点击停止播放的时候,我们需要做的是释放资源,具体如下:

void CcbdplayerDlg::OnBnClickedButtonStopPlay()
{
	// TODO:  在此添加控件通知处理程序代码

	waveOutReset(m_hWaveOut);

	waveOutUnprepareHeader(m_hWaveOut, &m_WaveHead1, sizeof(WAVEHDR));

	waveOutUnprepareHeader(m_hWaveOut, &m_WaveHead2, sizeof(WAVEHDR));

	waveOutClose(m_hWaveOut);

	if (m_fTmp)
	{
		fclose(m_fTmp);
		m_fTmp = NULL;
	}

	GetDlgItem(IDC_BUTTON_PLAY)->EnableWindow(TRUE);

	GetDlgItem(IDC_BUTTON_STOP_PLAY)->EnableWindow(FALSE);

	m_bStartPlay = false;

	return;
}

需要代码的小伙伴,可以从下面的地址下载:

https://download.csdn.net/download/u011711997/10478662



阅读更多
个人分类: 音视频开发
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭