2016-07-26 114 views
1

我有一個C++ Win32應用程序需要能夠在每次觸發某個事件時播放外部文件.wav。目前,我有代碼看起來像這樣:Win32 PlaySound重疊音頻

void CALLBACK timerCall(HWND hwnd, UINT msg, UINT timer, DWORD time) 
{ 
    if(/*some condition is met*/) 
    { 
     std::cout << "Detected event" << std::endl; 
     PlaySound("file.wav", NULL, SND_FILENAME | SND_ASYNC); 
    } 
} 

的問題是,該.wav文件是幾秒鐘長,我想事件的每次調用播放聲音的新實例。沒有SND_ASYNC那裏它不會觸發事件,直到聲音結束播放;這是通過添加SND_ASYNC解決的。但是,現在如果在聲音已經播放時再次觸發事件,則會中斷播放並簡單地重新開始而不是重疊聲音。

如何阻止PlaySound的新呼叫中斷前一個操作並強制聲音重疊?

回答

2

「waveaudio」設備(由PlaySound使用)不支持同時播放多個文件。嘗試使用.mp3文件。下面的例子。

#include <Windows.h> 
#include <stdio.h> 
#include <stdexcept> 

#ifdef _UNICODE 
#define stprintf_s swprintf_s 
#else 
#define stprintf_s sprintf_s 
#endif 

class Player { 
public: 
    Player(LPCTSTR lpFileName) { 
     MCI_OPEN_PARMS openp; 
     MCI_SET_PARMS setp; 
     openp.dwCallback = NULL; 
     openp.lpstrDeviceType = reinterpret_cast<LPCTSTR>(MCI_ALL_DEVICE_ID); 
     openp.lpstrElementName = lpFileName; 
     TCHAR name[32]; 
     static int alias = 0; 
     stprintf_s(name, TEXT("alias%08d"), alias++); 
     openp.lpstrAlias = name; 
     checkerror(mciSendCommand(0, MCI_OPEN, MCI_WAIT | MCI_OPEN_ELEMENT | MCI_OPEN_SHAREABLE | MCI_OPEN_ALIAS, reinterpret_cast<DWORD_PTR>(&openp))); 
     _device = openp.wDeviceID; 
     setp.dwCallback = NULL; 
     setp.dwTimeFormat = 0; 
     if (mciSendCommand(openp.wDeviceID, MCI_SET, MCI_SET_TIME_FORMAT, reinterpret_cast<DWORD_PTR>(&setp)) == DWORD(-1)) { 
      close(); 
      throw std::runtime_error("Can't open MCI device"); 
     } 
    } 

    Player(Player const&) = delete; 

    Player(Player&& other) { 
     _device = other._device; 
     other._device = 0; 
    } 

    ~Player() { 
     if (_device != 0) { 
      close(); 
     } 
    } 

    void play(HWND hWndNotify) { 
     MCI_PLAY_PARMS params; 
     params.dwCallback = reinterpret_cast<DWORD_PTR>(hWndNotify); 
     params.dwFrom = NULL; 
     params.dwTo = NULL; 
     checkerror(mciSendCommand(_device, MCI_PLAY, (hWndNotify != 0) ? MCI_NOTIFY : 0, reinterpret_cast<DWORD_PTR>(&params))); 
    } 

    void rewind() { 
     MCI_SEEK_PARMS params; 
     checkerror(mciSendCommand(_device, MCI_SEEK, MCI_WAIT | MCI_SEEK_TO_START, reinterpret_cast<DWORD_PTR>(&params))); 
    } 

    void pause() { 
     MCI_GENERIC_PARMS params; 
     params.dwCallback = NULL; 
     checkerror(mciSendCommand(_device, MCI_PAUSE, MCI_WAIT, reinterpret_cast<DWORD_PTR>(&params))); 
    } 

    void stop() { 
     MCI_GENERIC_PARMS params; 
     params.dwCallback = NULL; 
     checkerror(mciSendCommand(_device, MCI_STOP, MCI_WAIT, reinterpret_cast<DWORD_PTR>(&params))); 
    } 

    MCIDEVICEID device() const { return _device; } 

private: 
    MCIDEVICEID _device; 

    static void checkerror(MCIERROR code) { 
     if (code != 0) { 
      char buffer[260]; 
      mciGetErrorStringA(code, buffer, sizeof(buffer) - 1); 
      throw std::runtime_error(buffer); 
     } 
    } 

    void close() { 
     MCI_GENERIC_PARMS params; 
     params.dwCallback = NULL; 
     checkerror(mciSendCommand(_device, MCI_CLOSE, MCI_WAIT, reinterpret_cast<DWORD_PTR>(&params))); 
    } 

}; 


#include <map> 
#include <string> 
#include <mutex> 

#ifdef _UNICODE 
typedef std::wstring tstring; 
#else 
typedef std::string tstring; 
#endif 

class Repeater { 
public: 

    Repeater(LPCTSTR fn) : fn(fn) { 
     hWnd = CreateWindowEx(0, TEXT("STATIC"), NULL, 0, 0, 0, 0, 0, HWND_DESKTOP, NULL, GetModuleHandle(NULL), 0); 
     if (hWnd == NULL) 
      throw std::runtime_error("Can't create window"); 
     oldproc = reinterpret_cast<WNDPROC>(GetWindowLongPtr(hWnd, GWLP_WNDPROC)); 
     SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LONG>(this)); 
     SetWindowLongPtr(hWnd, GWLP_WNDPROC, reinterpret_cast<LONG>(&myproc)); 
    } 

    ~Repeater() { 
     DestroyWindow(hWnd); 
    } 

    void play() { 
     Player player(fn.c_str()); 
     std::lock_guard<std::recursive_mutex> lock(devmap_mutex); 
     player.play(hWnd); 
     devmap.insert(decltype(devmap)::value_type(player.device(), std::move(player))); 
    } 

    HWND wnd() const { return hWnd; } 

    void stop() { 
     std::lock_guard<std::recursive_mutex> lock(devmap_mutex); 
     devmap.clear(); 
    } 


private: 
    HWND hWnd; 
    tstring fn; 
    std::recursive_mutex devmap_mutex; 
    std::map<MCIDEVICEID, Player> devmap; 
    WNDPROC oldproc; 

    static LRESULT CALLBACK myproc(_In_ HWND hWnd, _In_ UINT Msg, _In_ WPARAM wParam, _In_ LPARAM lParam) { 
     auto self = reinterpret_cast<Repeater*>(GetWindowLong(hWnd, GWLP_USERDATA)); 
     switch (Msg) { 
     case MM_MCINOTIFY: { 
      // see https://msdn.microsoft.com/ru-ru/library/windows/desktop/dd757358(v=vs.85).aspx 
      std::lock_guard<std::recursive_mutex> lock(self->devmap_mutex); 
      self->devmap.erase(static_cast<MCIDEVICEID>(lParam)); 
      return 0; 
     } 
     case WM_DESTROY: { 
      SetWindowLongPtr(hWnd, GWLP_WNDPROC, reinterpret_cast<LONG>(self->oldproc)); 
     } 
     default: 
      break; 
     } 
     return CallWindowProc(self->oldproc, hWnd, Msg, wParam, lParam); 
    } 

}; 

int main() { 
    // USE MP3. Forget about WAV. 
    LPCTSTR filename = TEXT("c:\\Users\\Vyacheslav\\Music\\Ori\\soundtrack\\Racing the Lava.mp3"); 

#if 0 
    // without notifications 
    Player dev1(filename), dev2(filename); 
    dev1.play(); 
    Sleep(1000); 
    dev2.play(); 
    Sleep(10000); 
#else 
    // with notifications 
    { 
     Repeater rep(filename); 

     std::thread thread([&rep] { 
      for (int i = 0; i < 5; ++i) { 
       rep.play(); 
       Sleep(1000); 
      } 
      Sleep(1000); 
      rep.stop(); // .stop() MUST be called from the same thread as ALL .play() !!! 
      PostMessage(rep.wnd(), WM_QUIT, 0, 0); // interrupt message processing queue 
     }); 

     MSG msg; 
     while (GetMessage(&msg, 0, 0, 0)) { 
      if (msg.message == WM_QUIT) 
       break; 
      TranslateMessage(&msg); 
      DispatchMessage(&msg); 
     } 
     thread.join(); 
    } 
    Sleep(10000); // silence 
#endif 

    return 0; 
} 
+0

現在它的行爲就好像我沒有'SND_ASYNC'(它以同步方式播放)。 –

+0

@ coder108解決方案已更新。 PlaySound不允許這樣做。 MCI不支持同時播放多個.wav文件。上述解決方案之後的下一步是使用DirectShow。 – slavanap

+0

謝謝你的迴應。在運行更新的代碼(我只替換了文件名)後,我收到錯誤「您正在使用的MCI設備不支持指定的命令」。我嘗試了使用MP3而不是WAV來調用mciSendString,並且仍然存在與之前相同的問題(我用代碼更新了原始問題) –