如何在C++程序中播放或打开*.mp3或*.wav音频文件?

19

我是一名计算机科学专业的学生。我有一个期末项目,需要开发一个带有图形和声音的简短游戏。


1
WAV信息 - Michael
5
要得到有价值和有帮助的回答,而不是评论让你去搜索网络,你需要说明你已经尝试过什么。也就是说,展示一些尝试加载你的 .mp3 文件的示例代码。你是否尝试使用了任何现有的库(但失败了)?这些信息可以更有帮助。 - wmorrison365
3
附言:您可以查看这些库是如何实现的:http://en.cppreference.com/w/cpp/links/libs - wmorrison365
1
这个问题在SO上已经有答案了。你可以搜索一下,最受欢迎的似乎是FMOD和SDL,它们都被用在很多游戏中。 - PALEN
2
@Dudeson:谢谢。我相信这个页面是一样的。 - Michael
显示剩余2条评论
6个回答

68

首先,编写以下代码:

#include <Mmsystem.h>
#include <mciapi.h>
//these two headers are already included in the <Windows.h> header
#pragma comment(lib, "Winmm.lib")

打开*.mp3文件:

mciSendString("open \"*.mp3\" type mpegvideo alias mp3", NULL, 0, NULL);

播放 *.mp3 音频文件:

mciSendString("play mp3", NULL, 0, NULL);

播放并等待*.mp3播放完毕:

mciSendString("play mp3 wait", NULL, 0, NULL);

重新播放(从头开始播放)*.mp3文件:
mciSendString("play mp3 from 0", NULL, 0, NULL);

为了重放并等待*.mp3播放完成:

mciSendString("play mp3 from 0 wait", NULL, 0, NULL);

要播放*.mp3文件并使其像循环一样在结束时重新播放:

mciSendString("play mp3 repeat", NULL, 0, NULL);

如果您想在*.mp3播放完成后执行某些操作,那么您需要使用WNDCLASSEX结构的RegisterClassEx函数,使用CreateWindowEx函数创建窗口并在while循环中处理其消息,并使用GetMessage、TranslateMessage和DispatchMessage函数调用:
mciSendString("play mp3 notify", NULL, 0, hwnd); //hwnd is an handle to the window returned from CreateWindowEx. If this doesn't work, then replace the hwnd with MAKELONG(hwnd, 0).

在窗口过程中,添加case MM_MCINOTIFY:代码将在mp3播放完成时执行。 但是,如果您正在编写控制台应用程序并且不涉及 windows,则可以通过在dwCreationFlags参数中指定CREATE_SUSPENDED标志来创建处于暂停状态的线程,并将返回值保存在一个静态变量中,例如我称其为mp3。这个static变量的类型当然是HANDLE。 这里是此线程的lpStartAddressThreadProc
DWORD WINAPI MP3Proc(_In_ LPVOID lpParameter) //lpParameter can be a pointer to a structure that store data that you cannot access outside of this function. You can prepare this structure before `CreateThread` and give it's address in the `lpParameter`
{
    Data *data = (Data*)lpParameter; //If you call this structure Data, but you can call it whatever you want.
    while (true)
    {
        mciSendString("play mp3 from 0 wait", NULL, 0, NULL);
        //Do here what you want to do when the mp3 playback is over
        SuspendThread(GetCurrentThread()); //or the handle of this thread that you keep in a static variable instead
    }
}

现在你所要做的就是每次想要重新播放你的mp3时调用ResumeThread(mp3);,这样它就会在每次结束后自动重复播放。
你可以使用#define play_my_mp3 ResumeThread(mp3);来使你的代码更易读。
当然,如果你只想播放一次mp3文件并在其结束后执行其他操作,那么你可以删除while (true)SuspendThreadfrom 0代码。
如果你仅仅删除SuspendThread调用,那么声音将会一遍又一遍地播放,并且在每次结束后都会执行某些操作。这等同于:
mciSendString("play mp3 repeat notify", NULL, 0, hwnd); //or MAKELONG(hwnd, 0) instead

在Windows系统中。

在播放 *.mp3 文件的过程中暂停:

mciSendString("pause mp3", NULL, 0, NULL);

并且总结一下:
mciSendString("resume mp3", NULL, 0, NULL);

停止中间的过程:
mciSendString("stop mp3", NULL, 0, NULL);

请注意,您无法恢复已停止的声音,只能暂停,但您可以通过执行播放命令来重新播放它。

当您播放完这个*.mp3时,请不要忘记:

mciSendString("close mp3", NULL, 0, NULL);

所有这些操作也适用于(处理)wave文件,但是对于wave文件,您可以使用“waveaudio”而不是“mpegvideo”。此外,您可以直接播放它们而无需打开它们:
PlaySound("*.wav", GetModuleHandle(NULL), SND_FILENAME);

如果您不想指定模块的句柄:

sndPlaySound("*.wav", SND_FILENAME);

如果您不想等待播放完成:
PlaySound("*.wav", GetModuleHandle(NULL), SND_FILENAME | SND_ASYNC);
//or
sndPlaySound("*.wav", SND_FILENAME | SND_ASYNC);

要循环播放音频文件:

PlaySound("*.wav", GetModuleHandle(NULL), SND_FILENAME | SND_ASYNC | SND_LOOP);
//or
sndPlaySound("*.wav", SND_FILENAME | SND_ASYNC | SND_LOOP);

请注意,您必须同时指定 SND_ASYNCSND_LOOP 标志,因为您永远不会等待重复播放无数次的声音结束!
此外,您可以使用 fopen 函数打开波形文件,并使用 fread 函数将其所有字节复制到缓冲区(一个巨大的字节数组),然后:
PlaySound(buffer, GetModuleHandle(NULL), SND_MEMORY);
//or
PlaySound(buffer, GetModuleHandle(NULL), SND_MEMORY | SND_ASYNC);
//or
PlaySound(buffer, GetModuleHandle(NULL), SND_MEMORY | SND_ASYNC | SND_LOOP);
//or
sndPlaySound(buffer, SND_MEMORY);
//or
sndPlaySound(buffer, SND_MEMORY | SND_ASYNC);
//or
sndPlaySound(buffer, SND_MEMORY | SND_ASYNC | SND_LOOP);

可以使用OpenFileCreateFileCreateFile2函数之一,以及ReadFileReadFileEx函数之一,代替fopenfread函数。

希望这完美地回答了你的问题。


9
哇,我简直不敢相信这个答案到目前为止还没有得到足够的关注!对于像我这样希望在他们的C++代码中轻松实现.mp3声音的人来说,这是一个非常好且详细的资源! - Sossenbinder
1
*.mp3 必须替换为 mp3 文件的完整路径和文件名。 - Paul Ogilvie
1
我的VS2008系统中不存在头文件“mciapi.h”,但所需的定义已包含在“MMSystem.h”中(后者又包含在“Windows.h”中)。 - Paul Ogilvie
1
不好意思,但是如果没有使用mciSendString("... wait"),仅使用mciSendString("play ...")将无法产生任何效果,程序会在启动后立即结束,至少对我来说是这样的。我需要做更多的事情吗? - platinoob_
@AdrianMaire 你应该意识到这个问题是特别针对Windows操作系统的。 - Nikita Demodov
显示剩余2条评论

8

http://sfml-dev.org/documentation/2.0/classsf_1_1Music.php

SFML没有mp3支持,正如其他人所建议的那样。我通常使用Audacity将所有音乐转换为ogg格式,并将所有音效保留为wav格式。
加载和播放wav文件很简单(粗略示例):

http://www.sfml-dev.org/tutorials/2.0/audio-sounds.php

#include <SFML/Audio.hpp>
...
sf::SoundBuffer buffer;
if (!buffer.loadFromFile("sound.wav")){
    return -1;
}
sf::Sound sound;
sound.setBuffer(buffer);
sound.play();

流式传输OGG音乐文件也很简单:

#include <SFML/Audio.hpp>
...
sf::Music music;
if (!music.openFromFile("music.ogg"))
    return -1; // error
music.play();

1
通过使用预编译的二进制文件,对我来说是有效的。 - V-SHY
可能需要 Windows 版本。Unix 需要 sudo aptitude install libcsfml-dev,然后查看 https://github.com/SFML/CSFML/tree/master/include/SFML/Audio 获取说明。为什么答案被关闭?这是一个重要而明确的问题。尝试使用 sfSoundBuffer* buffer = sfSoundBuffer_createFromFile("/mysounds/morse.wav"); sfSound*sound = sfSound_create(); sfSound_setBuffer(sound, buffer); sfSound_play(sound); - DragonLord

5

如果您想播放*.mp3或*.wav文件,我认为最简单的方法是使用SFML


7
请记住,由于许可问题,SFML不支持MP3格式。但是您同样可以使用不太常见但同样出色的OGG格式。 - Lukas

4

尝试在VC++中使用简单的C++代码。

#include <windows.h>
#include <iostream>
#pragma comment(lib, "winmm.lib")

int main(int argc, char* argv[])
{
std::cout<<"Sound playing... enjoy....!!!";
PlaySound("C:\\temp\\sound_test.wav", NULL, SND_FILENAME); //SND_FILENAME or SND_LOOP
return 0;
}

3

我建议你在游戏中使用FMOD。它能够播放几乎所有的声音文件,并且在C++中实现起来非常简单。将FMOD和DirectX一起使用可以发挥强大的作用,而且并不难。如果您熟悉单例类,我建议在您的win main cpp中创建一个声音管理器的单例类,然后可以随时访问它以加载或播放新的音乐或音效。 以下是一个音频管理器示例:

    #pragma once

#ifndef H_AUDIOMANAGER
#define H_AUDIOMANAGER

#include <string>
#include <Windows.h>
#include "fmod.h"
#include "fmod.hpp"
#include "fmod_codec.h"
#include "fmod_dsp.h"
#include "fmod_errors.h"
#include "fmod_memoryinfo.h"
#include "fmod_output.h"

class AudioManager
{
public:
    // Destructor
    ~AudioManager(void);

    void Initialize(void);  // Initialize sound components
    void Shutdown(void);    // Shutdown sound components

    // Singleton instance manip methods
    static AudioManager* GetInstance(void);
    static void DestroyInstance(void);

    // Accessors
    FMOD::System* GetSystem(void)
        {return soundSystem;}

    // Sound playing
    void Play(FMOD::Sound* sound);  // Play a sound/music with default channel
    void PlaySFX(FMOD::Sound* sound);   // Play a sound effect with custom channel
    void PlayBGM(FMOD::Sound* sound);   // Play background music with custom channel

    // Volume adjustment methods
    void SetBGMVolume(float volume);
    void SetSFXVolume(float volume);

private:
    static AudioManager* instance;  // Singleton instance
    AudioManager(void);  // Constructor

    FMOD::System* soundSystem;  // Sound system object
    FMOD_RESULT result;
    FMOD::Channel* bgmChannel;  // Channel for background music
    static const int numSfxChannels = 4;
    FMOD::Channel* sfxChannels[numSfxChannels]; // Channel for sound effects
};

#endif

2
这不是真正关于OP问题的,但你能解释一下为什么你有单独的关闭方法而不是依赖于析构函数吗? - jaho
2
由于声音系统应该具有许多组件,因此您可以在那里处理声音系统的所有对象,而不是在析构函数中处理,因为析构函数将销毁声音管理器,但不会销毁从声音类创建的任何对象,仅销毁其自身的类。 - Josh
2
你可以将放置在shutdown()方法中的相同代码放置到析构函数中。这确实是析构函数的目的,释放对象持有的所有资源。我经常看到这种两步销毁的设计,但从未理解其背后的原因。为什么不使用C++提供的默认设施呢?此外,您始终可以使用智能指针,而无需担心显式释放资源。我只是看不到单独的shutdown()函数的好处。 - jaho

2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接