C++ 播放音频流(PCM裸流)

    直接上代码,如果有需要可以直接建一个win32控制台程序然后将代码拷过去改个文件名就可以用了(注意将声道和频率与你自己的文件对应)。当然我自己也用VS2008写了个例子上传了,如果有需要下载地址如下:点击打开链接

    这份代码是打开文件截取一段数据然后播放的,可以轻松的经过加一条线程的方式改成网络传输的形式,但经过本人测试,因为没有缓存机制会有“哒哒”的噪声,也就是说这份代码在网络实时音频上的表现并不太好。为了解决这个问题,可以加上缓存机制,本人因为一开始用的是事件响应方式,所以一直困在这个框架里,不能很好的利用缓存的机制解决上面提到的问题,后来尝试了用回调函数的方式来响应数据播放完成的消息,问题就轻松的解决了。那部分的代码会在稍候放上去。

#include "stdafx.h"
#include <stdio.h>
#include <Windows.h>
#pragma comment(lib, "winmm.lib")

char buf[1024 * 1024 * 4];

int _tmain(int argc, _TCHAR* argv[]) {
	FILE*           thbgm;//文件
	int             cnt;
	HWAVEOUT        hwo;
	WAVEHDR         wh;
	WAVEFORMATEX    wfx;
	HANDLE          wait;

	wfx.wFormatTag = WAVE_FORMAT_PCM;//设置波形声音的格式
	wfx.nChannels = 1;//设置音频文件的通道数量
	wfx.nSamplesPerSec = 8000;//设置每个声道播放和记录时的样本频率
	wfx.nAvgBytesPerSec = 16000;//设置请求的平均数据传输率,单位byte/s。这个值对于创建缓冲大小是很有用的
	wfx.nBlockAlign = 2;//以字节为单位设置块对齐
	wfx.wBitsPerSample = 16;
	wfx.cbSize = 0;//额外信息的大小
	wait = CreateEvent(NULL, 0, 0, NULL);
	waveOutOpen(&hwo, WAVE_MAPPER, &wfx, (DWORD_PTR)wait, 0L, CALLBACK_EVENT);//打开一个给定的波形音频输出装置来进行回放
	fopen_s(&thbgm, "paomo.pcm", "rb");
	cnt = fread(buf, sizeof(char), 1024 * 1024 * 4, thbgm);//读取文件4M的数据到内存来进行播放,通过这个部分的修改,增加线程可变成网络音频数据的实时传输。当然如果希望播放完整的音频文件,也是要在这里稍微改一改
	int dolenght = 0;
	int playsize = 1024;
	while (cnt) {//这一部分需要特别注意的是在循环回来之后不能花太长的时间去做读取数据之类的工作,不然在每个循环的间隙会有“哒哒”的噪音
		wh.lpData = buf + dolenght;
		wh.dwBufferLength = playsize;
		wh.dwFlags = 0L;
		wh.dwLoops = 1L;
		waveOutPrepareHeader(hwo, &wh, sizeof(WAVEHDR));//准备一个波形数据块用于播放
		waveOutWrite(hwo, &wh, sizeof(WAVEHDR));//在音频媒体中播放第二个函数wh指定的数据
		WaitForSingleObject(wait, INFINITE);//用来检测hHandle事件的信号状态,在某一线程中调用该函数时,线程暂时挂起,如果在挂起的INFINITE毫秒内,线程所等待的对象变为有信号状态,则该函数立即返回
		dolenght = dolenght + playsize;
		cnt = cnt - playsize;
	}
	waveOutClose(hwo);
	fclose(thbgm);
	return 0;
}

    离写上面部分已经过了快一年,现在回看之前写的代码感觉略为坑爹,或许是进步了吧。之前说要把双缓存的代码放出来,哪知道后来忙别的项目去了,这部分就丢到一边,去老项目中提取代码感觉好烦一直没弄。在这一年中不少人发私信问我关于这部分代码如何写的事,没想到现在做音频的人还真不少呢。Ok,既然挖了坑就要填,今天乘着周末写了一个双缓存的Demo工程,代码如下:

#include <stdio.h>
#include <Windows.h>

#pragma comment(lib, "winmm.lib")

#define DATASIZE 1024*512 //分次截取数据大小
FILE*			pcmfile;  //音频文件
HWAVEOUT        hwo;

void CALLBACK WaveCallback(HWAVEOUT hWave, UINT uMsg, DWORD dwInstance, DWORD dw1, DWORD dw2)//回调函数
{
	switch (uMsg)
	{
		case WOM_DONE://上次缓存播放完成,触发该事件
		{
			LPWAVEHDR pWaveHeader = (LPWAVEHDR)dw1;
			pWaveHeader->dwBufferLength = fread(pWaveHeader->lpData, 1, DATASIZE, pcmfile);;
			waveOutPrepareHeader(hwo, pWaveHeader, sizeof(WAVEHDR));
			waveOutWrite(hwo, pWaveHeader, sizeof(WAVEHDR));
			break;
		}
	}
}

void main() 
{
	int             cnt;
	WAVEHDR         wh1;
	WAVEHDR         wh2;
	WAVEFORMATEX    wfx;

	fopen_s(&pcmfile, "paomo.pcm", "rb");//打开文件

	wfx.wFormatTag = WAVE_FORMAT_PCM;//设置波形声音的格式
	wfx.nChannels = 1;//设置音频文件的通道数量
	wfx.nSamplesPerSec = 8000;//设置每个声道播放和记录时的样本频率
	wfx.nAvgBytesPerSec = 16000;//设置请求的平均数据传输率,单位byte/s。这个值对于创建缓冲大小是很有用的
	wfx.nBlockAlign = 2;//以字节为单位设置块对齐
	wfx.wBitsPerSample = 16;
	wfx.cbSize = 0;//额外信息的大小

	waveOutOpen(&hwo, WAVE_MAPPER, &wfx, (DWORD)WaveCallback, 0L, CALLBACK_FUNCTION);//打开一个给定的波形音频输出装置来进行声音播放,方式为回调函数方式。如果是对话框程序,可以将第五个参数改为(DWORD)this,操作跟本Demo程序相似

	wh1.dwLoops = 0L;//播放区一
	wh1.lpData = new char[DATASIZE];
	wh1.dwBufferLength = DATASIZE; 
	fread(wh1.lpData, 1, DATASIZE, pcmfile);
	wh1.dwFlags = 0L;
	waveOutPrepareHeader(hwo, &wh1, sizeof(WAVEHDR));//准备一个波形数据块用于播放
	waveOutWrite(hwo, &wh1, sizeof(WAVEHDR));//在音频媒体中播放第二个参数指定的数据,也相当于开启一个播放区的意思

	wh2.dwLoops = 0L;//播放区二,基本同上
	wh2.lpData = new char[DATASIZE];
	wh2.dwBufferLength = DATASIZE;
	fread(wh2.lpData, 1, DATASIZE, pcmfile);
	wh2.dwFlags = 0L;
	waveOutPrepareHeader(hwo, &wh2, sizeof(WAVEHDR));
	waveOutWrite(hwo, &wh2, sizeof(WAVEHDR));

	while (wh1.dwBufferLength != 0 || wh2.dwBufferLength != 0)//如果文件还在没播放完则等待500ms
	{
		Sleep(500);
	}
	waveOutUnprepareHeader(hwo, &wh1, sizeof(WAVEHDR));//清理数据
	waveOutUnprepareHeader(hwo, &wh2, sizeof(WAVEHDR));

	delete []wh1.lpData;
	delete []wh2.lpData;
	fclose(pcmfile);//关闭文件
	return;
}

    同上面一样,如果想要这个工程的可以到这个链接去下载。不过提醒下,本人已然抛弃了VS2008,直接用VS2013,如果还在用老平台的话要用还是要折腾一会的。

  • 6
    点赞
  • 54
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
将实时接收的PCM二进制数据转换为WAV格式需要进行以下步骤: 1. 创建WAV文件头。WAV文件头包括文件标识、文件大小、音频格式、声道数、采样率、位深度等信息。 2. 将PCM数据写入WAV文件中。PCM数据可以直接写入WAV文件中,也可以压缩成其他格式再写入WAV文件中。 以下是一个简单的C++代码示例,将PCM数据转换为WAV格式: ```c++ #include <iostream> #include <fstream> #include <cstring> using namespace std; // WAV文件头结构体 struct WAVHeader { char riff[4]; // "RIFF" int32_t size; // 文件总大小 char wave[4]; // "WAVE" char fmt[4]; // "fmt " int32_t fmt_size; // fmt块大小 int16_t format; // 音频格式 int16_t channels; // 声道数 int32_t sample_rate; // 采样率 int32_t byte_rate; // 每秒数据量 int16_t block_align; // 数据块对齐 int16_t bits_per_sample; // 位深度 char data[4]; // "data" int32_t data_size; // 数据大小 }; int main() { int16_t pcm_data; // PCM数据 int32_t sample_rate = 44100; // 采样率 int16_t bits_per_sample = 16; // 位深度 int16_t channels = 1; // 声道数 ofstream wav_file("output.wav", ios::out | ios::binary); // 创建WAV文件 if (!wav_file) { cerr << "Failed to create WAV file." << endl; return 1; } WAVHeader wav_header; strncpy(wav_header.riff, "RIFF", 4); strncpy(wav_header.wave, "WAVE", 4); strncpy(wav_header.fmt, "fmt ", 4); wav_header.fmt_size = 16; wav_header.format = 1; wav_header.channels = channels; wav_header.sample_rate = sample_rate; wav_header.bits_per_sample = bits_per_sample; wav_header.byte_rate = sample_rate * channels * bits_per_sample / 8; wav_header.block_align = channels * bits_per_sample / 8; strncpy(wav_header.data, "data", 4); wav_header.data_size = 0; wav_file.write((char *)&wav_header, sizeof(wav_header)); // 写入WAV文件头 while (true) { // 循环读取PCM数据并写入WAV文件 // 从输入中读取PCM数据 cin.read((char *)&pcm_data, sizeof(pcm_data)); if (cin.fail()) break; // 将PCM数据写入WAV文件 wav_file.write((char *)&pcm_data, sizeof(pcm_data)); wav_header.data_size += sizeof(pcm_data); } wav_header.size = 4 + (8 + wav_header.fmt_size) + (8 + wav_header.data_size); wav_file.seekp(0); wav_file.write((char *)&wav_header, sizeof(wav_header)); // 更新WAV文件头 wav_file.close(); // 关闭WAV文件 return 0; } ``` 这个示例代码假设PCM数据是从标准输入中读取的,并将WAV文件写入到名为“output.wav”的文件中。你可以根据需要修改代码来适应你的实际应用场景。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值