Sign in to follow this  
cloud007

help for anyone with sound problems

Recommended Posts

Hello, everyone. I just thought that I'd post this for the heck of it, since I just found out a great way to play WAV and MP3 files without having sound gaps or large memory allocations. I found code for doing this in the documentation for VC++ 2003, added a few modifications, and converted it to MFC: Header file:
#pragma once

#include <afxmt.h> // synchronization classes
#include <amstream.h> // DirectSound interfaces

#pragma comment(lib, "strmiids.lib")

class WaveBuffer {
 public:
  ~WaveBuffer();
  bool init(HWAVEOUT hWo, int size); // initialization procedure
  bool write(PBYTE pData, int nBytes, int &bytesWritten); // write wave data to buffer
  void flush(); // flush buffer to device
 private:
  WAVEHDR m_hdr; // structure for writing
  HWAVEOUT m_hWo; // device handle
  int m_nBytes; // bytes written so far
};

class WaveOut {
 public:
  WaveOut(LPCWAVEFORMATEX format, int nBuffers, int bufferSize);
  ~WaveOut();
  void write(PBYTE pData, int nBytes); // write wave data to buffers
  void flush(); // flush buffers to device
  void wait();
  void reset(); // reset device
 private:
  CSemaphore *m_cSem; // resource semaphore
  int m_nBuffers; // number of buffers
  int m_currentBuffer; // current buffer
  bool m_noBuffer; // true if no buffer is available at the moment
  WaveBuffer *m_hdrs; // buffers
  HWAVEOUT m_hWo; // device handle
  bool m_playing; // true if currently playing
};

class WaveFile {
 public:
  WaveFile(LPCWSTR fileName);
  ~WaveFile();
  CWinThread *playFile(bool loop); // returns a thread object for synchronization
  void stopFile();
  HRESULT renderStreamToDevice(); // thread calls this function
 private:
  HRESULT renderFileToMmstream(LPCWSTR fileName); // initialization
  // DirectX interfaces
  IMultiMediaStream *m_pMmstream;
  IAudioData *m_pAudioData;
  IAudioStreamSample *m_pSample;
  PBYTE m_pBuffer; // audio buffer
  WaveOut *m_wo; // device class
  bool m_playing;
  bool m_shouldLoop;
};

Implementation file:
#include "stdafx.h" // standard includes
#include "wavefile.h" // class definitions

#pragma warning(disable:4311 4312) // size conversion warnings

#define WAVEFILE_DATA_SIZE 5000

void CALLBACK waveCallback(HWAVEOUT hWo, UINT msg, DWORD user, DWORD par1, DWORD par2);
UINT playProc(LPVOID vParam);

WaveBuffer::~WaveBuffer() {
  if(m_hdr.lpData) {
    m_hdr.dwFlags = 0; // reset flags
    // uninitialize and delete allocated memory
    ::waveOutUnprepareHeader(m_hWo, &m_hdr, sizeof(WAVEHDR));
    ::LocalFree((HLOCAL)m_hdr.lpData);
  }
}

bool WaveBuffer::init(HWAVEOUT hWo, int size) {
  m_hWo = hWo;
  m_nBytes = 0;
  // initialize WAVEHDR structure
  memset(&m_hdr, 0, sizeof(WAVEHDR));
  m_hdr.lpData = (LPSTR)::LocalAlloc(LMEM_FIXED, size);
  if(!m_hdr.lpData)
    return false;
  m_hdr.dwBufferLength = size;
  ::waveOutPrepareHeader(m_hWo, &m_hdr, sizeof(WAVEHDR));
  return true;
}

// returns true if data was written to device
bool WaveBuffer::write(PBYTE pData, int nBytes, int &bytesWritten) {
  bytesWritten = min((int)m_hdr.dwBufferLength - m_nBytes, nBytes);
  memcpy(m_hdr.lpData + m_nBytes, pData, bytesWritten);
  m_nBytes += bytesWritten;
  if(m_nBytes == (int)m_hdr.dwBufferLength) {
    flush(); // write to device
    return true;
  }
  return false;
}

void WaveBuffer::flush() {
  m_nBytes = 0; // reset pointer
  ::waveOutWrite(m_hWo, &m_hdr, sizeof(WAVEHDR)); // write to device
}

void CALLBACK waveCallback(HWAVEOUT hWo, UINT msg, DWORD user, DWORD par1, DWORD par2) {
  if(msg == WOM_DONE)
    ((CSemaphore *)user)->Unlock(1); // unlock one buffer
}

WaveOut::WaveOut(LPCWAVEFORMATEX format, int nBuffers, int bufferSize) {
  m_nBuffers = nBuffers;
  m_currentBuffer = 0;
  m_noBuffer = true;
  m_cSem = new CSemaphore(m_nBuffers, m_nBuffers);
  m_hdrs = new WaveBuffer[m_nBuffers];
  m_playing = false;
  ::waveOutOpen(&m_hWo, WAVE_MAPPER, format, (DWORD)waveCallback, (DWORD)m_cSem, CALLBACK_FUNCTION);
  for(int i = 0; i < m_nBuffers; ++i)
    m_hdrs[i].init(m_hWo, bufferSize);
}

WaveOut::~WaveOut() {
  ::waveOutReset(m_hWo); // purge any playing sounds
  delete[] m_hdrs;
  ::waveOutClose(m_hWo);
  delete m_cSem;
}

void WaveOut::write(PBYTE pData, int nBytes) {
  m_playing = true;
  // do until either the user stops or we run out of data
  while(m_playing && nBytes) {
    if(m_noBuffer) {
      // wait for an available buffer
      ::WaitForSingleObject(*m_cSem, INFINITE);
      m_noBuffer = false;
    }
    int nWritten;
    if(m_hdrs[m_currentBuffer].write(pData, nBytes, nWritten)) {
      m_noBuffer = true;
      m_currentBuffer = (m_currentBuffer + 1) % m_nBuffers;
      nBytes -= nWritten;
      pData += nWritten;
    }
    else
      break;
  }
}

void WaveOut::flush() {
  if(!m_noBuffer) {
    m_hdrs[m_currentBuffer].flush();
    m_noBuffer = true;
    m_currentBuffer = (m_currentBuffer + 1) % m_nBuffers;
  }
}

void WaveOut::wait() {
  flush();
  for(int i = 0; i < m_nBuffers; ++i)
    ::WaitForSingleObject(*m_hSem, INFINITE); // wait for buffers
  m_hSem->Unlock(m_nBuffers); // release buffer locks
}

void WaveOut::reset() {
  m_playing = false; // stop playing
  ::waveOutReset(m_hWo); // stop sounds
}

WaveFile::WaveFile(LPCWSTR fileName) {
  m_wo = NULL;
  m_playing = false;
  m_shouldLoop = false;
  renderFileToMmstream(fileName);
}

WaveFile::~WaveFile() {
  m_wo->reset();
  m_playing = false;
  delete m_wo;
  m_pSample->Release();
  m_pAudioData->Release();
  m_pMmstream->Release();
  ::LocalFree((HLOCAL)m_pBuffer);
}

CWinThread *WaveFile::playFile(bool loop) {
  m_shouldLoop = loop;
  m_playing = true;
  return AfxBeginThread(playProc, (void *)this);
}

void WaveFile::stopFile() {
  if(!m_playing)
    return; // don't bother with it if we aren't currently playing
  m_shouldLoop = false;
  m_playing = false;
  m_pMmstream->Seek(0); // reset file pointer
}

HRESULT WaveFile::renderStreamToDevice() {
  HRESULT hr;
  CEvent ev;
  do {
    while(m_playing) {
      hr = m_pSample->Update(0, ev, NULL, 0); // update file buffer
      if(FAILED(hr) || (hr == MS_S_ENDOFSTREAM))
        break;
      ::WaitForSingleObject(ev, INFINITE); // wait for reading to finish
      DWORD len;
      m_pAudioData->GetInfo(NULL, NULL, &len); // get data length
      m_wo->write(m_pBuffer, len); // write to device
    }
    m_pMmstream->Seek(0); // reset file pointer
  } while(m_shouldLoop);
  m_wo->reset();
  return S_OK;
}

// internal initialization
HRESULT WaveFile::renderFileToMmstream(LPCWSTR fileName) {
  // This section is pretty much taken whole-cloth from documentation.
  if(strlen(fileName) > MAX_PATH)
    return E_INVALIDARG;
  IAMMultiMediaStream *pAmstream;
  HRESULT hr = ::CoCreateInstance(CLSID_AMMultiMediaStream, NULL, CLSCTX_INPROC_SERVER, IID_IAMMultiMediaStream, (void **)&pAmstream);
  if(FAILED(hr))
    return hr;
  pAmstream->Initialize(STREAMTYPE_READ, AMMSF_NOGRAPHTHREAD, NULL);
  pAmstream->AddMediaStream(NULL, &MSPID_PrimaryAudio, 0, NULL);
  hr = pAmstream->OpenFile(fileName, AMMSF_RUN);
  delete[] wFileName;
  if(FAILED(hr)) {
    pAmstream->Release();
    return hr;
  }
  hr = pAmstream->QueryInterface(IID_IMultiMediaStream, (void **)&m_pMmstream);
  pAmstream->Release();
  if(FAILED(hr))
    return hr;
  WAVEFORMATEX wfx;
  IMediaStream *pStream = NULL;
  IAudioMediaStream *pAudioStream = NULL;
  hr = m_pMmstream->GetMediaStream(MSPID_PrimaryAudio, &pStream);
  if(FAILED(hr))
    return hr;
  pStream->QueryInterface(IID_IAudioMediaStream, (void **)&pAudioStream);
  pStream->Release();
  hr = ::CoCreateInstance(CLSID_AMAudioData, NULL, CLSCTX_INPROC_SERVER, IID_IAudioData, (void **)&m_pAudioData);
  if(FAILED(hr)) {
    pAudioStream->Release();
    return hr;
  }
  m_pBuffer = (PBYTE)::LocalAlloc(LMEM_FIXED, WAVEFILE_DATA_SIZE);
  if(!m_pBuffer) {
    pAudioStream->Release();
    return E_OUTOFMEMORY;
  }
  pAudioStream->GetFormat(&wfx);
  m_pAudioData->SetBuffer(WAVEFILE_DATA_SIZE, m_pBuffer, 0);
  m_pAudioData->SetFormat(&wfx);
  hr = pAudioStream->CreateSample(m_pAudioData, 0, &m_pSample);
  pAudioStream->Release();
  if(FAILED(hr))
    return hr;
  m_wo = new WaveOut(&wfx, 4, (wfx.nSamplesPerSec / 11025) * 1024);
  return S_OK;
}

UINT playProc(LPVOID vParam) {
  ((WaveFile *)vParam)->renderStreamToDevice();
  return 0;
}

#pragma warning(default:4311 4312) // reset warning flags

Hope this helps!

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this