TXT音樂播放器與DirectSound與C++,開發筆記與EXE免費下載(一)

一、前言

之前提到,使用C語言開發TXT音樂播放器、使用PlaySound方法播放wav文件時,無法同時播放多個wav文件:當開始播放下一個wav文件時,之前正在播放的wav文件就會停止,導致音樂播放不連貫,卡頓,體驗極差。

通過百度發現,PlaySound方法確實是無法同時播放多個文件的,mciSendString也不行!

在找C語言的其它音樂播放方法的途中,終於找到了一個:DirectSound方法,可以實現同時播放多個wav文件,然後就開始長達多日的踩坑爬坑之旅......

 

二、軟件說明

由於各種原因,本次軟件採用C++開發,使用了MFC,使用了Microsoft DirectX SDK (June 2010),使用了自定義CWaveFile.h、CWaveFile.cpp、DxErr.h、dxerr.cpp等相關技術與文件,目前將最終生成的exe包與測試用音樂txt上傳到了CSDN上,大家可以免費下載;目前仍在完善中,敬請期待後續文章與資源。

CSDN免費下載鏈接:https://download.csdn.net/download/BHSZZY/12447447

二、軟件截圖

 

三、開發(踩坑)流程

1.首先,本人是想嘗試使用C語言中使用DirectSound方法的,並且也想順便寫個圖形界面;然而用C語言搞圖形界面實在是有難度(主要是百度不到),更重要的原因是DirectSound方法需要的兩個資源文件CWaveFile.h、CWaveFile.cpp,這兩個明顯是C++寫的,如果想直接用這兩個文件,那我也只能用C++開發了。至於這兩個文件是什麼,我會在下面提到。

 

2.決定了用C++開發圖形界面,然後開始百度,找了不少直接上代碼進行開發圖形界面的,然而看了半天還是不懂;這明顯比Java的JFrame複雜多了好吧!期間,我都想用VB、C#開發圖形界面了,然而又發現不能使用CWaveFile.cpp;最後,終於找到C++快速開發圖形界面的方法了:MFC

 

3.決定了用MFC,然而使用Visual Studio 2017新建項目時,發現不能創建MFC項目;又百度了半天,原來還得單獨下載這個功能;創建了MFC項目吧,又找不到哪裏直接拖控件;琢磨了半天,才發現創建MFC時要選擇“基於對話框”;創建完成後什麼頁面也沒有,還得自己從右側"資源視圖"標籤中,找到"Dialog"文件夾,打開裏面的文件,才能顯示圖形界面;然後再從左側工具箱把控件拖過去。

 

4.拖好控件後,雙擊控件可以進入對應的cpp代碼文件,並自動創建一個默認的事件函數,一般是onClick的;實際上這種方法有時不太好用(親測不好用);我創建的MFC自帶2個Dialog(都在同一個cpp中,一個主要的一個關於的,關於的那個窗口是內部class),本來我是在第1個dialog中雙擊控件的,然而不知道怎麼的跳轉到了第2個dialog中創建了onClick方法,然後我添加點擊事件後發現怎麼點按鈕都沒有觸發,程序又不報錯,很鬱悶(對MFC還是不熟的原因);後來才發現它給我生成的方法屬於第2個Dialog,略坑。

 

5.因此,最好右擊控件選擇“添加事件處理程序”,在“消息列表”中選擇需要的監聽函數(click、focus等),然後會自動生成相關的方法,在其中編寫處理邏輯即可。右擊控件菜單中的“添加變量”、“類嚮導”也挺好用的。

 

6.MFC線程問題:這也是個坑。點擊按鈕後,如果不使用線程執行,那麼在你的方法執行完畢之前,窗口是處於卡死狀態的,只有後續方法執行完畢後,窗口才能再次響應。因此必須開啓線程。網上有相關線程開啓方法,個人覺得還是thread好用,樣例如下:

要調用的begin方法:

void CMFC2Dlg::begin(char url[], int sleepTime, HWND nhwnd) {

//內容

}

//其中CMFC2Dlg::的意思是我在頭文件CMFC2Dlg.h中聲明瞭函數 static void begin(char url[], int sleepTime, HWND nhwnd);然後在CMFC2Dlg.cpp中實現了函數體。

啓動線程的方法:

thread th1(begin, url, sleepTime, m_hWnd);

th1.detach();

//其中thread是線程對象,需要#include<thread>與using namespace std;begin是函數名,之後是參數。

 

7.說起線程,不得不說一個坑,那就是C++線程中的方法(例如上方的begin)必須是靜態的(static),否則總會報錯(不支持begin與參數啥的),然而你直接搜C++使用線程時,網上的教程很少告訴你這一點,代碼例子中也沒有提到(他們的方法爲啥不加static呢?),就算是常識,個人覺得也應該寫明白,不然像我這樣的萌新是半天找不出來哪裏錯了的,一直以爲是thread的參數哪裏出問題了,換個其他開啓線程的方法能不能行(當然都不行,只要你的方法沒有寫static)。C++這個報錯報的也不明顯,你說不支持begin與參數啥的,我怎樣才能想到是由於沒有static呢?你就不能直說“該方法方法不是static,不能使用線程”嗎?

 

8.與線程相關的一個坑,由於我在線程中需要使用HWND的m_hWnd對象;衆所周知在Dialog的普通方法中是可以直接使用m_hWnd這個參數的,它在afxwin.h中(我的cpp中居然沒有引用,我懷疑創建時自動生成的代碼 #include "afxdialogex.h" 中包含了),就是一個窗體相關的對象;然而線程必須使用靜態方法(static),在static方法中使用m_hWnd會報錯,使用相關的獲取窗體m_hWnd的方法也會報錯,總之就是不能用;我猜是靜態方法創建時窗體還沒生成,導致不讓使用這個對象;那怎麼辦呢?卡了我半天,才反應過來,可以在普通方法中,在啓動線程時,把m_hWnd當成參數傳過去就可以了。

 

9.與C++報錯相關的一個坑,這是關於網上下載的CWaveFile.h、CWaveFile.cpp這兩個文件的;好不容易在網上找到大佬的這兩個文件的代碼,趕緊創建文件,複製、粘貼進去;放到C++裏一用,發現各種報錯,找不到對應的標識符什麼的;例如這一句:

        WAVEFORMATEX* m_pwfx;

然後報錯"無法識別的標識符WAVEFORMATEX",大概就這個意思,點開相關文件,發現"WAVEFORMATEX"被紅波浪線標註了;

然後我想,是缺少什麼相關的文件需要include嗎?

找了半天,添加了一堆頭文件,又出了一堆錯誤,依然不知道爲什麼;新增的錯誤是缺少其它頭文件,拜它影響,這個本質的錯誤我幾乎都忽略了。

後來我又刪除了項目,重新建立了一個,添加最少的頭文件,終於重新鎖定了這個錯誤,但是怎麼解決呢?"WAVEFORMATEX"還是被紅波浪線標註突。

突然靈光一現,想到了網上覆制的代碼有非空格的空白符的問題,於是刪掉了那句之前的空白,改爲:

WAVEFORMATEX* m_pwfx;//刪掉了前面的空格

居然就通過了!

在此不得不吐槽C++的報錯機制,你發現無法識別的非空格空白符了,那你直接標紅那些空白符就行了,你標紅"WAVEFORMATEX"是幾個意思?太容易讓人誤解了吧?

這還是.h文件的,內容較少,好修改;關於.cpp文件中的無法識別的非空格空白符,報錯也不明顯,語句又多,把每句之前和之後的多餘的空白符刪掉有些難度。

因此下方我會直接提供代碼的壓縮包,而不是源碼,導致出現非空格空白符錯誤讓人莫名其妙。

 

10.關於網上下載的CWaveFile.h、CWaveFile.cpp;首先,當你安裝Microsoft DirectX SDK (June 2010)時,我的在C:\Program Files (x86)\Microsoft DirectX SDK (June 2010)\Samples\C++\DXUT\Optional下,會有SDKwavefile.h與SDKwavefile.cpp兩個文件,意思一樣,不過使用時還需要  #include "DXUT.h"  等相關的頭文件,我整了半天,還是不會用;

因此找到了網上大佬自己封裝的無需DXUT.h的文件,使用時自己再加上#include "DxErr.h"就行。

 

11.關於CWaveFile.h、CWaveFile.cpp​​​​​​​的第二點:這兩個文件主要是用來讀取wav文件的,使用時要記得#include "CWaveFile.h"
#include <CWaveFile.cpp>;本人就是忘了包含cpp文件導致使用時找不到Open方法(還有包含cpp這種操作,百度這兩文件的使用方法居然不寫明白);還有關於#include "DxErr.h",#include <dxerr.h>;雖然CWaveFile.cpp中已經寫過了,可是會報錯,因此我刪掉了那一句並寫到了自己的主cpp中;雖然有大佬說直接刪掉就行,可是後續的相關變量就又要報錯了,爲了省事還是寫上吧。關於播放wav文件的Play方法,使用的是dsound.h中的方法。

 

12.關於C++靜態變量的坑:在C++中,需要在.h文件中聲明靜態變量(例如static int isPlay;),然後需要在.cpp文件中初始化(例如int CMFC2Dlg::isPlay = 1;),然後才能正常在cpp其它方法中使用。(與java不同,我想在方法中直接使用,結果就報錯了,還得初始化。)

 

13.關於VS2017使用DirectSound方法的環境的配置:這是個大坑,因此全程加粗。

本人配置了好幾次,總是出各種莫名其妙還很難百度的問題,因此刪了好幾次項目重新搭建,現在把可以使用的配置流程寫在下方:

(1)安裝Microsoft DirectX SDK (June 2010),如果出現s1023錯誤,就卸載更高的版本“Microsoft Visual C++ 2010 x86 Redistributable - 1010.0.40219”和“Microsoft Visual C++ 2010 x64 Redistributable - 1010.0.40219”,再重新安裝DXSDK_Jun10

(2)打開VS2017

(3)項目 - XXX(你的項目名)屬性 - VC++目錄,包含目錄 中增加 $(DXSDK_DIR) Include ;庫目錄中增加 $(DXSDK_DIR)Lib\x86

(4)項目 - XXX(你的項目名)屬性 - C/VC++ - 常規,附加包含目錄中增加 D:\headFile 。這是我自己創建的目錄,其中包含CWaveFile.h、CWaveFile.cpp​​​​​​​ 這兩個文件。順便,我在其中也放了dxerr.cpp和DxErr.h這兩個文件。

(5)CWaveFile.h、CWaveFile.cpp​​​​​​​這兩個文件從網上找代碼複製粘貼,有個大佬的CSDN中有,或者下載我之後的源代碼;dxerr.cpp和DxErr.h這兩個文件通過搜索你安裝的Microsoft DirectX SDK (June 2010)就能找到,複製出來放到自定義目錄下。

(6)項目 - XXX(你的項目名)屬性 - C/VC++ - 預處理器,預處理器定義中添加 “_CRT_SECURE_NO_WARNINGS” ,可以讓你正常使用例如fopen等方法,否則會報錯不安全而不讓使用。

(7)項目 - XXX(你的項目名)屬性 - 鏈接器 - 輸入,附加依賴項添加

d3d9.lib
d3dx10d.lib
d3dx9d.lib
dxguid.lib
winmm.lib
comctl32.lib
dsound.lib
DxErr.lib
legacy_stdio_definitions.lib

這是我用到的(反正添加後沒報錯,不礙事)。

(8)在主cpp中添加
#include <iostream>
#include<thread>
using namespace std;
#include <string.h>
#include <stdio.h>
#include  <direct.h>
#include <mmsystem.h>
#include <dsound.h>
#include "CWaveFile.h"
#include <CWaveFile.cpp>
#include "DxErr.h"
#include <dxerr.h>

還有一些是創建MFC自動生成的include,在此我就不寫了(不同版本的VS可能不同,我猜)

(9)終於可以使用DirectSound方法播放wav文件了(應該),在此貼一個播放代碼:

boolean CMFC2Dlg::playMusic(LPWSTR url, HWND nhwnd)
{
    
    LPDIRECTSOUNDBUFFER8 g_pDSBuffer8 = NULL; //buffer
    LPDIRECTSOUND8 g_pDsd = NULL; //dsound
    CWaveFile *g_pWaveFile = NULL;
    //下面初始化DirectSound工作。
    HRESULT hr;
    if (FAILED(hr = DirectSoundCreate8(NULL, &g_pDsd, NULL)))
        return FALSE;
    //設置設備的協作度
    if (FAILED(hr = g_pDsd->SetCooperativeLevel(nhwnd, DSSCL_PRIORITY)))
        return FALSE;
    
    g_pWaveFile = new CWaveFile;
    g_pWaveFile->Open((url), NULL, WAVEFILE_READ);
    DSBUFFERDESC dsbd;
    ZeroMemory(&dsbd, sizeof(DSBUFFERDESC));
    dsbd.dwSize = sizeof(DSBUFFERDESC);
    dsbd.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLFX | DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2;
    dsbd.dwBufferBytes = g_pWaveFile->GetSize();//MAX_AUDIO_BUF * BUFFERNOTIFYSIZE ; 
    dsbd.lpwfxFormat = g_pWaveFile->m_pwfx;
    LPDIRECTSOUNDBUFFER lpbuffer;
    //創建輔助緩衝區對象
    if (FAILED(hr = g_pDsd->CreateSoundBuffer(&dsbd, &lpbuffer, NULL)))
        return false;
    if (FAILED(hr = lpbuffer->QueryInterface(IID_IDirectSoundBuffer8, (LPVOID*)&g_pDSBuffer8)))
        return false;
    lpbuffer->Release();
    //準備工作做完了,下面就開始播放了
    LPVOID lplockbuf;
    DWORD len;
    DWORD dwWrite;

    g_pDSBuffer8->Lock(0, 0, &lplockbuf, &len, NULL, NULL, DSBLOCK_ENTIREBUFFER);
    g_pWaveFile->Read((BYTE*)lplockbuf, len, &dwWrite);
    g_pDSBuffer8->Unlock(lplockbuf, len, NULL, 0);
    g_pDSBuffer8->SetCurrentPosition(0);
    g_pDSBuffer8->Play(0, 0, NULL);

    return true;
}

//其中最後的Play方法可以傳參數循環播放(NULL那裏),url是wav地址,我用的絕對路徑;nhwnd是HWND對象,在普通方法中直接傳入m_hWnd即可(不用聲明,直接就有)

//DirectSound播放wav的方法比PlaySound等好太多了,優點上方有寫

(10)如果運行時發現DXGI_STATUS_OCCLUDED錯誤,在項目屬性-->VC++目錄-->包含目錄:將 $(WindowsSDK_IncludePath) 放在 $(DXSDK_DIR)Include 前面(網上這麼說)

不過現在,我的包含目錄是這樣的:$(DXSDK_DIR) Include;$(IncludePath)

並沒有這個$(WindowsSDK_IncludePath);

這個錯誤我之前遇到過,重新搭建項目就沒有發現了。

 

四、總結

本文主要講述了在Visual Studio 2017環境下基於C++中使用DirectSound播放wav文件的方法,由於是事後總結的,可能會有遺漏的地方,如果大家按照以上方法還是不能使用DirectSound,還請指出來,作者會查明原因並補充環境搭建流程,謝謝!

本文還免費分享了作者自制的C++版TXT音樂播放器.exe,按照指定格式寫好txt簡譜後就可以播放,便於扒譜獲得簡譜後測試是否正確。如有bug,還請指出,作者會繼續完善,謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章