#include "..\etool.h" #include "..\emanager.h" using namespace e2d; #if HIGHER_THAN_VS2010 #ifndef SAFE_DELETE #define SAFE_DELETE(p) { if (p) { delete (p); (p)=nullptr; } } #endif #ifndef SAFE_DELETE_ARRAY #define SAFE_DELETE_ARRAY(p) { if (p) { delete[] (p); (p)=nullptr; } } #endif inline bool TraceError(wchar_t* sPrompt) { WARN_IF(true, "Music error: %s failed!", sPrompt); return false; } inline bool TraceError(wchar_t* sPrompt, HRESULT hr) { WARN_IF(true, "Music error: %s (%#X)", sPrompt, hr); return false; } Music::Music() : m_bOpened(false) , m_bPlaying(false) , m_pwfx(nullptr) , m_hmmio(nullptr) , m_pResourceBuffer(nullptr) , m_pbWaveData(nullptr) , m_dwSize(0) , m_pSourceVoice(nullptr) { } e2d::Music::Music(String strFileName) : m_bOpened(false) , m_bPlaying(false) , m_pwfx(nullptr) , m_hmmio(nullptr) , m_pResourceBuffer(nullptr) , m_pbWaveData(nullptr) , m_dwSize(0) , m_pSourceVoice(nullptr) { this->open(strFileName); } Music::~Music() { close(); } bool Music::open(String strFileName) { if (m_bOpened) { WARN_IF(true, L"Music can be opened only once!"); return false; } if (strFileName.isEmpty()) { WARN_IF(true, L"Music::open Invalid file name."); return false; } IXAudio2 * pXAudio2 = MusicManager::getIXAudio2(); if (!pXAudio2) { WARN_IF(true, L"IXAudio2 nullptr pointer error!"); return false; } // 定位 wave 文件 wchar_t strFilePath[MAX_PATH]; if (!_findMediaFileCch(strFilePath, MAX_PATH, strFileName)) { WARN_IF(true, L"Failed to find media file: %s", (const wchar_t*)strFileName); return false; } m_hmmio = mmioOpen(strFilePath, nullptr, MMIO_ALLOCBUF | MMIO_READ); if (nullptr == m_hmmio) { return TraceError(L"mmioOpen"); } if (!_readMMIO()) { // 读取非 wave 文件时 ReadMMIO 调用失败 mmioClose(m_hmmio, 0); return TraceError(L"_readMMIO"); } if (!_resetFile()) return TraceError(L"_resetFile"); // 重置文件后,wave 文件的大小是 m_ck.cksize m_dwSize = m_ck.cksize; // 将样本数据读取到内存中 m_pbWaveData = new BYTE[m_dwSize]; if (!_read(m_pbWaveData, m_dwSize)) { TraceError(L"Failed to read WAV data"); SAFE_DELETE_ARRAY(m_pbWaveData); return false; } // 创建音源 HRESULT hr; if (FAILED(hr = pXAudio2->CreateSourceVoice(&m_pSourceVoice, m_pwfx))) { TraceError(L"Error %#X creating source voice", hr); SAFE_DELETE_ARRAY(m_pbWaveData); return false; } m_bOpened = true; m_bPlaying = false; return true; } bool Music::play(int nLoopCount) { if (!m_bOpened) { WARN_IF(true, "Music::play Failed: Music must be opened first!"); return false; } if (m_pSourceVoice == nullptr) { WARN_IF(true, "Music::play Failed: IXAudio2SourceVoice Null pointer exception!"); return false; } if (m_bPlaying) { stop(); } nLoopCount = min(nLoopCount, XAUDIO2_LOOP_INFINITE - 1); nLoopCount = (nLoopCount < 0) ? XAUDIO2_LOOP_INFINITE : nLoopCount; // 提交 wave 样本数据 XAUDIO2_BUFFER buffer = { 0 }; buffer.pAudioData = m_pbWaveData; buffer.Flags = XAUDIO2_END_OF_STREAM; buffer.AudioBytes = m_dwSize; buffer.LoopCount = nLoopCount; HRESULT hr; if (FAILED(hr = m_pSourceVoice->SubmitSourceBuffer(&buffer))) { TraceError(L"Error %#X submitting source buffer", hr); m_pSourceVoice->DestroyVoice(); SAFE_DELETE_ARRAY(m_pbWaveData); return false; } if (SUCCEEDED(hr = m_pSourceVoice->Start(0))) { m_bPlaying = true; } return SUCCEEDED(hr); } void Music::pause() { if (m_pSourceVoice) { if (SUCCEEDED(m_pSourceVoice->Stop())) { m_bPlaying = false; } } } void Music::resume() { if (m_pSourceVoice) { if (SUCCEEDED(m_pSourceVoice->Start())) { m_bPlaying = true; } } } void Music::stop() { if (m_pSourceVoice) { if (SUCCEEDED(m_pSourceVoice->Stop())) { m_pSourceVoice->ExitLoop(); m_pSourceVoice->FlushSourceBuffers(); m_bPlaying = false; } } } void Music::close() { if (m_pSourceVoice) { m_pSourceVoice->Stop(); m_pSourceVoice->FlushSourceBuffers(); m_pSourceVoice->DestroyVoice(); m_pSourceVoice = nullptr; } if (m_hmmio != nullptr) { mmioClose(m_hmmio, 0); m_hmmio = nullptr; } SAFE_DELETE_ARRAY(m_pResourceBuffer); SAFE_DELETE_ARRAY(m_pbWaveData); SAFE_DELETE_ARRAY(m_pwfx); m_bOpened = false; m_bPlaying = false; } bool Music::isPlaying() const { if (m_bOpened && m_pSourceVoice) { XAUDIO2_VOICE_STATE state; m_pSourceVoice->GetState(&state); if (state.BuffersQueued == 0) { m_bPlaying = false; } return m_bPlaying; } else { return false; } } double Music::getVolume() const { float fVolume = 0.0f; if (m_pSourceVoice) { m_pSourceVoice->GetVolume(&fVolume); } return static_cast(fVolume); } bool Music::setVolume(double fVolume) { if (m_pSourceVoice) { return SUCCEEDED(m_pSourceVoice->SetVolume(min(max(static_cast(fVolume), -224), 224))); } return false; } double Music::getFrequencyRatio() const { float fFrequencyRatio = 0.0f; if (m_pSourceVoice) { m_pSourceVoice->GetFrequencyRatio(&fFrequencyRatio); } return static_cast(fFrequencyRatio); } bool Music::setFrequencyRatio(double fFrequencyRatio) { if (m_pSourceVoice) { fFrequencyRatio = min(max(fFrequencyRatio, XAUDIO2_MIN_FREQ_RATIO), XAUDIO2_MAX_FREQ_RATIO); return SUCCEEDED(m_pSourceVoice->SetFrequencyRatio(static_cast(fFrequencyRatio))); } return false; } IXAudio2SourceVoice * Music::getIXAudio2SourceVoice() const { return m_pSourceVoice; } bool Music::_readMMIO() { MMCKINFO ckIn; PCMWAVEFORMAT pcmWaveFormat; memset(&ckIn, 0, sizeof(ckIn)); m_pwfx = nullptr; if ((0 != mmioDescend(m_hmmio, &m_ckRiff, nullptr, 0))) return TraceError(L"mmioDescend"); // 确认文件是一个合法的 wave 文件 if ((m_ckRiff.ckid != FOURCC_RIFF) || (m_ckRiff.fccType != mmioFOURCC('W', 'A', 'V', 'E'))) return TraceError(L"mmioFOURCC"); // 在输入文件中查找 'fmt' 块 ckIn.ckid = mmioFOURCC('f', 'm', 't', ' '); if (0 != mmioDescend(m_hmmio, &ckIn, &m_ckRiff, MMIO_FINDCHUNK)) return TraceError(L"mmioDescend"); // 'fmt' 块至少应和 PCMWAVEFORMAT 一样大 if (ckIn.cksize < (LONG)sizeof(PCMWAVEFORMAT)) return TraceError(L"sizeof(PCMWAVEFORMAT)"); // 将 'fmt' 块读取到 pcmWaveFormat 中 if (mmioRead(m_hmmio, (HPSTR)&pcmWaveFormat, sizeof(pcmWaveFormat)) != sizeof(pcmWaveFormat)) return TraceError(L"mmioRead"); // 分配 WAVEFORMATEX,但如果它不是 PCM 格式,再读取一个 WORD 大小 // 的数据,这个数据就是额外分配的大小 if (pcmWaveFormat.wf.wFormatTag == WAVE_FORMAT_PCM) { m_pwfx = (WAVEFORMATEX*)new CHAR[sizeof(WAVEFORMATEX)]; // 拷贝数据 memcpy(m_pwfx, &pcmWaveFormat, sizeof(pcmWaveFormat)); m_pwfx->cbSize = 0; } else { // 读取额外数据的大小 WORD cbExtraBytes = 0L; if (mmioRead(m_hmmio, (CHAR*)&cbExtraBytes, sizeof(WORD)) != sizeof(WORD)) return TraceError(L"mmioRead"); m_pwfx = (WAVEFORMATEX*)new CHAR[sizeof(WAVEFORMATEX) + cbExtraBytes]; // 拷贝数据 memcpy(m_pwfx, &pcmWaveFormat, sizeof(pcmWaveFormat)); m_pwfx->cbSize = cbExtraBytes; // 读取额外数据 if (mmioRead(m_hmmio, (CHAR*)(((BYTE*)&(m_pwfx->cbSize)) + sizeof(WORD)), cbExtraBytes) != cbExtraBytes) { SAFE_DELETE(m_pwfx); return TraceError(L"mmioRead"); } } if (0 != mmioAscend(m_hmmio, &ckIn, 0)) { SAFE_DELETE(m_pwfx); return TraceError(L"mmioAscend"); } return true; } bool Music::_resetFile() { // Seek to the data if (-1 == mmioSeek(m_hmmio, m_ckRiff.dwDataOffset + sizeof(FOURCC), SEEK_SET)) return TraceError(L"mmioSeek"); // Search the input file for the 'data' chunk. m_ck.ckid = mmioFOURCC('d', 'a', 't', 'a'); if (0 != mmioDescend(m_hmmio, &m_ck, &m_ckRiff, MMIO_FINDCHUNK)) return TraceError(L"mmioDescend"); return true; } bool Music::_read(BYTE* pBuffer, DWORD dwSizeToRead) { MMIOINFO mmioinfoIn; // current status of m_hmmio if (0 != mmioGetInfo(m_hmmio, &mmioinfoIn, 0)) return TraceError(L"mmioGetInfo"); UINT cbDataIn = dwSizeToRead; if (cbDataIn > m_ck.cksize) cbDataIn = m_ck.cksize; m_ck.cksize -= cbDataIn; for (DWORD cT = 0; cT < cbDataIn; cT++) { // Copy the bytes from the io to the buffer. if (mmioinfoIn.pchNext == mmioinfoIn.pchEndRead) { if (0 != mmioAdvance(m_hmmio, &mmioinfoIn, MMIO_READ)) return TraceError(L"mmioAdvance"); if (mmioinfoIn.pchNext == mmioinfoIn.pchEndRead) return TraceError(L"mmioinfoIn.pchNext"); } // Actual copy. *((BYTE*)pBuffer + cT) = *((BYTE*)mmioinfoIn.pchNext); mmioinfoIn.pchNext++; } if (0 != mmioSetInfo(m_hmmio, &mmioinfoIn, 0)) return TraceError(L"mmioSetInfo"); return true; } bool Music::_findMediaFileCch(wchar_t* strDestPath, int cchDest, const wchar_t * strFilename) { bool bFound = false; if (nullptr == strFilename || nullptr == strDestPath || cchDest < 10) return false; // Get the exe name, and exe path wchar_t strExePath[MAX_PATH] = { 0 }; wchar_t strExeName[MAX_PATH] = { 0 }; wchar_t* strLastSlash = nullptr; GetModuleFileName(HINST_THISCOMPONENT, strExePath, MAX_PATH); strExePath[MAX_PATH - 1] = 0; strLastSlash = wcsrchr(strExePath, TEXT('\\')); if (strLastSlash) { wcscpy_s(strExeName, MAX_PATH, &strLastSlash[1]); // Chop the exe name from the exe path *strLastSlash = 0; // Chop the .exe from the exe name strLastSlash = wcsrchr(strExeName, TEXT('.')); if (strLastSlash) *strLastSlash = 0; } wcscpy_s(strDestPath, cchDest, strFilename); if (GetFileAttributes(strDestPath) != 0xFFFFFFFF) return true; // Search all parent directories starting at .\ and using strFilename as the leaf name wchar_t strLeafName[MAX_PATH] = { 0 }; wcscpy_s(strLeafName, MAX_PATH, strFilename); wchar_t strFullPath[MAX_PATH] = { 0 }; wchar_t strFullFileName[MAX_PATH] = { 0 }; wchar_t strSearch[MAX_PATH] = { 0 }; wchar_t* strFilePart = nullptr; GetFullPathName(L".", MAX_PATH, strFullPath, &strFilePart); if (strFilePart == nullptr) return false; while (strFilePart != nullptr && *strFilePart != '\0') { swprintf_s(strFullFileName, MAX_PATH, L"%s\\%s", strFullPath, strLeafName); if (GetFileAttributes(strFullFileName) != 0xFFFFFFFF) { wcscpy_s(strDestPath, cchDest, strFullFileName); bFound = true; break; } swprintf_s(strFullFileName, MAX_PATH, L"%s\\%s\\%s", strFullPath, strExeName, strLeafName); if (GetFileAttributes(strFullFileName) != 0xFFFFFFFF) { wcscpy_s(strDestPath, cchDest, strFullFileName); bFound = true; break; } swprintf_s(strSearch, MAX_PATH, L"%s\\..", strFullPath); GetFullPathName(strSearch, MAX_PATH, strFullPath, &strFilePart); } if (bFound) return true; // 失败时,将文件作为路径返回,同时也返回错误代码 wcscpy_s(strDestPath, cchDest, strFilename); return false; } #else e2d::Music::Music() : m_wnd(NULL) , m_dev(0L) , m_nMusicID(0) , m_bPlaying(false) , m_nRepeatTimes(0) { m_wnd = CreateWindowEx( WS_EX_APPWINDOW, MUSIC_CLASS_NAME, NULL, WS_POPUPWINDOW, 0, 0, 0, 0, NULL, NULL, MusicManager::getHInstance(), NULL); if (m_wnd) { SetWindowLongPtr(m_wnd, GWLP_USERDATA, (LONG_PTR)this); } } e2d::Music::~Music() { close(); DestroyWindow(m_wnd); } bool e2d::Music::open(String pFileName) { if (pFileName.isEmpty()) return false; close(); MCI_OPEN_PARMS mciOpen = { 0 }; mciOpen.lpstrDeviceType = 0; mciOpen.lpstrElementName = pFileName; MCIERROR mciError; mciError = mciSendCommand( 0, MCI_OPEN, MCI_OPEN_ELEMENT, reinterpret_cast(&mciOpen) ); if (mciError == 0) { m_dev = mciOpen.wDeviceID; m_nMusicID = pFileName.getHashCode(); m_bPlaying = false; return true; } return false; } bool e2d::Music::play(int nLoopCount) { if (!m_dev) { return false; } MCI_PLAY_PARMS mciPlay = { 0 }; mciPlay.dwCallback = reinterpret_cast(m_wnd); // 播放声音 MCIERROR mciError = mciSendCommand( m_dev, MCI_PLAY, MCI_FROM | MCI_NOTIFY, reinterpret_cast(&mciPlay) ); if (!mciError) { m_bPlaying = true; m_nRepeatTimes = nLoopCount; return true; } return false; } void e2d::Music::close() { if (m_bPlaying) { stop(); } if (m_dev) { _sendCommand(MCI_CLOSE); } m_dev = 0; m_bPlaying = false; } void e2d::Music::pause() { _sendCommand(MCI_PAUSE); m_bPlaying = false; } void e2d::Music::resume() { _sendCommand(MCI_RESUME); m_bPlaying = true; } void e2d::Music::stop() { _sendCommand(MCI_STOP); m_bPlaying = false; } bool e2d::Music::isPlaying() const { return m_bPlaying; } double Music::getVolume() const { return 1.0f; } bool Music::setVolume(double fVolume) { return false; } double Music::getFrequencyRatio() const { return 1.0f; } bool Music::setFrequencyRatio(double fFrequencyRatio) { return false; } void e2d::Music::_sendCommand(int nCommand, DWORD_PTR param1, DWORD_PTR parma2) { // 空设备时忽略这次操作 if (!m_dev) { return; } // 向当前设备发送操作 mciSendCommand(m_dev, nCommand, param1, parma2); } LRESULT WINAPI e2d::Music::MusicProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { e2d::Music * pPlayer = NULL; if (Msg == MM_MCINOTIFY && wParam == MCI_NOTIFY_SUCCESSFUL && (pPlayer = (Music *)GetWindowLongPtr(hWnd, GWLP_USERDATA))) { if (pPlayer->m_nRepeatTimes > 0) { pPlayer->m_nRepeatTimes--; } if (pPlayer->m_nRepeatTimes) { mciSendCommand(static_cast(lParam), MCI_SEEK, MCI_SEEK_TO_START, 0); MCI_PLAY_PARMS mciPlay = { 0 }; mciPlay.dwCallback = reinterpret_cast(hWnd); mciSendCommand(static_cast(lParam), MCI_PLAY, MCI_NOTIFY, reinterpret_cast(&mciPlay)); } else { pPlayer->m_bPlaying = false; return 0; } } return DefWindowProc(hWnd, Msg, wParam, lParam); } #endif