本人擅长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秒左右的空间。