Created
March 16, 2018 20:55
-
-
Save StrikerX3/b6c13e678f71517612b1a42afa34526a to your computer and use it in GitHub Desktop.
DirectSound buffer example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "stdafx.h" | |
#include "circular_buffer.h" | |
CircularBuffer::CircularBuffer(int size) | |
{ | |
init(size, false); | |
} | |
CircularBuffer::CircularBuffer(int size, bool filled) | |
{ | |
init(size, filled); | |
} | |
void CircularBuffer::init(int size, bool filled) | |
{ | |
m_bufSize = size; | |
m_readPos = m_writePos = 0; | |
m_buf = new char[size]; | |
m_writeLock = CreateEvent( | |
NULL, // default security attributes | |
TRUE, // manual-reset event | |
FALSE, // initial state is nonsignaled | |
TEXT("WriteLock") // object name | |
); | |
InitializeCriticalSection(&m_bufAccess); | |
if (filled) | |
{ | |
ResetEvent(m_writeLock); | |
m_available = size; | |
ZeroMemory(m_buf, size*sizeof(char)); | |
} | |
else | |
{ | |
SetEvent(m_writeLock); | |
m_available = 0; | |
} | |
} | |
CircularBuffer::~CircularBuffer() | |
{ | |
delete [] m_buf; | |
CloseHandle(m_writeLock); | |
DeleteCriticalSection(&m_bufAccess); | |
} | |
/// Reads up to sz bytes into out, without blocking, | |
/// returning the number of bytes actually read | |
int CircularBuffer::read(char *out, int sz) | |
{ | |
if (sz <= 0) return 0; // optimization + precondition check | |
EnterCriticalSection(&m_bufAccess); | |
int szRead = min(sz, m_available); | |
int newPos = (m_readPos + szRead) % m_bufSize; | |
// copy two segments of the buffer | |
// segment 1 = m_readPos .. m_bufSize-1 | |
// segment 2 = 0 .. newPos % m_bufSize | |
int len1 = min(szRead, m_bufSize - m_readPos); | |
int len2 = max(0, szRead - len1); | |
CopyMemory(out , m_buf + m_readPos, len1); | |
CopyMemory(out + len1, m_buf , len2); | |
m_available -= szRead; | |
m_readPos = newPos; | |
// notify writer thread that there is enough space available now | |
SetEvent(m_writeLock); | |
LeaveCriticalSection(&m_bufAccess); | |
return szRead; | |
} | |
/// Write up to sz bytes from in, without blocking, | |
/// returning the number of bytes actually written | |
int CircularBuffer::write(char *in, int sz) | |
{ | |
if (sz <= 0) return 0; // optimization + precondition check | |
EnterCriticalSection(&m_bufAccess); | |
int szWrite = min(sz, m_bufSize - m_available); | |
int newPos = (m_writePos + szWrite) % m_bufSize; | |
// copy two segments of the buffer | |
// segment 1 = m_writePos .. m_bufSize-1 | |
// segment 2 = 0 .. newPos % m_bufSize | |
int len1 = min(szWrite, m_bufSize - m_writePos); | |
int len2 = max(0, szWrite - len1); | |
CopyMemory(m_buf + m_writePos, in , len1); | |
CopyMemory(m_buf , in + len1, len2); | |
m_available += szWrite; | |
m_writePos = newPos; | |
if (m_available == m_bufSize) | |
{ | |
// buffer is full | |
ResetEvent(m_writeLock); | |
} | |
LeaveCriticalSection(&m_bufAccess); | |
return szWrite; | |
} | |
/// Writes bytes from in, blocking until all sz bytes were written | |
void CircularBuffer::writeFully(char *in, int sz) | |
{ | |
int remaining = sz; | |
while (remaining > 0) | |
{ | |
// wait until there is enough space available in the buffer | |
WaitForSingleObject(m_writeLock, INFINITE); | |
// write data | |
int written = write(in, remaining); | |
in += written; | |
remaining -= written; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
class CircularBuffer | |
{ | |
private: | |
HANDLE m_writeLock; | |
CRITICAL_SECTION m_bufAccess; | |
int m_readPos; | |
int m_writePos; | |
int m_available; | |
char* m_buf; | |
int m_bufSize; | |
void init(int size, bool filled); | |
public: | |
CircularBuffer(int size); | |
CircularBuffer(int size, bool filled); | |
~CircularBuffer(); | |
/// Read up to sz bytes into out, without blocking, | |
/// returning the number of bytes actually read | |
int read(char *out, int sz); | |
/// Write up to sz bytes from out, without blocking, | |
/// returning the number of bytes actually written | |
int write(char *in, int sz); | |
/// Write bytes from out, blocking until all sz bytes were written | |
void writeFully(char *in, int sz); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "stdafx.h" | |
#include "dsounddriver.h" | |
#include <iostream> | |
#include <string> | |
DWORD WINAPI ThreadProc(LPVOID lpParam); | |
void checkError(HRESULT hr, std::string msg) | |
{ | |
if (FAILED(hr)) | |
throw HRESULT_exception(hr, msg); | |
} | |
DirectSoundStream::DirectSoundStream(int sampleRate, int bitsPerSample, int numChannels, unsigned int bufferSize) | |
: m_sampleRate(sampleRate) | |
, m_bitsPerSample(bitsPerSample) | |
, m_numChannels(numChannels) | |
, m_running(false) | |
, m_active(true) | |
, m_underruns(0) | |
, m_lastEvt(-1) | |
{ | |
bufferSize = bufferSize / numBlocks * numBlocks; | |
m_bufferSize = bufferSize; | |
int bytesPerSample = m_bitsPerSample / 8 * numChannels; | |
m_bufferSizeBytes = m_bufferSize * bytesPerSample; | |
WAVEFORMATEX wfx; | |
DSBUFFERDESC dsbdesc; | |
// initialize DirectSound | |
checkError(DirectSoundCreate8(NULL, &m_lpds, NULL), "Failed to initialize DirectSound instance"); | |
checkError(CoInitializeEx(NULL, 0), "Failed to initialize COM"); | |
checkError(m_lpds->SetCooperativeLevel(GetConsoleWindow(), DSSCL_NORMAL), "Failed to set DS cooperative level"); | |
// set wave format | |
memset(&wfx, 0, sizeof(WAVEFORMATEX)); | |
wfx.wFormatTag = WAVE_FORMAT_PCM; | |
wfx.nChannels = m_numChannels; | |
wfx.wBitsPerSample = m_bitsPerSample; | |
wfx.nSamplesPerSec = m_sampleRate; | |
wfx.nBlockAlign = bytesPerSample; | |
wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign; | |
// set buffer description | |
memset(&dsbdesc, 0, sizeof(DSBUFFERDESC)); | |
dsbdesc.dwSize = sizeof(DSBUFFERDESC); | |
dsbdesc.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2; | |
dsbdesc.dwBufferBytes = m_bufferSizeBytes; | |
dsbdesc.lpwfxFormat = &wfx; | |
// create secondary sound buffer | |
checkError(m_lpds->CreateSoundBuffer(&dsbdesc, &m_lpdsbuffer, NULL), "Failed to create sound buffer"); | |
m_blockSize = dsbdesc.dwBufferBytes / numBlocks / bytesPerSample; | |
m_blockSizeBytes = m_blockSize * bytesPerSample; | |
// create notification handles | |
m_notifyEvents = new HANDLE[numBlocks]; | |
for (int i=0; i<numBlocks; i++) | |
{ | |
m_notifyEvents[i] = CreateEvent(NULL, TRUE, FALSE, NULL); | |
} | |
LPDIRECTSOUNDNOTIFY8 lpdsNotify; | |
m_positionNotifies = new DSBPOSITIONNOTIFY[numBlocks]; | |
checkError(m_lpdsbuffer->QueryInterface(IID_IDirectSoundNotify8, (LPVOID*)&lpdsNotify), "Query for Notification Positions failed"); | |
int pos = 0; | |
for (int i=0; i<numBlocks; i++) | |
{ | |
m_positionNotifies[i].dwOffset = pos; | |
m_positionNotifies[i].hEventNotify = m_notifyEvents[i]; | |
pos += m_blockSizeBytes; | |
} | |
checkError(lpdsNotify->SetNotificationPositions(numBlocks, m_positionNotifies), "Failed to set Notification Positions"); | |
lpdsNotify->Release(); | |
m_cbuf = new CircularBuffer(m_bufferSizeBytes); | |
m_tempBuf = new char[m_bufferSizeBytes]; | |
m_bufthread = CreateThread(NULL, 0, ThreadProc, this, 0, NULL); | |
} | |
DirectSoundStream::~DirectSoundStream() | |
{ | |
m_active = false; | |
WaitForSingleObject(m_bufthread, INFINITE); | |
stop(); | |
m_lpdsbuffer->Release(); | |
m_lpds->Release(); | |
CloseHandle(m_bufthread); | |
for (int i=0; i<numBlocks; i++) | |
{ | |
CloseHandle(m_notifyEvents[i]); | |
} | |
delete [] m_notifyEvents; | |
delete [] m_positionNotifies; | |
delete [] m_tempBuf; | |
} | |
void DirectSoundStream::start() | |
{ | |
// already started | |
if (m_running) | |
return; | |
// clear the audio buffer | |
LPVOID lpvWrite1; | |
DWORD dwLength1; | |
LPVOID lpvWrite2; | |
DWORD dwLength2; | |
HRESULT hr = m_lpdsbuffer->Lock(0, m_bufferSizeBytes, &lpvWrite1, &dwLength1, &lpvWrite2, &dwLength2, DSBLOCK_ENTIREBUFFER); | |
if (SUCCEEDED(hr)) | |
{ | |
ZeroMemory(lpvWrite1, dwLength1); | |
ZeroMemory(lpvWrite2, dwLength2); | |
} | |
checkError(m_lpdsbuffer->Unlock(lpvWrite1, dwLength1, lpvWrite2, dwLength2), "Failed to unlock buffer"); | |
// prepare and play buffer | |
checkError(m_lpdsbuffer->SetCurrentPosition(0), "Failed to set buffer position"); | |
checkError(m_lpdsbuffer->Play(0, 0, DSBPLAY_LOOPING), "Could not play buffer"); | |
m_running = true; | |
m_available = 0; | |
m_underruns = 0; | |
} | |
void DirectSoundStream::stop() | |
{ | |
if (!m_running) | |
return; | |
// stop the buffer | |
checkError(m_lpdsbuffer->Stop(), "Could not stop buffer"); | |
} | |
int DirectSoundStream::waitForBuffer() | |
{ | |
// wait for a notification | |
DWORD evt; | |
DWORD obj = WaitForMultipleObjects(numBlocks, m_notifyEvents, FALSE, INFINITE); | |
evt = obj-WAIT_OBJECT_0; | |
ResetEvent(m_notifyEvents[evt]); | |
// check for buffer underrun | |
int evtDelta = evt-m_lastEvt; | |
if (m_lastEvt != -1 && evtDelta != -numBlocks+1 && evtDelta != 1) | |
m_underruns++; | |
m_lastEvt = evt; | |
return ((evt+1)%numBlocks) * m_blockSizeBytes; | |
} | |
void DirectSoundStream::output() | |
{ | |
LPVOID lpvWrite1, lpvWrite2; | |
DWORD dwLength1, dwLength2; | |
int writePos = waitForBuffer(); | |
m_available += m_blockSizeBytes; | |
if (m_available > m_bufferSizeBytes) | |
{ | |
m_underruns++; | |
m_available %= m_bufferSizeBytes; | |
} | |
// read from the circular buffer | |
int read = m_cbuf->read(m_tempBuf, m_available); | |
HRESULT hr = m_lpdsbuffer->Lock(writePos, read, &lpvWrite1, &dwLength1, &lpvWrite2, &dwLength2, 0); | |
bool updated = SUCCEEDED(hr); | |
if (updated) | |
{ | |
CopyMemory(lpvWrite1, m_tempBuf, dwLength1); | |
CopyMemory(lpvWrite2, m_tempBuf+dwLength1, dwLength2); | |
m_available -= dwLength1 + dwLength2; | |
} | |
checkError(m_lpdsbuffer->Unlock(lpvWrite1, dwLength1, lpvWrite2, dwLength2), "Failed to unlock buffer"); | |
} | |
void DirectSoundStream::write(char *buf, unsigned int sz) | |
{ | |
m_cbuf->writeFully(buf, sz); | |
} | |
DWORD WINAPI ThreadProc(LPVOID lpParam) | |
{ | |
DirectSoundStream *stream = (DirectSoundStream *)lpParam; | |
while (stream->isActive()) | |
stream->output(); | |
return 0; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include "stdafx.h" | |
#include "circular_buffer.h" | |
#include <exception> | |
static const int numBlocks = 4; | |
class DirectSoundStream | |
{ | |
private: | |
unsigned int m_bufferSize; | |
unsigned int m_bufferSizeBytes; | |
int m_sampleRate; | |
int m_bitsPerSample; | |
int m_numChannels; | |
bool m_running; | |
bool m_active; | |
CircularBuffer *m_cbuf; | |
char *m_tempBuf; | |
int m_available; | |
unsigned int m_underruns; | |
LPDIRECTSOUND8 m_lpds; | |
LPDIRECTSOUNDBUFFER m_lpdsbuffer; | |
unsigned int m_blockSize; | |
unsigned int m_blockSizeBytes; | |
HANDLE *m_notifyEvents; | |
DSBPOSITIONNOTIFY *m_positionNotifies; | |
HANDLE m_bufthread; | |
DWORD m_lastEvt; | |
int waitForBuffer(); | |
public: | |
DirectSoundStream(int sampleRate, int bitsPerSample, int numChannels, unsigned int bufferSize); | |
~DirectSoundStream(); | |
void start(); | |
void stop(); | |
void output(); | |
void write(char *buf, unsigned int sz); | |
inline int getUnderruns() { return m_underruns; } | |
inline bool isActive() { return m_active; } | |
}; | |
struct HRESULT_exception : public std::exception | |
{ | |
HRESULT hr; | |
std::string msg; | |
HRESULT_exception(HRESULT _hr, std::string &_msg) | |
: hr(_hr) | |
, msg(_msg) | |
{ | |
} | |
const char* what() const throw() | |
{ | |
return "HRESULT call failed"; | |
} | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
void usage_example() | |
{ | |
// create and start an audio stream | |
DirectSoundStream audio(48000, 16, 2, 2048); | |
audio.start(); | |
char *buffer = ...; | |
unsigned int buffer_size = ...; | |
// buffer contains encoded audio data | |
// buffer_size is the number of bytes to write | |
// write buffer to the audio stream | |
audio.write(buffer, buffer_size); | |
// audio stream is stopped and released on destruction, | |
// so keep a pointer around if you don't want it to stop abruptly | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment