Skip to content

Instantly share code, notes, and snippets.

@StrikerX3
Created March 16, 2018 20:55
Show Gist options
  • Save StrikerX3/b6c13e678f71517612b1a42afa34526a to your computer and use it in GitHub Desktop.
Save StrikerX3/b6c13e678f71517612b1a42afa34526a to your computer and use it in GitHub Desktop.
DirectSound buffer example
#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;
}
}
#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);
};
#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;
}
#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";
}
};
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