简体   繁体   中英

PlaySound in C++ Console application?

EDITED SO THE CODE IS CORRECT (THANKS TO ta.speot.is) - NEW QUESTION AT BOTTOM

So I have been playing with the console as I am at that level, and we have been asked to make our first "project" for a assessment. I have the basic application all done.. but i wanted to jaz it up a bit and add some sounds. Sounds that will play from the console.

This test works(kind of), as it will play a sound file, but there are 2 problems...

  1. When the sound starts playing, the application freezes until it finishes playing.

  2. When I try to compile as a "release" it errors with a "linking error" - fatal error LNK1120: 1 unresolved externals.

#include <iostream>
#include <windows.h>
#include <mmsystem.h>
using namespace std;

int main(){
    //PlaySound(TEXT("mywavsound.wav"), NULL, SND_FILENAME); - My erroring code
    PlaySound(TEXT("mywavsound.wav"), NULL, SND_FILENAME | SND_ASYNC);// - the correct code
        
    int test = 0;
    cin>>test;
    return 0;
}

So my questions are...

  1. How can I play sounds without freezing the console, so I can for example play looping music file while the project is running? Also it would be great if I could play other sounds on top of it, for example when you press enter it will plays a sound without stopping the music.

  2. How do I add the wav file so it compiles as a release?

EDIT

I know about the SND_ASYNC thing but I do not know how to use it, I can't seem to use it without the thing not compiling.. does anyone have an example of a code using SND_ASYNC ?

EDIT 2

So I have this working now.... using the

PlaySound(TEXT("mysound.wav"), NULL, SND_FILENAME | SND_ASYNC);

Now I am wondering how I can get 1 or more sounds to play at once, for if I call PlaySound() twice with that flag it will stop the 1st one and play the 2nd.. Is there a way to play 2 sounds at once?

How can I play sounds without freezing the console?

If you Google for PlaySound this is the first result :

fdwSound

...

SND_ASYNC The sound is played asynchronously and PlaySound returns immediately after beginning the sound. To terminate an asynchronously played waveform sound, call PlaySound with pszSound set to NULL .

You should familiarise yourself with search engines and what they are capable of.

How do I add the wav file so it compiles as a release?

You need to link winmm.lib in both Release and Debug configurations. Alternatively, add

#pragma comment(lib, "winmm.lib")

to the top of your source code.

With mciSendstring() you can play multiple sounds simultaneously :)

Examples:

mciSendString("play wave1.wav", NULL, 0, NULL);

mciSendString("play wave2.wav", NULL, 0, NULL);

Most of your question were answered. However, for playing more sounds at once. For this purpose, you can do it by using SDL_Mixer. So far I was looking for any good way of playing audio, and making it work, I came to this.

The good point is, that SDL_Mixer is multiplatform (tested on both win, linux). If you know what you are doing, you can write code that compiles on both OSses.

Another good thing is, that you can play multiple sounds at once (maybe even up to 30, but check it online). The library is aviable for Windows (DLLs and header files) and Linux (installation through console). I will not go to further detail on instalation. It supports wav and oog and much more file types.

The drawbacks, however, are as follows: Upon making it work, wich took me hell lot of time, I figured out that audio crackles and is low quality, allthough the original file plays fine in media player. Upon research, I found out that this is for long time a bug in library, and still not fixed. Some people said they fixed it by putting volume up to 100 but it did not work for me. Second drawback is that it needs a bit more coding than just type

 PlaySound(TEXT("mywavsound.wav"), NULL, SND_FILENAME | SND_ASYNC);// - the correct code

Next drawback was figured by me, and that a wav file does not load, saying that the format is not correct. Allthough I used like 3 converters, and downloaded many files through internet, it did not work. It did work, however, upon downloading test project and using their .wav file in archive. However, I solved it by converting all my music to .oog

Like this, still I do not know what music library would be best to use. Depends what your console is supposed to be...? are you writing game or just media player? or just testing...?

All previous answers are explaining it pretty well. I would just like to give a basic example of how to do it:

#include<iostream>
#include<windows.h>
int main(){
    std::string name;
    std::string a1 = "happy.wav";
    std::string a2 = "apple.wav";

    PlaySound((a1.c_str()),NULL,SND_SYNC);
    PlaySound((a2.c_str()),NULL,SND_SYNC);
    return 0;
}

Given above is a code snippet, where I am using the PlaySound() to play two wav sound files.

Note. Currently the two wav files are in the same directory where the cpp code file is.

Is there a way to play 2 sounds at once?

You need to use a more powerful multimedia API than PlaySound() . For instance, you can use XAudio2 instead:

XAudio2 is a low-level audio API. It provides a signal processing and mixing foundation for games that is similar to its predecessors, DirectSound and XAudio.

...

The following is a list of XAudio2 features and new functionality that enable developers to improve performance in their games.

...

  • Submixing

    Submixing combines several sounds into a single audio stream — for example, an engine sound made up of composite parts, all of which are playing simultaneously. Also, you can use submixing to process and combine similar parts of a game. For example, you could combine all game sound effects to allow a user volume setting to be applied while a separate setting controls music volume. Combined with DSP, submixing provides the type of data routing and processing necessary for today's games. XAudio2 allows for arbitrary levels of submixing, enabling the creation of complex sounds and game mixes.

...

  • Nonblocking API Model

    With few exceptions, an XAudio2 method call will not block the audio processing engine. This means that a client can safely make a set of method calls at any time without blocking on long-running calls causing delays. The exceptions are the IXAudio2Voice::DestroyVoice method (which may block the engine until the voice being destroyed is finished processing) and the methods that terminate the audio thread: IXAudio2::StopEngine and IXAudio2::Release . Note that while XAudio2 method calls will not block the audio processing engine, the XAudio2 methods contain critical sections and may themselves become blocked in some circumstances.

Following solution is for wav files only

In my 2nd semester of university, I made a console(ASCII) game in C++ as semester project. While making that game, It was required to play multiple audio files. After googling, I wrote following code:

Audio.h

#pragma once
#include <xaudio2.h>
#include <iostream>
#include <string>
using namespace std;
#ifdef _XBOX 
#define fourccRIFF 'RIFF'
#define fourccDATA 'data'
#define fourccFMT 'fmt '
#define fourccWAVE 'WAVE'
#define fourccXWMA 'XWMA'
#define fourccDPDS 'dpds'
#endif

#ifndef _XBOX
#define fourccRIFF 'FFIR'
#define fourccDATA 'atad'
#define fourccFMT ' tmf'
#define fourccWAVE 'EVAW'
#define fourccXWMA 'AMWX'
#define fourccDPDS 'sdpd'
#endif
class Audio
{
private:
    HRESULT hr;
    IXAudio2* pXAudio2;
    IXAudio2MasteringVoice* pMasterVoice;
    HRESULT FindChunk(HANDLE hFile, DWORD fourcc, DWORD& dwChunkSize, DWORD& dwChunkDataPosition);
    HRESULT ReadChunkData(HANDLE hFile, void* buffer, DWORD buffersize, DWORD bufferoffset);
public:
    Audio();
    int Play(string path, float volume = 1, bool ShouldLoop = false); // plays the audio file with specified volume and can be looped
    string BasePath; // Directory where all audio files (relevant to project) are stored i.e if all audio files are stored in "D:\game" than set BasePath to "D:\game", this will be automatically added in path of every audio file
};

Audio.cpp

#include "Audio.h"
Audio::Audio()
{
    BasePath = "";
    hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
    if (FAILED(hr))
        cout << hr;
    pXAudio2 = nullptr;
    if (FAILED(hr = XAudio2Create(&pXAudio2, 0, XAUDIO2_DEFAULT_PROCESSOR)))
        cout << hr;

    pMasterVoice = nullptr;
    if (FAILED(hr = pXAudio2->CreateMasteringVoice(&pMasterVoice)))
        cout << hr;
}
int Audio::Play(string path, float volume, bool ShouldLoop) {
    path = BasePath + "\\" + path;
    WAVEFORMATEXTENSIBLE wfx = { 0 };
    XAUDIO2_BUFFER buffer = { 0 };
#ifdef _XBOX
    char* strFileName = path;
#else
    TCHAR* strFileName = new TCHAR[path.size() + 1];
    strFileName[path.size()] = 0;
    std::copy(path.begin(), path.end(), strFileName);
#endif
    // Open the file
    HANDLE hFile = CreateFile(
        strFileName,
        GENERIC_READ,
        FILE_SHARE_READ,
        NULL,
        OPEN_EXISTING,
        0,
        NULL);

    if (INVALID_HANDLE_VALUE == hFile)
        return HRESULT_FROM_WIN32(GetLastError());

    if (INVALID_SET_FILE_POINTER == SetFilePointer(hFile, 0, NULL, FILE_BEGIN))
        return HRESULT_FROM_WIN32(GetLastError());
    DWORD dwChunkSize;
    DWORD dwChunkPosition;
    //check the file type, should be fourccWAVE or 'XWMA'
    FindChunk(hFile, fourccRIFF, dwChunkSize, dwChunkPosition);
    DWORD filetype;
    ReadChunkData(hFile, &filetype, sizeof(DWORD), dwChunkPosition);
    if (filetype != fourccWAVE)
        return S_FALSE;

    FindChunk(hFile, fourccFMT, dwChunkSize, dwChunkPosition);
    ReadChunkData(hFile, &wfx, dwChunkSize, dwChunkPosition);
    FindChunk(hFile, fourccDATA, dwChunkSize, dwChunkPosition);
    BYTE* pDataBuffer = new BYTE[dwChunkSize];
    ReadChunkData(hFile, pDataBuffer, dwChunkSize, dwChunkPosition);

    buffer.AudioBytes = dwChunkSize;  // size of the audio buffer in bytes
    buffer.pAudioData = pDataBuffer;  // buffer containing audio data
    buffer.Flags = XAUDIO2_END_OF_STREAM; // tell the source voice not to expect any data after this buffer
    if (ShouldLoop) buffer.LoopCount = XAUDIO2_LOOP_INFINITE;
    IXAudio2SourceVoice* pSourceVoice;
    if (FAILED(hr = pXAudio2->CreateSourceVoice(&pSourceVoice, (WAVEFORMATEX*)&wfx))) cout << hr;
    //if (FAILED(hr = pSourceVoice->SetSourceSampleRate(20000))) cout << hr;
    if (FAILED(hr = pSourceVoice->SubmitSourceBuffer(&buffer))) cout << hr;
    //if (FAILED(hr = pSourceVoice->SetFrequencyRatio(1.2))) cout << hr;
    pSourceVoice->SetVolume(volume);
    if (FAILED(hr = pSourceVoice->Start(0)))
        cout << hr;

    return 0;
}


HRESULT Audio::FindChunk(HANDLE hFile, DWORD fourcc, DWORD& dwChunkSize, DWORD& dwChunkDataPosition)
{
    HRESULT hr = S_OK;
    if (INVALID_SET_FILE_POINTER == SetFilePointer(hFile, 0, NULL, FILE_BEGIN))
        return HRESULT_FROM_WIN32(GetLastError());

    DWORD dwChunkType;
    DWORD dwChunkDataSize;
    DWORD dwRIFFDataSize = 0;
    DWORD dwFileType;
    DWORD bytesRead = 0;
    DWORD dwOffset = 0;

    while (hr == S_OK)
    {
        DWORD dwRead;
        if (0 == ReadFile(hFile, &dwChunkType, sizeof(DWORD), &dwRead, NULL))
            hr = HRESULT_FROM_WIN32(GetLastError());

        if (0 == ReadFile(hFile, &dwChunkDataSize, sizeof(DWORD), &dwRead, NULL))
            hr = HRESULT_FROM_WIN32(GetLastError());

        switch (dwChunkType)
        {
        case fourccRIFF:
            dwRIFFDataSize = dwChunkDataSize;
            dwChunkDataSize = 4;
            if (0 == ReadFile(hFile, &dwFileType, sizeof(DWORD), &dwRead, NULL))
                hr = HRESULT_FROM_WIN32(GetLastError());
            break;

        default:
            if (INVALID_SET_FILE_POINTER == SetFilePointer(hFile, dwChunkDataSize, NULL, FILE_CURRENT))
                return HRESULT_FROM_WIN32(GetLastError());
        }

        dwOffset += sizeof(DWORD) * 2;

        if (dwChunkType == fourcc)
        {
            dwChunkSize = dwChunkDataSize;
            dwChunkDataPosition = dwOffset;
            return S_OK;
        }

        dwOffset += dwChunkDataSize;

        if (bytesRead >= dwRIFFDataSize) return S_FALSE;

    }

    return S_OK;

}
HRESULT Audio::ReadChunkData(HANDLE hFile, void* buffer, DWORD buffersize, DWORD bufferoffset)
{
    HRESULT hr = S_OK;
    if (INVALID_SET_FILE_POINTER == SetFilePointer(hFile, bufferoffset, NULL, FILE_BEGIN))
        return HRESULT_FROM_WIN32(GetLastError());
    DWORD dwRead;
    if (0 == ReadFile(hFile, buffer, buffersize, &dwRead, NULL))
        hr = HRESULT_FROM_WIN32(GetLastError());
    return hr;
}

In main file use it like this

#include "Audio.h"

string getExePath(string x) // removes fileName from path i.e "D:\path\to\exe\sample.exe" changes it into "D:\path\to\exe"
{
    std::string f = x;
    return f.substr(0, f.find_last_of("\\/"));
}

int main(int argc, char* argv[]) {
    string exeLocation = argv[0]; // returns path of currently running program i.e "D:\path\to\exe\sample.exe"
    Audio audioManager;
    audioManager.BasePath = getExePath(exeLocation);
    audioManager.Play("bg.wav");
    while(1){}
    return 0;
}

Note

I don't know the drawbacks of using this code because I wrote it along time ago and most of code is taken from documentation of XAudio2 or some other sources

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM