/* orxonox - the future of 3D-vertical-scrollers Copyright (C) 2004 orx This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. ### File Specific: main-programmer: Benjamin Grauer co-programmer: ... Beware: The Ogg-Player is a __Threaded__ Stream, this means, that invoking play() creates a Thread, that loops through the Music-File. The Thread seems to be safe, but it is not guarantied. ------------------------------------------------------------------- A Simple Version of this can be found at http://www.devmaster.net Thanks a lot for the nice work, and the easy portability to our Project. */ #define DEBUG_SPECIAL_MODULE DEBUG_MODULE_SOUND #include #include "ogg_player.h" #include "sound_engine.h" #include "debug.h" #include "compiler.h" #include "helper_functions.h" #ifdef HAVE_SDL_SDL_H #include #include #else #include #include #endif namespace OrxSound { /** * initializes an Ogg-player from a file * @param fileName the file to load */ OggPlayer::OggPlayer(const std::string& fileName) { this->setClassID(CL_SOUND_OGG_PLAYER, "OggPlayer"); this->state = OggPlayer::None; this->source = 0; this->buffers[0] = 0; this->buffers[1] = 0; this->musicThreadID = NULL; if (!fileName.empty()) { if (this->open(fileName)) this->setName(fileName); } } /** * @brief deletes the OggPlayer */ OggPlayer::~OggPlayer() { this->release(); } /////////////// // INTERFACE // /////////////// /** * @brief opens a file for playback * @param fileName the file to open */ bool OggPlayer::open(const std::string& fileName) { OrxThread::MutexLock musicLock(&this->musicMutex); // release old Ogg-File (if loaded) if (this->state & OggPlayer::FileOpened) this->release(); // allocating Buffers if (this->buffers[0] == 0) alGenBuffers(2, this->buffers); SoundEngine::checkError("Allocating Buffers", __LINE__); if (this->buffers[0] != 0 && this->buffers[1] != 0) state |= OggPlayer::BuffersAllocated; else { PRINTF(2)("Unable to allocate al-Buffers\n"); this->release(); return false; } // allocating source if (this->source == 0) SoundEngine::getInstance()->popALSource(this->source); if (this->source != 0) state |= OggPlayer::SourceAllocated; else { PRINTF(2)("No more Sources Availiable (maybe you should consider raising the source-count.)\n"); this->release(); return false; } // opening the FILE; int result; if(!(oggFile = fopen(fileName.c_str(), "rb"))) { PRINTF(2)("Could not open Ogg file."); this->release(); return false; } // reading the Stream. if((result = ov_open(oggFile, &oggStream, NULL, 0)) < 0) { PRINTF(2)("Could not open Ogg stream. %s", getVorbisError(result)); fclose(oggFile); this->release(); return false; } this->state |= OggPlayer::FileOpened; // acquiring the vorbis-properties. vorbisInfo = ov_info(&oggStream, -1); vorbisComment = ov_comment(&oggStream, -1); this->retrieveFileInfo(&oggStream); if(vorbisInfo->channels == 1) format = AL_FORMAT_MONO16; else format = AL_FORMAT_STEREO16; // setting the Source Properties. alSource3f(source, AL_POSITION, 0.0, 0.0, 0.0); alSource3f(source, AL_VELOCITY, 0.0, 0.0, 0.0); alSource3f(source, AL_DIRECTION, 0.0, 0.0, 0.0); alSourcef (source, AL_ROLLOFF_FACTOR, 0.0 ); alSourcei (source, AL_SOURCE_RELATIVE, AL_TRUE ); alSourcef (source, AL_GAIN, SoundEngine::getInstance()->getMusicVolume()); SoundEngine::checkError("OggPlayer::open()::SetSourceProperties", __LINE__); // Set to State Stopped this->state |= OggPlayer::Stopped; return true; } /** * @brief start Playing Music. * @returns true on success false otherwise. */ bool OggPlayer::play() { if (!(this->state & OggPlayer::FileOpened)) return false; this->state &= ~(OggPlayer::Stopped | OggPlayer::Paused); if (!this->playback()) return false; if (this->musicThreadID == NULL) return ((this->musicThreadID = SDL_CreateThread(OggPlayer::musicThread, (void*)this)) != NULL); return true; } /** * @brief stop the OggPlayer from Playing. */ void OggPlayer::stop() { this->state &= ~(OggPlayer::Playing | OggPlayer::Paused); this->state |= OggPlayer::Stopped; this->suspend(); this->rewind(); } /** * @brief Pause the Playing. */ void OggPlayer::pause() { this->state &= ~OggPlayer::Playing; if (!(this->state & OggPlayer::Stopped)) this->state |= OggPlayer::Paused; this->suspend(); } /** * @brief rewind to the beginning, and play (if already playing) */ void OggPlayer::rewind() { this->jumpTo(0.0f); } /** * @brief jump to Second timeCode in the MusicFile * @param timeCode where to jump to. */ void OggPlayer::jumpTo(float timeCode) { if (this->state & OggPlayer::FileOpened) { OrxThread::MutexLock musicLock(&this->musicMutex); ov_time_seek(&this->oggStream, timeCode); } } /** * @returns the Length of the Music in Seconds */ float OggPlayer::length() { if (this->state & OggPlayer::FileOpened) return ov_time_total(&this->oggStream, -1); else return 0.0f; } /** * @returns true if the file is playing */ bool OggPlayer::isPlaying() { if (!(this->state & OggPlayer::FileOpened)) return false; ALenum state; alGetSourcei(this->source, AL_SOURCE_STATE, &state); return (state == AL_PLAYING); } //////////////////////// // INTERNAL FUNCTIONS // //////////////////////// /** * @brief creates a Thread for Playing back the music even if the rest of the Engine is slow * @param oggPlayer the OggPlayer to play back * @returns -1 on error. */ int OggPlayer::musicThread(void* oggPlayer) { if (oggPlayer == NULL) return -1; OggPlayer* ogg = (OggPlayer*)oggPlayer; PRINTF(4)("STARTIG AUDIO THREAD\n"); while (ogg->state & OggPlayer::Playing) { { OrxThread::MutexLock musicLock(&ogg->musicMutex); ogg->update(); } SDL_Delay(10); } PRINTF(4)("End the AudioThread\n"); return 0; } /** * @brief plays back the sound * @return true if running, false otherwise */ bool OggPlayer::playback() { if (!(this->state & OggPlayer::FileOpened)) return false; if(this->state & OggPlayer::Playing) return true; this->state |= OggPlayer::Playing; OrxThread::MutexLock musicLock(&this->musicMutex); if(!this->stream(this->buffers[0]) || !this->stream(this->buffers[1])) { this->state &= ~OggPlayer::Playing; return false; } alSourceQueueBuffers(this->source, 2, this->buffers); if (DEBUG_LEVEL >= 3) SoundEngine::checkError("OggPlayer::playback()::alSourceQueueBuffers", __LINE__); alSourcePlay(this->source); if (DEBUG_LEVEL >= 3) SoundEngine::checkError("OggPlayer::playback()::alSourcePlay", __LINE__); return true; } /** * @brief waits for the AudioThread to be finished. */ void OggPlayer::suspend() { if (this->musicThreadID != NULL) { assert (!(this->state & Playing)); SDL_WaitThread(this->musicThreadID, NULL); this->musicThreadID = NULL; } if (this->state & OggPlayer::SourceAllocated) { alSourceStop(this->source); alSourcei(this->source, AL_BUFFER, 0); } } /** * @brief updates the stream, this has to be done every few parts of a second, for sound-consistency * @returns true, if the Sound is playing flawlessly */ bool OggPlayer::update() { int processed = 0; bool active = true; alGetSourcei(source, AL_BUFFERS_PROCESSED, &processed); if (DEBUG_LEVEL >= 3) SoundEngine::checkError("OggPlayer::update()::alGetSourceI", __LINE__); while(processed--) { ALuint buffer; alSourceUnqueueBuffers(source, 1, &buffer); if (DEBUG_LEVEL >= 3) SoundEngine::checkError("OggPlayer::update()::unqueue", __LINE__); active = stream(buffer); alSourceQueueBuffers(source, 1, &buffer); if (DEBUG_LEVEL >= 3) SoundEngine::checkError("OggPlayer::update()::queue", __LINE__); } int play; alGetSourcei(source, AL_SOURCE_STATE, &play); if (play != AL_PLAYING) { alSourcePlay(source); PRINTF(2)("Filling Audio Gap\n"); } return active; } /** * @brief gets a new Stream from buffer * @param buffer the buffer to get the stream from * @return true, if everything worked as planed */ bool OggPlayer::stream(ALuint buffer) { if (unlikely(!(this->state & Playing))) return false; char pcm[OGG_PLAYER_BUFFER_SIZE]; int size = 0; int section; int result; while(size < OGG_PLAYER_BUFFER_SIZE) { result = ov_read(&this->oggStream, pcm + size, OGG_PLAYER_BUFFER_SIZE - size, 0, 2, 1, §ion); if(result > 0) size += result; else if(result < 0) throw getVorbisError(result); else /* eof */ ov_time_seek(&this->oggStream, 0.0); } if(size == 0) return false; #ifdef SDL_BIG_ENDIAN int cnt = OGG_PLAYER_BUFFER_SIZE/2; Uint16* wavBufferAsShorts = ( Uint16* )pcm; for ( int i = 0; i < cnt; ++i, ++wavBufferAsShorts ) *wavBufferAsShorts = SDL_Swap16( *wavBufferAsShorts ); #endif alBufferData(buffer, format, pcm, size, vorbisInfo->rate); if (DEBUG_LEVEL >= 3) SoundEngine::checkError("OggPlayer::stream()::BUFFER", __LINE__); return true; } /** * @brief releases a stream */ void OggPlayer::release() { if (this->state & OggPlayer::SourceAllocated) { assert(alIsSource(this->source)); if (this->state & OggPlayer::Playing); { // Kill the Music Thread. this->state &= ~OggPlayer::Playing; this->suspend(); SoundEngine::checkError("OggPlayer::release()::alSourceStop", __LINE__); } empty(); alSourcei(this->source, AL_BUFFER, 0); SoundEngine::getInstance()->pushALSource(this->source); this->source = 0; this->state &= ~SourceAllocated; } if (this->state & OggPlayer::BuffersAllocated) { assert (this->buffers[0] != 0 && this->buffers[1] != 0); alDeleteBuffers(2, buffers); SoundEngine::checkError("OggPlayer::release()::alDeleteBuffers", __LINE__); this->buffers[0] = 0; this->buffers[1] = 0; this->state &= ~OggPlayer::BuffersAllocated; } if (this->state & OggPlayer::FileOpened) { ov_clear(&oggStream); this->state &= ~OggPlayer::FileOpened; } } /** * @brief empties the buffers */ void OggPlayer::empty() { int queued; alGetSourcei(source, AL_BUFFERS_QUEUED, &queued); while(queued--) { ALuint buffer; alSourceUnqueueBuffers(source, 1, &buffer); SoundEngine::checkError("OggPlayer::empty()::unqueue Buffers", __LINE__); } } void OggPlayer::retrieveFileInfo(OggVorbis_File* file) { vorbis_comment* comment = ov_comment(file, -1); if (comment == NULL) { PRINTF(2)("Retrieving Comment of %s failed\n", this->getCName()); return; } for (int i = 0; i < comment->comments; ++i) { if (!nocaseCmp("artist=", comment->user_comments[i], 7)) this->_artist = comment->user_comments[i]+7; if (!nocaseCmp("title=", comment->user_comments[i], 6)) this->_title = comment->user_comments[i] + 6; if (!nocaseCmp("album=", comment->user_comments[i], 6)) this->_album = comment->user_comments[i]+6; } } ///////////////////// // DEBUG_LEVEL FUNCTIONS // ///////////////////// /** * displays some info about the ogg-file */ void OggPlayer::debug() const { std::cout << "version " << vorbisInfo->version << "\n" << "channels " << vorbisInfo->channels << "\n" << "rate (hz) " << vorbisInfo->rate << "\n" << "bitrate upper " << vorbisInfo->bitrate_upper << "\n" << "bitrate nominal " << vorbisInfo->bitrate_nominal << "\n" << "bitrate lower " << vorbisInfo->bitrate_lower << "\n" << "bitrate window " << vorbisInfo->bitrate_window << "\n" << "\n" << "vendor " << vorbisComment->vendor << "\n"; for(int i = 0; i < vorbisComment->comments; i++) std::cout << " " << vorbisComment->user_comments[i] << "\n"; std::cout << std::endl; } void OggPlayer::printState() const { PRINTF(0)("OggPlayer is in the following States: "); if (this->state & OggPlayer::FileOpened) PRINT(0)("FileOpened "); if (this->state & OggPlayer::SourceAllocated) PRINT(0)("SourceAllocated "); if (this->state & OggPlayer::BuffersAllocated) PRINT(0)("BuffersAllocated "); if (this->state & OggPlayer::Stopped) PRINT(0)("Stopped "); if (this->state & OggPlayer::Playing) PRINT(0)("Playing "); if (this->state & OggPlayer::Paused) PRINT(0)("Paused "); if (this->state & OggPlayer::Error) PRINT(0)("Error "); PRINT(0)("\n"); } /** * returns errors * @param code the error-code * @return the error as a String */ const char* OggPlayer::getVorbisError(int code) { switch(code) { case OV_EREAD: return ("Read from media."); case OV_ENOTVORBIS: return ("Not Vorbis data."); case OV_EVERSION: return ("Vorbis version mismatch."); case OV_EBADHEADER: return ("Invalid Vorbis header."); case OV_EFAULT: return ("Internal logic fault (bug or heap/stack corruption."); default: return ("Unknown Ogg error."); } } }