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

  • 相关阅读:
    7月15日考试 题解(链表+状压DP+思维题)
    暑假集训日记
    C# .NET 使用 NPOI 生成 .xlsx 格式 Excel
    JavaSE 基础 第42节 局部内部类
    JavaSE 基础 第41节 匿名内部类
    JavaSE 基础 第40节 内部类概述
    JavaSE 基础 第39节 接口的应用
    JavaSE 基础 第38节 接口的实现
    JavaSE 基础 第37节 接口概述
    JavaSE 基础 第36节 抽象类概述与使用
  • 原文地址:https://www.cnblogs.com/codeToSuccess/p/13906252.html
Copyright © 2011-2022 走看看