本人擅长C#开发,pcm播放本来想用C#实现,但是考虑到视频的解码使用C++做的,于是用C++实现了pcm的播放。这里使用微软的directsound库。

第一步:

读取一个pcm文件

	FILE * InFile;
	InFile = fopen("testu.pcm", "rb");

此文件为单通道,8000采样率,16bit每次采样。

第二步:

定期读取pcm文件,每次读取一秒的数据:iChannel*SampleRate*2 这里大小为1*8000*2 =16000

	while (true)
	{
		int iReadSize = fread(frame_buf, 1, iChannel*SampleRate*2, InFile);
		if (iReadSize>0)
		{
			pcmPlay.PushData(frame_buf, iReadSize);
		}
		else
		{
			break;
		}
		Sleep(1000);
	}

第三步:

初始化directsound相关变量:

	m_iBufNofitySize = sample_rate*channels;
	offset = m_iBufNofitySize;
	m_Audiobuf = new char[m_iBufNofitySize * 10];
	//Init DirectSound
	if (FAILED(DirectSoundCreate8(NULL, &m_pDS, NULL)))
	{
		return false;
	}
	SetConsoleTitle(TEXT("pcmplayer"));//Console Title
	if (FAILED(m_pDS->SetCooperativeLevel(FindWindow(NULL, TEXT("pcmplayer")), DSSCL_NORMAL)))
		return FALSE;
	DSBUFFERDESC dsbd;
	memset(&dsbd, 0, sizeof(dsbd));
	dsbd.dwSize = sizeof(dsbd);
	dsbd.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2;
	dsbd.dwBufferBytes = MAX_AUDIO_BUF * m_iBufNofitySize;
	dsbd.lpwfxFormat = (WAVEFORMATEX*)malloc(sizeof(WAVEFORMATEX));
	dsbd.lpwfxFormat->wFormatTag = WAVE_FORMAT_PCM;
	/* format type */
	(dsbd.lpwfxFormat)->nChannels = channels;
	/* number of channels (i.e. mono, stereo...) */
	(dsbd.lpwfxFormat)->nSamplesPerSec = sample_rate;
	/* sample rate */
	(dsbd.lpwfxFormat)->nAvgBytesPerSec = sample_rate * (bits_per_sample / 8)*channels;
	/* for buffer estimation */
	(dsbd.lpwfxFormat)->nBlockAlign = (bits_per_sample / 8)*channels;
	/* block size of data */
	(dsbd.lpwfxFormat)->wBitsPerSample = bits_per_sample;
	/* number of bits per sample of mono data */
	(dsbd.lpwfxFormat)->cbSize = 0;

	if (FAILED(m_pDS->CreateSoundBuffer(&dsbd, &m_pDSBuffer, NULL))) 
	{
		return false;
	}
	if (FAILED(m_pDSBuffer->QueryInterface(IID_IDirectSoundBuffer8, (LPVOID*)&m_pDSBuffer8))) 
	{
		return false;
	}
	//Get IDirectSoundNotify8
	if (FAILED(m_pDSBuffer8->QueryInterface(IID_IDirectSoundNotify, (LPVOID*)&m_pDSNotify))) 
	{
		return false;
	}
	for (int i = 0; i<MAX_AUDIO_BUF; i++) 
	{
		m_pDSPosNotify[i].dwOffset = i * m_iBufNofitySize;
		m_event[i] = ::CreateEvent(NULL, false, false, NULL);
		m_pDSPosNotify[i].hEventNotify = m_event[i];
	}
	m_pDSNotify->SetNotificationPositions(MAX_AUDIO_BUF, m_pDSPosNotify);
	m_pDSNotify->Release();
	m_pDSBuffer8->SetCurrentPosition(0);
	m_pDSBuffer8->Play(0, 0, DSBPLAY_LOOPING);

 

 第五步:开启一个线程,定期去读取数据,并放入directsound缓存进行播放

	LPVOID buf = NULL;
	DWORD  buf_len = 0;
	if ((res >= WAIT_OBJECT_0) && (res <= WAIT_OBJECT_0 + 3)) 
	{
		m_pDSBuffer8->Lock(offset, m_iBufNofitySize, &buf, &buf_len, NULL, NULL, 0);
		if (m_iAudioLenth > m_iBufNofitySize)
		{
			AutoLock lock(m_bufLock);
			memcpy(buf, m_Audiobuf, m_iBufNofitySize);
			m_iAudioLenth = m_iAudioLenth - m_iBufNofitySize;
			memcpy(m_Audiobuf, m_Audiobuf + m_iBufNofitySize, m_iAudioLenth);
		}
		else
		{
			memset(buf, 0, m_iBufNofitySize);
		}
		offset += buf_len;
		offset %= (m_iBufNofitySize * MAX_AUDIO_BUF);
		//printf("this is %7d of buffer\n", offset);
		m_pDSBuffer8->Unlock(buf, buf_len, NULL, 0);
	}
	res = WaitForMultipleObjects(MAX_AUDIO_BUF, m_event, FALSE, INFINITE);
	return 1;

第六步:

播放结束后,释放相关内容。

这里,主要讲一下directsound的播放原理。

首先我们要这只播放缓存:

dsbd.dwBufferBytes = MAX_AUDIO_BUF * m_iBufNofitySize;

 但是我们如何知道这段音频播放完了呢?我们可以设置一个通知大小,即播放完多大空间,就通知一次。然后我们就把这段播放完的空间,用新的音频数据进行填充,由于directsound是在指定空间循环播放的,我们也就可以实现音频的循环播放。

音频1 音频2 音频3 音频4

directsound播放顺序为音频1---》音频2---》音频3---》音频4---》音频1 每一段播放完,就会通知一次。

另外,需要提醒播放缓存不能设置太小,会没有播放效果,不过设置过大,延迟又比较大,所以我把每一段缓存这只为1秒左右的空间。