Implement ogg file playing for OpenAL-based audio system. (#1096)

issue1247
Sven Eberhardt 2014-08-07 13:46:45 +02:00
parent 4f1aa7c5cf
commit cfe242b2b8
5 changed files with 234 additions and 10 deletions

View File

@ -517,16 +517,16 @@ bool C4Application::PreInit()
if (!MusicSystem.Init("Frontend.*"))
Log(LoadResStr("IDS_PRC_NOMUSIC"));
// Play some music!
if (fUseStartupDialog && !isEditor && Config.Sound.FEMusic)
MusicSystem.Play();
Game.SetInitProgress(fUseStartupDialog ? 34.0f : 2.0f);
// Sound
if (!SoundSystem.Init())
Log(LoadResStr("IDS_PRC_NOSND"));
// Play some music! - after sound init because sound system might be needed by music system
if (fUseStartupDialog && !isEditor && Config.Sound.FEMusic)
MusicSystem.Play();
Game.SetInitProgress(fUseStartupDialog ? 35.0f : 3.0f);
if (fUseStartupDialog)
@ -647,6 +647,7 @@ void C4Application::GameTick()
break;
case C4AS_Startup:
SoundSystem.Execute();
MusicSystem.Execute();
// wait for the user to start a game
break;
case C4AS_StartGame:

View File

@ -25,6 +25,21 @@
#include <fmod_errors.h>
#endif
#if AUDIO_TK == AUDIO_TK_OPENAL
#if defined(__APPLE__)
#import <CoreFoundation/CoreFoundation.h>
#import <AudioToolbox/AudioToolbox.h>
#else
#ifdef _WIN32
// This is an ugly hack to make FreeALUT not dllimport everything.
#define _XBOX
#endif
#include <alut.h>
#undef _XBOX
#endif
#define alErrorCheck(X) X; { ALenum err = alGetError(); if (err) LogF("%s (%x)", #X, err); }
#endif
/* helpers */
bool C4MusicFile::ExtractFile()
@ -391,4 +406,175 @@ void C4MusicFileSDL::SetVolume(int iLevel)
Mix_VolumeMusic((int) ((iLevel * MIX_MAX_VOLUME) / 100));
}
#elif AUDIO_TK == AUDIO_TK_OPENAL
/* Ogg Vobis */
C4MusicFileOgg::C4MusicFileOgg() : playing(false), current_section(0), channel(0), streaming_done(false)
{
for (size_t i=0; i<num_buffers; ++i)
buffers[i] = 0;
}
C4MusicFileOgg::~C4MusicFileOgg()
{
Stop();
}
bool C4MusicFileOgg::Play(bool loop)
{
// stop previous
Stop();
// Get channel to use
alGenSources(1, (ALuint*)&channel);
if (!channel) return false;
// Load compressed data all at once (because reading/seeking inside C4Group is problematic). Uncompress while playing.
char *file_contents;
size_t file_size;
if (!C4Group_ReadFile(FileName, &file_contents, &file_size))
return false;
data.SetOwnedData((BYTE *)file_contents, file_size);
// Prepare ogg reader
vorbis_info* info;
memset(&ogg_file, 0, sizeof(ogg_file));
ov_callbacks callbacks;
callbacks.read_func = &::C4SoundLoaders::VorbisLoader::read_func;
callbacks.seek_func = &::C4SoundLoaders::VorbisLoader::seek_func;
callbacks.close_func = &::C4SoundLoaders::VorbisLoader::close_func;
callbacks.tell_func = &::C4SoundLoaders::VorbisLoader::tell_func;
// open using callbacks
if (ov_open_callbacks(&data, &ogg_file, NULL, 0, callbacks) != 0)
{
ov_clear(&ogg_file);
return false;
}
// get information about music
info = ov_info(&ogg_file, -1);
if (info->channels == 1)
ogg_info.format = AL_FORMAT_MONO16;
else
ogg_info.format = AL_FORMAT_STEREO16;
ogg_info.sample_rate = info->rate;
ogg_info.sample_length = ov_time_total(&ogg_file, -1)/1000.0;
playing = true;
streaming_done = false;
// prepare read
ogg_info.sound_data.resize(num_buffers * buffer_size);
alGenBuffers(num_buffers, buffers);
// Fill initial buffers
for (size_t i=0; i<num_buffers; ++i)
if (!FillBuffer(i)) break; // if this fails, the piece is shorter than the initial buffers
// play!
alErrorCheck(alSourcePlay(channel));
return true;
}
void C4MusicFileOgg::Stop(int fadeout_ms)
{
if (playing)
{
// clear ogg file
ov_clear(&ogg_file);
alSourceStop(channel);
// clear queue
ALint num_queued=0;
alErrorCheck(alGetSourcei(channel, AL_BUFFERS_QUEUED, &num_queued));
ALuint buffer;
for (size_t i = 0; i < num_queued; ++i)
alErrorCheck(alSourceUnqueueBuffers(channel, 1, &buffer));
}
// clear buffers
if (channel)
{
alSourcei(channel, AL_BUFFER, 0);
alDeleteBuffers(num_buffers, buffers);
alDeleteSources(1, &channel);
}
}
void C4MusicFileOgg::CheckIfPlaying()
{
Execute();
if (!playing) Application.MusicSystem.NotifySuccess();
}
void C4MusicFileOgg::SetVolume(int iLevel)
{
}
bool C4MusicFileOgg::FillBuffer(size_t idx)
{
// uncompress from ogg data
int endian = 0;
long bytes_read_total = 0, bytes_read;
char uncompressed_data[buffer_size];
do {
bytes_read = ov_read(&ogg_file, uncompressed_data+bytes_read_total, (buffer_size-bytes_read_total)*sizeof(BYTE), endian, 2, 1, &current_section);
bytes_read_total += bytes_read;
} while (bytes_read > 0 && bytes_read_total < buffer_size);
// buffer data
if (bytes_read_total)
{
ALuint buffer = buffers[idx];
alErrorCheck(alBufferData(buffer, ogg_info.format, uncompressed_data, bytes_read_total, ogg_info.sample_rate));
// queue buffer
alErrorCheck(alSourceQueueBuffers(channel, 1, &buffer));
}
// streaming done?
if (bytes_read_total < buffer_size)
{
// streaming done.
return false;
}
else
{
// might have more data to stream
return true;
}
}
void C4MusicFileOgg::Execute()
{
if (playing)
{
// get processed buffer count
ALint num_processed = 0;
alErrorCheck(alGetSourcei(channel, AL_BUFFERS_PROCESSED, &num_processed));
bool done = false;
while (num_processed--)
{
// refill processed buffers
ALuint buffer;
alErrorCheck(alSourceUnqueueBuffers(channel, 1, &buffer));
size_t buffer_idx;
for (buffer_idx=0; buffer_idx<num_buffers; ++buffer_idx)
if (buffers[buffer_idx] == buffer) break;
if (!done) done = !FillBuffer(buffer_idx);
}
if (done) streaming_done = true;
// check if done
ALint state = 0;
alErrorCheck(alGetSourcei(channel, AL_SOURCE_STATE, &state));
if (state != AL_PLAYING && streaming_done)
{
Stop();
}
else if (state == AL_STOPPED)
{
alErrorCheck(alSourcePlay(channel));
}
}
}
#endif

View File

@ -24,6 +24,8 @@
#define USE_RWOPS
#include <SDL_mixer.h>
#undef USE_RWOPS
#elif AUDIO_TK == AUDIO_TK_OPENAL
#include <C4SoundLoaders.h>
#endif
/* Base class */
@ -138,6 +140,32 @@ protected:
char *Data;
Mix_Music * Music;
};
#elif AUDIO_TK == AUDIO_TK_OPENAL
class C4MusicFileOgg : public C4MusicFile
{
public:
C4MusicFileOgg();
~C4MusicFileOgg();
bool Play(bool loop = false);
void Stop(int fadeout_ms = 0);
void CheckIfPlaying();
void SetVolume(int);
private:
enum { num_buffers = 4, buffer_size = 160*1024 };
::C4SoundLoaders::VorbisLoader::CompressedData data;
::C4SoundLoaders::SoundInfo ogg_info;
OggVorbis_File ogg_file;
bool playing, streaming_done;
ALuint buffers[num_buffers];
ALuint channel;
int current_section;
bool FillBuffer(size_t idx);
void Execute(); // fill processed buffers
};
#endif
#endif

View File

@ -239,8 +239,11 @@ void C4MusicSystem::Load(const char *szFile)
// safety
if (!szFile || !*szFile) return;
C4MusicFile *NewSong=NULL;
// get extension
#if AUDIO_TK == AUDIO_TK_FMOD
#if AUDIO_TK == AUDIO_TK_OPENAL
// openal: Only ogg supported
const char *szExt = GetExtension(szFile);
if (SEqualNoCase(szExt, "ogg")) NewSong = new C4MusicFileOgg;
#elif AUDIO_TK == AUDIO_TK_FMOD
const char *szExt = GetExtension(szFile);
// get type
switch (GetMusicFileTypeByExtension(GetExtension(szFile)))
@ -396,7 +399,7 @@ void C4MusicSystem::Clear()
void C4MusicSystem::Execute()
{
#if AUDIO_TK != AUDIO_TK_SDL_MIXER
if (!::Game.iTick35)
if (!::Game.iTick35 || !Game.IsRunning)
#endif
{
if (!PlayMusicFile)

View File

@ -70,15 +70,21 @@ namespace C4SoundLoaders
#if AUDIO_TK == AUDIO_TK_OPENAL
class VorbisLoader: public SoundLoader
{
private:
public: // needed by C4MusicFileOgg
struct CompressedData
{
public:
BYTE* data;
size_t data_length;
size_t data_pos;
CompressedData(BYTE* data, size_t data_length): data(data), data_length(data_length), data_pos(0)
{}
bool is_data_owned; // if true, dtor will delete data
CompressedData(BYTE* data, size_t data_length): data(data), data_length(data_length), data_pos(0), is_data_owned(false) {}
CompressedData() : data(NULL), data_length(0), data_pos(0), is_data_owned(false) {}
void SetOwnedData(BYTE* data, size_t data_length)
{ clear(); this->data=data; this->data_length=data_length; this->data_pos=0; is_data_owned=true; }
~CompressedData() { clear(); }
void clear() { if (is_data_owned) delete [] data; data=NULL; }
};
static size_t read_func(void* ptr, size_t byte_size, size_t size_to_read, void* datasource);
static int seek_func(void* datasource, ogg_int64_t offset, int whence);