FMOD as new sound engine?

Programmers discuss here anything related to FreeOrion programming. Primarily for the developers to discuss.

Moderator: Committer

Message
Author
MareviQ
Space Kraken
Posts: 100
Joined: Tue Aug 09, 2005 6:47 pm
Location: Somewhere in Poland

#31 Post by MareviQ »

Well, it turns out that not much is needed to make OpenAL work without using alutInit(). Here is the affected part of the code:

Code: Select all

namespace {
    void InitOpenAL(int num_sources, ALuint *sources, ALuint *music_buffers)
    {
        ALCcontext *m_context;
        ALCdevice *m_device;
        log4cpp::Category& logger = HumanClientApp::GetApp()->Logger();
        
        m_device = alcOpenDevice(NULL); /* currently only select the default output device - usually a NULL-terminated
                                         * string desctribing a device can be passed here (of type ALchar*)
                                         */
        if (m_device == NULL) {
            logger.errorStream() << "Unable to initialise OpenAL device: " << alGetString(alGetError()) << "\n";
        } else {
            m_context = alcCreateContext(m_device,NULL); // instead of NULL we can pass a ALCint* pointing to a set of
            alcMakeContextCurrent(m_context);            // attributes (ALC_FREQUENCY, ALC_REFRESH and ALC_SYNC)
            alutInitWithoutContext(NULL,NULL); // we need to init alut or we won't be able to read .wav files
            alGenSources(num_sources, sources);
            alGenBuffers(2, music_buffers);
            for (int i=0; i<num_sources; i++) {
                alSourcei(sources[i], AL_SOURCE_RELATIVE, AL_TRUE);
            }
            logger.debugStream() << "OpenAL initialized. Version " 
                                 << alGetString(AL_VERSION)
                                 << "Renderer "
                                 << alGetString(AL_RENDERER)
                                 << "Vendor "
                                 << alGetString(AL_VENDOR)
                                 << "\nExtensions: " 
                                 << alGetString(AL_EXTENSIONS)
                                 << "\n";
        }
    }
}
I'm not sure if music will work (I seem to remember reading somewhere that a wrapper function for fopen/fclose will be nedeed to make vorbis work on windows but I'm not sure). However sounds should work without problems.

User avatar
Geoff the Medio
Programming, Design, Admin
Posts: 13587
Joined: Wed Oct 08, 2003 1:33 am
Location: Munich

#32 Post by Geoff the Medio »

I updated, but I still get no sound.

MareviQ
Space Kraken
Posts: 100
Joined: Tue Aug 09, 2005 6:47 pm
Location: Somewhere in Poland

#33 Post by MareviQ »

okay, I'm running out of ideas...
try putting this somewhere in the init code (should be after alcMakeContextCurrent... or even better - after alutInitWithoutContext)

Code: Select all

alListenerf(AL_GAIN,1.0);
This should work if the problem is caused by OpenAL initializing the default sound volume with something stupid (like 0). You may change it to anything you like (1000.0 or something) but it should get restricted to 1.0... still, it might not so that's worth a try.

looks like I'll have to get a hand on a Windows machine to work something out if this doesn't help

User avatar
Geoff the Medio
Programming, Design, Admin
Posts: 13587
Joined: Wed Oct 08, 2003 1:33 am
Location: Munich

#34 Post by Geoff the Medio »

No change.

Note that I'm compiling using MSVC, not SCons (or any of your modifications to the build system therein)... I don't suppose that could be part of the problem?

MareviQ
Space Kraken
Posts: 100
Joined: Tue Aug 09, 2005 6:47 pm
Location: Somewhere in Poland

#35 Post by MareviQ »

nope, it shouldn't. I'll try to figure something out using my test programs (not FreeOrion itself, it would take ages for me to meet all the dependencies) and post my progress here. Note: it will be in an emulated enviroment (VMPlayer) but I hope it won't make a difference. Please point me to where can I obtain MSVC (assuming it can be downloaded for free) so I can recreate your situation better

User avatar
Geoff the Medio
Programming, Design, Admin
Posts: 13587
Joined: Wed Oct 08, 2003 1:33 am
Location: Munich

#36 Post by Geoff the Medio »

MSVC 2005 Express Edition is available from MS: http://msdn.microsoft.com/vstudio/expre ... /download/

I don't believe anyone has tested whether the SVN project files are compatible with the Express Edition though... There are apparently some weird inconsistencies that make SCons not work properly with the EE, but I'm not sure if those affect the IDE.

MareviQ
Space Kraken
Posts: 100
Joined: Tue Aug 09, 2005 6:47 pm
Location: Somewhere in Poland

#37 Post by MareviQ »

Okay, I took this test-app I created during learning how OpenAL works and managed to get it running on a Windows 2k SP4 on a virtual machine (VMware Player):

Code: Select all

#include <AL/al.h>
#include <AL/alc.h>
#include <AL/alut.h>
#include <stdio.h>
#include <stdlib.h>

int main (int argc, char** argv)
{
	ALCdevice						*m_Device;
	ALCcontext						*m_Context;
	ALuint							m_sources[3];
	ALuint							m_buffer[2];
	ALenum							error;
	ALboolean						test;
	
	m_Device = alcOpenDevice(NULL);
	m_Context = alcCreateContext(m_Device, NULL);
	alcMakeContextCurrent(m_Context);
	test = alutInitWithoutContext(NULL, NULL);
	if (test == AL_FALSE)
		printf("we suck soooo much\n");
	error = alutGetError();
		printf("Do we suck?: %s\n",alGetString(error));
	printf("Audio Enabled. Printing info: Version: %s\n",alGetString(AL_VERSION));
	printf("Renderer: %s\n",alGetString(AL_RENDERER));
	printf("Vendor: %s\n",alGetString(AL_VENDOR));
	printf("Extensions: %s",alGetString(AL_EXTENSIONS));
 
	
	alGenSources(3, m_sources);
	
	alSourcei(m_sources[0],AL_SOURCE_RELATIVE,AL_TRUE);
	alSourcei(m_sources[1],AL_SOURCE_RELATIVE,AL_TRUE);
	
	m_buffer[0] = alutCreateBufferHelloWorld();
	if (m_buffer[0] == AL_NONE)
	{
		error = alutGetError();
		printf("we suck: %s\n",alutGetErrorString(error));
		if (error == ALUT_ERROR_INVALID_OPERATION)
		printf("init required!!\n");
	}
	
	alSourcei(m_sources[0],AL_BUFFER,m_buffer[0]);
	alSourcePlay(m_sources[0]);
	getchar();
	return(0);
}
It runs without any modifications to the code (just needed to put all the headers of OpenAL in AL subdirectory). I linked it to the OpenAL32.lib provided in the 1.1 SDK aviable from openal.org and to alut.lib provided there. It ran correctly. I do not know how it would run on a base system as I installed the OpenAL libraries (also the one aviable on openal.org) over the default windows implementation (if there was any). It should play "Hello, world" and quit after pressing any key. Could you please verify if it runs on your system?

MareviQ
Space Kraken
Posts: 100
Joined: Tue Aug 09, 2005 6:47 pm
Location: Somewhere in Poland

#38 Post by MareviQ »

What came after some testing on windows:
- "hello world" works without problems
- loading a .wav file and playing it works
- loading a .ogg file all at once to the memory and playing it works with some code changes (otherwise crashes the app)
- buffering a .ogg and playing it with buffer swapping doesn't work (still, this may be due to my buggy test code or couse the virtual machine is SLOW)

those tests weren't conducted using the FreeOrion but test apps that utilize the same ways of doing those tasks (well, apart from the "hello world" and the buffering) as they do in FreeOrion.

The system is the same as before: a nearly vanilla (installed DirectX 9.1c, 7zip, Free Download Manager, WinG SDK and the Platfrom SDK) Windows 2k SP4 + OpenAL dlls from the OpenAL SDK (using the installer found in the redist folder).

for the OpenAL and alut dependencies the binary SDK's from http://www.openal.org were used

for the ogg / vorbis / vorbisfile the source code found on http://www.xiph.org was compiled (release mode was set for all the projects, and the dynamic versions were used).

The compiler used was MSVC 2005 Express Edition

Observation: turning the speakers on makes your sound play better. So does turning up the volume (and I learned this the hard way)

User avatar
Geoff the Medio
Programming, Design, Admin
Posts: 13587
Joined: Wed Oct 08, 2003 1:33 am
Location: Munich

#39 Post by Geoff the Medio »

I ran the test app, and heard "hello world".

Immediately afterwards, I ran FO, but heard nothing.

In another matter, if you get a virtual machine up and running that can compile and run FO, can you try running without the .NET 2.0 framework installed, after compiling using the MSVC project files (not SCons), to see if the framework is still needed? I seem to recall tweaking a few things to see if I could get rid of the dependency, though don't have any extra test machines to use to see if it actually worked...

MareviQ
Space Kraken
Posts: 100
Joined: Tue Aug 09, 2005 6:47 pm
Location: Somewhere in Poland

#40 Post by MareviQ »

I'm away for the weekend but when I'm back I'll get to work on this. Still not by compiling FreeOrion (I believe that would be quite an overkill) but by clever copy&paste :) (namely: copy all the relevant code from FreeOrion, make it work as a standalone app and then re-merge the required changes into FO)
And one more thing: you may not hear music in FO but do you hear sounds or not? The .wav handling code should work without problems on windows...

User avatar
Geoff the Medio
Programming, Design, Admin
Posts: 13587
Joined: Wed Oct 08, 2003 1:33 am
Location: Munich

#41 Post by Geoff the Medio »

No sound of any kind.

MareviQ
Space Kraken
Posts: 100
Joined: Tue Aug 09, 2005 6:47 pm
Location: Somewhere in Poland

#42 Post by MareviQ »

Okay, this is the moment we've been waiting for: a OpenAL implementation that should work in windows (and it does... at least on my virtual machine).

It's a direct code copy from FO only after slight modifications (the FO code was nearly foolproof anyway). It needs to be linked to Openal32.lib alut.lib and vorbisfile.lib (will work properly with dynamic version). It also ahders to openal header placement in windows (no AL subdirectory). It should play both .ogg and .wav files - just pass the file (only one) as it's argument.

Code: Select all

//the Linux way
//#include <AL/al.h>
//#include <AL/alc.h>
//#include <AL/alut.h>

//the Windows way
#include <al.h>
#include <alc.h>
#include <alut.h>


#include <vorbis/vorbisfile.h>

#include <stdlib.h>
#include <map>
#include <string>

#define M_SOURCES_NUM 16 // the number of sources for OpenAL to create. Should be 2 or more
#define BUFFER_SIZE 409600 // the size of the buffer we read music data into.

    ALuint                            m_sources[M_SOURCES_NUM];         ///< OpenAL sound sources. The first one is used for music
    int                               m_music_loops;        ///< the number of loops of the current music to play (< 0 for loop forever)
///    int                               m_next_music_time;    ///< the time in ms that the next loop of the current music should play (0 if no repeats are scheduled)
    std::string                       m_music_name;         ///< the name of the currently-playing music file
    std::map<std::string, ALuint>     m_buffers;            ///< the currently-cached (and possibly playing) sounds, if any; keyed on filename
    ALuint                            m_music_buffers[2];   ///< two additional buffers for music. statically defined as they'll be changed many time.
    OggVorbis_File                    m_ogg_file;           ///< the currently open ogg file
    ALenum                            m_ogg_format;         ///< mono or stereo
    ALsizei                           m_ogg_freq;           ///< sampling frequency
    
    
//The Windows way
  static int _fseek64_wrap(FILE *f,ogg_int64_t off,int whence){

    if(f==NULL)return(-1);

    return fseek(f,off,whence);

  }



  ov_callbacks callbacks = {

    (size_t (*)(void *, size_t, size_t, void *))  fread,

    (int (*)(void *, ogg_int64_t, int))           _fseek64_wrap,

    (int (*)(void *))                             fclose,

    (long (*)(void *))                            ftell

  };

int RefillBuffer(ALuint *bufferName)
{
    ALenum m_openal_error;
    int endian = 0; /// 0 for little-endian (x86), 1 for big-endian (ppc)
    int bitStream,bytes,bytes_new;
    char array[BUFFER_SIZE];
    bytes=0;
    
    if (alcGetCurrentContext() != NULL)
    {
        /* First, let's fill up the buffer. We need the loop, as ov_read treats (BUFFER_SIZE - bytes) to read as a suggestion only */
        do
        {
            bytes_new = ov_read(&m_ogg_file, &array[bytes],(BUFFER_SIZE - bytes), endian, 2, 1, &bitStream);
            bytes += bytes_new;
            if (bytes_new == 0)
            {
                if (m_music_loops != 0) // enter here if we need to play the same file again
                {
                    if (m_music_loops > 0)
                        m_music_loops--;
                    ov_time_seek(&m_ogg_file,0.0); // rewind to beginning
                }
                else
                    break;
            }
        } while ((BUFFER_SIZE - bytes) > 4096);
        if (bytes > 0)
        {
            alBufferData(bufferName[0], m_ogg_format, array, static_cast < ALsizei > (bytes),m_ogg_freq);
            m_openal_error = alGetError();
            if (m_openal_error != AL_NONE)
                printf("RefillBuffer: OpenAL ERROR: %s\n",alGetString(m_openal_error));
        }
        else
        {
            m_music_name.clear();  // m_music_name.clear() must always be called before ov_clear. Otherwise
            ov_clear(&m_ogg_file); // the app might think we still have something to play.
            return 1;
        }
        return 0;
    }
    return 1;
}

int RenderBegin()
{
    ALint    state;
    int      num_buffers_processed;
    
    if ((alcGetCurrentContext() != NULL) && (m_music_name.size() > 0))
    {
        alGetSourcei(m_sources[0],AL_BUFFERS_PROCESSED,&num_buffers_processed);
        while (num_buffers_processed > 0)
        {
            ALuint buffer_name_yay;
            alSourceUnqueueBuffers (m_sources[0], 1, &buffer_name_yay);
            if (RefillBuffer(&buffer_name_yay))
                return 0; /// this happens if RefillBuffer returns 1, meaning it encountered EOF and the file shouldn't be repeated
            alSourceQueueBuffers(m_sources[0],1,&buffer_name_yay);
            num_buffers_processed--;
        }
        alGetSourcei(m_sources[0], AL_SOURCE_STATE, &state);
        if (state == AL_STOPPED)  /// this may happen if the source plays all its buffers before we manage to refill them
            alSourcePlay(m_sources[0]);
    }
    else
        return 0;
    return 1;
}

void PlaySound(const std::string& filename)
{
    ALuint m_current_buffer;
    ALenum m_source_state;
    int m_i;
    int m_found_buffer = 1;
    int m_found_source = 0;
    
    if (alcGetCurrentContext() != NULL)
    {
        /* First check if the sound data of the file we want to play is already buffered somewhere */
        std::map<std::string, ALuint>::iterator it = m_buffers.find(filename);
        if (it != m_buffers.end())
            m_current_buffer=it->second;
        else 
        {
            /* We buffer the file if it wasn't previously */
            if ((m_current_buffer = alutCreateBufferFromFile(filename.c_str())) != AL_NONE)
                m_buffers[filename] = m_current_buffer;
            else
            {
                printf("PlaySound: Cannot create buffer for: %s Reason: %s\n",filename.c_str(),alutGetErrorString(alutGetError()));
                m_found_buffer = 0;
            }
        }
        if (m_found_buffer)
        {
            /* Now that we have the buffer, we need to find a source to send it to */
            for (m_i=1;m_i<M_SOURCES_NUM;m_i++) // as we're playing sounds we start at 1. 0 is reserved for music
            {
                alGetSourcei(m_sources[m_i],AL_SOURCE_STATE,&m_source_state);
                if ((m_source_state != AL_PLAYING) && (m_source_state != AL_PAUSED))
                {
                    m_found_source = 1;
                    alSourcei(m_sources[m_i], AL_BUFFER, m_current_buffer);
                    alSourcePlay(m_sources[m_i]);
                    break; // so that the sound won't block all the sources
                }
            }
            if (!m_found_source)
                printf("PlaySound: Could not find aviable source - playback aborted\n");
        }
        m_source_state = alGetError();
        if (m_source_state != AL_NONE)
            printf("PlaySound: OpenAL ERROR: %s",alGetString(m_source_state));
            /* it's important to check for errors, as some functions (mainly alut) won't work properly if
             * they're called when there is a unchecked previous error. */
    }
}

void StopMusic()
{
    int num_buffers_processed;
    ALuint buffer_name_yay;
    
    if (alcGetCurrentContext() != NULL)
    {
        alSourceStop(m_sources[0]);
        if (m_music_name.size() > 0)
        {
            m_music_name.clear();  // do this to avoid music being re-started by other functions
            ov_clear(&m_ogg_file); // and unload the file for good measure. the file itself is closed now, don't re-close it again
        }
        alGetSourcei(m_sources[0],AL_BUFFERS_PROCESSED,&num_buffers_processed);         // we need to unqueue any unplayed buffers
        alSourceUnqueueBuffers (m_sources[0], num_buffers_processed, &buffer_name_yay); // otherwise they'll cause problems if we open another file
    }
}

void PlayMusic(const std::string& filename, int loops /* = 0*/)
{
    ALenum m_openal_error;
    FILE *m_f = NULL;
    vorbis_info *m_ogg_info;
	int verify = 1;

    m_music_loops = 0;
    
    if (alcGetCurrentContext() != NULL)
    {
        if (m_music_name.size() > 0)
            StopMusic();
        
        if ((m_f = fopen(filename.c_str(), "rb")) != NULL) // make sure we CAN open it
        {
			//The Linux way
			//if (!(ov_test(m_f, &m_ogg_file, NULL, 0))) // check if it's a proper ogg
			//The Windows way
            if (!(ov_test_callbacks(m_f, &m_ogg_file, NULL, 0,callbacks))) // check if it's a proper ogg
            {
                ov_test_open(&m_ogg_file); // it is, now fully open the file
                /* now we need to take some info we will need later */
                m_ogg_info = ov_info(&m_ogg_file, -1);
                if (m_ogg_info->channels == 1)
                    m_ogg_format = AL_FORMAT_MONO16;
                else
                    m_ogg_format = AL_FORMAT_STEREO16;
                m_ogg_freq = m_ogg_info->rate;
                m_music_loops = loops;
                /* fill up the buffers and queue them up for the first time */
                if (!RefillBuffer(&m_music_buffers[0]))
				{
				    alSourceQueueBuffers(m_sources[0],1,&m_music_buffers[0]);
				    printf("Filled buffer 1\n");
				}
				else
					verify = 0;
                m_openal_error = alGetError();
                if (m_openal_error != AL_NONE)
                    printf("PlayMusic: OpenAL ERROR after queuing buffers (1): %s\n",alGetString(m_openal_error));
                if (!RefillBuffer(&m_music_buffers[1]))
		{
                    alSourceQueueBuffers(m_sources[0],1,&m_music_buffers[1]);
		    printf("Filled buffer 2\n");
		}
				else
					verify = 0;
				m_openal_error = alGetError();
                if (m_openal_error != AL_NONE)
                    printf("PlayMusic: OpenAL ERROR after queuing buffers (2): %s\n",alGetString(m_openal_error));
                alSourcePlay(m_sources[0]);
		if (verify)
                    m_music_name = filename; // yup, we're playing something
            }
            else
            {
                printf("PlayMusic: unable to open file %s possibly not a .ogg vorbis file. Aborting\n",filename.c_str());
                m_music_name.clear(); //just in case
                ov_clear(&m_ogg_file);
            }
        }
        else
            printf("PlayMusic: unable to open file %s I/O Error. Aborting\n",filename.c_str());
    }
    m_openal_error = alGetError();
    if (m_openal_error != AL_NONE)
        printf("PlayMusic: OpenAL ERROR: %s\n",alGetString(m_openal_error));
}

int main(int argc,char** argv)
{
    std::string filename;
    
    ALCcontext *m_context;
    ALCdevice *m_device;

    m_device = alcOpenDevice(NULL); /* currently only select the default output device - usually a NULL-terminated
                                     * string desctribing a device can be passed here (of type ALchar*)
                                     */
    if (m_device == NULL) {
        printf("Unable to initialise OpenAL device: %s\n",alGetString(alGetError()));
    } else {
        m_context = alcCreateContext(m_device,NULL); // instead of NULL we can pass a ALCint* pointing to a set of
        alcMakeContextCurrent(m_context);            // attributes (ALC_FREQUENCY, ALC_REFRESH and ALC_SYNC)
        alutInitWithoutContext(NULL,NULL); // we need to init alut or we won't be able to read .wav files
        alListenerf(AL_GAIN,1.0);
        alGenSources(M_SOURCES_NUM, m_sources);
        alGenBuffers(2, m_music_buffers);
        for (int i=0; i<M_SOURCES_NUM; i++) {
            alSourcei(m_sources[i], AL_SOURCE_RELATIVE, AL_TRUE);
        }
        printf("OpenAL initialized. Version %s Renderer %s Vendor %s\nExtensions: %s\n",alGetString(AL_VERSION),alGetString(AL_RENDERER),alGetString(AL_VENDOR),alGetString(AL_EXTENSIONS));
    }
    filename = argv[1];
    printf("Testing .ogg playback\n");
    PlayMusic(filename,0);
    while(RenderBegin())
        ;
    printf("Testing general playback\n");
    PlaySound(filename);
    printf("Testing DONE - press any key to exit\n");
    getchar();
    return 0;
}
Please, tell me if it also works on your system. If it does, I'll merge the changes into FreeOrion files and post them (files, not changes) here

User avatar
Geoff the Medio
Programming, Design, Admin
Posts: 13587
Joined: Wed Oct 08, 2003 1:33 am
Location: Munich

#43 Post by Geoff the Medio »

It worked.

However:

On Windows, it should be

#include <AL/alut.h>

(just for alut.h ... the OpenAL headers are in

C:\OpenAL 1.1 SDK\include

while the alut ones are in

C:\alut-1.1.0\include\AL

and I point the compiler to the ...\include directories of each dependency.

Also, when compiling, I get:

c:\openaltest\openaltest.cpp(37) : warning C4244: 'argument' : conversion from 'ogg_int64_t' to 'long', possible loss of data
c:\openaltest\openaltest.cpp(208) : warning C4996: 'fopen' was declared deprecated
c:\program files\microsoft visual studio 8\vc\include\stdio.h(234) : see declaration of 'fopen'
Message: 'This function or variable may be unsafe. Consider using fopen_s instead. To disable deprecation, use _CRT_SECURE_NO_DEPRECATE. See online help for details.'

FO uses boost, so you should probably switch to using boost's file io stuff, which FO uses through GG... (or so is my semi-validated assumption)...

MareviQ
Space Kraken
Posts: 100
Joined: Tue Aug 09, 2005 6:47 pm
Location: Somewhere in Poland

#44 Post by MareviQ »

Okay, here is the updated FO code. It uses the FREEORION_WIN32 define to check if it's compiled on windows (found the #ifdefs somewhere in the code already so it should work out-of-the-box)

HumanClientAppSoundOpenAL.cpp:

Code: Select all

/* THIS IS UNTESTED CODE! Please give feedback if it works. */
#include "HumanClientAppSoundOpenAL.h"
#include "../../util/OptionsDB.h"

#ifdef FREEORION_WIN32
//#include <al.h> // already included in HumanClientAppSoundOpenAL.h
#include <alc.h>
#else
//#include <AL/al.h> // already included in HumanClientAppSoundOpenAL.h
#include <AL/alc.h>

#endif

#include <AL/alut.h>
//#include <vorbis/vorbisfile.h> // already included in HumanClientAppSoundOpenAL.h

namespace {
    void InitOpenAL(int num_sources, ALuint *sources, ALuint *music_buffers)
    {
        ALCcontext *m_context;
        ALCdevice *m_device;
        log4cpp::Category& logger = HumanClientApp::GetApp()->Logger();
        
        m_device = alcOpenDevice(NULL); /* currently only select the default output device - usually a NULL-terminated
                                         * string desctribing a device can be passed here (of type ALchar*)
                                         */
        if (m_device == NULL) {
            logger.errorStream() << "Unable to initialise OpenAL device: " << alGetString(alGetError()) << "\n";
        } else {
            m_context = alcCreateContext(m_device,NULL); // instead of NULL we can pass a ALCint* pointing to a set of
            alcMakeContextCurrent(m_context);            // attributes (ALC_FREQUENCY, ALC_REFRESH and ALC_SYNC)
            alutInitWithoutContext(NULL,NULL); // we need to init alut or we won't be able to read .wav files
            alListenerf(AL_GAIN,1.0);
            alGenSources(num_sources, sources);
            alGenBuffers(2, music_buffers);
            for (int i=0; i<num_sources; i++) {
                alSourcei(sources[i], AL_SOURCE_RELATIVE, AL_TRUE);
            }
            logger.debugStream() << "OpenAL initialized. Version " 
                                 << alGetString(AL_VERSION)
                                 << "Renderer "
                                 << alGetString(AL_RENDERER)
                                 << "Vendor "
                                 << alGetString(AL_VENDOR)
                                 << "\nExtensions: " 
                                 << alGetString(AL_EXTENSIONS)
                                 << "\n";
        }
    }
}

#ifdef FREEORION_WIN32
static int _fseek64_wrap(FILE *f,ogg_int64_t off,int whence){
    if(f==NULL)return(-1);
    return fseek(f,off,whence);
}
#endif

HumanClientAppSoundOpenAL::HumanClientAppSoundOpenAL()
    : HumanClientApp()
{
    InitOpenAL(M_SOURCES_NUM, m_sources, m_music_buffers);
}

HumanClientAppSoundOpenAL::~HumanClientAppSoundOpenAL()
{
    alutExit();
}

int HumanClientAppSoundOpenAL::RefillBuffer(ALuint *bufferName)
{
    log4cpp::Category& logger = HumanClientApp::GetApp()->Logger();
    ALenum m_openal_error;
    int endian = 0; /// 0 for little-endian (x86), 1 for big-endian (ppc)
    int bitStream,bytes,bytes_new;
    char array[BUFFER_SIZE];
    bytes=0;
    
    if (alcGetCurrentContext() != NULL)
    {
        /* First, let's fill up the buffer. We need the loop, as ov_read treats (BUFFER_SIZE - bytes) to read as a suggestion only */
        do
        {
            bytes_new = ov_read(&m_ogg_file, &array[bytes],(BUFFER_SIZE - bytes), endian, 2, 1, &bitStream);
            bytes += bytes_new;
            if (bytes_new == 0)
            {
                if (m_music_loops != 0) // enter here if we need to play the same file again
                {
                    if (m_music_loops > 0)
                        m_music_loops--;
                    ov_time_seek(&m_ogg_file,0.0); // rewind to beginning
                }
                else
                    break;
            }
        } while ((BUFFER_SIZE - bytes) > 4096);
        if (bytes > 0)
        {
            alBufferData(bufferName[0], m_ogg_format, array, static_cast < ALsizei > (bytes),m_ogg_freq);
            m_openal_error = alGetError();
            if (m_openal_error != AL_NONE)
                logger.errorStream() << "RefillBuffer: OpenAL ERROR: " << alGetString(m_openal_error);
        }
        else
        {
            m_music_name.clear();  // m_music_name.clear() must always be called before ov_clear. Otherwise
            ov_clear(&m_ogg_file); // the app might think we still have something to play.
            return 1;
        }
        return 0;
    }
    return 1;
}

void HumanClientAppSoundOpenAL::PlayMusic(const std::string& filename, int loops /* = 0*/)
{
    ALenum m_openal_error;
    log4cpp::Category& logger = HumanClientApp::GetApp()->Logger();
    FILE *m_f = NULL;
    vorbis_info *m_ogg_info;
    m_music_loops = 0;

#ifdef FREEORION_WIN32
    ov_callbacks callbacks = {
    (size_t (*)(void *, size_t, size_t, void *))  fread,
    (int (*)(void *, ogg_int64_t, int))           _fseek64_wrap,
    (int (*)(void *))                             fclose,
    (long (*)(void *))                            ftell
    };
#endif

    if (alcGetCurrentContext() != NULL)
    {
        if (m_music_name.size() > 0)
            StopMusic();
        
        if ((m_f = fopen(filename.c_str(), "rb")) != NULL) // make sure we CAN open it
        {
#ifdef FREEORION_WIN32
            if (!(ov_test_callbacks(m_f, &m_ogg_file, NULL, 0, callbacks))) // check if it's a proper ogg
#else
            if (!(ov_test(m_f, &m_ogg_file, NULL, 0))) // check if it's a proper ogg
#endif
            {
                ov_test_open(&m_ogg_file); // it is, now fully open the file
                /* now we need to take some info we will need later */
                m_ogg_info = ov_info(&m_ogg_file, -1);
                if (m_ogg_info->channels == 1)
                    m_ogg_format = AL_FORMAT_MONO16;
                else
                    m_ogg_format = AL_FORMAT_STEREO16;
                m_ogg_freq = m_ogg_info->rate;
                m_music_loops = loops;
                /* fill up the buffers and queue them up for the first time */
                if (!RefillBuffer(&m_music_buffers[0]))
                {
                    alSourceQueueBuffers(m_sources[0],1,&m_music_buffers[0]); // queue up the buffer if we manage to fill it
                    if (!RefillBuffer(&m_music_buffers[1]))
                    {
                        alSourceQueueBuffers(m_sources[0],1,&m_music_buffers[1]);
                        m_music_name = filename; // yup, we're playing something that takes up more than 2 buffers
                    }
                    alSourcePlay(m_sources[0]); // play if at least one buffer is queued
                }
            }
            else
            {
                logger.errorStream() << "PlayMusic: unable to open file " << filename.c_str() << " possibly not a .ogg vorbis file. Aborting\n";
                m_music_name.clear(); //just in case
                ov_clear(&m_ogg_file);
            }
        }
        else
            logger.errorStream() << "PlayMusic: unable to open file " << filename.c_str() << " I/O Error. Aborting\n";
    }
    m_openal_error = alGetError();
    if (m_openal_error != AL_NONE)
        logger.errorStream() << "PlayMusic: OpenAL ERROR: " << alGetString(m_openal_error);
}

void HumanClientAppSoundOpenAL::StopMusic()
{
    int num_buffers_processed;
    ALuint buffer_name_yay;
    
    if (alcGetCurrentContext() != NULL)
    {
        alSourceStop(m_sources[0]);
        if (m_music_name.size() > 0)
        {
            m_music_name.clear();  // do this to avoid music being re-started by other functions
            ov_clear(&m_ogg_file); // and unload the file for good measure. the file itself is closed now, don't re-close it again
        }
        alGetSourcei(m_sources[0],AL_BUFFERS_PROCESSED,&num_buffers_processed);         // we need to unqueue any unplayed buffers
        alSourceUnqueueBuffers (m_sources[0], num_buffers_processed, &buffer_name_yay); // otherwise they'll cause problems if we open another file
    }
}

void HumanClientAppSoundOpenAL::PlaySound(const std::string& filename)
{
    log4cpp::Category& logger = HumanClientApp::GetApp()->Logger();
    ALuint m_current_buffer;
    ALenum m_source_state;
    int m_i;
    int m_found_buffer = 1;
    int m_found_source = 0;
    
    if (alcGetCurrentContext() != NULL)
    {
        /* First check if the sound data of the file we want to play is already buffered somewhere */
        std::map<std::string, ALuint>::iterator it = m_buffers.find(filename);
        if (it != m_buffers.end())
            m_current_buffer=it->second;
        else 
        {
            /* We buffer the file if it wasn't previously */
            if ((m_current_buffer = alutCreateBufferFromFile(filename.c_str())) != AL_NONE)
                m_buffers[filename] = m_current_buffer;
            else
            {
                logger.errorStream() << "PlaySound: Cannot create buffer for: " << filename.c_str() << " Reason:" << alutGetErrorString(alutGetError());
                m_found_buffer = 0;
            }
        }
        if (m_found_buffer)
        {
            /* Now that we have the buffer, we need to find a source to send it to */
            for (m_i=1;m_i<M_SOURCES_NUM;m_i++) // as we're playing sounds we start at 1. 0 is reserved for music
            {
                alGetSourcei(m_sources[m_i],AL_SOURCE_STATE,&m_source_state);
                if ((m_source_state != AL_PLAYING) && (m_source_state != AL_PAUSED))
                {
                    m_found_source = 1;
                    alSourcei(m_sources[m_i], AL_BUFFER, m_current_buffer);
                    alSourcePlay(m_sources[m_i]);
                    break; // so that the sound won't block all the sources
                }
            }
            if (!m_found_source)
                logger.errorStream() << "PlaySound: Could not find aviable source - playback aborted\n";
        }
        m_source_state = alGetError();
        if (m_source_state != AL_NONE)
            logger.errorStream() << "PlaySound: OpenAL ERROR: " << alGetString(m_source_state);
            /* it's important to check for errors, as some functions (mainly alut) won't work properly if
             * they're called when there is a unchecked previous error. */
    }
}

void HumanClientAppSoundOpenAL::FreeSound(const std::string& filename)
{
    ALenum m_openal_error;
    log4cpp::Category& logger = HumanClientApp::GetApp()->Logger();
    std::map<std::string, ALuint>::iterator it = m_buffers.find(filename);
    
    if (it != m_buffers.end()) {
        alDeleteBuffers(1, &(it->second));
        m_openal_error = alGetError();
        if (m_openal_error != AL_NONE)
            logger.errorStream() << "FreeSound: OpenAL ERROR: " << alGetString(m_openal_error);
        else
            m_buffers.erase(it); /* we don't erase if there was an error, as the buffer may not have been
                                    removed - potential memory leak */
    }
}

void HumanClientAppSoundOpenAL::FreeAllSounds()
{
    ALenum m_openal_error;
    log4cpp::Category& logger = HumanClientApp::GetApp()->Logger();
    
    for (std::map<std::string, ALuint>::iterator it = m_buffers.begin(); 
         it != m_buffers.end(); ++it) {
        alDeleteBuffers(1, &(it->second));
        m_openal_error = alGetError();
        if (m_openal_error != AL_NONE)
            logger.errorStream() << "FreeAllSounds: OpenAL ERROR: " << alGetString(m_openal_error);
        else
            m_buffers.erase(it); /* same as in FreeSound */
    }
}

void HumanClientAppSoundOpenAL::SetMusicVolume(int vol)
{
    ALenum m_openal_error;
    log4cpp::Category& logger = HumanClientApp::GetApp()->Logger();
    
    /* normalize value, then apply to all sound sources */
    vol = std::max(0, std::min(vol, 255));
    GetOptionsDB().Set<int>("music-volume", vol);
    if (alcGetCurrentContext() != NULL)
    {
        alSourcef(m_sources[0],AL_GAIN, ((ALfloat) vol)/255.0);
        /* it is highly unlikely that we'll get an error here but better safe than sorry */
        m_openal_error = alGetError();
        if (m_openal_error != AL_NONE)
            logger.errorStream() << "PlaySound: OpenAL ERROR: " << alGetString(m_openal_error);
    }
}

void HumanClientAppSoundOpenAL::SetUISoundsVolume(int vol)
{
    ALenum m_openal_error;
    log4cpp::Category& logger = HumanClientApp::GetApp()->Logger();
    
    /* normalize value, then apply to all sound sources */
    vol = std::max(0, std::min(vol, 255));
    GetOptionsDB().Set<int>("UI.sound.volume", vol);
    if (alcGetCurrentContext() != NULL)
    {
        for (int it=1; it < M_SOURCES_NUM; it++)
            alSourcef(m_sources[it],AL_GAIN, ((ALfloat) vol)/255.0);
        /* it is highly unlikely that we'll get an error here but better safe than sorry */
        m_openal_error = alGetError();
        if (m_openal_error != AL_NONE)
            logger.errorStream() << "PlaySound: OpenAL ERROR: " << alGetString(m_openal_error);
    }
}

void HumanClientAppSoundOpenAL::RenderBegin()
{
    ALint    state;
    int      num_buffers_processed;
    
    if ((alcGetCurrentContext() != NULL) && (m_music_name.size() > 0))
    {
        alGetSourcei(m_sources[0],AL_BUFFERS_PROCESSED,&num_buffers_processed);
        while (num_buffers_processed > 0)
        {
            ALuint buffer_name_yay;
            alSourceUnqueueBuffers (m_sources[0], 1, &buffer_name_yay);
            if (RefillBuffer(&buffer_name_yay))
                break; /// this happens if RefillBuffer returns 1, meaning it encountered EOF and the file shouldn't be repeated
            alSourceQueueBuffers(m_sources[0],1,&buffer_name_yay);
            num_buffers_processed--;
        }
        alGetSourcei(m_sources[0], AL_SOURCE_STATE, &state);
        if (state == AL_STOPPED)  /// this may happen if the source plays all its buffers before we manage to refill them
            alSourcePlay(m_sources[0]);
    }
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // this is the only line in SDLGUI::RenderBegin()
}

HumanClientAppSoundOpenAL.h:

Code: Select all

#include "HumanClientApp.h"

#ifdef FREEORION_WIN32
#include <al.h>
#else
#include <AL/al.h>
#endif

#include <vorbis/vorbisfile.h>

#define M_SOURCES_NUM 16 // the number of sources for OpenAL to create. Should be 2 or more
#define BUFFER_SIZE 409600 // the size of the buffer we read music data into.

class HumanClientAppSoundOpenAL : public HumanClientApp
{
public:
    HumanClientAppSoundOpenAL();
    virtual ~HumanClientAppSoundOpenAL();

    virtual void PlayMusic(const std::string& filename, int loops = 0);
    virtual void StopMusic();
    virtual void PlaySound(const std::string& filename);
    virtual void FreeSound(const std::string& filename);
    virtual void FreeAllSounds();
    virtual void SetMusicVolume(int vol);
    virtual void SetUISoundsVolume(int vol);

private:
    ALuint                            m_sources[M_SOURCES_NUM];         ///< OpenAL sound sources. The first one is used for music
    int                               m_music_loops;        ///< the number of loops of the current music to play (< 0 for loop forever)
///    int                               m_next_music_time;    ///< the time in ms that the next loop of the current music should play (0 if no repeats are scheduled)
    std::string                       m_music_name;         ///< the name of the currently-playing music file
    std::map<std::string, ALuint>     m_buffers;            ///< the currently-cached (and possibly playing) sounds, if any; keyed on filename
    ALuint                            m_music_buffers[2];   ///< two additional buffers for music. statically defined as they'll be changed many time.
    OggVorbis_File                    m_ogg_file;           ///< the currently open ogg file
    ALenum                            m_ogg_format;         ///< mono or stereo
    ALsizei                           m_ogg_freq;           ///< sampling frequency
    
    int RefillBuffer(ALuint *bufferName); ///< delegated here for simplicity - it's used by both PlayMusic and RenderBegin. Returns 1 if encounteres end of playback (no more music loops).
    virtual void RenderBegin();
};
Yes, I know it would be wuch shorter to post .diffs but I'm lazy and copy&paste is much quicker than retrieving a old version, making the diff and making sure it didn't add some "added a blank line" stuff. If this version won't work then I'm _REALLY_ out of ideas. And as for those warnings: yes, they're to be expected really - there is nothing we can do about the potential data loss when converting between formats but I'll take a look at boost to check if anything there could be used to substitute the fopen, fclose, fseek, ftell and fread functions

User avatar
Geoff the Medio
Programming, Design, Admin
Posts: 13587
Joined: Wed Oct 08, 2003 1:33 am
Location: Munich

#45 Post by Geoff the Medio »

Still no sound in FO itself. I ran the command line test app simultaneously, and got sound from it while not getting sound from FO, so it's not a speakers / hardware volume issue.

Maybe throw in some debug code to be sure you're getting the path to the files to load from the rest of the engine correctly? That's a significant difference between the command line and in-enging situations...

Post Reply