2014년 11월 21일 금요일

waveIn/waveOut 함수 안전하게 사용하는 법

윈도우의 waveIn/waveOut 계열 함수는 콜백을 등록해서 호출받는 방식으로 동작하는데 콜백함수는 wave 드라이버의 쓰레드에서 호출하기 때문에 콜백함수에서 윈도우 시스템 API를 호출하면 죽거나 데드락이 걸리는 문제가 발생한다. 이것을 해결하기 위해 큐를 사용해 외부 쓰레드에서 waveIn/waveOut 결과를 처리한다.

< WaveBuffer.h >
 #ifndef __WAVE_BUFFER_H__  
 #define __WAVE_BUFFER_H__  
   
 #include <windows.h>  
 #include <MMSystem.h>  
   
 #include "BufferQueue.h"  
   
 class WaveBuffer {  
 public:  
      WAVEHDR*     pWaveHdr;  
      int               length;  
      bool          bDeleteData;  
   
      WaveBuffer(WAVEHDR *hdr, int len, bool deleteData = true) {  
           pWaveHdr = hdr;  
           length = len;  
           bDeleteData = deleteData;  
      }  
   
      virtual ~WaveBuffer() {  
           if (pWaveHdr && bDeleteData) {  
                delete[] pWaveHdr->lpData;  
                delete pWaveHdr;  
           }  
      }  
 };  
   
 class WaveBufferQueue {  
 public:  
      WaveBufferQueue(bool deleteData = true);  
      virtual ~WaveBufferQueue();  
   
      int push_back(WAVEHDR *pWaveHdr);  
      WaveBuffer* pop_front();  
   
      void clear() { m_pWaveQue->clear(); }  
      int count() { return m_pWaveQue->count(); }  
   
 private:  
      BufferQueue<WaveBuffer>     *m_pWaveQue;  
      int          m_nCount;  
      bool     m_bDeleteData;  
 };  
   
   
 #endif  
   
BufferQueue 템플릿 클래스는 C++ double linked-list Template Queue 포스트 참조

< WaveBuffer.cpp >
 #include "WaveBuffer.h"  
   
   
 WaveBufferQueue::WaveBufferQueue(bool deleteData)  
 {  
      m_pWaveQue = new BufferQueue<WaveBuffer>(0);     // 무제한 버퍼  
      m_bDeleteData = deleteData;  
 }  
   
 WaveBufferQueue::~WaveBufferQueue()  
 {  
      if (m_pWaveQue) {  
           delete m_pWaveQue;  
           m_pWaveQue = NULL;  
      }  
 }  
   
 int WaveBufferQueue::push_back(WAVEHDR *pWaveHdr)  
 {  
      WaveBuffer *wave = new WaveBuffer(pWaveHdr, pWaveHdr->dwBufferLength, m_bDeleteData);  
      return m_pWaveQue->push_back(wave);  
 }  
   
 WaveBuffer* WaveBufferQueue::pop_front()  
 {  
      return m_pWaveQue->pop_front();  
 }  
   

< WaveIn.h >
 #ifndef __WAVE_IN_H__  
 #define __WAVE_IN_H__  
   
 #include <windows.h>  
 #include <MMSystem.h>  
   
 #include "WaveBuffer.h"  
   
 class WaveIn  
 {  
 public:  
      WaveIn();  
      virtual ~WaveIn();  
   
      /* WaveIn API */  
      int open(WAVEFORMATEX waveInFormat, int buff_count, double duration);  
      void close();  
      void setWaveInFunc(void (*func)(WAVEHDR *pHdr, void *pData), void *pData)   
      {   
           waveInHandlerFunc = func;   
           m_pWaveInHandlerData = pData;   
      }  
   
 public:  
      void processWaveInDone(WAVEHDR *pHdr);  
      void processWaveInClose();  
      void running();       
   
 protected:  
      HWAVEIN          m_hWaveIn;  
      HANDLE          m_hWaveInThread;  
      BOOL          m_bWaveInRun;  
      WAVEHDR*     m_pWaveInHdr;  
      int               m_nWaveInHdrCount;  
   
      int                         m_nWaveInHdrPrepareCount;  
      WaveBufferQueue*     m_pWaveBufferQue;  
   
      WAVEFORMATEX     m_waveInFormatEx;  
   
      void (*waveInHandlerFunc)(WAVEHDR *pHdr, void *pData);  
      void *m_pWaveInHandlerData;  
   
      void waveInPrintError(MMRESULT result, LPCTSTR str);  
 };  
   
 #endif  
   

<WaveIn.cpp >
 #include "WaveIn.h"  
 #include "GlobalEnv.h"  
 #include <assert.h>  
 #include <process.h>  
 #include <stdio.h>  
   
 #pragma comment(lib, "winmm.lib")  
   
 WaveIn::WaveIn()  
 {  
      m_hWaveIn = NULL;  
      m_hWaveInThread = NULL;  
      m_bWaveInRun = FALSE;  
   
      memset(&m_waveInFormatEx, 0, sizeof(m_waveInFormatEx));  
      m_pWaveInHdr = NULL;  
      m_nWaveInHdrCount = 0;  
   
      m_pWaveBufferQue = NULL;  
   
      waveInHandlerFunc = NULL;  
      m_pWaveInHandlerData = NULL;  
 }  
   
 WaveIn::~WaveIn()  
 {  
      close();  
 }  
   
 unsigned __stdcall Thread_WaveIn(LPVOID lpParam)  
 {  
      WaveIn *pWaveIn = (WaveIn *)lpParam;  
      pWaveIn->running();  
      return 0;  
 }  
   
 void WaveIn::running()  
 {  
      MMRESULT mRes;  
      WAVEHDR *pHdr;  
      WaveBuffer *pWave;  
   
      while (1)  
      {  
           pWave = m_pWaveBufferQue->pop_front();  
           if (pWave == NULL)  
           {  
                Sleep(1);  
                continue;  
           }  
   
           pHdr = pWave->pWaveHdr;  
   
           mRes = waveInUnprepareHeader(m_hWaveIn, pHdr, sizeof(WAVEHDR));  
           if (mRes != MMSYSERR_NOERROR) {  
                waveInPrintError(mRes, "waveInUnprepareHeader");  
                assert(FALSE);  
                continue;  
           }  
           m_nWaveInHdrPrepareCount--;  
   
           if (waveInHandlerFunc)  
                waveInHandlerFunc(pHdr, m_pWaveInHandlerData);  
   
           delete pWave;  
   
           if (m_bWaveInRun)  
           {  
                mRes = waveInPrepareHeader(m_hWaveIn, pHdr, sizeof(WAVEHDR));  
                if (mRes != MMSYSERR_NOERROR) {  
                     waveInPrintError(mRes, "waveInPrepareHeader");  
                     assert(FALSE);  
                     continue;  
                }  
   
                mRes = waveInAddBuffer(m_hWaveIn, pHdr, sizeof(WAVEHDR));  
                if (mRes != MMSYSERR_NOERROR) {  
                     waveInPrintError(mRes, "waveInAddBuffer");  
                     assert(FALSE);  
                     continue;  
                }  
                m_nWaveInHdrPrepareCount++;  
           }  
           else  
           {  
                //DXPRINTF("[%s] WaveIn Hdr Count:%d\n", __FUNCTION__, m_nWaveInHdrPrepareCount);  
                if (m_pWaveBufferQue->count() == 0 && m_nWaveInHdrPrepareCount == 0) {  
                     break;  
                }  
           }  
      }  
 }  
   
 void CALLBACK waveInProc(HWAVEIN hwi, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)  
 {  
      WaveIn *pWaveIn = (WaveIn *)dwInstance;  
   
      switch(uMsg)  
      {  
      case WIM_OPEN:  
           DXPRINTF("WIM_OPEN\n");  
           break;  
      case WIM_CLOSE:  
           DXPRINTF("WIM_CLOSE\n");  
           pWaveIn->processWaveInClose();  
           break;  
      case WIM_DATA:   
           pWaveIn->processWaveInDone((WAVEHDR *)dwParam1);  
           break;  
      default:  
           break;  
      }  
 }  
   
 void WaveIn::processWaveInDone(WAVEHDR *pHdr)  
 {  
      if (m_pWaveBufferQue)  
           m_pWaveBufferQue->push_back(pHdr);  
 }  
   
 void WaveIn::processWaveInClose()  
 {  
 }  
   
 int WaveIn::open(WAVEFORMATEX waveInFormat, int buff_count, double duration)  
 {  
      MMRESULT mRes;  
   
      if (m_bWaveInRun)  
           return -1;  
   
      memset(&m_waveInFormatEx, 0, sizeof(m_waveInFormatEx));  
      m_waveInFormatEx.wFormatTag = WAVE_FORMAT_PCM;  
      m_waveInFormatEx.nChannels = waveInFormat.nChannels;  
      m_waveInFormatEx.nSamplesPerSec = waveInFormat.nSamplesPerSec;  
      m_waveInFormatEx.wBitsPerSample = waveInFormat.wBitsPerSample;  
      m_waveInFormatEx.nBlockAlign = m_waveInFormatEx.nChannels*m_waveInFormatEx.wBitsPerSample/8;  
      m_waveInFormatEx.nAvgBytesPerSec = m_waveInFormatEx.nSamplesPerSec*m_waveInFormatEx.nBlockAlign;  
      m_waveInFormatEx.cbSize = 0;  
   
      mRes = waveInOpen(&m_hWaveIn, WAVE_MAPPER, &m_waveInFormatEx,   
           (DWORD_PTR)waveInProc, (DWORD_PTR)this, CALLBACK_FUNCTION);  
   
      if (mRes != MMSYSERR_NOERROR) {  
           waveInPrintError(mRes, "waveInOpen");  
           return -2;  
      }  
   
      /* prepare wavein header */  
      int buff_size = (int)(m_waveInFormatEx.nAvgBytesPerSec*duration);  
      buff_size *= m_waveInFormatEx.nChannels;  
   
      m_nWaveInHdrCount = buff_count;  
      m_pWaveInHdr = new WAVEHDR[m_nWaveInHdrCount];  
   
      for (int i=0; i<m_nWaveInHdrCount; i++)  
      {  
           memset(&m_pWaveInHdr[i], 0, sizeof(WAVEHDR));  
           m_pWaveInHdr[i].lpData = new char[buff_size];  
           m_pWaveInHdr[i].dwBufferLength = buff_size;  
           m_pWaveInHdr[i].dwUser = i;  
   
           mRes = waveInPrepareHeader(m_hWaveIn, &m_pWaveInHdr[i], sizeof(WAVEHDR));  
           if (mRes != MMSYSERR_NOERROR) {  
                waveInPrintError(mRes, "waveInPrepareHeader");  
                assert(FALSE);  
                continue;  
           }  
   
           mRes = waveInAddBuffer(m_hWaveIn, &m_pWaveInHdr[i], sizeof(WAVEHDR));  
           if (mRes != MMSYSERR_NOERROR) {  
                waveInPrintError(mRes ,"waveInAddBuffer");  
                assert(FALSE);  
                continue;  
           }  
      }  
   
      m_nWaveInHdrPrepareCount = m_nWaveInHdrCount;  
   
      m_pWaveBufferQue = new WaveBufferQueue(false);  
   
      m_bWaveInRun = TRUE;  
      m_hWaveInThread = (HANDLE)_beginthreadex(NULL, 0, Thread_WaveIn, this, 0, NULL);  
   
      mRes = waveInStart(m_hWaveIn);  
      if (mRes != MMSYSERR_NOERROR) {  
           waveInPrintError(mRes, "waveInStart");  
           return -1;  
      }  
   
      return 0;  
 }  
   
 void WaveIn::close()  
 {  
      MMRESULT mRes;  
   
      if (!m_bWaveInRun || !m_hWaveIn)  
           return;  
   
      m_bWaveInRun = FALSE;  
   
      WaitForSingleObject(m_hWaveInThread, INFINITE);  
   
      mRes = waveInStop(m_hWaveIn);  
      if (mRes != MMSYSERR_NOERROR)  
           waveInPrintError(mRes, "waveInStop");  
   
      for (int i=0; i<m_nWaveInHdrCount; i++)  
           delete[] m_pWaveInHdr[i].lpData;  
      delete m_pWaveInHdr;  
   
      mRes = waveInClose(m_hWaveIn);  
      if (mRes != MMSYSERR_NOERROR)  
           waveInPrintError(mRes ,"waveInClose");  
   
      if (m_pWaveBufferQue) {  
           delete m_pWaveBufferQue;  
           m_pWaveBufferQue = NULL;  
      }  
   
      CloseHandle(m_hWaveInThread);  
 }  
   
 void WaveIn::waveInPrintError(MMRESULT result, LPCTSTR str)  
 {  
      char errmsg[128] = {0};  
      waveInGetErrorText(result, errmsg, sizeof(errmsg));  
      DXPRINTF("%s waveInError: %d %s\n", str, result, errmsg);  
 }  
   

< WaveOut.h >
 #ifndef __WAVE_OUT_H__  
 #define __WAVE_OUT_H__  
   
 #include <windows.h>  
 #include <MMSystem.h>  
 #include <MMReg.h>  
 #include <stdio.h>  
   
 #include "WaveBuffer.h"  
   
 class WaveOut  
 {  
 public:  
      WaveOut();  
      virtual ~WaveOut();  
   
      /* WaveOut API */  
      int open(WAVEFORMATEX waveOutFormat, DWORD channel_layout);  
      void close();  
      int waveOutAdd(unsigned char *buff, int len);  
   
      void setAudioVolume(int val);  
      DWORD getAudioVolume();  
      void reset();  
      void setMaxWaveOutBufferCount(int maxCount) { m_nMaxWaveOutBufferCount = maxCount; }  
        
      int channels() { return m_waveOutFormatEx.Format.nChannels; }  
      int sampleRate() { return m_waveOutFormatEx.Format.nSamplesPerSec; }  
      int channelLayout() { return m_waveOutFormatEx.dwChannelMask; }  
   
 public:  
      void processWaveOutDone(WAVEHDR *pHdr);  
      void processWaveOutClose();  
      void running();  
   
 protected:  
      HWAVEOUT     m_hWaveOut;  
      HANDLE          m_hWaveOutThread;  
      BOOL          m_bWaveOutRun;  
      int               m_nMaxWaveOutBufferCount;  
   
      int                         m_nWaveOutHdrPrepareCount;  
      WaveBufferQueue*     m_pWaveBufferQue;  
   
      DWORD          m_nAudioVolume;  
   
      WAVEFORMATEXTENSIBLE     m_waveOutFormatEx;  
      WAVEOUTCAPS                    m_waveOutCaps;  
   
      void waveOutPrintError(MMRESULT result, LPCTSTR str);  
   
      FILE     *m_pFile;     // audio dump  
 };  
   
 #endif  
   

< WaveOut.cpp >
 #include "WaveOut.h"  
 #include "GlobalEnv.h"  
 #include <assert.h>  
 #include <process.h>  
 #include <stdio.h>  
   
 #pragma comment(lib, "winmm.lib")  
   
 WaveOut::WaveOut()  
 {  
      m_hWaveOut = NULL;  
      m_hWaveOutThread = NULL;  
      m_bWaveOutRun = FALSE;  
   
      m_nWaveOutHdrPrepareCount = 0;  
      m_pWaveBufferQue = new WaveBufferQueue();  
   
      m_nAudioVolume = 0xFFFFFFFF;  
   
      ZeroMemory(&m_waveOutCaps, sizeof(WAVEOUTCAPS));  
      ZeroMemory(&m_waveOutFormatEx, sizeof(WAVEFORMATEXTENSIBLE));  
   
      m_nMaxWaveOutBufferCount = 3;  
   
      m_pFile = NULL;  
 }  
   
 WaveOut::~WaveOut()  
 {  
      close();  
      DX_DELETE_OBJECT(m_pWaveBufferQue);  
 }  
   
 unsigned __stdcall Thread_WaveOut(LPVOID lpParam)  
 {  
      WaveOut *pWaveOut = (WaveOut *)lpParam;  
      pWaveOut->running();  
      return 0;  
 }  
   
 void WaveOut::running()  
 {  
      MMRESULT mRes;  
      WaveBuffer *pWave;  
      WAVEHDR *pHdr;  
   
      while (1)  
      {  
           pWave = m_pWaveBufferQue->pop_front();  
           if (pWave == NULL) {  
                Sleep(1);  
                goto skip;  
           }  
             
           pHdr = pWave->pWaveHdr;  
   
           mRes = waveOutUnprepareHeader(m_hWaveOut, pHdr, sizeof(WAVEHDR));  
           if (mRes != MMSYSERR_NOERROR) {  
                waveOutPrintError(mRes, "waveOutUnprepareHeader");  
                assert(FALSE);  
                continue;  
           }  
   
           delete pWave;  
           InterlockedDecrement((LPLONG)&m_nWaveOutHdrPrepareCount);  
   
 skip:  
           if (!m_bWaveOutRun)  
           {  
                if (m_pWaveBufferQue->count() == 0 && m_nWaveOutHdrPrepareCount == 0) {  
                     break;  
                }  
           }  
      }  
 }  
   
 void WaveOut::processWaveOutDone(WAVEHDR *pHdr)  
 {       
      if (m_pWaveBufferQue) {  
           m_pWaveBufferQue->push_back(pHdr);  
      }  
 }  
   
 void CALLBACK waveOutProc(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2)  
 {  
      WaveOut *pWaveOut = (WaveOut *)dwInstance;  
   
      switch(uMsg)  
      {  
      case WOM_OPEN:  
           DXPRINTF("WOM_OPEN\n");  
           break;  
      case WOM_CLOSE:  
           DXPRINTF("WOM_CLOSE\n");  
           break;  
      case WOM_DONE:  
           pWaveOut->processWaveOutDone((WAVEHDR *)dwParam1);  
           break;  
      default:  
           break;  
      }  
 }  
   
 int WaveOut::waveOutAdd(unsigned char *buff, int len)  
 {  
      MMRESULT mRes;  
   
      if (!m_bWaveOutRun)  
           return -1;  
   
      //DXPRINTF("wave out buffer count : %d (%d)\n", m_nWaveOutHdrPrepareCount, len);  
      if (m_nMaxWaveOutBufferCount > 0) {  
           if (m_nWaveOutHdrPrepareCount > m_nMaxWaveOutBufferCount) {  
                DXPRINTF("wave out buffer overflow : %d (%d)\n", m_nWaveOutHdrPrepareCount, len);  
                return 0;  
           }  
      }  
   
      WAVEHDR *pHdr = new WAVEHDR;  
      ZeroMemory(pHdr, sizeof(WAVEHDR));  
   
      pHdr->lpData = new char[len];  
      memcpy(pHdr->lpData, buff, len);  
      pHdr->dwBufferLength = len;  
      pHdr->dwFlags = 0;  
   
      mRes = waveOutPrepareHeader(m_hWaveOut, pHdr, sizeof(WAVEHDR));  
      if (mRes != MMSYSERR_NOERROR) {  
           waveOutPrintError(mRes, "waveOutPrepareHeader");  
           delete[] pHdr->lpData;  
           delete pHdr;  
           assert(FALSE);  
           return -2;  
      }  
   
      mRes = waveOutWrite(m_hWaveOut, pHdr, sizeof(WAVEHDR));  
      if (mRes != MMSYSERR_NOERROR) {  
           waveOutPrintError(mRes, "waveOutWrite");  
           delete[] pHdr->lpData;  
           delete pHdr;  
           assert(FALSE);  
           return -3;  
      }  
   
      InterlockedIncrement((LPLONG)&m_nWaveOutHdrPrepareCount);  
   
      if (m_pFile) fwrite(buff, len, 1, m_pFile);  
   
      return 0;  
 }  
   
 int WaveOut::open(WAVEFORMATEX waveOutFormat, DWORD channel_layout)  
 {  
      MMRESULT mRes;  
   
      if (m_bWaveOutRun)  
           return -1;  
   
      m_waveOutFormatEx;  
      memset(&m_waveOutFormatEx, 0, sizeof(WAVEFORMATEXTENSIBLE));  
      m_waveOutFormatEx.Format.wFormatTag = WAVE_FORMAT_PCM;  
      m_waveOutFormatEx.Format.nChannels = waveOutFormat.nChannels;  
      m_waveOutFormatEx.Format.nSamplesPerSec = waveOutFormat.nSamplesPerSec;  
      m_waveOutFormatEx.Format.wBitsPerSample = waveOutFormat.wBitsPerSample;  
      m_waveOutFormatEx.Format.nBlockAlign = waveOutFormat.nChannels*waveOutFormat.wBitsPerSample/8;  
      m_waveOutFormatEx.Format.nAvgBytesPerSec = waveOutFormat.nSamplesPerSec*waveOutFormat.nBlockAlign;  
   
      if (waveOutFormat.nChannels <= 2) {  
           m_waveOutFormatEx.Format.cbSize = 0;  
      } else {  
           m_waveOutFormatEx.dwChannelMask = channel_layout;  
           m_waveOutFormatEx.Samples.wValidBitsPerSample = waveOutFormat.wBitsPerSample;  
           m_waveOutFormatEx.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;  
           m_waveOutFormatEx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;  
           m_waveOutFormatEx.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);  
      }  
   
      mRes = waveOutOpen(&m_hWaveOut, WAVE_MAPPER, (WAVEFORMATEX *)&m_waveOutFormatEx,  
           (DWORD_PTR)waveOutProc, (DWORD_PTR)this, CALLBACK_FUNCTION);  
   
      if (mRes != MMSYSERR_NOERROR) {  
           waveOutPrintError(mRes, "waveOutOpen");  
           return -2;  
      }  
   
      mRes = waveOutSetVolume(m_hWaveOut, m_nAudioVolume);  
      if (mRes != MMSYSERR_NOERROR) {  
           waveOutPrintError(mRes, "waveOutWrite");  
      }  
   
      m_bWaveOutRun = TRUE;  
      m_hWaveOutThread = (HANDLE)_beginthreadex(NULL, 0, Thread_WaveOut, this, 0, NULL);  
      if (!m_hWaveOutThread) {  
           DXPRINTF("[%s] waveout thread create error, err:%d\n", __FUNCTION__, GetLastError());  
           return -3;  
      }  
   
      ZeroMemory(&m_waveOutCaps, sizeof(WAVEOUTCAPS));  
      mRes = waveOutGetDevCaps((UINT_PTR)m_hWaveOut, &m_waveOutCaps, sizeof(WAVEOUTCAPS));  
      if (mRes != MMSYSERR_NOERROR) {  
           waveOutPrintError(mRes, "waveOutGetDevCaps");  
      }  
   
 #if 0  
      m_pFile = fopen("audio.wav", "wb");  
 #endif  
   
      return 0;  
 }  
   
 void WaveOut::close()  
 {  
      MMRESULT mRes;  
   
      if (!m_bWaveOutRun || !m_hWaveOut)  
           return;  
   
      waveOutReset(m_hWaveOut);  
      m_bWaveOutRun = FALSE;  
   
      WaitForSingleObject(m_hWaveOutThread, INFINITE);  
   
      mRes = waveOutClose(m_hWaveOut);  
      if (mRes != MMSYSERR_NOERROR)  
           waveOutPrintError(mRes, "waveOutClose");  
   
      CloseHandle(m_hWaveOutThread);  
      m_hWaveOutThread = NULL;  
   
      if (m_pFile) {  
           fclose(m_pFile);  
           m_pFile = NULL;  
      }  
 }  
   
 void WaveOut::setAudioVolume(int val)  
 {  
      m_nAudioVolume = val;  
   
      if (m_hWaveOut) {  
           MMRESULT mRes = waveOutSetVolume(m_hWaveOut, m_nAudioVolume);  
           if (mRes != MMSYSERR_NOERROR) {  
                waveOutPrintError(mRes, "waveOutSetVolume");  
           }  
      }  
 }  
   
 DWORD WaveOut::getAudioVolume()  
 {  
      if (m_hWaveOut) {  
           MMRESULT mRes = waveOutGetVolume(m_hWaveOut, &m_nAudioVolume);  
           if (mRes == MMSYSERR_NOERROR) return m_nAudioVolume;  
           waveOutPrintError(mRes, "waveOutGetVolume");  
      }  
      return 0;  
 }  
   
 void WaveOut::reset()  
 {  
      if (m_hWaveOut)  
           waveOutReset(m_hWaveOut);  
 }  
   
 void WaveOut::waveOutPrintError(MMRESULT result, LPCTSTR str)  
 {  
      char errmsg[128] = {0};  
      waveOutGetErrorText(result, errmsg, sizeof(errmsg));  
      DXPRINTF("%s waveOutError: %d %s\n", str, result, errmsg);  
 }  
   

< WaveOut 사용법 >
WaveOut* m_pWaveOut = new WaveOut();
...
// waveout 열기
int open(int channel, int sample_rate, int channel_layout)
{
// open waveout
WAVEFORMATEX waveformat;
memset(&waveformat, 0, sizeof(WAVEFORMATEX));
waveformat.wFormatTag = WAVE_FORMAT_PCM;
waveformat.nChannels = channel;
waveformat.nSamplesPerSec = sample_rate;
waveformat.wBitsPerSample = 16;
waveformat.nBlockAlign = waveformat.nChannels*waveformat.wBitsPerSample/8;
waveformat.nAvgBytesPerSec = waveformat.nSamplesPerSec*waveformat.nBlockAlign;
waveformat.cbSize = 0;

int ret = m_pWaveOut->open(waveformat, channel_layout);
if (ret < 0) m_pWaveOut->close();
return ret;
}

// waveout 출력 - buff : 오디오 pcm 데이터, size : 오디오 pcm 데이터 사이즈
int waveout(unsigned char *buff, int size)
{
     return m_pWaveOut->waveOutAdd(buff, size);
}


// waveout 닫기
void close()
{
     m_pWaveOut->close();
}

2014년 10월 23일 목요일

live555 visual c++ project (VS2008)

http://www.mediafire.com/download/gnh5p065adar47a/live555.zip

live.2014.10.21.tar.gz 버전
testProgs 나머지 실행프로그램들은 만들어진 프로젝트 참고하면 쉽게 만들수 있음
x64 버전도 win32 버전 그데로 사용가능
테스트는 첨부된 test.264 사용

2014년 2월 12일 수요일

ffmpeg av_lockmgr_register 사용법

ffmpeg의 thread-safe 하지않은 함수들 - av_register_all, avcodec_open2, avcodec_close
등의 함수들은 보통 호출부에 뮤텍스 락을 걸어주어야 하는데 av_lockmgr_register 함수를
이용하면 이것을 간단하게 해결할 수 있다.

#include <windows.h>
extern "C" {
#include "libavformat/avformat.h"
}

static bool isInit = false;
static HANDLE hMutex = CreateMutex(NULL, FALSE, NULL);

static int lockmgr(void **mtx, enum AVLockOp op)
{
   switch(op) {
      case AV_LOCK_CREATE:
          *mtx = CreateMutex(0, FALSE, 0);
          if(!*mtx)
              return 1;
          return 0;
      case AV_LOCK_OBTAIN:
          return !!WaitForSingleObject(*mtx, INFINITE);
      case AV_LOCK_RELEASE:
          return !!ReleaseMutex(*mtx);
      case AV_LOCK_DESTROY:
          CloseHandle(*mtx);
          return 0;
   }
   return 1;
}

void InitFFmpegLib()
{
WaitForSingleObject(hMutex, INFINITE);

if (!isInit) {
av_register_all();

if (av_lockmgr_register(lockmgr)) {
printf("Could not initialize lock manager!\n");
exit(1);
}
isInit = true;
}

ReleaseMutex(hMutex);
}

프로그램 시작부분에서 InitFFmpegLib() 함수를 호출하면 그 다음부터 thread-unsafe 한
ffmpeg api 함수들은 자동으로 뮤텍스 락을 건다.

2014년 1월 8일 수요일

copy AVFrame using av_image_copy

AVFrame* clone_av_frame(AVFrame *frame)
{
    AVFrame *new_frame = avcodec_alloc_frame();
    enum AVPixelFormat fmt = (enum AVPixelFormat)frame->format;
    int ret = av_image_alloc(new_frame->data, new_frame->linesize, frame->width, frame-height, fmt, 1);
    if (ret < 0) {
        av_free(new_frame->data[0]);
        av_free(new_frame);
        return 0;
    }
    
    av_image_copy(new_frame->data, new_frame->linesize, (const uint8_t **)frame->data, 
                            frame->linesize, fmt, frame->width, frame->height);

    // copy to new frame
    new_frame->width = frame->width;
    new_frame->height = frame->height;
    new_frame->pict_type = frame->pict_type;
    new_frame->format = (int)fmt;
    ...

    return new_frame;
}

void free_av_frame(AVFrame *frame)
{
    if (frame) {
        av_free(frame->data[0]);
        av_free(frame);
    }
}

2013년 12월 26일 목요일

RTP Header Parsing

< RTPHeader.h >

#ifndef __RTP_HEADER_H__
#define __RTP_HEADER_H__

typedef unsigned short      WORD;
typedef unsigned long       DWORD;

typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;

typedef unsigned int u_int32_t;
typedef unsigned short u_int16_t;
typedef unsigned char u_int8_t;

typedef struct
{
WORD    cc:     4;      /* csrc count */
WORD    ext:    1;      /* header extension flag */
WORD    pad:    1;      /* padding flag - for encryption */
WORD    ver:    2;      /* protocal version */
WORD    pt:     7;      /* payload type */
WORD    mk:     1;      /* marker bit - for profile */
WORD seq; /* sequence number of this packet */
DWORD ts; /* timestamp of this packet */
DWORD ssrc; /* source of packet */
} RTP_HEADER;

class RtpHeader {
public:
RtpHeader(uint8_t *buf, int len);
virtual ~RtpHeader();

uint8_t* payload();
int length() { return fLength; }
int payloadLen();

uint16_t version() { return fVersion; }
uint16_t padding() { return fPadding; }
uint16_t extension() { return fExtension; }
uint16_t csrcCount() { return fCSRCCount; }
uint16_t markerBit() { return fMarkerBit; }
uint16_t payloadType() { return fPayloadType; }
uint16_t sequenceNum() { return fSequenceNum; }
uint32_t timestamp() { return fTimestamp; }
uint32_t ssrc() { return fSSRC; }

private:
uint8_t* fBuf;
int fLength;
uint16_t fVersion;
uint16_t fPadding;
uint16_t fExtension;
uint16_t fCSRCCount;
uint16_t fMarkerBit;
uint16_t fPayloadType;
uint16_t fSequenceNum;
uint32_t fTimestamp;
uint32_t fSSRC;

uint8_t *fCurPtr;
};

#endif


< RTPHeader.cpp >

#include <Winsock2.h>
#include "RTPHeader.h"

RtpHeader::RtpHeader(uint8_t *buf, int len) : fBuf(NULL), fLength(0), fVersion(0), fPadding(0), fExtension(0), fCSRCCount(0),
fMarkerBit(0), fPayloadType(0), fSequenceNum(0), fTimestamp(0), fSSRC(0)
{
if (len < sizeof(RTP_HEADER))
return;

fBuf = fCurPtr = buf;
fLength = len;

RTP_HEADER *p = (RTP_HEADER *)buf;
fCSRCCount = p->cc;
fExtension = p->ext;
fPadding = p->pad;
fVersion = p->ver;
fPayloadType = p->pt;
fMarkerBit = p->mk;
fSequenceNum = ntohs(p->seq);
fTimestamp = ntohl(p->ts);
fSSRC = ntohl(p->ssrc);

fCurPtr += sizeof(RTP_HEADER);

// check RTP version (it must be 2)
if (fVersion != 2)
DPRINTF("invalid rtp version %u\n", fVersion);

// skip CSRC
if (fCSRCCount > 0) {
if (payloadLen() <= fCSRCCount*4) {
DPRINTF("invalid rtp header, CSRC count error %u\n", fCSRCCount);
} else {
fCurPtr += (fCSRCCount*4);
}
}

// skip Extension field
if (fExtension) {
if (payloadLen() <= 4) {
DPRINTF("invalid rtp header, extension length error\n");
} else {
unsigned extHdr = ntohl(*(unsigned *)fCurPtr); fCurPtr += 4;
unsigned remExtSize = 4*(extHdr&0xFFFF);
if (payloadLen() <= remExtSize) {
DPRINTF("invalid rtp header, extension size error %u\n", remExtSize);
} else {
fCurPtr += remExtSize;
}
}
}

// remove padding
if (fPadding) {
if (payloadLen() <= 0) {
DPRINTF("invalid rtp header, padding error\n");
} else {
unsigned numPaddingBytes = (unsigned)fBuf[fLength-1];
if (payloadLen() <= numPaddingBytes) {
DPRINTF("invalid rtp header, padding number error\n");
} else {
fLength -= numPaddingBytes;
    fPadding = p->pad = 0;
}
}
}
}

RtpHeader::~RtpHeader()
{
}

uint8_t* RtpHeader::payload()
{
return fCurPtr;
}

int RtpHeader::payloadLen()
{
uint8_t *ptrLast = &fBuf[fLength-1];
return ptrLast-fCurPtr+1;
}


< 사용 >
...
RtpHeader *rtp = new RtpHeader((uint8_t*)buf, len);

unsigned short pt = rtp->payloadType();
unsigned short mk = rtp->markerBit();
unsigned short seqnum = rtp->sequenceNum();
unsigned int ts = rtp->timestamp();
unsigned int rtpSSRC = rtp->ssrc();

 delete rtp;

2013년 12월 18일 수요일

vc++에서 ffmpeg dll 로드실패 처리 - avcodec_register_all 실행시 crash

vc++ 에서 ffmpeg dll (--enable-shared 옵션) 링크에 성공한 후 실행하면 첫번째

avcodec_register_all 함수에서 죽는 현상 발생한다.(다른 함수들도 마찬가지)

vc++ property -> Linker -> Optimization -> References 를 Keep Unreferenced Data (/OPT:NOREF)

로 변경해서 적용할것.

2013년 8월 30일 금요일

convert AV_SAMPLE_FMT_FLTP to AV_SAMPLE_FMT_S16 with ffmpeg software resampler

extern "C" {
#include "libavcodec\avcodec.h"
#include "libswresample\swresample.h"
#include "libavutil\opt.h"
};

struct SwrContext *m_pSwrCtx;
AVCodec *m_pCodec;
AVCodecContext *m_pCodecCtx;
AVPacket m_avPacket;
AVFrame *m_pFrame;
uint8_t m_pOutBuf = new uint8_t[AVCODEC_MAX_AUDIO_FRAME_SIZE*5];
...

< open resampler >

m_pSwrCtx = swr_alloc();

uint64_t channel_layout = m_pCodecCtx->channel_layout;
if (channel_layout == 0)
channel_layout = av_get_default_channel_layout(m_pCodecCtx->channels);

av_opt_set_int(m_pSwrCtx, "in_channel_layout", channel_layout, 0);
av_opt_set_int(m_pSwrCtx, "in_sample_rate", m_pCodecCtx->sample_rate, 0);
av_opt_set_sample_fmt(m_pSwrCtx, "in_sample_fmt", m_pCodecCtx->sample_fmt, 0);

av_opt_set_int(m_pSwrCtx, "out_channel_layout", channel_layout, 0);
av_opt_set_int(m_pSwrCtx, "out_sample_rate", m_pCodecCtx->sample_rate, 0);
av_opt_set_sample_fmt(m_pSwrCtx, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0);


err = swr_init(m_pSwrCtx);

< do resampling >
...
int got_frame;
int retLen = avcodec_decode_audio4(m_pCodecCtx, m_pFrame, &got_frame, &m_avPacket);

if (retLen <= 0) {
DPRINTF("audio decode error : %d\n", retLen);
return retLen;
}

// audio resampling
int out_size = av_samples_get_buffer_size(NULL, m_pCodecCtx->channels, m_pFrame->nb_samples, m_pCodecCtx->sample_fmt, 1);

if (out_size > m_nOutBufSize) {
delete[] m_pOutBuf;
m_pOutBuf = new unsigned char[out_size];
m_nOutBufSize = out_size;
}

retLen = swr_convert(m_pSwrCtx, &m_pOutBuf, out_size,
(const uint8_t **)m_pFrame->extended_data, m_pFrame->nb_samples);

out_size = retLen*m_pCodecCtx->channels*av_get_bytes_per_sample(AV_SAMPLE_FMT_S16);
m_nOutBufSize = out_size;

return retLen;

< close resampler >

swr_free(&m_pSwrContext);