使用WindowsAPI进行录音和播放

方宏富
2023-12-01

录音

1、初始化

	memset(&hWaveIn, 0, sizeof(HWAVEIN));
	memset(&waveForm, 0, sizeof(WAVEFORMATEX));

	if (getAudioDevices())   //判断能否获取设备
	{
		//设置采样频率
		setWaveFormat(&waveForm, 2, 44100, 16);

		//初始化设备缓冲区
		for (int i = 0; i < 2; ++i)
		{
			whdr[i].lpData = (CHAR*)malloc(bufLength);
			memset(whdr[i].lpData, 0, bufLength);
			whdr[i].dwBufferLength = bufLength;	//缓冲区大小
			whdr[i].dwBytesRecorded = 0;		//已填充字节数,结束时未填充的自动处理
			whdr[i].dwUser = 0;				   	//用户自定义数据
			whdr[i].dwFlags = 0;				//用不着
			whdr[i].dwLoops = 0;				//用不着
			whdr[i].lpNext = NULL;				//用不着
			whdr[i].reserved = 0;				//用不着
		}
	}

bool InputSound::getAudioDevices()
{
	//获取音频设备数量
	int count = 0;
	count = waveInGetNumDevs();
	if (count == 0)
	{
		printf("未获取到音频设备,请检查设备\n");
		return false;
	}
	printf("音频输入设备数据量:%d\n", count);
	
	//获取设备的信息
	WAVEINCAPS waveIncaps;
	MMRESULT mmResult = waveInGetDevCaps(0, &waveIncaps, sizeof(WAVEINCAPS));
	if (mmResult != MMSYSERR_NOERROR)
	{
		printf("获取设备信息异常\n");
		return false;
	}
	//printf("音频输入设备:%s\n", waveIncaps.szPname);
	std::cout << "音频输入设备:" << waveIncaps.szPname << std::endl;

	return true;
}

2、设置录音的参数

void InputSound::setWaveFormat(LPWAVEFORMATEX waveformat, WORD nCh, DWORD nSampleRate, WORD BitsPerSample)
{
	waveformat->wFormatTag = WAVE_FORMAT_PCM;
	waveformat->nChannels = nCh;	//声道数
	waveformat->nSamplesPerSec = nSampleRate;	//采样率
	waveformat->nAvgBytesPerSec = nSampleRate * nCh * BitsPerSample / 8;	//每秒数据量
	waveformat->nBlockAlign = nCh * BitsPerSample / 8;	//单帧数据量
	waveformat->wBitsPerSample = BitsPerSample;		//采样精度
	waveformat->cbSize = 0;
}

3、开始录音

void InputSound::startRecording()
{
	try
	{
		MMRESULT mmResult = waveInOpen(&hWaveIn, WAVE_MAPPER, &waveForm, (DWORD)waveInProc, (DWORD)this, CALLBACK_FUNCTION);
		if (mmResult != MMSYSERR_NOERROR)
		{
			printf("设备启动失败\n");
			return;
		}
		 
		waveInPrepareHeader(hWaveIn, &whdr[0], sizeof(WAVEHDR));	//配置数据块
		waveInPrepareHeader(hWaveIn, &whdr[1], sizeof(WAVEHDR));


		//部署缓存
		waveInAddBuffer(hWaveIn, &whdr[0], sizeof(WAVEHDR));		//压入缓冲区
		waveInAddBuffer(hWaveIn, &whdr[1], sizeof(WAVEHDR));

		//发送录音开始消息
		waveInStart(hWaveIn);
		isStop = FALSE;

		printf("开始录音\n");
	}
	catch (...)
	{
		printf("启动失败\n");
	}
	return;
}

4、回调处理
需要注意,将数据写入文件后,需要经缓冲区清空,重新加到录音设备上。

DWORD CALLBACK InputSound::waveInProc(HWAVEIN hwavein, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)
{
	InputSound* inputSound = (InputSound*)dwInstance;
	char buf[bufLength] = {0};
	switch (uMsg)
	{
	case WIM_OPEN:		//设备成功打开
		printf("设备启动成功\n");
		break;

	case WIM_DATA:		//缓冲区数据填充完毕
		memcpy(&buf, ((LPWAVEHDR)dwParam1)->lpData, bufLength);	//拷贝数据内容
		
		FILE *pf;
		fopen_s(&pf, "./RecordSounds.pcm", "ab+");

		if (strlen(buf))
		{
			//写入文件
			fwrite(buf, 1, ((LPWAVEHDR)dwParam1)->dwBufferLength, pf);
		}


		if (inputSound->isStop != TRUE)
		{
			//清空数据内容
			memset(((LPWAVEHDR)dwParam1)->lpData, 0, bufLength);
			((LPWAVEHDR)dwParam1)->dwBytesRecorded = 0;

			waveInAddBuffer(hwavein, (PWAVEHDR)dwParam1, sizeof(WAVEHDR));				//重新添加缓存
		}

		fclose(pf);
		break;

	case WIM_CLOSE:		//操作完成
		printf("设备关闭成功\n");
		break;

	default:
		break;
	}
	return 0;
}

5、停止录音

void InputSound::stopRocording()
{
	try
	{
		isStop = TRUE;
		//设备停止
		waveInStop(hWaveIn);
		waveInReset(hWaveIn);

		//释放缓冲区
		waveInUnprepareHeader(hWaveIn, &whdr[0], sizeof(WAVEHDR));
		waveInUnprepareHeader(hWaveIn, &whdr[1], sizeof(WAVEHDR));
		
		printf("停止录音\n");
		//关闭设备
		waveInClose(hWaveIn);

	}
	catch (...)
	{
		printf("停止失败\n");
	}
	return;
	
}

播放

1、初始化
这里初始化的时候,将dwUser 这个变量作为了一个数据是否播放完的标记。
getAudioDevices()、setWaveFormat()两个函数与录音中处理类似。

if (getAudioDevices())
	{
		//设置播放频率
		setWaveFormat(&waveForm, 2, 44100, 16);

		for (int i = 0; i < 2; ++i)
		{
			whdr[i].lpData = (CHAR*)malloc(bufLength);
			memset(whdr[i].lpData, 0, bufLength);
			whdr[i].dwBufferLength = bufLength;	//缓冲区大小
			whdr[i].dwBytesRecorded = 0;		//已填充字节数,结束时未填充的自动处理
			whdr[i].dwUser = 0;				//用户自定义数据,0表示无内容,1表示有内容
			whdr[i].dwFlags = 0;				//用不着
			whdr[i].dwLoops = 0;				//用不着
			whdr[i].lpNext = NULL;				//用不着
			whdr[i].reserved = 0;				//用不着
		}
	}

2、开始播放

void OutputSound::startPlaying()
{
	try
	{
		MMRESULT mmResult = waveOutOpen(&hWaveOut, WAVE_MAPPER, &waveForm, (DWORD)waveOutProc, (DWORD)this, CALLBACK_FUNCTION);
		if (mmResult != MMSYSERR_NOERROR)
		{
			printf("设备启动失败\n");
			return;
		}

		
		FILE *pf;
		fopen_s(&pf, "./RecordSounds.pcm", "rb");


		while (true)
		{
			for (int i = 0; i < 2; ++i)
			{
				if (whdr[i].dwUser != 1)	//读取数据,发送给设备
				{
					char buf[bufLength] = { 0 };
					//whdr[i].dwBufferLength = fread(&buf, sizeof(char), bufLength, pf);
					fread(&buf, bufLength, 1, pf);
					memcpy(whdr[i].lpData, &buf, bufLength);
					whdr[i].dwUser = 1;

					waveOutPrepareHeader(hWaveOut, &whdr[i], sizeof(WAVEHDR));//准备缓冲
					waveOutWrite(hWaveOut, &whdr[i], sizeof(WAVEHDR));		//发送给设备
				}

			}

			if (feof(pf))
			{
				printf("播放结束\n");
			
			
				Sleep(1000);
				waveOutClose(hWaveOut);
				break;
			}
		}
		
		fclose(pf);
	}
	catch (...)
	{
		printf("播放失败\n");
	}
	return;
}

3、回调处理

DWORD CALLBACK OutputSound::waveOutProc(HWAVEOUT hwaveOut, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)
{
	LPWAVEHDR phdr = (LPWAVEHDR)dwParam1;
	switch (uMsg)
	{
	case WOM_OPEN:
		printf("播放设备启动\n");
		break;

	case WOM_DONE:
		//播放完了,是缓冲块可以重新读取数据
		phdr->dwUser = 0;
		break;

	case WOM_CLOSE:
		printf("播放设备关闭\n");
		break;

	default:
		break;
	}

	return 0;
}
 类似资料: