本人擅长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秒左右的空间。
为了便于大家学习和交流,采用vs2017开发,地址如下: