zoukankan      html  css  js  c++  java
  • 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)SamplesC++DXUTOptional下,会有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)Libx86

    (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,还请指出,作者会继续完善,谢谢!

  • 相关阅读:
    单点登录
    Found conflicts between different versions of the same dependent assembly that could not be resolved
    在Visual Studio Code中使用C#以及.net core
    GitBlit中出现 error: remote unpack failed: error Missing tree
    net user
    SwitchyOmega
    What's the difference between Unicode and UTF-8?
    2>&1
    [C++基金会]位计算 游戏开发中的应用
    Broadcast Receiver注意事项
  • 原文地址:https://www.cnblogs.com/codeToSuccess/p/13906252.html
Copyright © 2011-2022 走看看