// MP3Stream_acm.cpp : アプリケーションのエントリ ポイントを定義します。 // #include "stdafx.h" #include "MP3Stream_acm.h" #include <mmsystem.h> #include <dsound.h> #include "DSQuickLib.h" #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> // stat 関数を使う為インクルード #include <crtdbg.h> #include <TCHAR.h> #include <vfw.h> #include <mmreg.h> #include <msacm.h> #include "MP3StreamSound.h" #pragma comment (lib, "msacm32.lib") #pragma comment (lib, "winmm.lib") #pragma comment (lib, "vfw32.lib") LRESULT CALLBACK WndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam); LRESULT CALLBACK IOProc(LPMMIOINFO lpMMIOInfo, UINT uMessage, LPARAM lParam1, LPARAM lParam2); // グローバル変数 CDSoundEnv *g_pDSoundEnv = NULL; static char * g_lpData = NULL; static long g_cfileSize; CRITICAL_SECTION g_csSema; CMP3StreamSound *g_pMP3StreamSound = NULL; int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) { _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF ); WNDCLASSEX wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = (WNDPROC)WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(NULL, MAKEINTRESOURCE(IDI_APPLICATION)); wcex.hCursor = LoadCursor(NULL, MAKEINTRESOURCE(IDC_ARROW)); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wcex.lpszMenuName = NULL; wcex.lpszClassName = _T("mp3 realtime audio stream"); wcex.hIconSm = LoadIcon(NULL, IDI_APPLICATION); RegisterClassEx(&wcex); InitializeCriticalSection(&g_csSema); HWND hWnd; hWnd = CreateWindowEx(WS_EX_OVERLAPPEDWINDOW,wcex.lpszClassName,_T("mp3 realtime audio stream"), WS_VISIBLE|WS_CAPTION|WS_SYSMENU|WS_MINIMIZEBOX|WS_MAXIMIZEBOX, CW_USEDEFAULT,0,640,480,NULL,NULL,hInstance,NULL); if( !hWnd ) return FALSE; RECT bounds,client; GetWindowRect(hWnd,&bounds); GetClientRect(hWnd,&client); MoveWindow(hWnd,bounds.left,bounds.top, 640 * 2 - client.right, 480 * 2 - client.bottom, false ); ShowWindow( hWnd, nCmdShow ); UpdateWindow( hWnd ); g_pDSoundEnv = new CDSoundEnv(2,44100,16); if (g_pDSoundEnv){ if (SUCCEEDED(g_pDSoundEnv->Initialize(hWnd,DSSCL_PRIORITY))){ struct _stat status; if (0 == _tstat(_T("Sample.mp3"),&status)){ g_cfileSize = status.st_size; g_lpData = new char[g_cfileSize]; FILE *fp = fopen(_T("Sample.mp3"),_T("rb")); if (fp != NULL){ fread(g_lpData,g_cfileSize,1,fp); fclose(fp); } // mci:カスタムIOProc のインストール mmioInstallIOProc(mmioFOURCC('M', 'M', 'S', ' '), (LPMMIOPROC)IOProc, MMIO_INSTALLPROC | MMIO_GLOBALPROC); g_pMP3StreamSound = new CMP3StreamSound(g_pDSoundEnv,_T("test.MMS+")); g_pMP3StreamSound->Prepare(); g_pMP3StreamSound->Rewind(); g_pMP3StreamSound->Play(); } } } BOOL bFlag1 = false; BOOL bFlag2 = false; BOOL bFlag3 = false; BOOL bFlag4 = false; BOOL bPlaying = true; MSG msg; while(true){ if(PeekMessage(&msg, 0, 0, 0, PM_REMOVE)){ if(msg.message == WM_QUIT) break; TranslateMessage(&msg); DispatchMessage(&msg); }else{ g_pDSoundEnv->PollStatus(); // 再生のコントロール // キー 1 if (::GetAsyncKeyState('1')){ if (!bFlag1 && g_pMP3StreamSound){ bPlaying = true; g_pMP3StreamSound->Play(); } bFlag1 = true; }else{ bFlag1 = false; } // キー 2 if (::GetAsyncKeyState('2')){ if (!bFlag2 && g_pMP3StreamSound){ g_pMP3StreamSound->Pause(); bPlaying = false; } bFlag2 = true; }else{ bFlag2 = false; } // キー 3 if (::GetAsyncKeyState('3')){ if (!bFlag3 && g_pMP3StreamSound){ g_pMP3StreamSound->FadeOut(180); bPlaying = false; } bFlag3 = true; }else{ bFlag3 = false; } // キー 4 if (::GetAsyncKeyState('4')){ if (!bFlag4 && g_pMP3StreamSound){ g_pMP3StreamSound->Play(0,0,0,-1,0,60); bPlaying = true; } bFlag4 = true; }else{ bFlag4 = false; } if (!g_pMP3StreamSound->IsPlaying() && bPlaying){ Sleep(100); g_pMP3StreamSound->Rewind(); g_pMP3StreamSound->Play(); } } } if (g_pMP3StreamSound) delete g_pMP3StreamSound; if (g_pDSoundEnv) delete g_pDSoundEnv; EnterCriticalSection(&g_csSema); if (g_lpData != NULL){ mmioInstallIOProc(mmioFOURCC('M', 'M', 'S', ' '), NULL,MMIO_REMOVEPROC); delete g_lpData; g_lpData = NULL; } LeaveCriticalSection(&g_csSema); return (int)msg.wParam; } // // 関数:WndProc // 説明:ウインドウに渡されたイベントのハンドラ // LRESULT CALLBACK WndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam) { switch (message){ case WM_PAINT: { HDC hDC; PAINTSTRUCT paintStruct; hDC = BeginPaint(hWnd,&paintStruct); RECT rc; GetClientRect(hWnd,&rc); DrawText(hDC,_T("このアプリケーションはオーディオ再生のみ行います。"),-1,&rc,DT_NOCLIP); rc.top += 30; DrawText(hDC,_T("映像は再生されません。"),-1,&rc,DT_NOCLIP); rc.top += 30; rc.left += 40; DrawText(hDC,_T("key\'1\' -- Play MP3 Stream"),-1,&rc,DT_NOCLIP); rc.top += 20; DrawText(hDC,_T("key\'2\' -- Pause MP3 Stream"),-1,&rc,DT_NOCLIP); rc.top += 20; DrawText(hDC,_T("key\'3\' -- Fadeout MP3 Stream"),-1,&rc,DT_NOCLIP); rc.top += 20; DrawText(hDC,_T("key\'4\' -- Fadein MP3 Stream"),-1,&rc,DT_NOCLIP); ::EndPaint(hWnd,&paintStruct); } return DefWindowProc(hWnd, message, wParam, lParam); case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } // // mmio: IOProc // LRESULT CALLBACK IOProc(LPMMIOINFO lpMMIOInfo, UINT uMessage, LPARAM lParam1, LPARAM lParam2) { static BOOL alreadyOpened = FALSE; switch (uMessage) { case MMIOM_OPEN: if (alreadyOpened) return 0; alreadyOpened = TRUE; lpMMIOInfo->lDiskOffset = 0; return 0; case MMIOM_CLOSE: return 0; case MMIOM_READ:{ LPARAM dataRead = 0; EnterCriticalSection(&g_csSema); if (g_lpData != NULL){ dataRead = lParam2; memcpy((void *)lParam1, g_lpData+lpMMIOInfo->lDiskOffset, lParam2); lpMMIOInfo->lDiskOffset += lParam2; dataRead = lParam2; } LeaveCriticalSection(&g_csSema); return (dataRead); } case MMIOM_SEEK: switch (lParam2) { case SEEK_SET: lpMMIOInfo->lDiskOffset = lParam1; break; case SEEK_CUR: lpMMIOInfo->lDiskOffset += lParam1; break; case SEEK_END: lpMMIOInfo->lDiskOffset = g_cfileSize - lParam1; break; } return lpMMIOInfo->lDiskOffset; default: return -1; } }
#pragma once #include <vfw.h> #include <mmreg.h> #include <msacm.h> #include "DSQuickLib.h" #define MP3SOUNDSTREAM_NUM_BLOCKS 2 #define MP3SOUNDSTREAM_NUM_HISTORY 16 typedef struct _playhistory{ LONG lFilePos; LONG lPcmPos; } MP3PLAYHISTORY; class CMP3StreamSound : public CSoundObject { public: CMP3StreamSound(CDSoundEnv *pEnv, TCHAR *pFilename); virtual ~CMP3StreamSound(void); virtual void DoThread(); void PollStatus(FLOAT timeElapsed); HRESULT Prepare(); virtual HRESULT Play( DWORD dwPriority = 0, DWORD dwFlags = 0, LONG lVol = 0, LONG lFreq = -1, LONG lPan = 0 , LONG lFade = 0); virtual HRESULT Stop(); virtual HRESULT Rewind(); virtual void FadeOut(LONG lFadeLen); HRESULT Pause(); virtual BOOL IsPlaying(); protected: HRESULT Create(); HRESULT FillBufferWithSound( LPDIRECTSOUNDBUFFER pDSB); HRESULT SetNotify(); HRESULT MP3StreamReadAndDecode(void *buffer, LONG lSize); HRESULT CheckMP3Format(); HRESULT CheckMP3Header(LPBYTE pID); CDSoundEnv *m_pDSoundEnv; DWORD m_dwDSBufferSize; // Datas about the file TCHAR *m_pFilename; HMMIO m_hMMIO; LONG m_lStreamStartOffset; LONG m_lFileSize; LONG m_lFilePos; BOOL m_bSourceStreamActive; LONG m_lPCMSizeDetected; MPEGLAYER3WAVEFORMAT m_wfMP3; INT m_iFrameSize; BYTE *m_pMP3Frame; // 1フレーム分のMP3データ INT m_iSourceBufferSize; HACMSTREAM m_hAcmStream; BOOL m_bAcmHeaderPrepared; // ACMSTREAMHEADER m_acmStreamHeader; // Datas about converted audio WAVEFORMATEX m_wfPcm; DWORD m_dwSizeConverted; BYTE *m_pPcmBuffer; // PCM Buffer (target to convert) BYTE *m_pPcmBufferCurrent; // CurrentPos in m_pPcmBuffer LONG m_dwPcmBufferSizeRemain; // Size of PCM Buffer DWORD m_dwPcmBufferSize; // Managing Sound Buffer HANDLE m_hNotificationEvent[MP3SOUNDSTREAM_NUM_BLOCKS]; HANDLE m_hTerminator; DWORD m_dwThreadID; HANDLE m_hThread; // Play History (Pause 解除用) MP3PLAYHISTORY m_pHistory[MP3SOUNDSTREAM_NUM_HISTORY]; LONG m_lHistoryRead; LONG m_lHistoryWrite; LPDIRECTSOUNDBUFFER m_lpDSBuffer; LONG m_lVol; LONG m_lFreq; LONG m_lPan; LONG m_lCurrentPosition; BOOL m_bThreadDone; BOOL m_bThreadActive; DWORD m_dwNumNotification; DWORD m_dwNotifySize; DWORD m_dwBufferLengthInSec; DWORD m_dwLastPlayPos; DWORD m_dwPlayProgress; DWORD m_dwNextWriteOffset; DWORD m_dwPhase; DWORD m_dwPhaseAfterReplay; FLOAT m_fFadeTime; FLOAT m_fFadeLen; CRITICAL_SECTION m_CriticalSection; HRESULT RestoreBuffer( LPDIRECTSOUNDBUFFER pDSB, BOOL* pbWasRestored ); HRESULT Reset(); HRESULT HandleNotificationEvent(); };
#include "StdAfx.h" #include <mmsystem.h> #include <dxerr9.h> #include <dsound.h> #include <crtdbg.h> #include "DSQuickLib.h" #include "MP3StreamSound.h" DWORD WINAPI MP3StreamSound_DoThread(LPVOID pVoid); #define SAFE_RELEASE(o) {if (o){ (o)->Release(); (o) = NULL; }} #define SAFE_DELETE(o) {if (o){ delete (o); (o) = NULL; }} #define SAFE_DELETE_ARRAY(p) {if(p) { delete[] (p); (p)=NULL; } } #define SAFE_CLOSEHANDLE(h) {if(h) { CloseHandle(h); (h)=NULL; } } CMP3StreamSound::CMP3StreamSound(CDSoundEnv *pEnv, TCHAR *pFilename) { m_pDSoundEnv = pEnv; size_t len = _tcslen(pFilename); ++len; m_pFilename = new TCHAR[len]; _tcscpy_s(m_pFilename,len,pFilename); m_hMMIO = NULL; m_lStreamStartOffset = 0; m_lFileSize = 0; m_lFilePos = 0; m_bSourceStreamActive = FALSE; m_dwDSBufferSize = 0L; m_hThread = NULL; m_dwThreadID = 0; for (int i = 0; i < MP3SOUNDSTREAM_NUM_BLOCKS ; ++i) m_hNotificationEvent[i] = NULL; m_hTerminator = NULL; m_lpDSBuffer = NULL; m_dwNumNotification = MP3SOUNDSTREAM_NUM_BLOCKS; // バッファの分割数・作成途上 m_dwBufferLengthInSec = MP3SOUNDSTREAM_NUM_BLOCKS; // デフォルトのバッファ長1ブロック1秒 InitializeCriticalSection(&m_CriticalSection); m_dwPhase = 0; m_bThreadActive = FALSE; m_bThreadDone = FALSE; m_iSourceBufferSize = 1441; // m_pMP3Frame = new BYTE[m_iSourceBufferSize]; // 規格上 MP3 での最大フレームサイズ m_dwSizeConverted = 0; m_pPcmBuffer = NULL; m_pPcmBufferCurrent = NULL; m_dwPcmBufferSizeRemain = 0; m_lPCMSizeDetected = -1; m_lHistoryRead = 0; m_lHistoryWrite = 0; m_hAcmStream = NULL; ZeroMemory((BYTE*)&m_acmStreamHeader,sizeof(ACMSTREAMHEADER)); m_bAcmHeaderPrepared = FALSE; pEnv->AddSoundObject(this); } // CheckMP3Header // MP3 フレームヘッダを解析する。 // MP3 フォーマットでは、オーディオデータは、 // 1152 サンプル毎にフレーム単位でエンコードされる。 // 各フレームの先頭には32 ビットのフレームヘッダが格納され // ている。 // // フレームヘッダのフォーマットは以下のとおり // Bit :AAAA AAAA AAAB BCCD EEEE FFGH IIJJ KLMM // Byte:+---0----+----1----+----2----+----3----+ // A フレーム同期ビット // B ID 00:MPEG2.5 01:reserved 10:MPEG2 11:MPEG1 // C レイヤ 11:レイヤT 10:レイヤU 01:レイヤV 00:reserved // D 保護ビット 1:誤り検出訂正符号付加せず 0:同情報付加 // E ビットレート ・・・テーブル参照 // F サンプリング周波数・・・テーブル参照 // G パディングビット・・・ 1:余分なスロット※を含む 0:同含まない // H 私用ビット・・・規格上使用せず // I モード 00:ステレオ 01:ジョイントステレオ 10:デュアルチャンネル 11:シングルチャンネル // J モード拡張・・・ジョイントステレオの実装方式 // K 著作権 1:保護あり 0:保護なし // L オリジナル/コピー 1:オリジナル 0:コピー // M エンファシス 使用されるべきエンファシスの種類 // HRESULT CMP3StreamSound::CheckMP3Header(LPBYTE pID){ INT iBitRate; INT iFS; INT iFrameSize = 0; INT iPadding = 0; INT iChannel = 0; INT iVersion; static INT iLayer3BitrateTable[][16] = { {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0}, {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0} }; static INT iFSTable[][3] = { {44100, 48000, 32000}, {22050, 24000, 16000} }; if ((pID[0] != 0xff) || (0xe0 != (pID[1] & 0xe0))){ _RPT0(_CRT_WARN,_T("MPEGフレームヘッダが不正です\n")); return E_FAIL; } // MPEG バージョンのチェック switch ((pID[1] >> 3) & 0x03) { case 3: iVersion = 1; break; case 2: iVersion = 2; break; default: _RPT0(_CRT_WARN,_T("MPEGバージョンが不正です\n")); return E_FAIL; } // レイヤ3のみに対応 if ((pID[1] >> 1 & 0x03) != 1){ _RPT0(_CRT_WARN,_T("MP3 フォーマットではありません\n")); return E_FAIL; } // オーディオフォーマットの取得 iBitRate = iLayer3BitrateTable[iVersion - 1][pID[2] >> 4 & 15]; iFS = iFSTable[iVersion - 1][pID[2] >> 2 & 0x03]; iPadding = pID[2] >> 1 & 0x01; iChannel = ((pID[3] >> 6 & 3) == 3) ?(1):(2); iFrameSize = (WORD)((1152 * iBitRate * 1000 / iFS) / 8) + iPadding; m_iFrameSize = iFrameSize; m_wfMP3.wfx.wFormatTag = WAVE_FORMAT_MPEGLAYER3; m_wfMP3.wfx.nChannels = iChannel; m_wfMP3.wfx.nSamplesPerSec = iFS; m_wfMP3.wfx.nAvgBytesPerSec = (iBitRate * 1000) / 8; m_wfMP3.wfx.nBlockAlign = 1; m_wfMP3.wfx.wBitsPerSample = 0; m_wfMP3.wfx.cbSize = MPEGLAYER3_WFX_EXTRA_BYTES; m_wfMP3.wID = MPEGLAYER3_ID_MPEG; m_wfMP3.fdwFlags = iPadding ? MPEGLAYER3_FLAG_PADDING_ON : MPEGLAYER3_FLAG_PADDING_OFF; m_wfMP3.nBlockSize = iFrameSize; m_wfMP3.nFramesPerBlock = 1; m_wfMP3.nCodecDelay = 0x571; return S_OK; } // // MP3ファイルのフォーマットを確認する。 // HRESULT CMP3StreamSound::CheckMP3Format(){ if (m_hMMIO == NULL){ return E_FAIL; } BYTE id3[10]; m_lFileSize = mmioSeek(m_hMMIO,0,SEEK_END); mmioSeek(m_hMMIO,0,SEEK_SET); mmioRead(m_hMMIO,(HPSTR)id3,sizeof(id3)); // MPEG2 形式かチェック if (id3[0] == 'I' && id3[1] == 'D' && id3[2] == '3'){ // id3v2 形式 LONG lOffset = 10; // tag サイズの取得・・・BIG ENDIAN lOffset += id3[6] << 21; lOffset += id3[7] << 14; lOffset += id3[8] << 7; lOffset += id3[9]; m_lStreamStartOffset = lOffset; m_lFileSize -= lOffset; }else{ // id3v1 形式 m_lStreamStartOffset = 0; mmioSeek(m_hMMIO,128,SEEK_END); mmioRead(m_hMMIO,(HPSTR)id3,3); if (id3[0] == 'T' && id3[1] == 'A' && id3[2] == 'G'){ m_lFileSize -= 128; } } // MP3 フォーマットの確認 mmioSeek(m_hMMIO,m_lStreamStartOffset,SEEK_SET); m_lFilePos = m_lStreamStartOffset; mmioRead(m_hMMIO,(HPSTR)id3,4); // ストリームヘッダの読み込み CheckMP3Header(id3); mmioSeek(m_hMMIO,m_lFilePos,SEEK_SET); m_bSourceStreamActive = TRUE; return S_OK; } CMP3StreamSound::~CMP3StreamSound(void) { m_bThreadDone = TRUE; if (m_hTerminator != NULL){ SetEvent(m_hTerminator); while(m_bThreadActive){ Sleep(10); } SAFE_CLOSEHANDLE(m_hThread); } for (int i = 0; i < MP3SOUNDSTREAM_NUM_BLOCKS ; ++i) SAFE_CLOSEHANDLE(m_hNotificationEvent[i]); if (m_hAcmStream != NULL){ if (m_bAcmHeaderPrepared){ acmStreamUnprepareHeader(m_hAcmStream,&m_acmStreamHeader,0); m_bAcmHeaderPrepared = FALSE; } acmStreamClose(m_hAcmStream,0); } DeleteCriticalSection(&m_CriticalSection); SAFE_CLOSEHANDLE(m_hTerminator); SAFE_RELEASE(m_lpDSBuffer); SAFE_DELETE_ARRAY(m_pMP3Frame); SAFE_DELETE_ARRAY(m_pPcmBuffer); if (m_hMMIO != NULL){ mmioClose(m_hMMIO,0); m_hMMIO = NULL; } SAFE_DELETE_ARRAY(m_pFilename); m_pDSoundEnv->RemoveSoundObject(this); } // // 再生準備 // HRESULT CMP3StreamSound::Prepare(){ m_hMMIO = mmioOpen(m_pFilename,NULL,MMIO_READ); if (m_hMMIO == NULL){ _RPT0(_CRT_WARN,_T("ファイルが開けませんでした\n")); return E_FAIL; } CheckMP3Format(); // 再生フォーマットの決定 m_wfPcm.cbSize = sizeof(WAVEFORMATEX); m_wfPcm.wFormatTag = WAVE_FORMAT_PCM; m_wfPcm.nSamplesPerSec = m_wfMP3.wfx.nSamplesPerSec; if (0 != acmFormatSuggest(NULL, (WAVEFORMATEX*)&m_wfMP3, &m_wfPcm, sizeof(WAVEFORMATEX), ACM_FORMATSUGGESTF_WFORMATTAG|ACM_FORMATSUGGESTF_NSAMPLESPERSEC)){ goto e_Fail; } DWORD nAlign; nAlign = m_wfPcm.nBlockAlign; m_dwNotifySize = nAlign * (DWORD)m_wfPcm.nSamplesPerSec; m_dwNotifySize *= m_dwBufferLengthInSec; m_dwNotifySize /= m_dwNumNotification; m_dwNotifySize -= m_dwNotifySize % nAlign; for (int i = 0; i < MP3SOUNDSTREAM_NUM_BLOCKS; ++i) m_hNotificationEvent[i] = CreateEvent( NULL, FALSE, FALSE, NULL ); m_dwSizeConverted = 0; if (FAILED(Create())){ goto e_Fail; } m_hTerminator = CreateEvent( NULL, FALSE, FALSE, NULL ); m_hThread = CreateThread(NULL,0,::MP3StreamSound_DoThread,(LPVOID)this,0,&m_dwThreadID); m_dwPhase = STREAMSOUND_STANDBY; return S_OK; e_Fail: return E_FAIL; } // // ストリームバッファの構築 // HRESULT CMP3StreamSound::Create(){ LPDIRECTSOUND8 pDS; DSBUFFERDESC dsbd; HRESULT hr; if( m_pDSoundEnv == NULL || m_hMMIO == NULL) return CO_E_NOTINITIALIZED; m_dwDSBufferSize = m_dwNumNotification * m_dwNotifySize; ZeroMemory( &dsbd, sizeof(DSBUFFERDESC) ); dsbd.dwSize = sizeof(DSBUFFERDESC); dsbd.dwFlags = DSBCAPS_CTRLVOLUME | DSBCAPS_CTRLFREQUENCY | DSBCAPS_CTRLPAN | DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_LOCSOFTWARE ; dsbd.dwBufferBytes = m_dwDSBufferSize; dsbd.guid3DAlgorithm = GUID_NULL; dsbd.lpwfxFormat = &m_wfPcm; pDS = m_pDSoundEnv->GetDirectSound(); hr = pDS->CreateSoundBuffer( &dsbd, &m_lpDSBuffer, NULL ); SAFE_RELEASE(pDS); if (FAILED(hr)){ _RPT1(_CRT_WARN,_T("CreateSoundBuffer 失敗 0x%x"),hr); goto e_Fail; } SetNotify(); m_dwLastPlayPos = 0; m_dwPlayProgress = 0; m_dwNextWriteOffset = 0; FillBufferWithSound(m_lpDSBuffer); return S_OK; e_Fail: return hr; } //----------------------------------------------------------------------------- // Name: CMP3StreamSound::MP3StreamReadAndDecode() // Desc: サウンドファイルをバッファに転送する。 //----------------------------------------------------------------------------- HRESULT CMP3StreamSound::MP3StreamReadAndDecode(void *buffer,LONG lSize){ LONG lWritten = 0; HRESULT hr = E_FAIL; INT bits = (m_wfPcm.wBitsPerSample *m_wfPcm.nChannels) / 8; BYTE *pwrite = (BYTE*)buffer; if (m_pPcmBuffer == NULL){ m_dwPcmBufferSize = 1152 * bits; m_pPcmBuffer = new BYTE[m_dwPcmBufferSize]; } while (lSize > lWritten){ // バッファに残りがあれば書き込む if (m_dwPcmBufferSizeRemain > 0){ LONG lSizeWrite = lSize - lWritten; if (lSizeWrite > m_dwPcmBufferSizeRemain){ lSizeWrite = m_dwPcmBufferSizeRemain; } ::CopyMemory((BYTE*)pwrite,m_pPcmBufferCurrent,lSizeWrite); pwrite += lSizeWrite; lWritten += lSizeWrite; m_pPcmBufferCurrent += lSizeWrite; m_dwPcmBufferSizeRemain -= lSizeWrite; if (m_dwPcmBufferSizeRemain <= 0){ m_pPcmBufferCurrent = NULL; m_dwPcmBufferSizeRemain = 0; }else{ continue; } } if (!m_bSourceStreamActive){ // 再生が終了していたら残りを0 で埋める LONG lSizeWrite = lSize - lWritten; ZeroMemory(pwrite,lSizeWrite); lWritten += lSizeWrite; break; } if (m_pPcmBuffer == NULL){ m_bSourceStreamActive = FALSE; continue; } m_pPcmBufferCurrent = m_pPcmBuffer; m_dwPcmBufferSizeRemain = 0; BYTE id[4]; MMRESULT mmr; mmioRead(m_hMMIO,(HPSTR)id,4); // フレームヘッダ読み込み CheckMP3Header(id); if (!m_bAcmHeaderPrepared || m_iFrameSize > m_iSourceBufferSize){ if (m_bAcmHeaderPrepared){ acmStreamUnprepareHeader(m_hAcmStream,&m_acmStreamHeader,0); acmStreamClose(m_hAcmStream,0); m_bAcmHeaderPrepared = FALSE; } mmr = acmStreamOpen(&m_hAcmStream,NULL,(LPWAVEFORMATEX)&m_wfMP3,&m_wfPcm, NULL, 0, 0, ACM_STREAMOPENF_NONREALTIME); if (mmr != 0){ _RPT1(_CRT_WARN,_T("acmがオープンできませんでした エラーコード%d\n"),mmr); m_bSourceStreamActive = FALSE; continue; } if (m_pMP3Frame == NULL){ _RPT0(_CRT_WARN,_T("メモリが確保できませんでした")); m_bSourceStreamActive = FALSE; continue; } if (m_iFrameSize > m_iSourceBufferSize){ SAFE_DELETE_ARRAY(m_pMP3Frame); m_pMP3Frame = new BYTE[m_iFrameSize]; m_iSourceBufferSize = m_iFrameSize; } if (m_pPcmBuffer != NULL){ ZeroMemory(&m_acmStreamHeader, sizeof(ACMSTREAMHEADER)); m_acmStreamHeader.cbStruct = sizeof(ACMSTREAMHEADER); m_acmStreamHeader.pbSrc = m_pMP3Frame; m_acmStreamHeader.cbSrcLength = m_iSourceBufferSize; m_acmStreamHeader.pbDst = m_pPcmBuffer; m_acmStreamHeader.cbDstLength = m_dwPcmBufferSize; acmStreamPrepareHeader(m_hAcmStream,&m_acmStreamHeader,0); m_bAcmHeaderPrepared = TRUE; }else{ m_bSourceStreamActive = FALSE; continue; } } // History の保存 m_pHistory[m_lHistoryWrite].lFilePos = m_lFilePos; m_pHistory[m_lHistoryWrite].lPcmPos = m_dwSizeConverted; m_lHistoryWrite = (m_lHistoryWrite + 1) % MP3SOUNDSTREAM_NUM_HISTORY; if (m_lHistoryWrite == m_lHistoryRead){ m_lHistoryRead = (m_lHistoryRead + 1) % MP3SOUNDSTREAM_NUM_HISTORY; } CopyMemory(m_pMP3Frame,id,4); mmioRead(m_hMMIO,(HPSTR)m_pMP3Frame+4,m_iFrameSize - 4); DWORD dwLengthSaved = m_acmStreamHeader.cbSrcLength; m_acmStreamHeader.cbSrcLength = m_iFrameSize; acmStreamConvert(m_hAcmStream, &m_acmStreamHeader, 0); m_dwPcmBufferSizeRemain = m_acmStreamHeader.cbDstLengthUsed; m_dwSizeConverted += m_acmStreamHeader.cbDstLengthUsed; m_acmStreamHeader.cbSrcLength = dwLengthSaved; m_lFilePos += m_iFrameSize; if ((m_lFilePos - m_lStreamStartOffset) >= m_lFileSize){ m_bSourceStreamActive = FALSE; m_lPCMSizeDetected = m_dwSizeConverted; } } return S_OK; } //----------------------------------------------------------------------------- // Name: CMP3StreamSound::FillBufferWithSound() // Desc: サウンドファイルをバッファに転送する。 //----------------------------------------------------------------------------- HRESULT CMP3StreamSound::FillBufferWithSound( LPDIRECTSOUNDBUFFER pDSB ) { HRESULT hr; VOID* pDSLockedBuffer = NULL; // ロックされたバッファメモリへのポインタ DWORD dwDSLockedBufferSize = 0; // ロックされたバッファメモリのサイズ if( pDSB == NULL ) return CO_E_NOTINITIALIZED; if( FAILED( hr = RestoreBuffer( pDSB, NULL ) ) ) { _RPT0(_CRT_WARN, _T("RestoreBuffer")); return hr; } // 再生開始点を、再生進行カウンタにコピー m_dwPlayProgress = m_dwSizeConverted; // サウンドバッファのロック if( FAILED( hr = pDSB->Lock( 0, m_dwDSBufferSize, &pDSLockedBuffer, &dwDSLockedBufferSize, NULL, NULL, 0L ) ) ){ _RPT1(_CRT_WARN, _T("Lock"), hr ); return hr; } if (m_bAcmHeaderPrepared){ acmStreamUnprepareHeader(m_hAcmStream,&m_acmStreamHeader,0); acmStreamClose(m_hAcmStream,0); m_bAcmHeaderPrepared = FALSE; } MP3StreamReadAndDecode((BYTE*) pDSLockedBuffer,dwDSLockedBufferSize); pDSB->Unlock( pDSLockedBuffer, dwDSLockedBufferSize, NULL, 0 ); return S_OK; } //----------------------------------------------------------------------------- // Name: CMP3StreamSound::RestoreBuffer() // Desc: 失われたバッファをリストアする //----------------------------------------------------------------------------- HRESULT CMP3StreamSound::RestoreBuffer( LPDIRECTSOUNDBUFFER pDSB, BOOL* pbWasRestored ) { HRESULT hr; if( pDSB == NULL ) return CO_E_NOTINITIALIZED; if( pbWasRestored ) *pbWasRestored = FALSE; DWORD dwStatus; if( FAILED( hr = pDSB->GetStatus( &dwStatus ) ) ){ _RPT1(_CRT_WARN,_T("GetStatus %x\n"), hr ); return hr; } if( dwStatus & DSBSTATUS_BUFFERLOST ){ while(true){ hr = pDSB->Restore(); if( hr != DSERR_BUFFERLOST ) break; Sleep( 10 ); } if( pbWasRestored != NULL ) *pbWasRestored = TRUE; m_dwPlayProgress = 0L; return S_OK; }else{ return S_FALSE; } } // // CMP3StreamSound::SetNotify() // Notification イベントの実装 // HRESULT CMP3StreamSound::SetNotify() { HRESULT hr; DSBPOSITIONNOTIFY* pPosNotify = NULL; LPDIRECTSOUNDNOTIFY pDSNotify = NULL; DWORD dwC; if( FAILED( hr = m_lpDSBuffer->QueryInterface( IID_IDirectSoundNotify, (VOID**)&pDSNotify ) ) ) { _RPT1(_CRT_WARN,_T("QueryInterface 失敗 0x%x"),hr); goto e_Fail; } pPosNotify = new DSBPOSITIONNOTIFY[ m_dwNumNotification]; for( dwC = 0; dwC < m_dwNumNotification; dwC++ ) { pPosNotify[dwC].dwOffset = (m_dwNotifySize * dwC) + m_dwNotifySize - 1; pPosNotify[dwC].hEventNotify = m_hNotificationEvent[dwC]; } if( FAILED( hr = pDSNotify->SetNotificationPositions( m_dwNumNotification, pPosNotify ) ) ) { _RPT0(_CRT_WARN, _T("SetNotificationPositions") ); goto e_Fail; } SAFE_RELEASE( pDSNotify ); SAFE_DELETE_ARRAY( pPosNotify ); return S_OK; e_Fail: SAFE_RELEASE( pDSNotify ); SAFE_DELETE_ARRAY( pPosNotify ); return hr; } //---------------------------------------------------- // CMP3StreamSound::DoThread() // Streaming のスレッドを実行! //---------------------------------------------------- void CMP3StreamSound::DoThread(){ DWORD dwEvent; HRESULT hr; int i; m_bThreadActive = TRUE; HANDLE hEvents[MP3SOUNDSTREAM_NUM_BLOCKS + 1]; for (i = 0; i < MP3SOUNDSTREAM_NUM_BLOCKS ; ++i) hEvents[i] = m_hNotificationEvent[i]; hEvents[i] = m_hTerminator; while(!m_bThreadDone){ dwEvent = WaitForMultipleObjects( MP3SOUNDSTREAM_NUM_BLOCKS + 1, hEvents, FALSE, INFINITE); if (dwEvent >= WAIT_OBJECT_0 && dwEvent < WAIT_OBJECT_0 + MP3SOUNDSTREAM_NUM_BLOCKS){ //_RPT1(_CRT_WARN, _T("Notify:%d\n"),dwEvent - WAIT_OBJECT_0); EnterCriticalSection(&m_CriticalSection); hr = HandleNotificationEvent(); LeaveCriticalSection(&m_CriticalSection); if (FAILED(hr)){ m_lpDSBuffer->Stop(); m_dwPhase = STREAMSOUND_STOP; } } if (dwEvent == WAIT_OBJECT_0 + MP3SOUNDSTREAM_NUM_BLOCKS){ } } m_bThreadActive = FALSE; } //----------------------------------------------------------------------------- // Name: CMP3StreamSound::HandleNotificationEvent() // Desc: Notification のイベントハンドラ //----------------------------------------------------------------------------- HRESULT CMP3StreamSound::HandleNotificationEvent( ) { HRESULT hr; DWORD dwCurrentPlayPos; DWORD dwPlayDelta; VOID* pDSLockedBuffer = NULL; VOID* pDSLockedBuffer2 = NULL; DWORD dwDSLockedBufferSize; DWORD dwDSLockedBufferSize2; if( m_lpDSBuffer == NULL ) return CO_E_NOTINITIALIZED; // バッファのリストア BOOL bRestored; if( FAILED( hr = RestoreBuffer( m_lpDSBuffer, &bRestored ) ) ){ _RPT0(_CRT_WARN,_T("RestoreBuffer")); return hr; } if( bRestored ) { if( FAILED( hr = FillBufferWithSound( m_lpDSBuffer ) ) ){ _RPT0(_CRT_WARN,_T("FillBufferWithSound")); return hr; } return S_OK; } // DirectSoundBuffer をロック if( FAILED( hr = m_lpDSBuffer->Lock( m_dwNextWriteOffset, m_dwNotifySize, &pDSLockedBuffer, &dwDSLockedBufferSize, &pDSLockedBuffer2, &dwDSLockedBufferSize2, 0L ) ) ){ _RPT0(_CRT_WARN,_T("Lock")); return hr; } // m_dwDSBufferSize および m_dwNextWriteOffset はともに、 // m_dwNotifySize の倍数であるため、 pDSLockedBuffer2 は有効になる事は無い。 if( pDSLockedBuffer2 != NULL ){ return E_UNEXPECTED; } if( FAILED( hr = MP3StreamReadAndDecode((void*)pDSLockedBuffer,dwDSLockedBufferSize))){ m_lpDSBuffer->Unlock( pDSLockedBuffer, dwDSLockedBufferSize, NULL, 0 ); _RPT0(_CRT_WARN,_T("Read")); return hr; } m_lpDSBuffer->Unlock( pDSLockedBuffer, dwDSLockedBufferSize, NULL, 0 ); if( FAILED( hr = m_lpDSBuffer->GetCurrentPosition( &dwCurrentPlayPos, NULL ) ) ){ _RPT0(_CRT_WARN,_T("GetCurrentPosition")); return hr; } // 前回の再生地点からの進行量をチェックする。 if( dwCurrentPlayPos < m_dwLastPlayPos ) dwPlayDelta = ( m_dwDSBufferSize - m_dwLastPlayPos ) + dwCurrentPlayPos; else dwPlayDelta = dwCurrentPlayPos - m_dwLastPlayPos; m_dwPlayProgress += dwPlayDelta; //_RPT2(_CRT_WARN,_T("Progress:%x total %x\n"),dwPlayDelta,m_dwPlayProgress); m_dwLastPlayPos = dwCurrentPlayPos; // 再生地点をチェックし、完了時点でバッファをストップする。 if( m_dwPlayProgress >= m_dwSizeConverted/* + m_dwNotifySize*/) { m_dwPhase = STREAMSOUND_STOP; m_lpDSBuffer->Stop(); } // 次回ロックするアドレスの更新 m_dwNextWriteOffset += dwDSLockedBufferSize; m_dwNextWriteOffset %= m_dwDSBufferSize; // Circular buffer return S_OK; } //---------------------------------------------------- // CMP3StreamSound::Play() // Streaming の再生開始! //---------------------------------------------------- HRESULT CMP3StreamSound::Play( DWORD dwPriority, DWORD dwFlags, LONG lVol, LONG lFreq, LONG lPan , LONG lFade){ HRESULT hr; BOOL bRestored; LPDIRECTSOUNDBUFFER pDSB = this->m_lpDSBuffer; FLOAT fFadeTime; FLOAT fVol1, fVol2; DWORD dwCurrent; if( pDSB == NULL ){ _RPT0(_CRT_WARN,_T("No Sound Buffer")); return E_FAIL; } pDSB->Stop(); EnterCriticalSection(&m_CriticalSection); switch(m_dwPhase){ case STREAMSOUND_STANDBY: case STREAMSOUND_STOP: case STREAMSOUND_RUN: case STREAMSOUND_FADEIN: case STREAMSOUND_FADEOUT: m_lVol = lVol; m_lPan = lPan; m_lFreq = lFreq; m_dwPhaseAfterReplay = STREAMSOUND_RUN; m_fFadeTime = 0.0f; m_fFadeLen = (FLOAT)lFade; if (lFade != 0){ m_dwPhaseAfterReplay = STREAMSOUND_FADEIN; } m_dwPlayProgress = 0; if (m_dwPhase != STREAMSOUND_STANDBY){ Reset(); FillBufferWithSound(pDSB); } break; case STREAMSOUND_PAUSE: if (!m_bSourceStreamActive && m_dwPlayProgress >= m_dwSizeConverted){ m_dwPhase = STREAMSOUND_STOP; goto exitPlay; } dwCurrent = m_dwPlayProgress; Reset(); // 再生再開時のポジショニング if (m_lHistoryWrite != m_lHistoryRead){ INT i = m_lHistoryWrite; INT ix = -1; while(i != m_lHistoryRead){ if (m_pHistory[i].lPcmPos < dwCurrent) break; ix = i; --i; if (i < 0) i += MP3SOUNDSTREAM_NUM_HISTORY; } if (ix < 0){ m_dwPhase = STREAMSOUND_STOP; goto exitPlay; } m_lFilePos = m_pHistory[ix].lFilePos; m_dwSizeConverted = m_pHistory[ix].lPcmPos; m_lHistoryRead = m_lHistoryWrite; mmioSeek(m_hMMIO,m_lFilePos,SEEK_SET); FillBufferWithSound(pDSB); } break; default: _RPT0(_CRT_WARN, _T("サウンドバッファが再生可能になっていません。") ); hr = E_FAIL; goto exitPlay; break; } pDSB->SetCurrentPosition(0); // Restore the buffer if it was lost if( FAILED( hr = RestoreBuffer( pDSB, &bRestored ) ) ){ _RPT0(_CRT_WARN,_T("RestoreBuffer")); goto exitPlay; } if( bRestored ) { // The buffer was restored, so we need to fill it with new data if( FAILED( hr = FillBufferWithSound( pDSB ) ) ){ _RPT0(_CRT_WARN,_T("FillBufferWithSound")); goto exitPlay; } } fFadeTime = m_fFadeTime; if (fFadeTime > m_fFadeLen) fFadeTime = m_fFadeLen; switch(m_dwPhaseAfterReplay){ case STREAMSOUND_FADEIN: fVol1 = DSBVOLUME_MIN * (m_fFadeLen - fFadeTime) / m_fFadeLen; fVol2 = (fFadeTime / m_fFadeLen) * m_lVol; lVol = (LONG)(fVol1 + fVol2); pDSB->SetVolume( lVol ); break; case STREAMSOUND_FADEOUT: fVol1 = DSBVOLUME_MIN * fFadeTime / m_fFadeLen; fVol2 = ((m_fFadeLen - fFadeTime) / m_fFadeLen) * m_lVol; lVol = (LONG)(fVol1 + fVol2); pDSB->SetVolume( lVol ); break; default: pDSB->SetVolume( m_lVol ); break; } pDSB->SetPan( m_lPan ); if( m_lFreq != -1 ){ pDSB->SetFrequency( m_lFreq ); } if (SUCCEEDED(hr = pDSB->Play( 0, dwPriority, dwFlags | DSBPLAY_LOOPING ))){ m_dwPhase = m_dwPhaseAfterReplay; } exitPlay: LeaveCriticalSection(&m_CriticalSection); return hr; } //---------------------------------------------------- // CMP3StreamSound::Stop() // Streaming の再生終了! //---------------------------------------------------- HRESULT CMP3StreamSound::Stop(){ HRESULT hr = S_OK; switch(m_dwPhase){ case STREAMSOUND_RUN: case STREAMSOUND_FADEIN: case STREAMSOUND_FADEOUT: EnterCriticalSection(&m_CriticalSection); hr = m_lpDSBuffer->Stop(); if (FAILED(hr)){ _RPT0(_CRT_WARN,_T("Stop")); } m_dwPhase = STREAMSOUND_STOP; hr = Reset(); LeaveCriticalSection(&m_CriticalSection); break; case STREAMSOUND_PAUSE: EnterCriticalSection(&m_CriticalSection); m_dwPhase = STREAMSOUND_STOP; hr = Reset(); LeaveCriticalSection(&m_CriticalSection); break; } return hr; } HRESULT CMP3StreamSound::Rewind(){ HRESULT hr; if (m_dwPhase == STREAMSOUND_STANDBY) return S_OK; hr = Stop(); if (FAILED(hr)) return hr; Reset(); hr = FillBufferWithSound(m_lpDSBuffer); return hr; } //----------------------------------------------------------------------------- // Name: CMP3StreamSound::Reset() // Desc: バッファの再生ポジションを先頭に戻す //----------------------------------------------------------------------------- HRESULT CMP3StreamSound::Reset() { HRESULT hr; if( m_lpDSBuffer == NULL || m_hMMIO == NULL ) return CO_E_NOTINITIALIZED; m_dwLastPlayPos = 0; m_dwPlayProgress = 0; m_dwNextWriteOffset = 0; BOOL bRestored; if( FAILED( hr = RestoreBuffer( m_lpDSBuffer, &bRestored ) ) ){ _RPT0( _CRT_WARN, _T("RestoreBuffer")); goto exitReset; } // m_pWaveFile->ResetFile(); // 一旦AVI の再生ポジションを巻き戻しておく! //m_dwAviCurrentPos = 0; if (m_hMMIO != NULL){ mmioSeek(m_hMMIO,m_lStreamStartOffset,SEEK_SET); m_lFilePos = m_lStreamStartOffset; m_bSourceStreamActive = TRUE; } hr = m_lpDSBuffer->SetCurrentPosition( 0L ); m_pPcmBufferCurrent = m_pPcmBuffer; m_dwPcmBufferSizeRemain = 0; exitReset: return hr; } //----------------------------------------------------------------------------- // Name: CStreamSound::FadeOut() // Desc: FadeOut 開始 //----------------------------------------------------------------------------- void CMP3StreamSound::FadeOut(LONG lFadeLen){ EnterCriticalSection(&m_CriticalSection); FLOAT t; switch(m_dwPhase){ case STREAMSOUND_RUN: m_fFadeTime = 0; m_fFadeLen = (FLOAT)lFadeLen; m_dwPhase = STREAMSOUND_FADEOUT; break; case STREAMSOUND_FADEIN: t = m_fFadeTime / m_fFadeLen; if (t > 1.0f) t = 1.0f; m_fFadeLen = (FLOAT)lFadeLen; m_fFadeTime = m_fFadeLen - (t * m_fFadeLen); m_dwPhase = STREAMSOUND_FADEOUT; break; case STREAMSOUND_PAUSE: switch(m_dwPhaseAfterReplay){ case STREAMSOUND_RUN: m_fFadeTime = 0; m_fFadeLen = (FLOAT)lFadeLen; m_dwPhaseAfterReplay = STREAMSOUND_FADEOUT; break; case STREAMSOUND_FADEIN: t = m_fFadeTime / m_fFadeLen; if (t > 1.0f) t = 1.0f; m_fFadeLen = (FLOAT)lFadeLen; m_fFadeTime = m_fFadeLen - (t * m_fFadeLen); m_dwPhaseAfterReplay = STREAMSOUND_FADEOUT; break; case STREAMSOUND_FADEOUT: break; } break; } LeaveCriticalSection(&m_CriticalSection); } //----------------------------------------------------------------------------- // Name: CMP3StreamSound::PollStatus() // Desc: FADE-OUT, FADE-IN の処理 //----------------------------------------------------------------------------- void CMP3StreamSound::PollStatus(FLOAT timeElapsed){ FLOAT fFadeTime; FLOAT fVol1, fVol2; LONG lVol; switch(m_dwPhase){ case STREAMSOUND_FADEIN: EnterCriticalSection(&m_CriticalSection); if (m_dwPhase == STREAMSOUND_FADEIN){ fFadeTime = m_fFadeTime + timeElapsed; if (fFadeTime > m_fFadeLen){ m_dwPhase = STREAMSOUND_RUN; fFadeTime = m_fFadeLen; } m_fFadeTime = fFadeTime; fVol1 = DSBVOLUME_MIN * (m_fFadeLen - fFadeTime) / m_fFadeLen; fVol2 = (fFadeTime / m_fFadeLen) * m_lVol; lVol = (LONG)(fVol1 + fVol2); m_lpDSBuffer->SetVolume( lVol ); } LeaveCriticalSection(&m_CriticalSection); break; case STREAMSOUND_FADEOUT: lVol = m_lVol; fFadeTime = m_fFadeTime + timeElapsed; if (fFadeTime > m_fFadeLen){ Stop(); break; } m_fFadeTime = fFadeTime; EnterCriticalSection(&m_CriticalSection); fVol1 = DSBVOLUME_MIN * fFadeTime / m_fFadeLen; fVol2 = ((m_fFadeLen - fFadeTime) / m_fFadeLen) * m_lVol; lVol = (LONG)(fVol1 + fVol2); m_lpDSBuffer->SetVolume( lVol ); LeaveCriticalSection(&m_CriticalSection); if (lVol == DSBVOLUME_MIN){ Stop(); } break; } } HRESULT CMP3StreamSound::Pause(){ if( m_lpDSBuffer == NULL || m_hMMIO == NULL ) return CO_E_NOTINITIALIZED; EnterCriticalSection(&m_CriticalSection); if (m_dwPhase == STREAMSOUND_RUN || m_dwPhase == STREAMSOUND_FADEIN || m_dwPhase == STREAMSOUND_FADEOUT){ m_lpDSBuffer->Stop(); m_dwPhaseAfterReplay = m_dwPhase; m_dwPhase = STREAMSOUND_PAUSE; } LeaveCriticalSection(&m_CriticalSection); return S_OK; } BOOL CMP3StreamSound::IsPlaying(){ return m_dwPhase == STREAMSOUND_RUN || m_dwPhase == STREAMSOUND_FADEIN || m_dwPhase == STREAMSOUND_FADEOUT; } DWORD WINAPI MP3StreamSound_DoThread(LPVOID pVoid){ ((CMP3StreamSound*)pVoid)->DoThread(); return 0L; }